infrahub-server 1.2.9rc0__py3-none-any.whl → 1.3.0a0__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 (166) hide show
  1. infrahub/actions/constants.py +86 -0
  2. infrahub/actions/gather.py +114 -0
  3. infrahub/actions/models.py +241 -0
  4. infrahub/actions/parsers.py +104 -0
  5. infrahub/actions/schema.py +382 -0
  6. infrahub/actions/tasks.py +126 -0
  7. infrahub/actions/triggers.py +21 -0
  8. infrahub/cli/db.py +1 -2
  9. infrahub/computed_attribute/models.py +13 -0
  10. infrahub/computed_attribute/tasks.py +48 -26
  11. infrahub/config.py +9 -0
  12. infrahub/core/account.py +24 -47
  13. infrahub/core/attribute.py +53 -14
  14. infrahub/core/branch/models.py +8 -9
  15. infrahub/core/branch/tasks.py +0 -2
  16. infrahub/core/constants/infrahubkind.py +8 -0
  17. infrahub/core/constraint/node/runner.py +1 -1
  18. infrahub/core/convert_object_type/__init__.py +0 -0
  19. infrahub/core/convert_object_type/conversion.py +122 -0
  20. infrahub/core/convert_object_type/schema_mapping.py +56 -0
  21. infrahub/core/diff/calculator.py +65 -11
  22. infrahub/core/diff/combiner.py +38 -31
  23. infrahub/core/diff/coordinator.py +44 -28
  24. infrahub/core/diff/data_check_synchronizer.py +3 -2
  25. infrahub/core/diff/enricher/hierarchy.py +36 -27
  26. infrahub/core/diff/ipam_diff_parser.py +5 -4
  27. infrahub/core/diff/merger/merger.py +46 -16
  28. infrahub/core/diff/merger/serializer.py +1 -0
  29. infrahub/core/diff/model/field_specifiers_map.py +64 -0
  30. infrahub/core/diff/model/path.py +58 -58
  31. infrahub/core/diff/parent_node_adder.py +14 -16
  32. infrahub/core/diff/query/all_conflicts.py +1 -5
  33. infrahub/core/diff/query/artifact.py +10 -20
  34. infrahub/core/diff/query/diff_get.py +3 -6
  35. infrahub/core/diff/query/drop_nodes.py +42 -0
  36. infrahub/core/diff/query/field_specifiers.py +8 -7
  37. infrahub/core/diff/query/field_summary.py +2 -4
  38. infrahub/core/diff/query/filters.py +15 -1
  39. infrahub/core/diff/query/merge.py +284 -101
  40. infrahub/core/diff/query/save.py +26 -34
  41. infrahub/core/diff/query/summary_counts_enricher.py +34 -54
  42. infrahub/core/diff/query_parser.py +55 -65
  43. infrahub/core/diff/repository/deserializer.py +38 -24
  44. infrahub/core/diff/repository/repository.py +31 -12
  45. infrahub/core/diff/tasks.py +3 -3
  46. infrahub/core/graph/__init__.py +1 -1
  47. infrahub/core/manager.py +14 -11
  48. infrahub/core/migrations/graph/__init__.py +2 -0
  49. infrahub/core/migrations/graph/m003_relationship_parent_optional.py +1 -2
  50. infrahub/core/migrations/graph/m013_convert_git_password_credential.py +2 -4
  51. infrahub/core/migrations/graph/m019_restore_rels_to_time.py +11 -22
  52. infrahub/core/migrations/graph/m020_duplicate_edges.py +3 -6
  53. infrahub/core/migrations/graph/m021_missing_hierarchy_merge.py +1 -2
  54. infrahub/core/migrations/graph/m024_missing_hierarchy_backfill.py +1 -2
  55. infrahub/core/migrations/graph/m027_delete_isolated_nodes.py +50 -0
  56. infrahub/core/migrations/graph/m028_delete_diffs.py +38 -0
  57. infrahub/core/migrations/query/attribute_add.py +1 -2
  58. infrahub/core/migrations/query/attribute_rename.py +3 -6
  59. infrahub/core/migrations/query/delete_element_in_schema.py +3 -6
  60. infrahub/core/migrations/query/node_duplicate.py +3 -6
  61. infrahub/core/migrations/query/relationship_duplicate.py +3 -6
  62. infrahub/core/migrations/schema/node_attribute_remove.py +3 -6
  63. infrahub/core/migrations/schema/node_remove.py +3 -6
  64. infrahub/core/models.py +29 -2
  65. infrahub/core/node/__init__.py +18 -4
  66. infrahub/core/node/create.py +211 -0
  67. infrahub/core/protocols.py +51 -0
  68. infrahub/core/protocols_base.py +3 -0
  69. infrahub/core/query/__init__.py +2 -2
  70. infrahub/core/query/branch.py +27 -17
  71. infrahub/core/query/diff.py +186 -81
  72. infrahub/core/query/ipam.py +10 -20
  73. infrahub/core/query/node.py +65 -49
  74. infrahub/core/query/relationship.py +156 -58
  75. infrahub/core/query/resource_manager.py +1 -2
  76. infrahub/core/query/subquery.py +4 -6
  77. infrahub/core/relationship/model.py +4 -1
  78. infrahub/core/schema/__init__.py +2 -1
  79. infrahub/core/schema/attribute_parameters.py +36 -0
  80. infrahub/core/schema/attribute_schema.py +83 -8
  81. infrahub/core/schema/basenode_schema.py +25 -1
  82. infrahub/core/schema/definitions/core/__init__.py +21 -0
  83. infrahub/core/schema/definitions/internal.py +13 -3
  84. infrahub/core/schema/generated/attribute_schema.py +9 -3
  85. infrahub/core/schema/schema_branch.py +15 -7
  86. infrahub/core/validators/__init__.py +5 -1
  87. infrahub/core/validators/attribute/choices.py +1 -2
  88. infrahub/core/validators/attribute/enum.py +1 -2
  89. infrahub/core/validators/attribute/kind.py +1 -2
  90. infrahub/core/validators/attribute/length.py +13 -6
  91. infrahub/core/validators/attribute/optional.py +1 -2
  92. infrahub/core/validators/attribute/regex.py +5 -5
  93. infrahub/core/validators/attribute/unique.py +1 -3
  94. infrahub/core/validators/determiner.py +18 -2
  95. infrahub/core/validators/enum.py +7 -0
  96. infrahub/core/validators/node/hierarchy.py +3 -6
  97. infrahub/core/validators/query.py +1 -3
  98. infrahub/core/validators/relationship/count.py +6 -12
  99. infrahub/core/validators/relationship/optional.py +2 -4
  100. infrahub/core/validators/relationship/peer.py +3 -8
  101. infrahub/core/validators/tasks.py +1 -1
  102. infrahub/core/validators/uniqueness/query.py +12 -9
  103. infrahub/database/__init__.py +1 -3
  104. infrahub/events/group_action.py +1 -0
  105. infrahub/graphql/analyzer.py +139 -18
  106. infrahub/graphql/app.py +1 -1
  107. infrahub/graphql/loaders/node.py +1 -1
  108. infrahub/graphql/loaders/peers.py +1 -1
  109. infrahub/graphql/manager.py +4 -0
  110. infrahub/graphql/mutations/action.py +164 -0
  111. infrahub/graphql/mutations/convert_object_type.py +62 -0
  112. infrahub/graphql/mutations/main.py +24 -175
  113. infrahub/graphql/mutations/proposed_change.py +21 -18
  114. infrahub/graphql/queries/convert_object_type_mapping.py +36 -0
  115. infrahub/graphql/queries/diff/tree.py +2 -1
  116. infrahub/graphql/queries/relationship.py +1 -1
  117. infrahub/graphql/resolvers/many_relationship.py +4 -4
  118. infrahub/graphql/resolvers/resolver.py +4 -4
  119. infrahub/graphql/resolvers/single_relationship.py +2 -2
  120. infrahub/graphql/schema.py +6 -0
  121. infrahub/graphql/subscription/graphql_query.py +2 -2
  122. infrahub/graphql/types/branch.py +1 -1
  123. infrahub/menu/menu.py +31 -0
  124. infrahub/message_bus/messages/__init__.py +0 -10
  125. infrahub/message_bus/operations/__init__.py +0 -8
  126. infrahub/message_bus/operations/refresh/registry.py +1 -1
  127. infrahub/patch/queries/consolidate_duplicated_nodes.py +3 -6
  128. infrahub/patch/queries/delete_duplicated_edges.py +5 -10
  129. infrahub/prefect_server/models.py +1 -19
  130. infrahub/proposed_change/models.py +68 -3
  131. infrahub/proposed_change/tasks.py +907 -30
  132. infrahub/task_manager/models.py +10 -6
  133. infrahub/telemetry/database.py +1 -1
  134. infrahub/telemetry/tasks.py +1 -1
  135. infrahub/trigger/catalogue.py +2 -0
  136. infrahub/trigger/models.py +29 -3
  137. infrahub/trigger/setup.py +51 -15
  138. infrahub/trigger/tasks.py +4 -5
  139. infrahub/types.py +1 -1
  140. infrahub/webhook/models.py +2 -1
  141. infrahub/workflows/catalogue.py +85 -0
  142. infrahub/workflows/initialization.py +1 -3
  143. infrahub_sdk/timestamp.py +2 -2
  144. {infrahub_server-1.2.9rc0.dist-info → infrahub_server-1.3.0a0.dist-info}/METADATA +4 -4
  145. {infrahub_server-1.2.9rc0.dist-info → infrahub_server-1.3.0a0.dist-info}/RECORD +153 -146
  146. infrahub_testcontainers/container.py +0 -1
  147. infrahub_testcontainers/docker-compose.test.yml +4 -4
  148. infrahub_testcontainers/helpers.py +8 -2
  149. infrahub_testcontainers/performance_test.py +6 -3
  150. infrahub/message_bus/messages/check_generator_run.py +0 -26
  151. infrahub/message_bus/messages/finalize_validator_execution.py +0 -15
  152. infrahub/message_bus/messages/proposed_change/base_with_diff.py +0 -16
  153. infrahub/message_bus/messages/proposed_change/request_proposedchange_refreshartifacts.py +0 -11
  154. infrahub/message_bus/messages/request_generatordefinition_check.py +0 -20
  155. infrahub/message_bus/messages/request_proposedchange_pipeline.py +0 -23
  156. infrahub/message_bus/operations/check/__init__.py +0 -3
  157. infrahub/message_bus/operations/check/generator.py +0 -156
  158. infrahub/message_bus/operations/finalize/__init__.py +0 -3
  159. infrahub/message_bus/operations/finalize/validator.py +0 -133
  160. infrahub/message_bus/operations/requests/__init__.py +0 -9
  161. infrahub/message_bus/operations/requests/generator_definition.py +0 -140
  162. infrahub/message_bus/operations/requests/proposed_change.py +0 -629
  163. /infrahub/{message_bus/messages/proposed_change → actions}/__init__.py +0 -0
  164. {infrahub_server-1.2.9rc0.dist-info → infrahub_server-1.3.0a0.dist-info}/LICENSE.txt +0 -0
  165. {infrahub_server-1.2.9rc0.dist-info → infrahub_server-1.3.0a0.dist-info}/WHEEL +0 -0
  166. {infrahub_server-1.2.9rc0.dist-info → infrahub_server-1.3.0a0.dist-info}/entry_points.txt +0 -0
@@ -81,6 +81,21 @@ def deserialize_tracking_id(tracking_id_str: str) -> TrackingId:
81
81
  raise ValueError(f"{tracking_id_str} is not a valid TrackingId")
82
82
 
83
83
 
84
+ @dataclass
85
+ class NodeIdentifier:
86
+ """Uniquely identifying nodes that have had their kind or inheritance updated requires all of these fields"""
87
+
88
+ uuid: str
89
+ kind: str
90
+ db_id: str
91
+
92
+ def __hash__(self) -> int:
93
+ return hash(f"{self.uuid}:{self.kind}:{self.db_id}")
94
+
95
+ def __str__(self) -> str:
96
+ return f"{self.kind} '{self.uuid}' ({self.db_id})"
97
+
98
+
84
99
  @dataclass
85
100
  class NodeDiffFieldSummary:
86
101
  kind: str
@@ -307,18 +322,26 @@ class ParentNodeInfo:
307
322
 
308
323
  @dataclass
309
324
  class EnrichedDiffNode(BaseSummary):
310
- uuid: str
311
- kind: str
325
+ identifier: NodeIdentifier
312
326
  label: str
313
327
  path_identifier: str = field(default="", kw_only=True)
314
328
  changed_at: Timestamp | None = field(default=None, kw_only=True)
315
329
  action: DiffAction
330
+ is_node_kind_migration: bool = field(default=False)
316
331
  conflict: EnrichedDiffConflict | None = field(default=None)
317
332
  attributes: set[EnrichedDiffAttribute] = field(default_factory=set)
318
333
  relationships: set[EnrichedDiffRelationship] = field(default_factory=set)
319
334
 
320
335
  def __hash__(self) -> int:
321
- return hash(self.uuid)
336
+ return hash(self.identifier)
337
+
338
+ @property
339
+ def uuid(self) -> str:
340
+ return self.identifier.uuid
341
+
342
+ @property
343
+ def kind(self) -> str:
344
+ return self.identifier.kind
322
345
 
323
346
  @property
324
347
  def num_properties(self) -> int:
@@ -402,11 +425,11 @@ class EnrichedDiffNode(BaseSummary):
402
425
  @classmethod
403
426
  def from_calculated_node(cls, calculated_node: DiffNode) -> EnrichedDiffNode:
404
427
  return EnrichedDiffNode(
405
- uuid=calculated_node.uuid,
406
- kind=calculated_node.kind,
428
+ identifier=calculated_node.identifier,
407
429
  label="",
408
430
  changed_at=calculated_node.changed_at,
409
431
  action=calculated_node.action,
432
+ is_node_kind_migration=calculated_node.is_node_kind_migration,
410
433
  attributes={
411
434
  EnrichedDiffAttribute.from_calculated_attribute(calculated_attribute=attr)
412
435
  for attr in calculated_node.attributes
@@ -473,24 +496,24 @@ class EnrichedDiffRoot(EnrichedDiffRootMetadata):
473
496
  nodes_with_parent_uuids |= {child_n.uuid for child_n in r.nodes}
474
497
  return {node for node in self.nodes if node.uuid not in nodes_with_parent_uuids}
475
498
 
476
- def get_node(self, node_uuid: str) -> EnrichedDiffNode:
499
+ def get_node(self, node_identifier: NodeIdentifier) -> EnrichedDiffNode:
477
500
  for n in self.nodes:
478
- if n.uuid == node_uuid:
501
+ if n.identifier == node_identifier:
479
502
  return n
480
- raise ValueError(f"No node {node_uuid} in diff root")
503
+ raise ValueError(f"No node {node_identifier} in diff root")
481
504
 
482
- def has_node(self, node_uuid: str) -> bool:
505
+ def has_node(self, node_identifier: NodeIdentifier) -> bool:
483
506
  try:
484
- self.get_node(node_uuid=node_uuid)
507
+ self.get_node(node_identifier=node_identifier)
485
508
  return True
486
509
  except ValueError:
487
510
  return False
488
511
 
489
- def get_node_map(self, node_uuids: set[str] | None = None) -> dict[str, EnrichedDiffNode]:
512
+ def get_node_map(self, node_uuids: set[str] | None = None) -> dict[NodeIdentifier, EnrichedDiffNode]:
490
513
  node_map = {}
491
514
  for node in self.nodes:
492
- if node_uuids is None or node.uuid in node_uuids:
493
- node_map[node.uuid] = node
515
+ if node_uuids is None or node.identifier.uuid in node_uuids:
516
+ node_map[node.identifier] = node
494
517
  return node_map
495
518
 
496
519
  def get_all_conflicts(self) -> dict[str, EnrichedDiffConflict]:
@@ -522,49 +545,6 @@ class EnrichedDiffRoot(EnrichedDiffRootMetadata):
522
545
  nodes={EnrichedDiffNode.from_calculated_node(calculated_node=n) for n in calculated_diff.nodes},
523
546
  )
524
547
 
525
- def add_parent(
526
- self,
527
- node_id: str,
528
- parent_id: str,
529
- parent_kind: str,
530
- parent_label: str,
531
- parent_rel_name: str,
532
- parent_rel_identifier: str,
533
- parent_rel_cardinality: RelationshipCardinality,
534
- parent_rel_label: str = "",
535
- ) -> EnrichedDiffNode:
536
- node = self.get_node(node_uuid=node_id)
537
- if not self.has_node(node_uuid=parent_id):
538
- parent = EnrichedDiffNode(
539
- uuid=parent_id,
540
- kind=parent_kind,
541
- label=parent_label,
542
- action=DiffAction.UNCHANGED,
543
- changed_at=None,
544
- )
545
- self.nodes.add(parent)
546
-
547
- else:
548
- parent = self.get_node(node_uuid=parent_id)
549
-
550
- if node.has_relationship(name=parent_rel_name):
551
- rel = node.get_relationship(name=parent_rel_name)
552
- rel.nodes.add(parent)
553
- else:
554
- node.relationships.add(
555
- EnrichedDiffRelationship(
556
- name=parent_rel_name,
557
- identifier=parent_rel_identifier,
558
- label=parent_rel_label,
559
- cardinality=parent_rel_cardinality,
560
- changed_at=None,
561
- action=DiffAction.UNCHANGED,
562
- nodes={parent},
563
- )
564
- )
565
-
566
- return parent
567
-
568
548
 
569
549
  @dataclass
570
550
  class EnrichedDiffsMetadata:
@@ -650,6 +630,14 @@ class EnrichedDiffs(EnrichedDiffsMetadata):
650
630
  def branch_node_uuids(self) -> set[str]:
651
631
  return {n.uuid for n in self.diff_branch_diff.nodes}
652
632
 
633
+ @property
634
+ def base_node_identifiers(self) -> set[NodeIdentifier]:
635
+ return {n.identifier for n in self.base_branch_diff.nodes}
636
+
637
+ @property
638
+ def branch_node_identifiers(self) -> set[NodeIdentifier]:
639
+ return {n.identifier for n in self.diff_branch_diff.nodes}
640
+
653
641
 
654
642
  @dataclass
655
643
  class CalculatedDiffs:
@@ -697,13 +685,21 @@ class DiffRelationship:
697
685
 
698
686
  @dataclass
699
687
  class DiffNode:
700
- uuid: str
701
- kind: str
688
+ identifier: NodeIdentifier
702
689
  changed_at: Timestamp
703
690
  action: DiffAction
691
+ is_node_kind_migration: bool = field(default=False)
704
692
  attributes: list[DiffAttribute] = field(default_factory=list)
705
693
  relationships: list[DiffRelationship] = field(default_factory=list)
706
694
 
695
+ @property
696
+ def uuid(self) -> str:
697
+ return self.identifier.uuid
698
+
699
+ @property
700
+ def kind(self) -> str:
701
+ return self.identifier.kind
702
+
707
703
 
708
704
  @dataclass
709
705
  class DiffRoot:
@@ -780,6 +776,10 @@ class DatabasePath:
780
776
  def node_db_id(self) -> str:
781
777
  return self.node_node.element_id
782
778
 
779
+ @property
780
+ def node_labels(self) -> frozenset[str]:
781
+ return self.node_node.labels
782
+
783
783
  @property
784
784
  def node_kind(self) -> str:
785
785
  return str(self.node_node.get("kind"))
@@ -2,14 +2,13 @@ from dataclasses import dataclass, field
2
2
 
3
3
  from infrahub.core.constants import DiffAction, RelationshipCardinality
4
4
 
5
- from .model.path import EnrichedDiffNode, EnrichedDiffRelationship, EnrichedDiffRoot
5
+ from .model.path import EnrichedDiffNode, EnrichedDiffRelationship, EnrichedDiffRoot, NodeIdentifier
6
6
 
7
7
 
8
8
  @dataclass
9
9
  class ParentNodeAddRequest:
10
- node_id: str
11
- parent_id: str
12
- parent_kind: str
10
+ node_identifier: NodeIdentifier
11
+ parent_identifier: NodeIdentifier
13
12
  parent_label: str
14
13
  parent_rel_name: str
15
14
  parent_rel_identifier: str
@@ -20,7 +19,7 @@ class ParentNodeAddRequest:
20
19
  class DiffParentNodeAdder:
21
20
  def __init__(self) -> None:
22
21
  self._diff_root: EnrichedDiffRoot | None = None
23
- self._node_map: dict[str, EnrichedDiffNode] = {}
22
+ self._node_map: dict[NodeIdentifier, EnrichedDiffNode] = {}
24
23
 
25
24
  def initialize(self, enriched_diff_root: EnrichedDiffRoot) -> None:
26
25
  self._diff_root = enriched_diff_root
@@ -31,33 +30,32 @@ class DiffParentNodeAdder:
31
30
  raise RuntimeError("Must call initialize before using")
32
31
  return self._diff_root
33
32
 
34
- def get_node(self, node_uuid: str) -> EnrichedDiffNode:
35
- return self._node_map[node_uuid]
33
+ def get_node(self, identifier: NodeIdentifier) -> EnrichedDiffNode:
34
+ return self._node_map[identifier]
36
35
 
37
- def has_node(self, node_uuid: str) -> bool:
38
- return node_uuid in self._node_map
36
+ def has_node(self, identifier: NodeIdentifier) -> bool:
37
+ return identifier in self._node_map
39
38
 
40
39
  def add_node(self, node: EnrichedDiffNode) -> None:
41
- if node.uuid in self._node_map:
40
+ if node.identifier in self._node_map:
42
41
  return
43
- self._node_map[node.uuid] = node
42
+ self._node_map[node.identifier] = node
44
43
  self.get_root().nodes.add(node)
45
44
 
46
45
  def add_parent(self, parent_request: ParentNodeAddRequest) -> EnrichedDiffNode:
47
46
  if not self._diff_root:
48
47
  raise RuntimeError("Must call initialize before using")
49
- node = self.get_node(node_uuid=parent_request.node_id)
50
- if not self.has_node(node_uuid=parent_request.parent_id):
48
+ node = self.get_node(identifier=parent_request.node_identifier)
49
+ if not self.has_node(identifier=parent_request.parent_identifier):
51
50
  parent = EnrichedDiffNode(
52
- uuid=parent_request.parent_id,
53
- kind=parent_request.parent_kind,
51
+ identifier=parent_request.parent_identifier,
54
52
  label=parent_request.parent_label,
55
53
  action=DiffAction.UNCHANGED,
56
54
  changed_at=None,
57
55
  )
58
56
  self.add_node(parent)
59
57
  else:
60
- parent = self.get_node(node_uuid=parent_request.parent_id)
58
+ parent = self.get_node(identifier=parent_request.parent_identifier)
61
59
 
62
60
  try:
63
61
  rel = node.get_relationship(name=parent_request.parent_rel_name)
@@ -37,22 +37,18 @@ AND (
37
37
  ($diff_id IS NOT NULL AND root.uuid = $diff_id)
38
38
  OR ($tracking_id IS NOT NULL AND root.tracking_id = $tracking_id AND root.diff_branch = $diff_branch_name)
39
39
  )
40
- CALL {
41
- WITH root
40
+ CALL (root) {
42
41
  MATCH (root)-[:DIFF_HAS_NODE]->(node:DiffNode)-[:DIFF_HAS_CONFLICT]->(node_conflict:DiffConflict)
43
42
  RETURN node.path_identifier AS path_identifier, node_conflict AS conflict
44
43
  UNION
45
- WITH root
46
44
  MATCH (root)-[:DIFF_HAS_NODE]->(node:DiffNode)-[:DIFF_HAS_ATTRIBUTE]->(:DiffAttribute)
47
45
  -[:DIFF_HAS_PROPERTY]->(property:DiffProperty)-[:DIFF_HAS_CONFLICT]->(attr_property_conflict:DiffConflict)
48
46
  RETURN property.path_identifier AS path_identifier, attr_property_conflict AS conflict
49
47
  UNION
50
- WITH root
51
48
  MATCH (root)-[:DIFF_HAS_NODE]->(node:DiffNode)-[:DIFF_HAS_RELATIONSHIP]->(:DiffRelationship)
52
49
  -[:DIFF_HAS_ELEMENT]->(element:DiffRelationshipElement)-[:DIFF_HAS_CONFLICT]->(rel_element_conflict:DiffConflict)
53
50
  RETURN element.path_identifier AS path_identifier, rel_element_conflict AS conflict
54
51
  UNION
55
- WITH root
56
52
  MATCH (root)-[:DIFF_HAS_NODE]->(node:DiffNode)-[:DIFF_HAS_RELATIONSHIP]->(:DiffRelationship)
57
53
  -[:DIFF_HAS_ELEMENT]->(:DiffRelationshipElement)-[:DIFF_HAS_PROPERTY]->(property:DiffProperty)
58
54
  -[:DIFF_HAS_CONFLICT]->(rel_property_conflict:DiffConflict)
@@ -40,8 +40,7 @@ class ArtifactDiffQuery(Query):
40
40
  // -----------------------
41
41
  MATCH (source_artifact:%(artifact_kind)s)-[r:IS_PART_OF]->(:Root)
42
42
  WHERE r.branch IN [$source_branch_name, $target_branch_name]
43
- CALL {
44
- WITH source_artifact
43
+ CALL (source_artifact) {
45
44
  MATCH (source_artifact)-[r:IS_PART_OF]->(:Root)
46
45
  WHERE %(source_branch_filter)s
47
46
  RETURN r AS root_rel
@@ -50,13 +49,11 @@ CALL {
50
49
  }
51
50
  WITH source_artifact, root_rel
52
51
  WHERE root_rel.status = "active"
53
- CALL {
54
- WITH source_artifact
52
+ CALL (source_artifact) {
55
53
  // -----------------------
56
54
  // get the artifact's target node
57
55
  // -----------------------
58
- CALL {
59
- WITH source_artifact
56
+ CALL (source_artifact) {
60
57
  OPTIONAL MATCH (source_artifact)-[rrel1:IS_RELATED]-(rel_node:Relationship)-[rrel2:IS_RELATED]-(target_node:Node)
61
58
  WHERE rel_node.name = $target_rel_identifier
62
59
  AND all(r IN [rrel1, rrel2] WHERE ( %(source_branch_filter)s ))
@@ -70,8 +67,7 @@ CALL {
70
67
  // -----------------------
71
68
  // get the artifact's definition node
72
69
  // -----------------------
73
- CALL {
74
- WITH source_artifact
70
+ CALL (source_artifact) {
75
71
  OPTIONAL MATCH (source_artifact)-[rrel1:IS_RELATED]-(rel_node:Relationship)-[rrel2:IS_RELATED]-(definition_node:Node)
76
72
  WHERE rel_node.name = $definition_rel_identifier
77
73
  AND all(r IN [rrel1, rrel2] WHERE ( %(source_branch_filter)s ))
@@ -85,8 +81,7 @@ CALL {
85
81
  // -----------------------
86
82
  // get the artifact's checksum
87
83
  // -----------------------
88
- CALL {
89
- WITH source_artifact
84
+ CALL (source_artifact) {
90
85
  OPTIONAL MATCH (source_artifact)-[attr_rel:HAS_ATTRIBUTE]->(attr:Attribute)-[value_rel:HAS_VALUE]->(attr_val:AttributeValue)
91
86
  WHERE attr.name = "checksum"
92
87
  AND all(r IN [attr_rel, value_rel] WHERE ( %(source_branch_filter)s ))
@@ -100,8 +95,7 @@ CALL {
100
95
  // -----------------------
101
96
  // get the artifact's storage_id
102
97
  // -----------------------
103
- CALL {
104
- WITH source_artifact
98
+ CALL (source_artifact) {
105
99
  OPTIONAL MATCH (source_artifact)-[attr_rel:HAS_ATTRIBUTE]->(attr:Attribute)-[value_rel:HAS_VALUE]->(attr_val:AttributeValue)
106
100
  WHERE attr.name = "storage_id"
107
101
  AND all(r IN [attr_rel, value_rel] WHERE ( %(source_branch_filter)s ))
@@ -137,13 +131,11 @@ CALL {
137
131
  ELSE NULL
138
132
  END AS source_storage_id
139
133
  }
140
- CALL {
134
+ CALL (target_node, definition_node){
141
135
  // -----------------------
142
136
  // get the corresponding artifact on the target branch, if it exists
143
137
  // -----------------------
144
- WITH target_node, definition_node
145
- CALL {
146
- WITH target_node, definition_node
138
+ CALL (target_node, definition_node) {
147
139
  OPTIONAL MATCH path = (target_node)-[trel1:IS_RELATED]-(trel_node:Relationship)-[trel2:IS_RELATED]-
148
140
  (target_artifact:%(artifact_kind)s)-[drel1:IS_RELATED]-(drel_node:Relationship)-[drel2:IS_RELATED]-(definition_node)
149
141
  WHERE trel_node.name = $target_rel_identifier
@@ -165,8 +157,7 @@ CALL {
165
157
  // -----------------------
166
158
  // get the artifact's checksum on target branch
167
159
  // -----------------------
168
- CALL {
169
- WITH target_artifact
160
+ CALL (target_artifact) {
170
161
  OPTIONAL MATCH (target_artifact)-[attr_rel:HAS_ATTRIBUTE]->(attr:Attribute)-[value_rel:HAS_VALUE]->(attr_val:AttributeValue)
171
162
  WHERE attr.name = "checksum"
172
163
  AND attr_rel.branch = $target_branch_name
@@ -178,8 +169,7 @@ CALL {
178
169
  // -----------------------
179
170
  // get the artifact's storage_id on target branch
180
171
  // -----------------------
181
- CALL {
182
- WITH target_artifact
172
+ CALL (target_artifact) {
183
173
  OPTIONAL MATCH (target_artifact)-[attr_rel:HAS_ATTRIBUTE]->(attr:Attribute)-[value_rel:HAS_VALUE]->(attr_val:AttributeValue)
184
174
  WHERE attr.name = "storage_id"
185
175
  AND attr_rel.branch = $target_branch_name
@@ -96,8 +96,7 @@ class EnrichedDiffGetQuery(Query):
96
96
  // -------------------------------------
97
97
  // Retrieve Parents
98
98
  // -------------------------------------
99
- CALL {
100
- WITH diff_node
99
+ CALL (diff_node) {
101
100
  OPTIONAL MATCH parents_path = (diff_node)-[:DIFF_HAS_RELATIONSHIP|DIFF_HAS_NODE*1..%(max_depth)s]->(:DiffNode)
102
101
  RETURN parents_path
103
102
  ORDER BY size(nodes(parents_path)) DESC
@@ -112,8 +111,7 @@ class EnrichedDiffGetQuery(Query):
112
111
  // -------------------------------------
113
112
  // Retrieve Attributes
114
113
  // -------------------------------------
115
- CALL {
116
- WITH diff_node
114
+ CALL (diff_node) {
117
115
  OPTIONAL MATCH (diff_node)-[:DIFF_HAS_ATTRIBUTE]->(diff_attribute:DiffAttribute)
118
116
  WITH diff_attribute
119
117
  OPTIONAL MATCH (diff_attribute)-[:DIFF_HAS_PROPERTY]->(diff_attr_property:DiffProperty)
@@ -126,8 +124,7 @@ class EnrichedDiffGetQuery(Query):
126
124
  // -------------------------------------
127
125
  // Retrieve Relationships
128
126
  // -------------------------------------
129
- CALL {
130
- WITH diff_node
127
+ CALL (diff_node) {
131
128
  OPTIONAL MATCH (diff_node)-[:DIFF_HAS_RELATIONSHIP]->(diff_relationship:DiffRelationship)
132
129
  WITH diff_relationship
133
130
  OPTIONAL MATCH (diff_relationship)-[:DIFF_HAS_ELEMENT]->(diff_rel_element:DiffRelationshipElement)
@@ -0,0 +1,42 @@
1
+ from typing import Any
2
+
3
+ from infrahub.core.diff.model.path import NodeIdentifier
4
+ from infrahub.core.query import Query, QueryType
5
+ from infrahub.database import InfrahubDatabase
6
+
7
+
8
+ class EnrichedDiffDropNodesQuery(Query):
9
+ name = "enriched_diff_drop_nodes"
10
+ type = QueryType.WRITE
11
+ insert_return = False
12
+
13
+ def __init__(self, enriched_diff_uuid: str, node_identifiers: list[NodeIdentifier], **kwargs: Any) -> None:
14
+ super().__init__(**kwargs)
15
+ self.enriched_diff_uuid = enriched_diff_uuid
16
+ self.node_identifiers = node_identifiers
17
+
18
+ async def query_init(self, db: InfrahubDatabase, **kwargs: Any) -> None: # noqa: ARG002
19
+ self.params = {
20
+ "diff_root_uuid": self.enriched_diff_uuid,
21
+ "node_uuids": [ni.uuid for ni in self.node_identifiers],
22
+ "node_identifiers_map": {ni.uuid: ni.kind for ni in self.node_identifiers},
23
+ }
24
+ query = """
25
+ MATCH (d_root:DiffRoot {uuid: $diff_root_uuid})
26
+ MATCH (d_root)-[:DIFF_HAS_NODE]->(dn:DiffNode)
27
+ WHERE dn.uuid IN $node_uuids
28
+ AND dn.kind IN $node_identifiers_map[dn.uuid]
29
+ OPTIONAL MATCH (dn)-[:DIFF_HAS_ATTRIBUTE]-(da:DiffAttribute)
30
+ OPTIONAL MATCH (da)-[*]->(diff_thing)
31
+ DETACH DELETE diff_thing
32
+ DETACH DELETE da
33
+ WITH dn
34
+ OPTIONAL MATCH (dn)-[:DIFF_HAS_RELATIONSHIP]->(dr:DiffRelationship)
35
+ OPTIONAL MATCH (dr)-[:DIFF_HAS_ELEMENT]->(dre:DiffRelationshipElement)
36
+ OPTIONAL MATCH (dre)-[*]->(diff_thing)
37
+ DETACH DELETE diff_thing
38
+ DETACH DELETE dre
39
+ DETACH DELETE dr
40
+ DETACH DELETE dn
41
+ """
42
+ self.add_to_query(query=query)
@@ -18,20 +18,21 @@ class EnrichedDiffFieldSpecifiersQuery(Query):
18
18
  CALL {
19
19
  MATCH (root:DiffRoot {uuid: $diff_id})-[:DIFF_HAS_NODE]->(node:DiffNode)-[:DIFF_HAS_ATTRIBUTE]->(attr:DiffAttribute)
20
20
  WHERE (root.is_merged IS NULL OR root.is_merged <> TRUE)
21
- RETURN node.uuid AS node_uuid, attr.name AS field_name
21
+ RETURN node.uuid AS node_uuid, node.kind AS node_kind, attr.name AS field_name
22
22
  UNION
23
23
  MATCH (root:DiffRoot {uuid: $diff_id})-[:DIFF_HAS_NODE]->(node:DiffNode)-[:DIFF_HAS_RELATIONSHIP]->(rel:DiffRelationship)
24
24
  WHERE (root.is_merged IS NULL OR root.is_merged <> TRUE)
25
- RETURN node.uuid AS node_uuid, rel.identifier AS field_name
25
+ RETURN node.uuid AS node_uuid, node.kind AS node_kind, rel.identifier AS field_name
26
26
  }
27
27
  """
28
28
  self.add_to_query(query=query)
29
- self.return_labels = ["node_uuid", "field_name"]
30
- self.order_by = ["node_uuid", "field_name"]
29
+ self.return_labels = ["node_uuid", "node_kind", "field_name"]
30
+ self.order_by = ["node_uuid", "node_kind", "field_name"]
31
31
 
32
- def get_node_field_specifier_tuples(self) -> Generator[tuple[str, str], None, None]:
32
+ def get_node_field_specifier_tuples(self) -> Generator[tuple[str, str, str], None, None]:
33
33
  for result in self.get_results():
34
34
  node_uuid = result.get_as_str("node_uuid")
35
+ node_kind = result.get_as_str("node_kind")
35
36
  field_name = result.get_as_str("field_name")
36
- if node_uuid and field_name:
37
- yield (node_uuid, field_name)
37
+ if node_uuid and node_kind and field_name:
38
+ yield (node_uuid, node_kind, field_name)
@@ -45,8 +45,7 @@ class EnrichedDiffNodeFieldSummaryQuery(Query):
45
45
  OPTIONAL MATCH (diff_root)-[:DIFF_HAS_NODE]->(n:DiffNode)
46
46
  WHERE n.action <> $unchanged_str
47
47
  WITH DISTINCT n.kind AS kind
48
- CALL {
49
- WITH kind
48
+ CALL (kind) {
50
49
  OPTIONAL MATCH (n:DiffNode {kind: kind})-[:DIFF_HAS_ATTRIBUTE]->(a:DiffAttribute)
51
50
  WHERE n.action <> $unchanged_str
52
51
  AND a.action <> $unchanged_str
@@ -54,8 +53,7 @@ class EnrichedDiffNodeFieldSummaryQuery(Query):
54
53
  RETURN collect(attr_name) AS attr_names
55
54
  }
56
55
  WITH kind, attr_names
57
- CALL {
58
- WITH kind
56
+ CALL (kind) {
59
57
  OPTIONAL MATCH (n:DiffNode {kind: kind})-[:DIFF_HAS_RELATIONSHIP]->(r:DiffRelationship)
60
58
  WHERE n.action <> $unchanged_str
61
59
  AND r.action <> $unchanged_str
@@ -1,6 +1,10 @@
1
+ from collections import defaultdict
2
+ from typing import Any
3
+
1
4
  from pydantic import BaseModel, Field
2
5
 
3
6
  from infrahub.core.constants import DiffAction
7
+ from infrahub.core.diff.model.path import NodeIdentifier
4
8
  from infrahub.core.query.utils import filter_and, filter_or
5
9
 
6
10
 
@@ -29,6 +33,7 @@ class IncExclActionFilterOptions(BaseModel):
29
33
  class EnrichedDiffQueryFilters(BaseModel):
30
34
  ids: list[str] = Field(default_factory=list)
31
35
  kind: IncExclFilterOptions = IncExclFilterOptions()
36
+ identifiers: list[NodeIdentifier] = Field(default_factory=list)
32
37
  namespace: IncExclFilterOptions = IncExclFilterOptions()
33
38
  status: IncExclActionFilterOptions = IncExclActionFilterOptions()
34
39
  only_conflicted: bool = Field(default=False)
@@ -37,6 +42,7 @@ class EnrichedDiffQueryFilters(BaseModel):
37
42
  def is_empty(self) -> bool:
38
43
  if (
39
44
  not self.ids
45
+ and not self.identifiers
40
46
  and self.only_conflicted is False
41
47
  and self.kind.is_empty
42
48
  and self.namespace.is_empty
@@ -48,12 +54,20 @@ class EnrichedDiffQueryFilters(BaseModel):
48
54
  def generate(self) -> tuple[str, dict]:
49
55
  default_filter = ""
50
56
 
51
- params = {}
57
+ params: dict[str, Any] = {}
52
58
 
53
59
  if self.ids:
54
60
  params["ids"] = self.ids
55
61
  return "diff_node.uuid in $ids", params
56
62
 
63
+ if self.identifiers:
64
+ params["ids"] = [n.uuid for n in self.identifiers]
65
+ id_kind_map: dict[str, list[str]] = defaultdict(list)
66
+ for node_identifier in self.identifiers:
67
+ id_kind_map[node_identifier.uuid].append(node_identifier.kind)
68
+ params["id_kind_map"] = id_kind_map
69
+ return "diff_node.uuid in $ids AND diff_node.kind IN $id_kind_map[diff_node.uuid]", params
70
+
57
71
  filters_list = []
58
72
 
59
73
  if self.is_empty: