infrahub-server 1.2.9rc0__py3-none-any.whl → 1.2.10__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 (52) hide show
  1. infrahub/computed_attribute/models.py +13 -0
  2. infrahub/computed_attribute/tasks.py +48 -26
  3. infrahub/core/attribute.py +43 -2
  4. infrahub/core/branch/models.py +8 -9
  5. infrahub/core/branch/tasks.py +0 -2
  6. infrahub/core/diff/calculator.py +65 -11
  7. infrahub/core/diff/combiner.py +38 -31
  8. infrahub/core/diff/coordinator.py +44 -28
  9. infrahub/core/diff/data_check_synchronizer.py +3 -2
  10. infrahub/core/diff/enricher/hierarchy.py +36 -27
  11. infrahub/core/diff/ipam_diff_parser.py +5 -4
  12. infrahub/core/diff/merger/merger.py +46 -16
  13. infrahub/core/diff/merger/serializer.py +1 -0
  14. infrahub/core/diff/model/field_specifiers_map.py +64 -0
  15. infrahub/core/diff/model/path.py +58 -58
  16. infrahub/core/diff/parent_node_adder.py +14 -16
  17. infrahub/core/diff/query/drop_nodes.py +42 -0
  18. infrahub/core/diff/query/field_specifiers.py +8 -7
  19. infrahub/core/diff/query/filters.py +15 -1
  20. infrahub/core/diff/query/merge.py +264 -28
  21. infrahub/core/diff/query/save.py +6 -2
  22. infrahub/core/diff/query_parser.py +50 -64
  23. infrahub/core/diff/repository/deserializer.py +38 -24
  24. infrahub/core/diff/repository/repository.py +31 -12
  25. infrahub/core/graph/__init__.py +1 -1
  26. infrahub/core/migrations/graph/__init__.py +2 -0
  27. infrahub/core/migrations/graph/m027_delete_isolated_nodes.py +50 -0
  28. infrahub/core/migrations/graph/m028_delete_diffs.py +38 -0
  29. infrahub/core/query/branch.py +27 -17
  30. infrahub/core/query/diff.py +162 -51
  31. infrahub/core/query/node.py +39 -5
  32. infrahub/core/query/relationship.py +105 -30
  33. infrahub/core/query/subquery.py +2 -2
  34. infrahub/core/relationship/model.py +1 -1
  35. infrahub/core/schema/schema_branch.py +3 -0
  36. infrahub/core/validators/uniqueness/query.py +7 -0
  37. infrahub/graphql/queries/diff/tree.py +2 -1
  38. infrahub/trigger/models.py +11 -1
  39. infrahub/trigger/setup.py +51 -15
  40. infrahub/trigger/tasks.py +1 -4
  41. infrahub/types.py +1 -1
  42. infrahub/webhook/models.py +2 -1
  43. infrahub/workflows/catalogue.py +9 -0
  44. infrahub/workflows/initialization.py +1 -3
  45. infrahub_sdk/timestamp.py +2 -2
  46. {infrahub_server-1.2.9rc0.dist-info → infrahub_server-1.2.10.dist-info}/METADATA +3 -3
  47. {infrahub_server-1.2.9rc0.dist-info → infrahub_server-1.2.10.dist-info}/RECORD +52 -48
  48. infrahub_testcontainers/docker-compose.test.yml +3 -3
  49. infrahub_testcontainers/performance_test.py +6 -3
  50. {infrahub_server-1.2.9rc0.dist-info → infrahub_server-1.2.10.dist-info}/LICENSE.txt +0 -0
  51. {infrahub_server-1.2.9rc0.dist-info → infrahub_server-1.2.10.dist-info}/WHEEL +0 -0
  52. {infrahub_server-1.2.9rc0.dist-info → infrahub_server-1.2.10.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)
@@ -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)
@@ -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: