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.
- infrahub/actions/constants.py +86 -0
- infrahub/actions/gather.py +114 -0
- infrahub/actions/models.py +241 -0
- infrahub/actions/parsers.py +104 -0
- infrahub/actions/schema.py +382 -0
- infrahub/actions/tasks.py +126 -0
- infrahub/actions/triggers.py +21 -0
- infrahub/cli/db.py +1 -2
- infrahub/computed_attribute/models.py +13 -0
- infrahub/computed_attribute/tasks.py +48 -26
- infrahub/config.py +9 -0
- infrahub/core/account.py +24 -47
- infrahub/core/attribute.py +53 -14
- infrahub/core/branch/models.py +8 -9
- infrahub/core/branch/tasks.py +0 -2
- infrahub/core/constants/infrahubkind.py +8 -0
- infrahub/core/constraint/node/runner.py +1 -1
- infrahub/core/convert_object_type/__init__.py +0 -0
- infrahub/core/convert_object_type/conversion.py +122 -0
- infrahub/core/convert_object_type/schema_mapping.py +56 -0
- infrahub/core/diff/calculator.py +65 -11
- infrahub/core/diff/combiner.py +38 -31
- infrahub/core/diff/coordinator.py +44 -28
- infrahub/core/diff/data_check_synchronizer.py +3 -2
- infrahub/core/diff/enricher/hierarchy.py +36 -27
- infrahub/core/diff/ipam_diff_parser.py +5 -4
- infrahub/core/diff/merger/merger.py +46 -16
- infrahub/core/diff/merger/serializer.py +1 -0
- infrahub/core/diff/model/field_specifiers_map.py +64 -0
- infrahub/core/diff/model/path.py +58 -58
- infrahub/core/diff/parent_node_adder.py +14 -16
- infrahub/core/diff/query/all_conflicts.py +1 -5
- infrahub/core/diff/query/artifact.py +10 -20
- infrahub/core/diff/query/diff_get.py +3 -6
- infrahub/core/diff/query/drop_nodes.py +42 -0
- infrahub/core/diff/query/field_specifiers.py +8 -7
- infrahub/core/diff/query/field_summary.py +2 -4
- infrahub/core/diff/query/filters.py +15 -1
- infrahub/core/diff/query/merge.py +284 -101
- infrahub/core/diff/query/save.py +26 -34
- infrahub/core/diff/query/summary_counts_enricher.py +34 -54
- infrahub/core/diff/query_parser.py +55 -65
- infrahub/core/diff/repository/deserializer.py +38 -24
- infrahub/core/diff/repository/repository.py +31 -12
- infrahub/core/diff/tasks.py +3 -3
- infrahub/core/graph/__init__.py +1 -1
- infrahub/core/manager.py +14 -11
- infrahub/core/migrations/graph/__init__.py +2 -0
- infrahub/core/migrations/graph/m003_relationship_parent_optional.py +1 -2
- infrahub/core/migrations/graph/m013_convert_git_password_credential.py +2 -4
- infrahub/core/migrations/graph/m019_restore_rels_to_time.py +11 -22
- infrahub/core/migrations/graph/m020_duplicate_edges.py +3 -6
- infrahub/core/migrations/graph/m021_missing_hierarchy_merge.py +1 -2
- infrahub/core/migrations/graph/m024_missing_hierarchy_backfill.py +1 -2
- infrahub/core/migrations/graph/m027_delete_isolated_nodes.py +50 -0
- infrahub/core/migrations/graph/m028_delete_diffs.py +38 -0
- infrahub/core/migrations/query/attribute_add.py +1 -2
- infrahub/core/migrations/query/attribute_rename.py +3 -6
- infrahub/core/migrations/query/delete_element_in_schema.py +3 -6
- infrahub/core/migrations/query/node_duplicate.py +3 -6
- infrahub/core/migrations/query/relationship_duplicate.py +3 -6
- infrahub/core/migrations/schema/node_attribute_remove.py +3 -6
- infrahub/core/migrations/schema/node_remove.py +3 -6
- infrahub/core/models.py +29 -2
- infrahub/core/node/__init__.py +18 -4
- infrahub/core/node/create.py +211 -0
- infrahub/core/protocols.py +51 -0
- infrahub/core/protocols_base.py +3 -0
- infrahub/core/query/__init__.py +2 -2
- infrahub/core/query/branch.py +27 -17
- infrahub/core/query/diff.py +186 -81
- infrahub/core/query/ipam.py +10 -20
- infrahub/core/query/node.py +65 -49
- infrahub/core/query/relationship.py +156 -58
- infrahub/core/query/resource_manager.py +1 -2
- infrahub/core/query/subquery.py +4 -6
- infrahub/core/relationship/model.py +4 -1
- infrahub/core/schema/__init__.py +2 -1
- infrahub/core/schema/attribute_parameters.py +36 -0
- infrahub/core/schema/attribute_schema.py +83 -8
- infrahub/core/schema/basenode_schema.py +25 -1
- infrahub/core/schema/definitions/core/__init__.py +21 -0
- infrahub/core/schema/definitions/internal.py +13 -3
- infrahub/core/schema/generated/attribute_schema.py +9 -3
- infrahub/core/schema/schema_branch.py +15 -7
- infrahub/core/validators/__init__.py +5 -1
- infrahub/core/validators/attribute/choices.py +1 -2
- infrahub/core/validators/attribute/enum.py +1 -2
- infrahub/core/validators/attribute/kind.py +1 -2
- infrahub/core/validators/attribute/length.py +13 -6
- infrahub/core/validators/attribute/optional.py +1 -2
- infrahub/core/validators/attribute/regex.py +5 -5
- infrahub/core/validators/attribute/unique.py +1 -3
- infrahub/core/validators/determiner.py +18 -2
- infrahub/core/validators/enum.py +7 -0
- infrahub/core/validators/node/hierarchy.py +3 -6
- infrahub/core/validators/query.py +1 -3
- infrahub/core/validators/relationship/count.py +6 -12
- infrahub/core/validators/relationship/optional.py +2 -4
- infrahub/core/validators/relationship/peer.py +3 -8
- infrahub/core/validators/tasks.py +1 -1
- infrahub/core/validators/uniqueness/query.py +12 -9
- infrahub/database/__init__.py +1 -3
- infrahub/events/group_action.py +1 -0
- infrahub/graphql/analyzer.py +139 -18
- infrahub/graphql/app.py +1 -1
- infrahub/graphql/loaders/node.py +1 -1
- infrahub/graphql/loaders/peers.py +1 -1
- infrahub/graphql/manager.py +4 -0
- infrahub/graphql/mutations/action.py +164 -0
- infrahub/graphql/mutations/convert_object_type.py +62 -0
- infrahub/graphql/mutations/main.py +24 -175
- infrahub/graphql/mutations/proposed_change.py +21 -18
- infrahub/graphql/queries/convert_object_type_mapping.py +36 -0
- infrahub/graphql/queries/diff/tree.py +2 -1
- infrahub/graphql/queries/relationship.py +1 -1
- infrahub/graphql/resolvers/many_relationship.py +4 -4
- infrahub/graphql/resolvers/resolver.py +4 -4
- infrahub/graphql/resolvers/single_relationship.py +2 -2
- infrahub/graphql/schema.py +6 -0
- infrahub/graphql/subscription/graphql_query.py +2 -2
- infrahub/graphql/types/branch.py +1 -1
- infrahub/menu/menu.py +31 -0
- infrahub/message_bus/messages/__init__.py +0 -10
- infrahub/message_bus/operations/__init__.py +0 -8
- infrahub/message_bus/operations/refresh/registry.py +1 -1
- infrahub/patch/queries/consolidate_duplicated_nodes.py +3 -6
- infrahub/patch/queries/delete_duplicated_edges.py +5 -10
- infrahub/prefect_server/models.py +1 -19
- infrahub/proposed_change/models.py +68 -3
- infrahub/proposed_change/tasks.py +907 -30
- infrahub/task_manager/models.py +10 -6
- infrahub/telemetry/database.py +1 -1
- infrahub/telemetry/tasks.py +1 -1
- infrahub/trigger/catalogue.py +2 -0
- infrahub/trigger/models.py +29 -3
- infrahub/trigger/setup.py +51 -15
- infrahub/trigger/tasks.py +4 -5
- infrahub/types.py +1 -1
- infrahub/webhook/models.py +2 -1
- infrahub/workflows/catalogue.py +85 -0
- infrahub/workflows/initialization.py +1 -3
- infrahub_sdk/timestamp.py +2 -2
- {infrahub_server-1.2.9rc0.dist-info → infrahub_server-1.3.0a0.dist-info}/METADATA +4 -4
- {infrahub_server-1.2.9rc0.dist-info → infrahub_server-1.3.0a0.dist-info}/RECORD +153 -146
- infrahub_testcontainers/container.py +0 -1
- infrahub_testcontainers/docker-compose.test.yml +4 -4
- infrahub_testcontainers/helpers.py +8 -2
- infrahub_testcontainers/performance_test.py +6 -3
- infrahub/message_bus/messages/check_generator_run.py +0 -26
- infrahub/message_bus/messages/finalize_validator_execution.py +0 -15
- infrahub/message_bus/messages/proposed_change/base_with_diff.py +0 -16
- infrahub/message_bus/messages/proposed_change/request_proposedchange_refreshartifacts.py +0 -11
- infrahub/message_bus/messages/request_generatordefinition_check.py +0 -20
- infrahub/message_bus/messages/request_proposedchange_pipeline.py +0 -23
- infrahub/message_bus/operations/check/__init__.py +0 -3
- infrahub/message_bus/operations/check/generator.py +0 -156
- infrahub/message_bus/operations/finalize/__init__.py +0 -3
- infrahub/message_bus/operations/finalize/validator.py +0 -133
- infrahub/message_bus/operations/requests/__init__.py +0 -9
- infrahub/message_bus/operations/requests/generator_definition.py +0 -140
- infrahub/message_bus/operations/requests/proposed_change.py +0 -629
- /infrahub/{message_bus/messages/proposed_change → actions}/__init__.py +0 -0
- {infrahub_server-1.2.9rc0.dist-info → infrahub_server-1.3.0a0.dist-info}/LICENSE.txt +0 -0
- {infrahub_server-1.2.9rc0.dist-info → infrahub_server-1.3.0a0.dist-info}/WHEEL +0 -0
- {infrahub_server-1.2.9rc0.dist-info → infrahub_server-1.3.0a0.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel
|
|
4
|
+
|
|
5
|
+
from infrahub.core.attribute import BaseAttribute
|
|
6
|
+
from infrahub.core.branch import Branch
|
|
7
|
+
from infrahub.core.constants import RelationshipCardinality
|
|
8
|
+
from infrahub.core.manager import NodeManager
|
|
9
|
+
from infrahub.core.node import Node
|
|
10
|
+
from infrahub.core.node.create import create_node
|
|
11
|
+
from infrahub.core.query.relationship import GetAllPeersIds
|
|
12
|
+
from infrahub.core.relationship import RelationshipManager
|
|
13
|
+
from infrahub.core.schema import NodeSchema
|
|
14
|
+
from infrahub.database import InfrahubDatabase
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class InputDataForDestField(BaseModel): # Only one of these fields can be not None
|
|
18
|
+
attribute_value: Any | None = None
|
|
19
|
+
peer_id: str | None = None
|
|
20
|
+
peers_ids: list[str] | None = None
|
|
21
|
+
|
|
22
|
+
@property
|
|
23
|
+
def value(self) -> Any:
|
|
24
|
+
fields = [self.attribute_value, self.peer_id, self.peers_ids]
|
|
25
|
+
set_fields = [f for f in fields if f is not None]
|
|
26
|
+
if len(set_fields) != 1:
|
|
27
|
+
raise ValueError("Exactly one of attribute_value, peer_id, or peers_ids must be set")
|
|
28
|
+
return set_fields[0]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class InputForDestField(BaseModel): # Only one of these fields can be not None
|
|
32
|
+
source_field: str | None = None
|
|
33
|
+
data: InputDataForDestField | None = None
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def value(self) -> Any:
|
|
37
|
+
if self.source_field is not None and self.data is not None:
|
|
38
|
+
raise ValueError("Only one of source_field or data can be set")
|
|
39
|
+
if self.source_field is None and self.data is None:
|
|
40
|
+
raise ValueError("Either source_field or data must be set")
|
|
41
|
+
return self.source_field if self.source_field is not None else self.data
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
async def get_out_rels_peers_ids(node: Node, db: InfrahubDatabase) -> list[str]:
|
|
45
|
+
all_peers: list[Node] = []
|
|
46
|
+
for name in node._relationships:
|
|
47
|
+
relm: RelationshipManager = getattr(node, name)
|
|
48
|
+
peers = await relm.get_peers(db=db)
|
|
49
|
+
all_peers.extend(peers.values())
|
|
50
|
+
return [peer.id for peer in all_peers]
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
async def build_data_new_node(db: InfrahubDatabase, mapping: dict[str, InputForDestField], node: Node) -> dict:
|
|
54
|
+
"""Value of a given field on the target kind to convert is either an input source attribute/relationship of the source node,
|
|
55
|
+
or a raw value."""
|
|
56
|
+
|
|
57
|
+
data = {}
|
|
58
|
+
for dest_field_name, input_for_dest_field in mapping.items():
|
|
59
|
+
value = input_for_dest_field.value
|
|
60
|
+
if isinstance(value, str): # source_field
|
|
61
|
+
item = getattr(node, value)
|
|
62
|
+
if isinstance(item, BaseAttribute):
|
|
63
|
+
data[dest_field_name] = item.value
|
|
64
|
+
elif isinstance(item, RelationshipManager):
|
|
65
|
+
if item.schema.cardinality == RelationshipCardinality.ONE:
|
|
66
|
+
peer = await item.get_peer(db=db)
|
|
67
|
+
if peer is not None:
|
|
68
|
+
data[dest_field_name] = {"id": peer.id}
|
|
69
|
+
# else, relationship is optional, and if the target relationship is mandatory an error will be raised during creation
|
|
70
|
+
elif item.schema.cardinality == RelationshipCardinality.MANY:
|
|
71
|
+
data[dest_field_name] = [{"id": peer.id} for _, peer in (await item.get_peers(db=db)).items()]
|
|
72
|
+
else:
|
|
73
|
+
raise ValueError(f"Unknown cardinality {item.schema.cardinality=}")
|
|
74
|
+
else: # user input data
|
|
75
|
+
data[dest_field_name] = value.value
|
|
76
|
+
return data
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
async def get_unidirectional_rels_peers_ids(node: Node, branch: Branch, db: InfrahubDatabase) -> list[str]:
|
|
80
|
+
"""
|
|
81
|
+
Returns peers ids of nodes connected to input `node` through an incoming unidirectional relationship.
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
out_rels_identifier = [rel.identifier for rel in node.get_schema().relationships]
|
|
85
|
+
query = await GetAllPeersIds.init(db=db, node_id=node.id, branch=branch, exclude_identifiers=out_rels_identifier)
|
|
86
|
+
await query.execute(db=db)
|
|
87
|
+
return query.get_peers_uuids()
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
async def convert_object_type(
|
|
91
|
+
node: Node, target_schema: NodeSchema, mapping: dict[str, InputForDestField], branch: Branch, db: InfrahubDatabase
|
|
92
|
+
) -> Node:
|
|
93
|
+
"""Delete the node and return the new created one. If creation fails, the node is not deleted, and raise an error.
|
|
94
|
+
An extra check is performed on input node peers relationships to make sure they are still valid."""
|
|
95
|
+
|
|
96
|
+
node_schema = node.get_schema()
|
|
97
|
+
if not isinstance(node_schema, NodeSchema):
|
|
98
|
+
raise ValueError(f"Only a node with a NodeSchema can be converted, got {type(node_schema)}")
|
|
99
|
+
|
|
100
|
+
async with db.start_transaction() as dbt: # noqa: PLR1702
|
|
101
|
+
deleted_node_out_rels_peer_ids = await get_out_rels_peers_ids(node=node, db=dbt)
|
|
102
|
+
deleted_node_unidir_rels_peer_ids = await get_unidirectional_rels_peers_ids(node=node, db=dbt, branch=branch)
|
|
103
|
+
|
|
104
|
+
deleted_nodes = await NodeManager.delete(db=dbt, branch=branch, nodes=[node], cascade_delete=False)
|
|
105
|
+
if len(deleted_nodes) != 1:
|
|
106
|
+
raise ValueError(f"Deleted {len(deleted_nodes)} nodes instead of 1")
|
|
107
|
+
|
|
108
|
+
data_new_node = await build_data_new_node(dbt, mapping, node)
|
|
109
|
+
node_created = await create_node(
|
|
110
|
+
data=data_new_node,
|
|
111
|
+
db=dbt,
|
|
112
|
+
branch=branch,
|
|
113
|
+
schema=target_schema,
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
# Make sure relationships with constraints are not broken by retrieving them
|
|
117
|
+
peers_ids = deleted_node_out_rels_peer_ids + deleted_node_unidir_rels_peer_ids
|
|
118
|
+
peers = await NodeManager.get_many(ids=peers_ids, db=dbt, prefetch_relationships=True, branch=branch)
|
|
119
|
+
for peer in peers.values():
|
|
120
|
+
peer.validate_relationships()
|
|
121
|
+
|
|
122
|
+
return node_created
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
from pydantic import BaseModel
|
|
2
|
+
|
|
3
|
+
from infrahub.core.constants import RelationshipCardinality
|
|
4
|
+
from infrahub.core.schema import NodeSchema
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class SchemaMappingValue(BaseModel):
|
|
8
|
+
is_mandatory: bool
|
|
9
|
+
source_field_name: str | None = None # None means there is no corresponding source field name
|
|
10
|
+
relationship_cardinality: RelationshipCardinality | None = None
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
SchemaMapping = dict[str, SchemaMappingValue]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def get_schema_mapping(source_schema: NodeSchema, target_schema: NodeSchema) -> SchemaMapping:
|
|
17
|
+
"""
|
|
18
|
+
Return fields mapping meant to be used for converting a node from `source_kind` to `target_kind`.
|
|
19
|
+
For any field of the target kind, field of the source kind will be matched if:
|
|
20
|
+
- It's an attribute with identical name and type.
|
|
21
|
+
- It's a relationship with identical peer kind and cardinality.
|
|
22
|
+
If there is no match, the mapping will only indicate whether the field is mandatory or not.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
target_field_to_source_field = {}
|
|
26
|
+
|
|
27
|
+
# Create lookup dictionaries for source attributes and relationships
|
|
28
|
+
source_attrs = {attr.name: attr for attr in source_schema.attributes}
|
|
29
|
+
source_rels = {rel.name: rel for rel in source_schema.relationships}
|
|
30
|
+
|
|
31
|
+
# Process attributes
|
|
32
|
+
for target_attr in target_schema.attributes:
|
|
33
|
+
source_attr = source_attrs.get(target_attr.name)
|
|
34
|
+
if source_attr and source_attr.kind == target_attr.kind:
|
|
35
|
+
target_field_to_source_field[target_attr.name] = SchemaMappingValue(
|
|
36
|
+
source_field_name=source_attr.name, is_mandatory=not target_attr.optional
|
|
37
|
+
)
|
|
38
|
+
else:
|
|
39
|
+
target_field_to_source_field[target_attr.name] = SchemaMappingValue(is_mandatory=not target_attr.optional)
|
|
40
|
+
|
|
41
|
+
# Process relationships
|
|
42
|
+
for target_rel in target_schema.relationships:
|
|
43
|
+
source_rel = source_rels.get(target_rel.name)
|
|
44
|
+
if source_rel and source_rel.peer == target_rel.peer and source_rel.cardinality == target_rel.cardinality:
|
|
45
|
+
target_field_to_source_field[target_rel.name] = SchemaMappingValue(
|
|
46
|
+
source_field_name=source_rel.name,
|
|
47
|
+
is_mandatory=not target_rel.optional,
|
|
48
|
+
relationship_cardinality=target_rel.cardinality,
|
|
49
|
+
)
|
|
50
|
+
else:
|
|
51
|
+
target_field_to_source_field[target_rel.name] = SchemaMappingValue(
|
|
52
|
+
is_mandatory=not target_rel.optional,
|
|
53
|
+
relationship_cardinality=target_rel.cardinality,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
return target_field_to_source_field
|
infrahub/core/diff/calculator.py
CHANGED
|
@@ -7,6 +7,7 @@ from infrahub.core.diff.query_parser import DiffQueryParser
|
|
|
7
7
|
from infrahub.core.query.diff import (
|
|
8
8
|
DiffCalculationQuery,
|
|
9
9
|
DiffFieldPathsQuery,
|
|
10
|
+
DiffMigratedKindNodesQuery,
|
|
10
11
|
DiffNodePathsQuery,
|
|
11
12
|
DiffPropertyPathsQuery,
|
|
12
13
|
)
|
|
@@ -14,7 +15,8 @@ from infrahub.core.timestamp import Timestamp
|
|
|
14
15
|
from infrahub.database import InfrahubDatabase
|
|
15
16
|
from infrahub.log import get_logger
|
|
16
17
|
|
|
17
|
-
from .model.
|
|
18
|
+
from .model.field_specifiers_map import NodeFieldSpecifierMap
|
|
19
|
+
from .model.path import CalculatedDiffs, DiffNode, DiffRoot, NodeIdentifier
|
|
18
20
|
|
|
19
21
|
log = get_logger()
|
|
20
22
|
|
|
@@ -26,8 +28,8 @@ class DiffCalculationRequest:
|
|
|
26
28
|
branch_from_time: Timestamp
|
|
27
29
|
from_time: Timestamp
|
|
28
30
|
to_time: Timestamp
|
|
29
|
-
current_node_field_specifiers:
|
|
30
|
-
new_node_field_specifiers:
|
|
31
|
+
current_node_field_specifiers: NodeFieldSpecifierMap | None = field(default=None)
|
|
32
|
+
new_node_field_specifiers: NodeFieldSpecifierMap | None = field(default=None)
|
|
31
33
|
|
|
32
34
|
|
|
33
35
|
class DiffCalculator:
|
|
@@ -58,7 +60,7 @@ class DiffCalculator:
|
|
|
58
60
|
)
|
|
59
61
|
log.info(f"Beginning one diff calculation query {limit=}, {offset=}")
|
|
60
62
|
await diff_query.execute(db=self.db)
|
|
61
|
-
log.info("Diff calculation query complete")
|
|
63
|
+
log.info(f"Diff calculation query complete {limit=}, {offset=}")
|
|
62
64
|
last_result = None
|
|
63
65
|
for query_result in diff_query.get_results():
|
|
64
66
|
diff_parser.read_result(query_result=query_result)
|
|
@@ -68,6 +70,56 @@ class DiffCalculator:
|
|
|
68
70
|
has_more_data = last_result.get_as_type("has_more_data", bool)
|
|
69
71
|
offset += limit
|
|
70
72
|
|
|
73
|
+
async def _apply_kind_migrated_nodes(
|
|
74
|
+
self, branch_diff: DiffRoot, calculation_request: DiffCalculationRequest
|
|
75
|
+
) -> None:
|
|
76
|
+
has_more_data = True
|
|
77
|
+
offset = 0
|
|
78
|
+
limit = config.SETTINGS.database.query_size_limit
|
|
79
|
+
diff_nodes_by_identifier = {n.identifier: n for n in branch_diff.nodes}
|
|
80
|
+
diff_nodes_to_add: list[DiffNode] = []
|
|
81
|
+
while has_more_data:
|
|
82
|
+
diff_query = await DiffMigratedKindNodesQuery.init(
|
|
83
|
+
db=self.db,
|
|
84
|
+
branch=calculation_request.diff_branch,
|
|
85
|
+
base_branch=calculation_request.base_branch,
|
|
86
|
+
diff_branch_from_time=calculation_request.branch_from_time,
|
|
87
|
+
diff_from=calculation_request.from_time,
|
|
88
|
+
diff_to=calculation_request.to_time,
|
|
89
|
+
limit=limit,
|
|
90
|
+
offset=offset,
|
|
91
|
+
)
|
|
92
|
+
log.info(f"Getting one batch of migrated kind nodes {limit=}, {offset=}")
|
|
93
|
+
await diff_query.execute(db=self.db)
|
|
94
|
+
log.info(f"Migrated kind nodes query complete {limit=}, {offset=}")
|
|
95
|
+
last_result = None
|
|
96
|
+
for migrated_kind_node in diff_query.get_migrated_kind_nodes():
|
|
97
|
+
migrated_kind_identifier = NodeIdentifier(
|
|
98
|
+
uuid=migrated_kind_node.uuid,
|
|
99
|
+
kind=migrated_kind_node.kind,
|
|
100
|
+
db_id=migrated_kind_node.db_id,
|
|
101
|
+
)
|
|
102
|
+
if migrated_kind_identifier in diff_nodes_by_identifier:
|
|
103
|
+
diff_node = diff_nodes_by_identifier[migrated_kind_identifier]
|
|
104
|
+
diff_node.is_node_kind_migration = True
|
|
105
|
+
continue
|
|
106
|
+
new_diff_node = DiffNode(
|
|
107
|
+
identifier=migrated_kind_identifier,
|
|
108
|
+
changed_at=migrated_kind_node.from_time,
|
|
109
|
+
action=migrated_kind_node.action,
|
|
110
|
+
is_node_kind_migration=True,
|
|
111
|
+
attributes=[],
|
|
112
|
+
relationships=[],
|
|
113
|
+
)
|
|
114
|
+
diff_nodes_by_identifier[migrated_kind_identifier] = new_diff_node
|
|
115
|
+
diff_nodes_to_add.append(new_diff_node)
|
|
116
|
+
last_result = migrated_kind_node
|
|
117
|
+
has_more_data = False
|
|
118
|
+
if last_result:
|
|
119
|
+
has_more_data = last_result.has_more_data
|
|
120
|
+
offset += limit
|
|
121
|
+
branch_diff.nodes.extend(diff_nodes_to_add)
|
|
122
|
+
|
|
71
123
|
async def calculate_diff(
|
|
72
124
|
self,
|
|
73
125
|
base_branch: Branch,
|
|
@@ -75,7 +127,7 @@ class DiffCalculator:
|
|
|
75
127
|
from_time: Timestamp,
|
|
76
128
|
to_time: Timestamp,
|
|
77
129
|
include_unchanged: bool = True,
|
|
78
|
-
previous_node_specifiers:
|
|
130
|
+
previous_node_specifiers: NodeFieldSpecifierMap | None = None,
|
|
79
131
|
) -> CalculatedDiffs:
|
|
80
132
|
if diff_branch.name == registry.default_branch:
|
|
81
133
|
diff_branch_from_time = from_time
|
|
@@ -91,7 +143,7 @@ class DiffCalculator:
|
|
|
91
143
|
)
|
|
92
144
|
node_limit = int(config.SETTINGS.database.query_size_limit / 10)
|
|
93
145
|
fields_limit = int(config.SETTINGS.database.query_size_limit / 3)
|
|
94
|
-
properties_limit =
|
|
146
|
+
properties_limit = config.SETTINGS.database.query_size_limit
|
|
95
147
|
|
|
96
148
|
calculation_request = DiffCalculationRequest(
|
|
97
149
|
base_branch=base_branch,
|
|
@@ -131,7 +183,7 @@ class DiffCalculator:
|
|
|
131
183
|
if base_branch.name != diff_branch.name:
|
|
132
184
|
current_node_field_specifiers = diff_parser.get_current_node_field_specifiers()
|
|
133
185
|
new_node_field_specifiers = diff_parser.get_new_node_field_specifiers()
|
|
134
|
-
|
|
186
|
+
base_calculation_request = DiffCalculationRequest(
|
|
135
187
|
base_branch=base_branch,
|
|
136
188
|
diff_branch=base_branch,
|
|
137
189
|
branch_from_time=diff_branch_from_time,
|
|
@@ -145,7 +197,7 @@ class DiffCalculator:
|
|
|
145
197
|
await self._run_diff_calculation_query(
|
|
146
198
|
diff_parser=diff_parser,
|
|
147
199
|
query_class=DiffNodePathsQuery,
|
|
148
|
-
calculation_request=
|
|
200
|
+
calculation_request=base_calculation_request,
|
|
149
201
|
limit=node_limit,
|
|
150
202
|
)
|
|
151
203
|
log.info("Diff node-level calculation queries for base complete")
|
|
@@ -154,7 +206,7 @@ class DiffCalculator:
|
|
|
154
206
|
await self._run_diff_calculation_query(
|
|
155
207
|
diff_parser=diff_parser,
|
|
156
208
|
query_class=DiffFieldPathsQuery,
|
|
157
|
-
calculation_request=
|
|
209
|
+
calculation_request=base_calculation_request,
|
|
158
210
|
limit=fields_limit,
|
|
159
211
|
)
|
|
160
212
|
log.info("Diff field-level calculation queries for base complete")
|
|
@@ -163,7 +215,7 @@ class DiffCalculator:
|
|
|
163
215
|
await self._run_diff_calculation_query(
|
|
164
216
|
diff_parser=diff_parser,
|
|
165
217
|
query_class=DiffPropertyPathsQuery,
|
|
166
|
-
calculation_request=
|
|
218
|
+
calculation_request=base_calculation_request,
|
|
167
219
|
limit=properties_limit,
|
|
168
220
|
)
|
|
169
221
|
log.info("Diff property-level calculation queries for base complete")
|
|
@@ -171,9 +223,11 @@ class DiffCalculator:
|
|
|
171
223
|
log.info("Parsing calculated diff")
|
|
172
224
|
diff_parser.parse(include_unchanged=include_unchanged)
|
|
173
225
|
log.info("Calculated diff parsed")
|
|
226
|
+
branch_diff = diff_parser.get_diff_root_for_branch(branch=diff_branch.name)
|
|
227
|
+
await self._apply_kind_migrated_nodes(branch_diff=branch_diff, calculation_request=calculation_request)
|
|
174
228
|
return CalculatedDiffs(
|
|
175
229
|
base_branch_name=base_branch.name,
|
|
176
230
|
diff_branch_name=diff_branch.name,
|
|
177
231
|
base_branch_diff=diff_parser.get_diff_root_for_branch(branch=base_branch.name),
|
|
178
|
-
diff_branch_diff=
|
|
232
|
+
diff_branch_diff=branch_diff,
|
|
179
233
|
)
|
infrahub/core/diff/combiner.py
CHANGED
|
@@ -14,6 +14,7 @@ from .model.path import (
|
|
|
14
14
|
EnrichedDiffRoot,
|
|
15
15
|
EnrichedDiffs,
|
|
16
16
|
EnrichedDiffSingleRelationship,
|
|
17
|
+
NodeIdentifier,
|
|
17
18
|
)
|
|
18
19
|
|
|
19
20
|
|
|
@@ -26,30 +27,35 @@ class NodePair:
|
|
|
26
27
|
class DiffCombiner:
|
|
27
28
|
def __init__(self) -> None:
|
|
28
29
|
# {child_uuid: (parent_uuid, parent_rel_name)}
|
|
29
|
-
self.
|
|
30
|
-
self.
|
|
31
|
-
self.
|
|
32
|
-
self.
|
|
33
|
-
self.
|
|
30
|
+
self._child_parent_identifier_map: dict[NodeIdentifier, tuple[NodeIdentifier, str]] = {}
|
|
31
|
+
self._parent_node_identifiers: set[NodeIdentifier] = set()
|
|
32
|
+
self._earlier_nodes_by_identifier: dict[NodeIdentifier, EnrichedDiffNode] = {}
|
|
33
|
+
self._later_nodes_by_identifier: dict[NodeIdentifier, EnrichedDiffNode] = {}
|
|
34
|
+
self._common_node_identifiers: set[NodeIdentifier] = set()
|
|
34
35
|
self._diff_branch_name: str | None = None
|
|
35
36
|
|
|
36
37
|
def _initialize(self, earlier_diff: EnrichedDiffRoot, later_diff: EnrichedDiffRoot) -> None:
|
|
37
38
|
self._diff_branch_name = earlier_diff.diff_branch_name
|
|
38
|
-
self.
|
|
39
|
-
self.
|
|
40
|
-
self.
|
|
41
|
-
self.
|
|
39
|
+
self._child_parent_identifier_map = {}
|
|
40
|
+
self._earlier_nodes_by_identifier = {}
|
|
41
|
+
self._later_nodes_by_identifier = {}
|
|
42
|
+
self._common_node_identifiers = set()
|
|
42
43
|
# map the parent of each node (if it exists), preference to the later diff
|
|
43
44
|
for diff_root in (earlier_diff, later_diff):
|
|
44
45
|
for child_node in diff_root.nodes:
|
|
45
46
|
for parent_rel in child_node.relationships:
|
|
46
47
|
for parent_node in parent_rel.nodes:
|
|
47
|
-
self.
|
|
48
|
+
self._child_parent_identifier_map[child_node.identifier] = (
|
|
49
|
+
parent_node.identifier,
|
|
50
|
+
parent_rel.name,
|
|
51
|
+
)
|
|
48
52
|
# UUIDs of all the parents, removing the stale parents from the earlier diff
|
|
49
|
-
self.
|
|
50
|
-
self.
|
|
51
|
-
self.
|
|
52
|
-
self.
|
|
53
|
+
self._parent_node_identifiers = {parent_tuple[0] for parent_tuple in self._child_parent_identifier_map.values()}
|
|
54
|
+
self._earlier_nodes_by_identifier = {n.identifier: n for n in earlier_diff.nodes}
|
|
55
|
+
self._later_nodes_by_identifier = {n.identifier: n for n in later_diff.nodes}
|
|
56
|
+
self._common_node_identifiers = set(self._earlier_nodes_by_identifier.keys()) & set(
|
|
57
|
+
self._later_nodes_by_identifier.keys()
|
|
58
|
+
)
|
|
53
59
|
|
|
54
60
|
@property
|
|
55
61
|
def diff_branch_name(self) -> str:
|
|
@@ -61,13 +67,13 @@ class DiffCombiner:
|
|
|
61
67
|
filtered_node_pairs: list[NodePair] = []
|
|
62
68
|
for earlier_node in earlier_diff.nodes:
|
|
63
69
|
later_node: EnrichedDiffNode | None = None
|
|
64
|
-
if earlier_node.
|
|
65
|
-
later_node = self.
|
|
70
|
+
if earlier_node.identifier in self._common_node_identifiers:
|
|
71
|
+
later_node = self._later_nodes_by_identifier[earlier_node.identifier]
|
|
66
72
|
# this is an out-of-date parent
|
|
67
73
|
if (
|
|
68
74
|
earlier_node.action is DiffAction.UNCHANGED
|
|
69
75
|
and (later_node is None or later_node.action is DiffAction.UNCHANGED)
|
|
70
|
-
and earlier_node.
|
|
76
|
+
and earlier_node.identifier not in self._parent_node_identifiers
|
|
71
77
|
):
|
|
72
78
|
continue
|
|
73
79
|
if later_node is None:
|
|
@@ -79,15 +85,15 @@ class DiffCombiner:
|
|
|
79
85
|
filtered_node_pairs.append(NodePair(earlier=earlier_node, later=later_node))
|
|
80
86
|
for later_node in later_diff.nodes:
|
|
81
87
|
# these have already been handled
|
|
82
|
-
if later_node.
|
|
88
|
+
if later_node.identifier in self._common_node_identifiers:
|
|
83
89
|
continue
|
|
84
90
|
filtered_node_pairs.append(NodePair(later=later_node))
|
|
85
91
|
return filtered_node_pairs
|
|
86
92
|
|
|
87
|
-
def _get_parent_relationship_name(self, node_id:
|
|
88
|
-
if node_id not in self.
|
|
93
|
+
def _get_parent_relationship_name(self, node_id: NodeIdentifier) -> str | None:
|
|
94
|
+
if node_id not in self._child_parent_identifier_map:
|
|
89
95
|
return None
|
|
90
|
-
return self.
|
|
96
|
+
return self._child_parent_identifier_map[node_id][1]
|
|
91
97
|
|
|
92
98
|
def _should_include(self, earlier: DiffAction, later: DiffAction) -> bool:
|
|
93
99
|
actions = {earlier, later}
|
|
@@ -284,7 +290,7 @@ class DiffCombiner:
|
|
|
284
290
|
self,
|
|
285
291
|
earlier_relationships: set[EnrichedDiffRelationship],
|
|
286
292
|
later_relationships: set[EnrichedDiffRelationship],
|
|
287
|
-
node_id:
|
|
293
|
+
node_id: NodeIdentifier,
|
|
288
294
|
) -> set[EnrichedDiffRelationship]:
|
|
289
295
|
earlier_rels_by_name = {rel.name: rel for rel in earlier_relationships}
|
|
290
296
|
later_rels_by_name = {rel.name: rel for rel in later_relationships}
|
|
@@ -365,7 +371,7 @@ class DiffCombiner:
|
|
|
365
371
|
combined_relationships = self._combine_relationships(
|
|
366
372
|
earlier_relationships=node_pair.earlier.relationships,
|
|
367
373
|
later_relationships=node_pair.later.relationships,
|
|
368
|
-
node_id=node_pair.later.
|
|
374
|
+
node_id=node_pair.later.identifier,
|
|
369
375
|
)
|
|
370
376
|
if all(ca.action is DiffAction.UNCHANGED for ca in combined_attributes) and all(
|
|
371
377
|
cr.action is DiffAction.UNCHANGED for cr in combined_relationships
|
|
@@ -380,15 +386,16 @@ class DiffCombiner:
|
|
|
380
386
|
combined_attributes
|
|
381
387
|
or combined_relationships
|
|
382
388
|
or combined_conflict
|
|
383
|
-
or node_pair.later.
|
|
389
|
+
or node_pair.later.identifier in self._parent_node_identifiers
|
|
384
390
|
):
|
|
385
391
|
combined_nodes.add(
|
|
386
392
|
EnrichedDiffNode(
|
|
387
|
-
|
|
388
|
-
kind=node_pair.later.kind,
|
|
393
|
+
identifier=node_pair.later.identifier,
|
|
389
394
|
label=node_pair.later.label,
|
|
390
395
|
changed_at=node_pair.later.changed_at or node_pair.earlier.changed_at,
|
|
391
396
|
action=combined_action,
|
|
397
|
+
is_node_kind_migration=node_pair.earlier.is_node_kind_migration
|
|
398
|
+
or node_pair.later.is_node_kind_migration,
|
|
392
399
|
path_identifier=node_pair.later.path_identifier,
|
|
393
400
|
attributes=combined_attributes,
|
|
394
401
|
relationships=combined_relationships,
|
|
@@ -398,12 +405,12 @@ class DiffCombiner:
|
|
|
398
405
|
return combined_nodes
|
|
399
406
|
|
|
400
407
|
def _link_child_nodes(self, nodes: Iterable[EnrichedDiffNode]) -> None:
|
|
401
|
-
|
|
402
|
-
for child_node in
|
|
403
|
-
if child_node.
|
|
408
|
+
nodes_by_identifier: dict[NodeIdentifier, EnrichedDiffNode] = {n.identifier: n for n in nodes}
|
|
409
|
+
for child_node in nodes_by_identifier.values():
|
|
410
|
+
if child_node.identifier not in self._child_parent_identifier_map:
|
|
404
411
|
continue
|
|
405
|
-
|
|
406
|
-
parent_node =
|
|
412
|
+
parent_identifier, parent_rel_name = self._child_parent_identifier_map[child_node.identifier]
|
|
413
|
+
parent_node = nodes_by_identifier[parent_identifier]
|
|
407
414
|
parent_rel = child_node.get_relationship(name=parent_rel_name)
|
|
408
415
|
parent_rel.nodes.add(parent_node)
|
|
409
416
|
|