infrahub-server 1.4.12__py3-none-any.whl → 1.5.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (234) hide show
  1. infrahub/actions/tasks.py +208 -16
  2. infrahub/api/artifact.py +3 -0
  3. infrahub/api/diff/diff.py +1 -1
  4. infrahub/api/internal.py +2 -0
  5. infrahub/api/query.py +2 -0
  6. infrahub/api/schema.py +27 -3
  7. infrahub/auth.py +5 -5
  8. infrahub/cli/__init__.py +2 -0
  9. infrahub/cli/db.py +160 -157
  10. infrahub/cli/dev.py +118 -0
  11. infrahub/cli/tasks.py +46 -0
  12. infrahub/cli/upgrade.py +56 -9
  13. infrahub/computed_attribute/tasks.py +19 -7
  14. infrahub/config.py +7 -2
  15. infrahub/core/attribute.py +35 -24
  16. infrahub/core/branch/enums.py +1 -1
  17. infrahub/core/branch/models.py +9 -5
  18. infrahub/core/branch/needs_rebase_status.py +11 -0
  19. infrahub/core/branch/tasks.py +72 -10
  20. infrahub/core/changelog/models.py +2 -10
  21. infrahub/core/constants/__init__.py +4 -0
  22. infrahub/core/constants/infrahubkind.py +1 -0
  23. infrahub/core/convert_object_type/object_conversion.py +201 -0
  24. infrahub/core/convert_object_type/repository_conversion.py +89 -0
  25. infrahub/core/convert_object_type/schema_mapping.py +27 -3
  26. infrahub/core/diff/calculator.py +2 -2
  27. infrahub/core/diff/model/path.py +4 -0
  28. infrahub/core/diff/payload_builder.py +1 -1
  29. infrahub/core/diff/query/artifact.py +1 -0
  30. infrahub/core/diff/query/delete_query.py +9 -5
  31. infrahub/core/diff/query/field_summary.py +1 -0
  32. infrahub/core/diff/query/merge.py +39 -23
  33. infrahub/core/graph/__init__.py +1 -1
  34. infrahub/core/initialization.py +7 -4
  35. infrahub/core/manager.py +3 -81
  36. infrahub/core/migrations/__init__.py +3 -0
  37. infrahub/core/migrations/exceptions.py +4 -0
  38. infrahub/core/migrations/graph/__init__.py +13 -10
  39. infrahub/core/migrations/graph/load_schema_branch.py +21 -0
  40. infrahub/core/migrations/graph/m013_convert_git_password_credential.py +1 -1
  41. infrahub/core/migrations/graph/m037_index_attr_vals.py +11 -30
  42. infrahub/core/migrations/graph/m039_ipam_reconcile.py +9 -7
  43. infrahub/core/migrations/graph/m041_deleted_dup_edges.py +149 -0
  44. infrahub/core/migrations/graph/m042_profile_attrs_in_db.py +147 -0
  45. infrahub/core/migrations/graph/m043_create_hfid_display_label_in_db.py +164 -0
  46. infrahub/core/migrations/graph/m044_backfill_hfid_display_label_in_db.py +864 -0
  47. infrahub/core/migrations/query/__init__.py +7 -8
  48. infrahub/core/migrations/query/attribute_add.py +8 -6
  49. infrahub/core/migrations/query/attribute_remove.py +134 -0
  50. infrahub/core/migrations/runner.py +54 -0
  51. infrahub/core/migrations/schema/attribute_kind_update.py +9 -3
  52. infrahub/core/migrations/schema/attribute_supports_profile.py +90 -0
  53. infrahub/core/migrations/schema/node_attribute_add.py +26 -5
  54. infrahub/core/migrations/schema/node_attribute_remove.py +13 -109
  55. infrahub/core/migrations/schema/node_kind_update.py +2 -1
  56. infrahub/core/migrations/schema/node_remove.py +2 -1
  57. infrahub/core/migrations/schema/placeholder_dummy.py +3 -2
  58. infrahub/core/migrations/shared.py +66 -19
  59. infrahub/core/models.py +2 -2
  60. infrahub/core/node/__init__.py +207 -54
  61. infrahub/core/node/create.py +53 -49
  62. infrahub/core/node/lock_utils.py +124 -0
  63. infrahub/core/node/node_property_attribute.py +230 -0
  64. infrahub/core/node/resource_manager/ip_address_pool.py +2 -1
  65. infrahub/core/node/resource_manager/ip_prefix_pool.py +2 -1
  66. infrahub/core/node/resource_manager/number_pool.py +2 -1
  67. infrahub/core/node/standard.py +1 -1
  68. infrahub/core/property.py +11 -0
  69. infrahub/core/protocols.py +8 -1
  70. infrahub/core/query/attribute.py +82 -15
  71. infrahub/core/query/diff.py +61 -16
  72. infrahub/core/query/ipam.py +16 -4
  73. infrahub/core/query/node.py +92 -212
  74. infrahub/core/query/relationship.py +44 -26
  75. infrahub/core/query/subquery.py +0 -8
  76. infrahub/core/relationship/model.py +69 -24
  77. infrahub/core/schema/__init__.py +56 -0
  78. infrahub/core/schema/attribute_schema.py +4 -2
  79. infrahub/core/schema/basenode_schema.py +42 -2
  80. infrahub/core/schema/definitions/core/__init__.py +2 -0
  81. infrahub/core/schema/definitions/core/check.py +1 -1
  82. infrahub/core/schema/definitions/core/generator.py +2 -0
  83. infrahub/core/schema/definitions/core/group.py +16 -2
  84. infrahub/core/schema/definitions/core/repository.py +7 -0
  85. infrahub/core/schema/definitions/core/transform.py +1 -1
  86. infrahub/core/schema/definitions/internal.py +12 -3
  87. infrahub/core/schema/generated/attribute_schema.py +2 -2
  88. infrahub/core/schema/generated/base_node_schema.py +6 -1
  89. infrahub/core/schema/manager.py +3 -0
  90. infrahub/core/schema/node_schema.py +1 -0
  91. infrahub/core/schema/relationship_schema.py +0 -1
  92. infrahub/core/schema/schema_branch.py +295 -10
  93. infrahub/core/schema/schema_branch_display.py +135 -0
  94. infrahub/core/schema/schema_branch_hfid.py +120 -0
  95. infrahub/core/validators/aggregated_checker.py +1 -1
  96. infrahub/database/graph.py +21 -0
  97. infrahub/display_labels/__init__.py +0 -0
  98. infrahub/display_labels/gather.py +48 -0
  99. infrahub/display_labels/models.py +240 -0
  100. infrahub/display_labels/tasks.py +192 -0
  101. infrahub/display_labels/triggers.py +22 -0
  102. infrahub/events/branch_action.py +27 -1
  103. infrahub/events/group_action.py +1 -1
  104. infrahub/events/node_action.py +1 -1
  105. infrahub/generators/constants.py +7 -0
  106. infrahub/generators/models.py +38 -12
  107. infrahub/generators/tasks.py +34 -16
  108. infrahub/git/base.py +42 -2
  109. infrahub/git/integrator.py +22 -14
  110. infrahub/git/tasks.py +52 -2
  111. infrahub/graphql/analyzer.py +9 -0
  112. infrahub/graphql/api/dependencies.py +2 -4
  113. infrahub/graphql/api/endpoints.py +16 -6
  114. infrahub/graphql/app.py +2 -4
  115. infrahub/graphql/initialization.py +2 -3
  116. infrahub/graphql/manager.py +213 -137
  117. infrahub/graphql/middleware.py +12 -0
  118. infrahub/graphql/mutations/branch.py +16 -0
  119. infrahub/graphql/mutations/computed_attribute.py +110 -3
  120. infrahub/graphql/mutations/convert_object_type.py +44 -13
  121. infrahub/graphql/mutations/display_label.py +118 -0
  122. infrahub/graphql/mutations/generator.py +25 -7
  123. infrahub/graphql/mutations/hfid.py +125 -0
  124. infrahub/graphql/mutations/ipam.py +73 -41
  125. infrahub/graphql/mutations/main.py +61 -178
  126. infrahub/graphql/mutations/profile.py +195 -0
  127. infrahub/graphql/mutations/proposed_change.py +8 -1
  128. infrahub/graphql/mutations/relationship.py +2 -2
  129. infrahub/graphql/mutations/repository.py +22 -83
  130. infrahub/graphql/mutations/resource_manager.py +2 -2
  131. infrahub/graphql/mutations/webhook.py +1 -1
  132. infrahub/graphql/queries/resource_manager.py +1 -1
  133. infrahub/graphql/registry.py +173 -0
  134. infrahub/graphql/resolvers/resolver.py +2 -0
  135. infrahub/graphql/schema.py +8 -1
  136. infrahub/graphql/schema_sort.py +170 -0
  137. infrahub/graphql/types/branch.py +4 -1
  138. infrahub/graphql/types/enums.py +3 -0
  139. infrahub/groups/tasks.py +1 -1
  140. infrahub/hfid/__init__.py +0 -0
  141. infrahub/hfid/gather.py +48 -0
  142. infrahub/hfid/models.py +240 -0
  143. infrahub/hfid/tasks.py +191 -0
  144. infrahub/hfid/triggers.py +22 -0
  145. infrahub/lock.py +119 -42
  146. infrahub/locks/__init__.py +0 -0
  147. infrahub/locks/tasks.py +37 -0
  148. infrahub/message_bus/types.py +1 -0
  149. infrahub/patch/plan_writer.py +2 -2
  150. infrahub/permissions/constants.py +2 -0
  151. infrahub/profiles/__init__.py +0 -0
  152. infrahub/profiles/node_applier.py +101 -0
  153. infrahub/profiles/queries/__init__.py +0 -0
  154. infrahub/profiles/queries/get_profile_data.py +98 -0
  155. infrahub/profiles/tasks.py +63 -0
  156. infrahub/proposed_change/tasks.py +67 -14
  157. infrahub/repositories/__init__.py +0 -0
  158. infrahub/repositories/create_repository.py +113 -0
  159. infrahub/server.py +9 -1
  160. infrahub/services/__init__.py +8 -5
  161. infrahub/services/adapters/http/__init__.py +5 -0
  162. infrahub/services/adapters/workflow/worker.py +14 -3
  163. infrahub/task_manager/event.py +5 -0
  164. infrahub/task_manager/models.py +7 -0
  165. infrahub/task_manager/task.py +73 -0
  166. infrahub/tasks/registry.py +6 -4
  167. infrahub/trigger/catalogue.py +4 -0
  168. infrahub/trigger/models.py +2 -0
  169. infrahub/trigger/setup.py +13 -4
  170. infrahub/trigger/tasks.py +6 -0
  171. infrahub/webhook/models.py +1 -1
  172. infrahub/workers/dependencies.py +3 -1
  173. infrahub/workers/infrahub_async.py +10 -2
  174. infrahub/workflows/catalogue.py +118 -3
  175. infrahub/workflows/initialization.py +21 -0
  176. infrahub/workflows/models.py +17 -2
  177. infrahub/workflows/utils.py +2 -1
  178. infrahub_sdk/branch.py +17 -8
  179. infrahub_sdk/checks.py +1 -1
  180. infrahub_sdk/client.py +376 -95
  181. infrahub_sdk/config.py +29 -2
  182. infrahub_sdk/convert_object_type.py +61 -0
  183. infrahub_sdk/ctl/branch.py +3 -0
  184. infrahub_sdk/ctl/check.py +2 -3
  185. infrahub_sdk/ctl/cli_commands.py +20 -12
  186. infrahub_sdk/ctl/config.py +8 -2
  187. infrahub_sdk/ctl/generator.py +6 -3
  188. infrahub_sdk/ctl/graphql.py +184 -0
  189. infrahub_sdk/ctl/repository.py +39 -1
  190. infrahub_sdk/ctl/schema.py +40 -10
  191. infrahub_sdk/ctl/task.py +110 -0
  192. infrahub_sdk/ctl/utils.py +4 -0
  193. infrahub_sdk/ctl/validate.py +5 -3
  194. infrahub_sdk/diff.py +4 -5
  195. infrahub_sdk/exceptions.py +2 -0
  196. infrahub_sdk/generator.py +7 -1
  197. infrahub_sdk/graphql/__init__.py +12 -0
  198. infrahub_sdk/graphql/constants.py +1 -0
  199. infrahub_sdk/graphql/plugin.py +85 -0
  200. infrahub_sdk/graphql/query.py +77 -0
  201. infrahub_sdk/{graphql.py → graphql/renderers.py} +88 -75
  202. infrahub_sdk/graphql/utils.py +40 -0
  203. infrahub_sdk/node/attribute.py +2 -0
  204. infrahub_sdk/node/node.py +28 -20
  205. infrahub_sdk/node/relationship.py +1 -3
  206. infrahub_sdk/playback.py +1 -2
  207. infrahub_sdk/protocols.py +54 -6
  208. infrahub_sdk/pytest_plugin/plugin.py +7 -4
  209. infrahub_sdk/pytest_plugin/utils.py +40 -0
  210. infrahub_sdk/repository.py +1 -2
  211. infrahub_sdk/schema/__init__.py +70 -4
  212. infrahub_sdk/schema/main.py +1 -0
  213. infrahub_sdk/schema/repository.py +8 -0
  214. infrahub_sdk/spec/models.py +7 -0
  215. infrahub_sdk/spec/object.py +54 -6
  216. infrahub_sdk/spec/processors/__init__.py +0 -0
  217. infrahub_sdk/spec/processors/data_processor.py +10 -0
  218. infrahub_sdk/spec/processors/factory.py +34 -0
  219. infrahub_sdk/spec/processors/range_expand_processor.py +56 -0
  220. infrahub_sdk/spec/range_expansion.py +118 -0
  221. infrahub_sdk/task/models.py +6 -4
  222. infrahub_sdk/timestamp.py +18 -6
  223. infrahub_sdk/transforms.py +1 -1
  224. {infrahub_server-1.4.12.dist-info → infrahub_server-1.5.0.dist-info}/METADATA +9 -10
  225. {infrahub_server-1.4.12.dist-info → infrahub_server-1.5.0.dist-info}/RECORD +233 -176
  226. infrahub_testcontainers/container.py +114 -2
  227. infrahub_testcontainers/docker-compose-cluster.test.yml +5 -0
  228. infrahub_testcontainers/docker-compose.test.yml +5 -0
  229. infrahub_testcontainers/models.py +2 -2
  230. infrahub_testcontainers/performance_test.py +4 -4
  231. infrahub/core/convert_object_type/conversion.py +0 -134
  232. {infrahub_server-1.4.12.dist-info → infrahub_server-1.5.0.dist-info}/LICENSE.txt +0 -0
  233. {infrahub_server-1.4.12.dist-info → infrahub_server-1.5.0.dist-info}/WHEEL +0 -0
  234. {infrahub_server-1.4.12.dist-info → infrahub_server-1.5.0.dist-info}/entry_points.txt +0 -0
@@ -133,7 +133,7 @@ class AttributeUpdateNodePropertyQuery(AttributeQuery):
133
133
  def __init__(
134
134
  self,
135
135
  prop_name: str,
136
- prop_id: str,
136
+ prop_id: str | None = None,
137
137
  **kwargs: Any,
138
138
  ):
139
139
  self.prop_name = prop_name
@@ -144,6 +144,8 @@ class AttributeUpdateNodePropertyQuery(AttributeQuery):
144
144
  async def query_init(self, db: InfrahubDatabase, **kwargs: dict[str, Any]) -> None: # noqa: ARG002
145
145
  at = self.at or self.attr.at
146
146
 
147
+ branch_filter, branch_params = self.branch.get_query_filter_path(at=at)
148
+ self.params.update(branch_params)
147
149
  self.params["attr_uuid"] = self.attr.id
148
150
  self.params["branch"] = self.branch.name
149
151
  self.params["branch_level"] = self.branch.hierarchy_level
@@ -151,21 +153,92 @@ class AttributeUpdateNodePropertyQuery(AttributeQuery):
151
153
  self.params["prop_name"] = self.prop_name
152
154
  self.params["prop_id"] = self.prop_id
153
155
 
154
- rel_name = f"HAS_{self.prop_name.upper()}"
156
+ rel_label = f"HAS_{self.prop_name.upper()}"
155
157
 
156
- query = (
158
+ if self.branch.is_default or self.branch.is_global:
159
+ node_query = """
160
+ MATCH (np:Node { uuid: $prop_id })-[r:IS_PART_OF]->(:Root)
161
+ WHERE r.branch IN $branch0
162
+ AND r.status = "active"
163
+ AND r.from <= $at AND (r.to IS NULL OR r.to > $at)
164
+ WITH np
165
+ LIMIT 1
157
166
  """
167
+ else:
168
+ node_query = """
169
+ MATCH (np:Node { uuid: $prop_id })-[r:IS_PART_OF]->(:Root)
170
+ WHERE %(branch_filter)s
171
+ ORDER BY r.branch_level DESC, r.from DESC, r.status ASC
172
+ LIMIT 1
173
+ WITH np
174
+ WHERE r.status = "active"
175
+ """ % {"branch_filter": branch_filter}
176
+ self.add_to_query(node_query)
177
+
178
+ attr_query = """
158
179
  MATCH (a:Attribute { uuid: $attr_uuid })
159
- MATCH (np:Node { uuid: $prop_id })
160
- CREATE (a)-[r:%s { branch: $branch, branch_level: $branch_level, status: "active", from: $at }]->(np)
161
- """
162
- % rel_name
163
- )
180
+ CREATE (a)-[r:%(rel_label)s { branch: $branch, branch_level: $branch_level, status: "active", from: $at }]->(np)
181
+ """ % {"rel_label": rel_label}
182
+ self.add_to_query(attr_query)
164
183
 
165
- self.add_to_query(query)
166
184
  self.return_labels = ["a", "np", "r"]
167
185
 
168
186
 
187
+ class AttributeClearNodePropertyQuery(AttributeQuery):
188
+ name = "attribute_clear_node_property"
189
+ type: QueryType = QueryType.WRITE
190
+ insert_return: bool = False
191
+
192
+ def __init__(
193
+ self,
194
+ prop_name: str,
195
+ prop_id: str | None = None,
196
+ **kwargs: Any,
197
+ ):
198
+ self.prop_name = prop_name
199
+ self.prop_id = prop_id
200
+
201
+ super().__init__(**kwargs)
202
+
203
+ async def query_init(self, db: InfrahubDatabase, **kwargs: dict[str, Any]) -> None: # noqa: ARG002
204
+ at = self.at or self.attr.at
205
+
206
+ branch_filter, branch_params = self.branch.get_query_filter_path(at=at)
207
+ self.params.update(branch_params)
208
+ self.params["attr_uuid"] = self.attr.id
209
+ self.params["branch"] = self.branch.name
210
+ self.params["branch_level"] = self.branch.hierarchy_level
211
+ self.params["at"] = at.to_string()
212
+ self.params["prop_name"] = self.prop_name
213
+ self.params["prop_id"] = self.prop_id
214
+
215
+ rel_label = f"HAS_{self.prop_name.upper()}"
216
+ query = """
217
+ MATCH (a:Attribute { uuid: $attr_uuid })-[r:%(rel_label)s]->(np:Node { uuid: $prop_id })
218
+ WITH DISTINCT a, np
219
+ CALL (a, np) {
220
+ MATCH (a)-[r:%(rel_label)s]->(np)
221
+ WHERE %(branch_filter)s
222
+ ORDER BY r.branch_level DESC, r.from DESC, r.status ASC
223
+ LIMIT 1
224
+ RETURN r AS property_edge
225
+ }
226
+ WITH a, np, property_edge
227
+ WHERE property_edge.status = "active"
228
+ CALL (property_edge) {
229
+ WITH property_edge
230
+ WHERE property_edge.branch = $branch
231
+ SET property_edge.to = $at
232
+ }
233
+ CALL (a, np, property_edge) {
234
+ WITH property_edge
235
+ WHERE property_edge.branch_level < $branch_level
236
+ CREATE (a)-[r:%(rel_label)s { branch: $branch, branch_level: $branch_level, status: "deleted", from: $at }]->(np)
237
+ }
238
+ """ % {"branch_filter": branch_filter, "rel_label": rel_label}
239
+ self.add_to_query(query)
240
+
241
+
169
242
  class AttributeGetQuery(AttributeQuery):
170
243
  name = "attribute_get"
171
244
  type: QueryType = QueryType.READ
@@ -204,7 +277,6 @@ async def default_attribute_query_filter(
204
277
  param_prefix: str | None = None,
205
278
  db: InfrahubDatabase | None = None, # noqa: ARG001
206
279
  partial_match: bool = False,
207
- support_profiles: bool = False,
208
280
  ) -> tuple[list[QueryElement], dict[str, Any], list[str]]:
209
281
  """Generate Query String Snippet to filter the right node."""
210
282
  attribute_value_label = GraphAttributeValueNode.get_default_label()
@@ -251,9 +323,6 @@ async def default_attribute_query_filter(
251
323
  query_where.append(f"toString(av.{filter_name}) =~ ${param_prefix}_{filter_name}")
252
324
  elif filter_name == "isnull":
253
325
  query_filter.append(QueryNode(name="av", labels=[attribute_value_label]))
254
- elif support_profiles:
255
- query_filter.append(QueryNode(name="av", labels=[attribute_value_label]))
256
- query_where.append(f"(av.{filter_name} = ${param_prefix}_{filter_name} OR av.is_default)")
257
326
  else:
258
327
  query_filter.append(
259
328
  QueryNode(
@@ -271,8 +340,6 @@ async def default_attribute_query_filter(
271
340
  if attribute_kind and attribute_kind == "List":
272
341
  query_params[f"{param_prefix}_{filter_name}"] = build_regex_attrs(values=filter_value)
273
342
  query_where.append(f"toString(av.value) =~ ${param_prefix}_{filter_name}")
274
- elif support_profiles:
275
- query_where.append(f"(av.value IN ${param_prefix}_value OR av.is_default)")
276
343
  else:
277
344
  query_where.append(f"av.value IN ${param_prefix}_value")
278
345
  query_params[f"{param_prefix}_value"] = filter_value
@@ -301,13 +301,15 @@ WITH p, q, diff_rel, CASE
301
301
  ELSE $from_time
302
302
  END AS row_from_time
303
303
  ORDER BY %(id_func)s(p) DESC
304
- SKIP $offset
305
- LIMIT $limit
304
+ SKIP toInteger($offset)
305
+ LIMIT toInteger($limit)
306
306
  // -------------------------------------
307
307
  // Add flag to indicate if there is more data after this
308
308
  // -------------------------------------
309
309
  WITH collect([p, q, diff_rel, row_from_time]) AS limited_results
310
- WITH limited_results, size(limited_results) = $limit AS has_more_data
310
+ // extra NULL row ensures that has_more_data is always returned, even if all results are filtered out below
311
+ WITH limited_results + [[NULL, NULL, NULL, NULL]] AS limited_results
312
+ WITH limited_results, size(limited_results) = ($limit + 1) AS has_more_data
311
313
  UNWIND limited_results AS one_result
312
314
  WITH one_result[0] AS p, one_result[1] AS q, one_result[2] AS diff_rel, one_result[3] AS row_from_time, has_more_data
313
315
  // -------------------------------------
@@ -470,14 +472,16 @@ AND (
470
472
  // Limit the number of paths
471
473
  // -------------------------------------
472
474
  WITH root, r_root, p, diff_rel, q
473
- ORDER BY r_root.from, p.uuid, q.uuid, diff_rel.branch, diff_rel.from
474
- SKIP $offset
475
- LIMIT $limit
475
+ ORDER BY r_root.from, p.uuid, q.uuid, q.name, diff_rel.branch, diff_rel.from
476
+ SKIP toInteger($offset)
477
+ LIMIT toInteger($limit)
476
478
  // -------------------------------------
477
479
  // Add flag to indicate if there is more data after this
478
480
  // -------------------------------------
479
481
  WITH collect([root, r_root, p, diff_rel, q]) AS limited_results
480
- WITH limited_results, size(limited_results) = $limit AS has_more_data
482
+ // extra NULL row ensures that has_more_data is always returned, even if all results are filtered out below
483
+ WITH limited_results + [[NULL, NULL, NULL, NULL, NULL]] AS limited_results
484
+ WITH limited_results, size(limited_results) = ($limit + 1) AS has_more_data
481
485
  UNWIND limited_results AS one_result
482
486
  WITH one_result[0] AS root, one_result[1] AS r_root, one_result[2] AS p, one_result[3] AS diff_rel, one_result[4] AS q, has_more_data
483
487
  // -------------------------------------
@@ -641,8 +645,28 @@ AND (
641
645
  )
642
646
  // skip paths where nodes/attrs/rels are updated after $from_time, those are handled in other queries
643
647
  AND (
644
- r_root.from <= $from_time AND (r_root.to IS NULL OR r_root.branch <> diff_rel.branch OR r_root.to <= $from_time)
645
- AND r_node.from <= $from_time AND (r_node.to IS NULL OR r_node.branch <> diff_rel.branch OR r_node.to <= $from_time)
648
+ (
649
+ r_root.branch = diff_rel.branch
650
+ AND r_root.from <= $from_time
651
+ AND (r_root.to IS NULL OR r_root.to >= $to_time)
652
+ )
653
+ OR (
654
+ r_root.branch <> diff_rel.branch
655
+ AND r_root.from <= $from_time
656
+ AND (r_root.to IS NULL OR r_root.to >= $branch_from_time)
657
+ )
658
+ )
659
+ AND (
660
+ (
661
+ r_node.branch = diff_rel.branch
662
+ AND r_node.from <= $from_time
663
+ AND (r_node.to IS NULL OR r_node.to >= $to_time)
664
+ )
665
+ OR (
666
+ r_node.branch <> diff_rel.branch
667
+ AND r_node.from <= $from_time
668
+ AND (r_node.to IS NULL OR r_node.to >= $branch_from_time)
669
+ )
646
670
  )
647
671
  )
648
672
  // time-based filters for new nodes
@@ -658,8 +682,27 @@ AND (
658
682
  )
659
683
  // skip paths where nodes/attrs/rels are updated after $branch_from_time, those are handled in other queries
660
684
  AND (
661
- r_root.from <= $branch_from_time AND (r_root.to IS NULL OR r_root.branch <> diff_rel.branch OR r_root.to <= $branch_from_time)
662
- AND r_node.from <= $branch_from_time AND (r_node.to IS NULL OR r_node.branch <> diff_rel.branch OR r_node.to <= $branch_from_time)
685
+ (
686
+ r_root.branch = diff_rel.branch
687
+ AND (r_root.to IS NULL OR r_root.to >= $to_time)
688
+ )
689
+ OR (
690
+ r_root.branch <> diff_rel.branch
691
+ AND r_root.from <= $branch_from_time
692
+ AND (r_root.to IS NULL OR r_root.to >= $branch_from_time)
693
+ )
694
+ )
695
+ AND (
696
+ (
697
+ r_node.branch = diff_rel.branch
698
+ AND r_node.from <= $branch_from_time
699
+ AND (r_node.to IS NULL OR r_node.to >= $to_time)
700
+ )
701
+ OR (
702
+ r_node.branch <> diff_rel.branch
703
+ AND r_node.from <= $branch_from_time
704
+ AND (r_node.to IS NULL OR r_node.to >= $branch_from_time)
705
+ )
663
706
  )
664
707
  )
665
708
  )
@@ -701,13 +744,15 @@ AND [%(id_func)s(n), type(r_node)] <> [%(id_func)s(q), type(diff_rel)]
701
744
  // -------------------------------------
702
745
  WITH diff_rel_path, r_root, n, r_node, p, diff_rel
703
746
  ORDER BY r_root.from, n.uuid, p.uuid, type(diff_rel), diff_rel.branch, diff_rel.from
704
- SKIP $offset
705
- LIMIT $limit
747
+ SKIP toInteger($offset)
748
+ LIMIT toInteger($limit)
706
749
  // -------------------------------------
707
750
  // Add flag to indicate if there is more data after this
708
751
  // -------------------------------------
709
752
  WITH collect([diff_rel_path, r_root, n, r_node, p, diff_rel]) AS limited_results
710
- WITH limited_results, size(limited_results) = $limit AS has_more_data
753
+ // extra NULL row ensures that has_more_data is always returned, even if all results are filtered out below
754
+ WITH limited_results + [[NULL, NULL, NULL, NULL, NULL, NULL]] AS limited_results
755
+ WITH limited_results, size(limited_results) = ($limit + 1) AS has_more_data
711
756
  UNWIND limited_results AS one_result
712
757
  WITH one_result[0] AS diff_rel_path, one_result[1] AS r_root, one_result[2] AS n,
713
758
  one_result[3] AS r_node, one_result[4] AS p, one_result[5] AS diff_rel, has_more_data
@@ -803,8 +848,8 @@ WHERE num_nodes_with_uuid > 1
803
848
  // -------------------------------------
804
849
  WITH node_uuid
805
850
  ORDER BY node_uuid
806
- SKIP $offset
807
- LIMIT $limit
851
+ SKIP toInteger($offset)
852
+ LIMIT toInteger($limit)
808
853
  WITH collect(node_uuid) AS node_uuids
809
854
  WITH node_uuids, size(node_uuids) = $limit AS has_more_data
810
855
  MATCH (:Root)<-[diff_rel:IS_PART_OF {branch: $branch_name}]-(n:Node)
@@ -450,12 +450,23 @@ class IPPrefixReconcileQuery(Query):
450
450
  // ------------------
451
451
  CALL (ip_node) {
452
452
  OPTIONAL MATCH parent_prefix_path = (ip_node)-[r1:IS_RELATED]->(:Relationship {name: "parent__child"})-[r2:IS_RELATED]->(current_parent:%(ip_prefix_kind)s)
453
- WHERE all(r IN relationships(parent_prefix_path) WHERE (%(branch_filter)s))
453
+ WHERE $is_prefix = TRUE
454
+ AND all(r IN relationships(parent_prefix_path) WHERE (%(branch_filter)s))
454
455
  RETURN current_parent, (r1.status = "active" AND r2.status = "active") AS parent_is_active
455
456
  ORDER BY r1.branch_level DESC, r1.from DESC, r1.status ASC, r2.branch_level DESC, r2.from DESC, r2.status ASC
456
457
  LIMIT 1
457
458
  }
458
- WITH ip_namespace, ip_node, CASE WHEN parent_is_active THEN current_parent ELSE NULL END as current_parent
459
+ WITH ip_namespace, ip_node, CASE WHEN parent_is_active THEN current_parent ELSE NULL END as prefix_parent
460
+ CALL (ip_node) {
461
+ OPTIONAL MATCH parent_prefix_path = (ip_node)-[r1:IS_RELATED]->(:Relationship {name: "ip_prefix__ip_address"})<-[r2:IS_RELATED]-(current_parent:%(ip_prefix_kind)s)
462
+ WHERE $is_prefix = FALSE
463
+ AND all(r IN relationships(parent_prefix_path) WHERE (%(branch_filter)s))
464
+ RETURN current_parent, (r1.status = "active" AND r2.status = "active") AS parent_is_active
465
+ ORDER BY r1.branch_level DESC, r1.from DESC, r1.status ASC, r2.branch_level DESC, r2.from DESC, r2.status ASC
466
+ LIMIT 1
467
+ }
468
+ WITH ip_namespace, ip_node, prefix_parent, CASE WHEN parent_is_active THEN current_parent ELSE NULL END as address_parent
469
+ WITH ip_namespace, ip_node, COALESCE(prefix_parent, address_parent) AS current_parent
459
470
  """ % {
460
471
  "branch_filter": branch_filter,
461
472
  "ip_prefix_kind": InfrahubKind.IPPREFIX,
@@ -467,7 +478,7 @@ class IPPrefixReconcileQuery(Query):
467
478
  // Get prefix node's current prefix children, if any exist
468
479
  // ------------------
469
480
  CALL (ip_node) {
470
- OPTIONAL MATCH child_prefix_path = (ip_node)<-[r1:IS_RELATED]-(:Relationship {name: "parent__child"})<-[r2:IS_RELATED]-(current_prefix_child:%(ip_prefix_kind)s)
481
+ OPTIONAL MATCH child_prefix_path = (ip_node:%(ip_prefix_kind)s)<-[r1:IS_RELATED]-(:Relationship {name: "parent__child"})<-[r2:IS_RELATED]-(current_prefix_child:%(ip_prefix_kind)s)
471
482
  WHERE all(r IN relationships(child_prefix_path) WHERE (%(branch_filter)s))
472
483
  WITH current_prefix_child, (r1.status = "active" AND r2.status = "active") AS is_active
473
484
  ORDER BY current_prefix_child.uuid, r1.branch_level DESC, r1.from DESC, r2.branch_level DESC, r2.from DESC
@@ -479,7 +490,7 @@ class IPPrefixReconcileQuery(Query):
479
490
  // Get prefix node's current address children, if any exist
480
491
  // ------------------
481
492
  CALL (ip_node) {
482
- OPTIONAL MATCH child_address_path = (ip_node)-[r1:IS_RELATED]-(:Relationship {name: "ip_prefix__ip_address"})-[r2:IS_RELATED]-(current_address_child:%(ip_address_kind)s)
493
+ OPTIONAL MATCH child_address_path = (ip_node:%(ip_prefix_kind)s)-[r1:IS_RELATED]->(:Relationship {name: "ip_prefix__ip_address"})<-[r2:IS_RELATED]-(current_address_child:%(ip_address_kind)s)
483
494
  WHERE all(r IN relationships(child_address_path) WHERE (%(branch_filter)s))
484
495
  WITH current_address_child, (r1.status = "active" AND r2.status = "active") AS is_active
485
496
  ORDER BY current_address_child.uuid, r1.branch_level DESC, r1.from DESC, r2.branch_level DESC, r2.from DESC
@@ -688,6 +699,7 @@ class IPPrefixReconcileQuery(Query):
688
699
  "ip_address_attribute_kind": ADDRESS_ATTRIBUTE_LABEL,
689
700
  }
690
701
  self.add_to_query(get_new_children_query)
702
+ self.order_by = ["ip_node.uuid"]
691
703
  self.return_labels = ["ip_node", "current_parent", "current_children", "new_parent", "new_children"]
692
704
 
693
705
  def _get_uuid_from_query(self, node_name: str) -> str | None: