infrahub-server 1.2.9__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.
- infrahub/computed_attribute/models.py +13 -0
- infrahub/computed_attribute/tasks.py +29 -28
- infrahub/core/attribute.py +43 -2
- infrahub/core/branch/models.py +8 -9
- infrahub/core/diff/calculator.py +61 -8
- infrahub/core/diff/combiner.py +37 -29
- infrahub/core/diff/enricher/hierarchy.py +4 -6
- infrahub/core/diff/merger/merger.py +29 -1
- infrahub/core/diff/merger/serializer.py +1 -0
- infrahub/core/diff/model/path.py +6 -3
- infrahub/core/diff/query/merge.py +264 -28
- infrahub/core/diff/query/save.py +6 -5
- infrahub/core/diff/query_parser.py +4 -15
- infrahub/core/diff/repository/deserializer.py +7 -6
- infrahub/core/graph/__init__.py +1 -1
- infrahub/core/migrations/graph/m028_delete_diffs.py +38 -0
- infrahub/core/query/diff.py +97 -13
- infrahub/core/query/node.py +26 -3
- infrahub/core/query/relationship.py +96 -35
- infrahub/core/relationship/model.py +1 -1
- infrahub/core/validators/uniqueness/query.py +7 -0
- infrahub/trigger/setup.py +13 -2
- infrahub/types.py +1 -1
- infrahub/webhook/models.py +2 -1
- infrahub/workflows/catalogue.py +9 -0
- infrahub_sdk/timestamp.py +2 -2
- {infrahub_server-1.2.9.dist-info → infrahub_server-1.2.10.dist-info}/METADATA +3 -3
- {infrahub_server-1.2.9.dist-info → infrahub_server-1.2.10.dist-info}/RECORD +33 -32
- infrahub_testcontainers/docker-compose.test.yml +2 -2
- infrahub_testcontainers/performance_test.py +6 -3
- {infrahub_server-1.2.9.dist-info → infrahub_server-1.2.10.dist-info}/LICENSE.txt +0 -0
- {infrahub_server-1.2.9.dist-info → infrahub_server-1.2.10.dist-info}/WHEEL +0 -0
- {infrahub_server-1.2.9.dist-info → infrahub_server-1.2.10.dist-info}/entry_points.txt +0 -0
infrahub/core/diff/model/path.py
CHANGED
|
@@ -87,13 +87,13 @@ class NodeIdentifier:
|
|
|
87
87
|
|
|
88
88
|
uuid: str
|
|
89
89
|
kind: str
|
|
90
|
-
|
|
90
|
+
db_id: str
|
|
91
91
|
|
|
92
92
|
def __hash__(self) -> int:
|
|
93
|
-
return hash(f"{self.uuid}:{self.kind}:{
|
|
93
|
+
return hash(f"{self.uuid}:{self.kind}:{self.db_id}")
|
|
94
94
|
|
|
95
95
|
def __str__(self) -> str:
|
|
96
|
-
return f"{self.kind} '{self.uuid}' ({
|
|
96
|
+
return f"{self.kind} '{self.uuid}' ({self.db_id})"
|
|
97
97
|
|
|
98
98
|
|
|
99
99
|
@dataclass
|
|
@@ -327,6 +327,7 @@ class EnrichedDiffNode(BaseSummary):
|
|
|
327
327
|
path_identifier: str = field(default="", kw_only=True)
|
|
328
328
|
changed_at: Timestamp | None = field(default=None, kw_only=True)
|
|
329
329
|
action: DiffAction
|
|
330
|
+
is_node_kind_migration: bool = field(default=False)
|
|
330
331
|
conflict: EnrichedDiffConflict | None = field(default=None)
|
|
331
332
|
attributes: set[EnrichedDiffAttribute] = field(default_factory=set)
|
|
332
333
|
relationships: set[EnrichedDiffRelationship] = field(default_factory=set)
|
|
@@ -428,6 +429,7 @@ class EnrichedDiffNode(BaseSummary):
|
|
|
428
429
|
label="",
|
|
429
430
|
changed_at=calculated_node.changed_at,
|
|
430
431
|
action=calculated_node.action,
|
|
432
|
+
is_node_kind_migration=calculated_node.is_node_kind_migration,
|
|
431
433
|
attributes={
|
|
432
434
|
EnrichedDiffAttribute.from_calculated_attribute(calculated_attribute=attr)
|
|
433
435
|
for attr in calculated_node.attributes
|
|
@@ -686,6 +688,7 @@ class DiffNode:
|
|
|
686
688
|
identifier: NodeIdentifier
|
|
687
689
|
changed_at: Timestamp
|
|
688
690
|
action: DiffAction
|
|
691
|
+
is_node_kind_migration: bool = field(default=False)
|
|
689
692
|
attributes: list[DiffAttribute] = field(default_factory=list)
|
|
690
693
|
relationships: list[DiffRelationship] = field(default_factory=list)
|
|
691
694
|
|
|
@@ -20,6 +20,7 @@ class DiffMergeQuery(Query):
|
|
|
20
20
|
node_diff_dicts: dict[str, Any],
|
|
21
21
|
at: Timestamp,
|
|
22
22
|
target_branch: Branch,
|
|
23
|
+
migrated_kinds_id_map: dict[str, str],
|
|
23
24
|
**kwargs: Any,
|
|
24
25
|
) -> None:
|
|
25
26
|
super().__init__(**kwargs)
|
|
@@ -27,6 +28,7 @@ class DiffMergeQuery(Query):
|
|
|
27
28
|
self.at = at
|
|
28
29
|
self.target_branch = target_branch
|
|
29
30
|
self.source_branch_name = self.branch.name
|
|
31
|
+
self.migrated_kinds_id_map = migrated_kinds_id_map
|
|
30
32
|
|
|
31
33
|
async def query_init(self, db: InfrahubDatabase, **kwargs: Any) -> None: # noqa: ARG002
|
|
32
34
|
self.params = {
|
|
@@ -35,19 +37,29 @@ class DiffMergeQuery(Query):
|
|
|
35
37
|
"branch_level": self.target_branch.hierarchy_level,
|
|
36
38
|
"target_branch": self.target_branch.name,
|
|
37
39
|
"source_branch": self.source_branch_name,
|
|
40
|
+
"migrated_kinds_id_map": self.migrated_kinds_id_map,
|
|
41
|
+
"migrated_kinds_uuids": list(self.migrated_kinds_id_map.keys()),
|
|
38
42
|
}
|
|
39
43
|
# ruff: noqa: E501
|
|
40
44
|
query = """
|
|
41
45
|
UNWIND $node_diff_dicts AS node_diff_map
|
|
46
|
+
WITH node_diff_map, node_diff_map.uuid IN $migrated_kinds_uuids AS is_node_kind_migration
|
|
47
|
+
WITH node_diff_map, is_node_kind_migration, CASE
|
|
48
|
+
WHEN $migrated_kinds_uuids IS NULL THEN NULL
|
|
49
|
+
WHEN is_node_kind_migration THEN $migrated_kinds_id_map[node_diff_map.uuid]
|
|
50
|
+
ELSE NULL
|
|
51
|
+
END AS node_db_id
|
|
42
52
|
CALL {
|
|
43
|
-
WITH node_diff_map
|
|
53
|
+
WITH node_diff_map, node_db_id
|
|
44
54
|
MATCH (n:Node {uuid: node_diff_map.uuid})
|
|
55
|
+
WHERE node_db_id IS NULL
|
|
56
|
+
OR %(id_func)s(n) = node_db_id
|
|
45
57
|
RETURN n
|
|
46
58
|
}
|
|
47
|
-
WITH n, node_diff_map
|
|
59
|
+
WITH n, node_diff_map, is_node_kind_migration
|
|
48
60
|
CALL {
|
|
49
|
-
WITH n, node_diff_map
|
|
50
|
-
WITH n, node_diff_map, CASE
|
|
61
|
+
WITH n, node_diff_map, is_node_kind_migration
|
|
62
|
+
WITH n, node_diff_map, is_node_kind_migration, CASE
|
|
51
63
|
WHEN node_diff_map.action = "ADDED" THEN "active"
|
|
52
64
|
WHEN node_diff_map.action = "REMOVED" THEN "deleted"
|
|
53
65
|
ELSE NULL
|
|
@@ -56,9 +68,11 @@ CALL {
|
|
|
56
68
|
// ------------------------------
|
|
57
69
|
// only make IS_PART_OF updates if node is ADDED or REMOVED
|
|
58
70
|
// ------------------------------
|
|
59
|
-
WITH n, node_diff_map, node_rel_status
|
|
60
|
-
WITH n, node_diff_map, node_rel_status
|
|
71
|
+
WITH n, node_diff_map, node_rel_status, is_node_kind_migration
|
|
72
|
+
WITH n, node_diff_map, node_rel_status, is_node_kind_migration
|
|
61
73
|
WHERE node_rel_status IS NOT NULL
|
|
74
|
+
// nodes with a migrated kind are handled in DiffMergeMigratedKindsQuery
|
|
75
|
+
AND is_node_kind_migration = FALSE
|
|
62
76
|
MATCH (root:Root)
|
|
63
77
|
// ------------------------------
|
|
64
78
|
// set IS_PART_OF.to, optionally, target branch
|
|
@@ -214,22 +228,30 @@ CALL {
|
|
|
214
228
|
// ------------------------------
|
|
215
229
|
CALL {
|
|
216
230
|
WITH n, relationship_diff_map
|
|
217
|
-
WITH
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
231
|
+
WITH
|
|
232
|
+
n, relationship_diff_map.peer_id AS rel_peer_id, relationship_diff_map.name AS rel_name,
|
|
233
|
+
CASE
|
|
234
|
+
WHEN relationship_diff_map.action = "ADDED" THEN "active"
|
|
235
|
+
WHEN relationship_diff_map.action = "REMOVED" THEN "deleted"
|
|
236
|
+
ELSE NULL
|
|
237
|
+
END AS related_rel_status,
|
|
238
|
+
CASE
|
|
239
|
+
WHEN $migrated_kinds_uuids IS NULL THEN NULL
|
|
240
|
+
WHEN relationship_diff_map.peer_id IN $migrated_kinds_uuids THEN $migrated_kinds_id_map[relationship_diff_map.peer_id]
|
|
241
|
+
ELSE NULL
|
|
242
|
+
END AS rel_peer_db_id
|
|
222
243
|
// ------------------------------
|
|
223
244
|
// determine the directions of each IS_RELATED
|
|
224
245
|
// ------------------------------
|
|
225
246
|
CALL {
|
|
226
|
-
WITH n, rel_name, rel_peer_id, related_rel_status
|
|
247
|
+
WITH n, rel_name, rel_peer_id, rel_peer_db_id, related_rel_status
|
|
227
248
|
MATCH (n)
|
|
228
249
|
-[source_r_rel_1:IS_RELATED]
|
|
229
250
|
-(r:Relationship {name: rel_name})
|
|
230
251
|
-[source_r_rel_2:IS_RELATED]
|
|
231
|
-
-(:Node {uuid: rel_peer_id})
|
|
232
|
-
WHERE
|
|
252
|
+
-(rel_peer:Node {uuid: rel_peer_id})
|
|
253
|
+
WHERE (rel_peer_db_id IS NULL OR %(id_func)s(rel_peer) = rel_peer_db_id)
|
|
254
|
+
AND source_r_rel_1.branch IN [$source_branch, $target_branch]
|
|
233
255
|
AND source_r_rel_2.branch IN [$source_branch, $target_branch]
|
|
234
256
|
AND source_r_rel_1.from <= $at AND source_r_rel_1.to IS NULL
|
|
235
257
|
AND source_r_rel_2.from <= $at AND source_r_rel_2.to IS NULL
|
|
@@ -247,27 +269,29 @@ CALL {
|
|
|
247
269
|
source_r_rel_1.hierarchy AS r1_hierarchy,
|
|
248
270
|
source_r_rel_2.hierarchy AS r2_hierarchy
|
|
249
271
|
}
|
|
250
|
-
WITH n, r, r1_dir, r2_dir, r1_hierarchy, r2_hierarchy, rel_name, rel_peer_id, related_rel_status
|
|
272
|
+
WITH n, r, r1_dir, r2_dir, r1_hierarchy, r2_hierarchy, rel_name, rel_peer_id, rel_peer_db_id, related_rel_status
|
|
251
273
|
CALL {
|
|
252
|
-
WITH n, rel_name, rel_peer_id, related_rel_status
|
|
274
|
+
WITH n, rel_name, rel_peer_id, rel_peer_db_id, related_rel_status
|
|
253
275
|
OPTIONAL MATCH (n)
|
|
254
276
|
-[target_r_rel_1:IS_RELATED {branch: $target_branch, status: "active"}]
|
|
255
277
|
-(:Relationship {name: rel_name})
|
|
256
278
|
-[target_r_rel_2:IS_RELATED {branch: $target_branch, status: "active"}]
|
|
257
|
-
-(:Node {uuid: rel_peer_id})
|
|
279
|
+
-(rel_peer:Node {uuid: rel_peer_id})
|
|
258
280
|
WHERE related_rel_status = "deleted"
|
|
281
|
+
AND (rel_peer_db_id IS NULL OR %(id_func)s(rel_peer) = rel_peer_db_id)
|
|
259
282
|
AND target_r_rel_1.from <= $at AND target_r_rel_1.to IS NULL
|
|
260
283
|
AND target_r_rel_2.from <= $at AND target_r_rel_2.to IS NULL
|
|
261
284
|
SET target_r_rel_1.to = $at
|
|
262
285
|
SET target_r_rel_2.to = $at
|
|
263
286
|
}
|
|
264
|
-
WITH n, r, r1_dir, r2_dir, r1_hierarchy, r2_hierarchy, rel_name, rel_peer_id, related_rel_status
|
|
287
|
+
WITH n, r, r1_dir, r2_dir, r1_hierarchy, r2_hierarchy, rel_name, rel_peer_id, rel_peer_db_id, related_rel_status
|
|
265
288
|
// ------------------------------
|
|
266
289
|
// conditionally create new IS_RELATED relationships on target_branch, if necessary
|
|
267
290
|
// ------------------------------
|
|
268
291
|
CALL {
|
|
269
|
-
WITH n, r, r1_dir, r2_dir, r1_hierarchy, r2_hierarchy, rel_name, rel_peer_id, related_rel_status
|
|
292
|
+
WITH n, r, r1_dir, r2_dir, r1_hierarchy, r2_hierarchy, rel_name, rel_peer_id, rel_peer_db_id, related_rel_status
|
|
270
293
|
MATCH (p:Node {uuid: rel_peer_id})
|
|
294
|
+
WHERE rel_peer_db_id IS NULL OR %(id_func)s(p) = rel_peer_db_id
|
|
271
295
|
OPTIONAL MATCH (n)
|
|
272
296
|
-[r_rel_1:IS_RELATED {branch: $target_branch, status: related_rel_status}]
|
|
273
297
|
-(:Relationship {name: rel_name})
|
|
@@ -320,7 +344,7 @@ CALL {
|
|
|
320
344
|
}
|
|
321
345
|
}
|
|
322
346
|
RETURN 1 AS done
|
|
323
|
-
"""
|
|
347
|
+
""" % {"id_func": db.get_id_function_name()}
|
|
324
348
|
self.add_to_query(query=query)
|
|
325
349
|
|
|
326
350
|
|
|
@@ -334,6 +358,7 @@ class DiffMergePropertiesQuery(Query):
|
|
|
334
358
|
property_diff_dicts: dict[str, Any],
|
|
335
359
|
at: Timestamp,
|
|
336
360
|
target_branch: Branch,
|
|
361
|
+
migrated_kinds_id_map: dict[str, str],
|
|
337
362
|
**kwargs: Any,
|
|
338
363
|
) -> None:
|
|
339
364
|
super().__init__(**kwargs)
|
|
@@ -341,6 +366,7 @@ class DiffMergePropertiesQuery(Query):
|
|
|
341
366
|
self.at = at
|
|
342
367
|
self.target_branch = target_branch
|
|
343
368
|
self.source_branch_name = self.branch.name
|
|
369
|
+
self.migrated_kinds_id_map = migrated_kinds_id_map
|
|
344
370
|
|
|
345
371
|
async def query_init(self, db: InfrahubDatabase, **kwargs: Any) -> None: # noqa: ARG002
|
|
346
372
|
self.params = {
|
|
@@ -349,53 +375,70 @@ class DiffMergePropertiesQuery(Query):
|
|
|
349
375
|
"branch_level": self.target_branch.hierarchy_level,
|
|
350
376
|
"target_branch": self.target_branch.name,
|
|
351
377
|
"source_branch": self.source_branch_name,
|
|
378
|
+
"migrated_kinds_id_map": self.migrated_kinds_id_map,
|
|
379
|
+
"migrated_kinds_uuids": list(self.migrated_kinds_id_map.keys()),
|
|
352
380
|
}
|
|
353
381
|
query = """
|
|
354
382
|
UNWIND $property_diff_dicts AS attr_rel_prop_diff
|
|
383
|
+
WITH attr_rel_prop_diff, CASE
|
|
384
|
+
WHEN $migrated_kinds_uuids IS NULL THEN NULL
|
|
385
|
+
WHEN attr_rel_prop_diff.node_uuid IN $migrated_kinds_uuids THEN $migrated_kinds_id_map[attr_rel_prop_diff.node_uuid]
|
|
386
|
+
ELSE NULL
|
|
387
|
+
END AS node_db_id,
|
|
388
|
+
CASE
|
|
389
|
+
WHEN $migrated_kinds_uuids IS NULL THEN NULL
|
|
390
|
+
WHEN attr_rel_prop_diff.peer_uuid IN $migrated_kinds_uuids THEN $migrated_kinds_id_map[attr_rel_prop_diff.peer_uuid]
|
|
391
|
+
ELSE NULL
|
|
392
|
+
END AS peer_db_id
|
|
355
393
|
CALL {
|
|
356
394
|
// ------------------------------
|
|
357
395
|
// find the Attribute node
|
|
358
396
|
// ------------------------------
|
|
359
|
-
WITH attr_rel_prop_diff
|
|
397
|
+
WITH attr_rel_prop_diff, node_db_id, peer_db_id
|
|
360
398
|
CALL {
|
|
361
|
-
WITH attr_rel_prop_diff
|
|
399
|
+
WITH attr_rel_prop_diff, node_db_id
|
|
362
400
|
OPTIONAL MATCH (n:Node {uuid: attr_rel_prop_diff.node_uuid})
|
|
363
401
|
-[has_attr:HAS_ATTRIBUTE]
|
|
364
402
|
->(attr:Attribute {name: attr_rel_prop_diff.attribute_name})
|
|
365
403
|
WHERE attr_rel_prop_diff.attribute_name IS NOT NULL
|
|
404
|
+
AND (node_db_id IS NULL OR %(id_func)s(n) = node_db_id)
|
|
366
405
|
AND has_attr.branch IN [$source_branch, $target_branch]
|
|
367
406
|
RETURN attr
|
|
368
407
|
ORDER BY has_attr.from DESC
|
|
369
408
|
LIMIT 1
|
|
370
409
|
}
|
|
371
410
|
CALL {
|
|
372
|
-
WITH attr_rel_prop_diff
|
|
411
|
+
WITH attr_rel_prop_diff, node_db_id, peer_db_id
|
|
373
412
|
OPTIONAL MATCH (n:Node {uuid: attr_rel_prop_diff.node_uuid})
|
|
374
413
|
-[r1:IS_RELATED]
|
|
375
414
|
-(rel:Relationship {name: attr_rel_prop_diff.relationship_id})
|
|
376
415
|
-[r2:IS_RELATED]
|
|
377
|
-
-(:Node {uuid: attr_rel_prop_diff.peer_uuid})
|
|
416
|
+
-(rel_peer:Node {uuid: attr_rel_prop_diff.peer_uuid})
|
|
378
417
|
WHERE attr_rel_prop_diff.relationship_id IS NOT NULL
|
|
418
|
+
AND (node_db_id IS NULL OR %(id_func)s(n) = node_db_id)
|
|
419
|
+
AND (peer_db_id IS NULL OR %(id_func)s(rel_peer) = peer_db_id)
|
|
379
420
|
AND r1.branch IN [$source_branch, $target_branch]
|
|
380
421
|
AND r2.branch IN [$source_branch, $target_branch]
|
|
381
422
|
RETURN rel
|
|
382
423
|
ORDER BY r1.branch_level DESC, r2.branch_level DESC, r1.from DESC, r2.from DESC
|
|
383
424
|
LIMIT 1
|
|
384
425
|
}
|
|
385
|
-
WITH attr_rel_prop_diff, COALESCE(attr, rel) AS attr_rel
|
|
426
|
+
WITH attr_rel_prop_diff, COALESCE(attr, rel) AS attr_rel, peer_db_id
|
|
427
|
+
WHERE attr_rel IS NOT NULL
|
|
386
428
|
UNWIND attr_rel_prop_diff.properties AS property_diff
|
|
387
429
|
// ------------------------------
|
|
388
430
|
// handle updates for properties under this attribute/relationship
|
|
389
431
|
// ------------------------------
|
|
390
432
|
CALL {
|
|
391
|
-
WITH attr_rel, property_diff
|
|
433
|
+
WITH attr_rel, property_diff, peer_db_id
|
|
392
434
|
// ------------------------------
|
|
393
435
|
// identify the correct property node to link
|
|
394
436
|
// ------------------------------
|
|
395
437
|
CALL {
|
|
396
|
-
WITH attr_rel, property_diff
|
|
438
|
+
WITH attr_rel, property_diff, peer_db_id
|
|
397
439
|
OPTIONAL MATCH (peer:Node {uuid: property_diff.value})
|
|
398
440
|
WHERE property_diff.property_type IN ["HAS_SOURCE", "HAS_OWNER"]
|
|
441
|
+
AND (peer_db_id IS NULL OR %(id_func)s(peer) = peer_db_id)
|
|
399
442
|
// ------------------------------
|
|
400
443
|
// the serialized diff might not include the values for IS_VISIBLE and IS_PROTECTED in
|
|
401
444
|
// some cases, so we need to figure them out here
|
|
@@ -497,10 +540,203 @@ CALL {
|
|
|
497
540
|
}
|
|
498
541
|
}
|
|
499
542
|
}
|
|
500
|
-
"""
|
|
543
|
+
""" % {"id_func": db.get_id_function_name()}
|
|
501
544
|
self.add_to_query(query=query)
|
|
502
545
|
|
|
503
546
|
|
|
547
|
+
class DiffMergeMigratedKindsQuery(Query):
|
|
548
|
+
name = "diff_merge_migrated_kinds"
|
|
549
|
+
type = QueryType.WRITE
|
|
550
|
+
insert_return = False
|
|
551
|
+
|
|
552
|
+
def __init__(
|
|
553
|
+
self,
|
|
554
|
+
migrated_uuids: list[str],
|
|
555
|
+
at: Timestamp,
|
|
556
|
+
target_branch: Branch,
|
|
557
|
+
**kwargs: Any,
|
|
558
|
+
) -> None:
|
|
559
|
+
super().__init__(**kwargs)
|
|
560
|
+
self.migrated_uuids = migrated_uuids
|
|
561
|
+
self.at = at
|
|
562
|
+
self.target_branch = target_branch
|
|
563
|
+
self.source_branch_name = self.branch.name
|
|
564
|
+
|
|
565
|
+
async def query_init(self, db: InfrahubDatabase, **kwargs: Any) -> None: # noqa: ARG002
|
|
566
|
+
self.params = {
|
|
567
|
+
"migrated_uuids": self.migrated_uuids,
|
|
568
|
+
"at": self.at.to_string(),
|
|
569
|
+
"branch_level": self.target_branch.hierarchy_level,
|
|
570
|
+
"target_branch": self.target_branch.name,
|
|
571
|
+
"source_branch": self.source_branch_name,
|
|
572
|
+
}
|
|
573
|
+
query = """
|
|
574
|
+
MATCH (n:Node)
|
|
575
|
+
WHERE n.uuid IN $migrated_uuids
|
|
576
|
+
CALL {
|
|
577
|
+
// --------------
|
|
578
|
+
// for each migrated node (created or deleted), find its latest edges on the source branch,
|
|
579
|
+
// check if they exist on the target, create them if not
|
|
580
|
+
// --------------
|
|
581
|
+
WITH n
|
|
582
|
+
MATCH (n)-[]-(peer)
|
|
583
|
+
WITH DISTINCT n, peer
|
|
584
|
+
CALL {
|
|
585
|
+
// --------------
|
|
586
|
+
// get the latest outbound edge for each type between n and peer
|
|
587
|
+
// --------------
|
|
588
|
+
WITH n, peer
|
|
589
|
+
MATCH (n)-[e {branch: $source_branch}]->(peer)
|
|
590
|
+
WHERE e.from <= $at AND e.to IS NULL
|
|
591
|
+
WITH e, type(e) AS edge_type
|
|
592
|
+
ORDER BY edge_type, e.from DESC
|
|
593
|
+
WITH edge_type, head(collect(e)) AS latest_source_edge
|
|
594
|
+
RETURN edge_type, latest_source_edge
|
|
595
|
+
}
|
|
596
|
+
CALL {
|
|
597
|
+
// --------------
|
|
598
|
+
// for each n, peer, edge_type, get the latest edge on target
|
|
599
|
+
// --------------
|
|
600
|
+
WITH n, peer, edge_type
|
|
601
|
+
OPTIONAL MATCH (n)-[e {branch: $target_branch}]->(peer)
|
|
602
|
+
WHERE type(e) = edge_type AND e.from <= $at
|
|
603
|
+
RETURN e AS latest_target_edge
|
|
604
|
+
ORDER BY e.from DESC
|
|
605
|
+
LIMIT 1
|
|
606
|
+
}
|
|
607
|
+
// --------------
|
|
608
|
+
// ignore edges of this type that already have the correct status on the target branch
|
|
609
|
+
// --------------
|
|
610
|
+
WITH n, peer, edge_type, latest_source_edge, latest_target_edge
|
|
611
|
+
WHERE (latest_target_edge IS NULL AND latest_source_edge.status = "active")
|
|
612
|
+
OR latest_source_edge.status <> latest_target_edge.status
|
|
613
|
+
CALL {
|
|
614
|
+
// --------------
|
|
615
|
+
// set the to time on active target branch edges that we are setting to deleted
|
|
616
|
+
// --------------
|
|
617
|
+
WITH latest_source_edge, latest_target_edge
|
|
618
|
+
WITH latest_source_edge, latest_target_edge
|
|
619
|
+
WHERE latest_target_edge IS NOT NULL
|
|
620
|
+
AND latest_source_edge.status = "deleted"
|
|
621
|
+
AND latest_target_edge.status = "active"
|
|
622
|
+
AND latest_target_edge.to IS NULL
|
|
623
|
+
SET latest_target_edge.to = $at
|
|
624
|
+
}
|
|
625
|
+
// --------------
|
|
626
|
+
// create the outbound edges on the target branch, one subquery per possible type
|
|
627
|
+
// --------------
|
|
628
|
+
CALL {
|
|
629
|
+
WITH n, latest_source_edge, peer, edge_type
|
|
630
|
+
WITH n, latest_source_edge, peer, edge_type
|
|
631
|
+
WHERE edge_type = "IS_PART_OF"
|
|
632
|
+
CREATE (n)-[new_edge:IS_PART_OF]->(peer)
|
|
633
|
+
SET new_edge = properties(latest_source_edge)
|
|
634
|
+
SET new_edge.from = $at
|
|
635
|
+
SET new_edge.branch_level = $branch_level
|
|
636
|
+
SET new_edge.branch = $target_branch
|
|
637
|
+
}
|
|
638
|
+
CALL {
|
|
639
|
+
WITH n, latest_source_edge, peer, edge_type
|
|
640
|
+
WITH n, latest_source_edge, peer, edge_type
|
|
641
|
+
WHERE edge_type = "IS_RELATED"
|
|
642
|
+
CREATE (n)-[new_edge:IS_RELATED]->(peer)
|
|
643
|
+
SET new_edge = properties(latest_source_edge)
|
|
644
|
+
SET new_edge.from = $at
|
|
645
|
+
SET new_edge.branch_level = $branch_level
|
|
646
|
+
SET new_edge.branch = $target_branch
|
|
647
|
+
}
|
|
648
|
+
CALL {
|
|
649
|
+
WITH n, latest_source_edge, peer, edge_type
|
|
650
|
+
WITH n, latest_source_edge, peer, edge_type
|
|
651
|
+
WHERE edge_type = "HAS_ATTRIBUTE"
|
|
652
|
+
CREATE (n)-[new_edge:HAS_ATTRIBUTE]->(peer)
|
|
653
|
+
SET new_edge = properties(latest_source_edge)
|
|
654
|
+
SET new_edge.from = $at
|
|
655
|
+
SET new_edge.branch_level = $branch_level
|
|
656
|
+
SET new_edge.branch = $target_branch
|
|
657
|
+
}
|
|
658
|
+
// --------------
|
|
659
|
+
// do all of this again for inbound edges
|
|
660
|
+
// --------------
|
|
661
|
+
WITH DISTINCT n, peer
|
|
662
|
+
CALL {
|
|
663
|
+
// --------------
|
|
664
|
+
// get the latest inbound edge for each type between n and peer
|
|
665
|
+
// --------------
|
|
666
|
+
WITH n, peer
|
|
667
|
+
MATCH (n)<-[e {branch: $source_branch}]-(peer)
|
|
668
|
+
WHERE e.from <= $at AND e.to IS NULL
|
|
669
|
+
WITH e, type(e) AS edge_type
|
|
670
|
+
ORDER BY edge_type, e.from DESC
|
|
671
|
+
WITH edge_type, head(collect(e)) AS latest_source_edge
|
|
672
|
+
RETURN edge_type, latest_source_edge
|
|
673
|
+
}
|
|
674
|
+
CALL {
|
|
675
|
+
// --------------
|
|
676
|
+
// for each n, peer, edge_type, get the latest edge on target
|
|
677
|
+
// --------------
|
|
678
|
+
WITH n, peer, edge_type
|
|
679
|
+
OPTIONAL MATCH (n)<-[e {branch: $target_branch}]-(peer)
|
|
680
|
+
WHERE type(e) = edge_type AND e.from <= $at
|
|
681
|
+
RETURN e AS latest_target_edge
|
|
682
|
+
ORDER BY e.from DESC
|
|
683
|
+
LIMIT 1
|
|
684
|
+
}
|
|
685
|
+
// --------------
|
|
686
|
+
// ignore edges of this type that already have the correct status on the target branch
|
|
687
|
+
// --------------
|
|
688
|
+
WITH n, peer, edge_type, latest_source_edge, latest_target_edge
|
|
689
|
+
WHERE latest_target_edge IS NULL OR latest_source_edge.status <> latest_target_edge.status
|
|
690
|
+
CALL {
|
|
691
|
+
// --------------
|
|
692
|
+
// set the to time on active target branch edges that we are setting to deleted
|
|
693
|
+
// --------------
|
|
694
|
+
WITH latest_source_edge, latest_target_edge
|
|
695
|
+
WITH latest_source_edge, latest_target_edge
|
|
696
|
+
WHERE latest_target_edge IS NOT NULL
|
|
697
|
+
AND latest_source_edge.status = "deleted"
|
|
698
|
+
AND latest_target_edge.status = "active"
|
|
699
|
+
AND latest_target_edge.to IS NULL
|
|
700
|
+
SET latest_target_edge.to = $at
|
|
701
|
+
}
|
|
702
|
+
// --------------
|
|
703
|
+
// create the outbound edges on the target branch, one subquery per possible type
|
|
704
|
+
// --------------
|
|
705
|
+
CALL {
|
|
706
|
+
WITH n, latest_source_edge, peer, edge_type
|
|
707
|
+
WITH n, latest_source_edge, peer, edge_type
|
|
708
|
+
WHERE edge_type = "IS_RELATED"
|
|
709
|
+
CREATE (n)<-[new_edge:IS_RELATED]-(peer)
|
|
710
|
+
SET new_edge = properties(latest_source_edge)
|
|
711
|
+
SET new_edge.from = $at
|
|
712
|
+
SET new_edge.branch_level = $branch_level
|
|
713
|
+
SET new_edge.branch = $target_branch
|
|
714
|
+
}
|
|
715
|
+
CALL {
|
|
716
|
+
WITH n, latest_source_edge, peer, edge_type
|
|
717
|
+
WITH n, latest_source_edge, peer, edge_type
|
|
718
|
+
WHERE edge_type = "HAS_OWNER"
|
|
719
|
+
CREATE (n)<-[new_edge:HAS_OWNER]-(peer)
|
|
720
|
+
SET new_edge = properties(latest_source_edge)
|
|
721
|
+
SET new_edge.from = $at
|
|
722
|
+
SET new_edge.branch_level = $branch_level
|
|
723
|
+
SET new_edge.branch = $target_branch
|
|
724
|
+
}
|
|
725
|
+
CALL {
|
|
726
|
+
WITH n, latest_source_edge, peer, edge_type
|
|
727
|
+
WITH n, latest_source_edge, peer, edge_type
|
|
728
|
+
WHERE edge_type = "HAS_SOURCE"
|
|
729
|
+
CREATE (n)<-[new_edge:HAS_SOURCE]-(peer)
|
|
730
|
+
SET new_edge = properties(latest_source_edge)
|
|
731
|
+
SET new_edge.from = $at
|
|
732
|
+
SET new_edge.branch_level = $branch_level
|
|
733
|
+
SET new_edge.branch = $target_branch
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
"""
|
|
737
|
+
self.add_to_query(query)
|
|
738
|
+
|
|
739
|
+
|
|
504
740
|
class DiffMergeRollbackQuery(Query):
|
|
505
741
|
name = "diff_merge_rollback"
|
|
506
742
|
type = QueryType.WRITE
|
infrahub/core/diff/query/save.py
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import json
|
|
2
1
|
from typing import Any, Iterable
|
|
3
2
|
|
|
4
3
|
from infrahub.core.query import Query, QueryType
|
|
@@ -86,16 +85,17 @@ UNWIND $node_details_list AS node_details
|
|
|
86
85
|
WITH
|
|
87
86
|
node_details.root_uuid AS root_uuid,
|
|
88
87
|
node_details.node_map AS node_map,
|
|
89
|
-
toString(node_details.node_map.node_properties.uuid) AS node_uuid
|
|
88
|
+
toString(node_details.node_map.node_properties.uuid) AS node_uuid,
|
|
89
|
+
node_details.node_map.node_properties.db_id AS node_db_id
|
|
90
90
|
MERGE (diff_root:DiffRoot {uuid: root_uuid})
|
|
91
|
-
MERGE (diff_root)-[:DIFF_HAS_NODE]->(diff_node:DiffNode {uuid: node_uuid})
|
|
91
|
+
MERGE (diff_root)-[:DIFF_HAS_NODE]->(diff_node:DiffNode {uuid: node_uuid, db_id: node_db_id})
|
|
92
92
|
WITH root_uuid, node_map, diff_node, (node_map.conflict_params IS NOT NULL) AS has_node_conflict
|
|
93
93
|
SET
|
|
94
94
|
diff_node.kind = node_map.node_properties.kind,
|
|
95
95
|
diff_node.label = node_map.node_properties.label,
|
|
96
|
-
diff_node.db_labels = node_map.node_properties.db_labels,
|
|
97
96
|
diff_node.changed_at = node_map.node_properties.changed_at,
|
|
98
97
|
diff_node.action = node_map.node_properties.action,
|
|
98
|
+
diff_node.is_node_kind_migration = node_map.node_properties.is_node_kind_migration,
|
|
99
99
|
diff_node.path_identifier = node_map.node_properties.path_identifier
|
|
100
100
|
WITH root_uuid, node_map, diff_node, has_node_conflict
|
|
101
101
|
CALL {
|
|
@@ -403,7 +403,8 @@ FOREACH (i in CASE WHEN has_property_conflict = TRUE THEN [1] ELSE [] END |
|
|
|
403
403
|
"node_properties": {
|
|
404
404
|
"uuid": enriched_node.uuid,
|
|
405
405
|
"kind": enriched_node.kind,
|
|
406
|
-
"
|
|
406
|
+
"db_id": enriched_node.identifier.db_id,
|
|
407
|
+
"is_node_kind_migration": enriched_node.is_node_kind_migration,
|
|
407
408
|
"label": enriched_node.label,
|
|
408
409
|
"changed_at": enriched_node.changed_at.to_string() if enriched_node.changed_at else None,
|
|
409
410
|
"action": enriched_node.action.value,
|
|
@@ -558,7 +558,7 @@ class DiffQueryParser:
|
|
|
558
558
|
|
|
559
559
|
def _get_diff_node(self, database_path: DatabasePath, diff_root: DiffRootIntermediate) -> DiffNodeIntermediate:
|
|
560
560
|
identifier = NodeIdentifier(
|
|
561
|
-
uuid=database_path.node_id, kind=database_path.node_kind,
|
|
561
|
+
uuid=database_path.node_id, kind=database_path.node_kind, db_id=database_path.node_db_id
|
|
562
562
|
)
|
|
563
563
|
if identifier not in diff_root.nodes_by_identifier:
|
|
564
564
|
diff_root.nodes_by_identifier[identifier] = DiffNodeIntermediate(
|
|
@@ -571,19 +571,6 @@ class DiffQueryParser:
|
|
|
571
571
|
else None,
|
|
572
572
|
)
|
|
573
573
|
diff_node = diff_root.nodes_by_identifier[identifier]
|
|
574
|
-
# special handling for nodes that have their kind updated, which results in 2 nodes with the same uuid
|
|
575
|
-
if diff_node.db_id != database_path.node_db_id and (
|
|
576
|
-
database_path.node_changed_at > diff_node.from_time
|
|
577
|
-
or (
|
|
578
|
-
database_path.node_changed_at >= diff_node.from_time
|
|
579
|
-
and (diff_node.status, database_path.node_status)
|
|
580
|
-
== (RelationshipStatus.DELETED, RelationshipStatus.ACTIVE)
|
|
581
|
-
)
|
|
582
|
-
):
|
|
583
|
-
diff_node.identifier.kind = database_path.node_kind
|
|
584
|
-
diff_node.db_id = database_path.node_db_id
|
|
585
|
-
diff_node.from_time = database_path.node_changed_at
|
|
586
|
-
diff_node.status = database_path.node_status
|
|
587
574
|
diff_node.track_database_path(database_path=database_path)
|
|
588
575
|
return diff_node
|
|
589
576
|
|
|
@@ -697,8 +684,10 @@ class DiffQueryParser:
|
|
|
697
684
|
branch_diff_root = self._diff_root_by_branch.get(branch)
|
|
698
685
|
if not branch_diff_root:
|
|
699
686
|
continue
|
|
687
|
+
base_diff_nodes_by_uuid = {n.uuid: n for n in base_diff_root.nodes_by_identifier.values()}
|
|
700
688
|
for identifier, diff_node in branch_diff_root.nodes_by_identifier.items():
|
|
701
|
-
|
|
689
|
+
# changes on a base branch node with a given UUID should apply to all diff branch nodes with that UUID
|
|
690
|
+
base_diff_node = base_diff_nodes_by_uuid.get(identifier.uuid)
|
|
702
691
|
if not base_diff_node:
|
|
703
692
|
continue
|
|
704
693
|
self._apply_attribute_previous_values(diff_node=diff_node, base_diff_node=base_diff_node)
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import json
|
|
2
|
-
|
|
3
1
|
from neo4j.graph import Node as Neo4jNode
|
|
4
2
|
from neo4j.graph import Path as Neo4jPath
|
|
5
3
|
|
|
@@ -86,11 +84,13 @@ class EnrichedDiffDeserializer:
|
|
|
86
84
|
) -> None:
|
|
87
85
|
for attribute_result in result.get_nested_node_collection("diff_attributes"):
|
|
88
86
|
diff_attr_node, diff_attr_property_node, diff_attr_property_conflict = attribute_result
|
|
89
|
-
if diff_attr_node is None
|
|
87
|
+
if diff_attr_node is None:
|
|
90
88
|
continue
|
|
91
89
|
enriched_attribute = self._deserialize_diff_attr(
|
|
92
90
|
diff_attr_node=diff_attr_node, enriched_root=enriched_root, enriched_node=enriched_node
|
|
93
91
|
)
|
|
92
|
+
if diff_attr_property_node is None:
|
|
93
|
+
continue
|
|
94
94
|
enriched_property = self._deserialize_diff_attr_property(
|
|
95
95
|
diff_attr_property_node=diff_attr_property_node,
|
|
96
96
|
enriched_attr=enriched_attribute,
|
|
@@ -147,7 +147,7 @@ class EnrichedDiffDeserializer:
|
|
|
147
147
|
parent_identifier = NodeIdentifier(
|
|
148
148
|
uuid=parent.get("uuid"),
|
|
149
149
|
kind=parent.get("kind"),
|
|
150
|
-
|
|
150
|
+
db_id=parent.get("db_id"),
|
|
151
151
|
)
|
|
152
152
|
parent_request = ParentNodeAddRequest(
|
|
153
153
|
node_identifier=current_node_identifier,
|
|
@@ -201,8 +201,8 @@ class EnrichedDiffDeserializer:
|
|
|
201
201
|
def _deserialize_diff_node(self, node_node: Neo4jNode, enriched_root: EnrichedDiffRoot) -> EnrichedDiffNode:
|
|
202
202
|
node_uuid = str(node_node.get("uuid"))
|
|
203
203
|
node_kind = str(node_node.get("kind"))
|
|
204
|
-
|
|
205
|
-
node_identifier = NodeIdentifier(uuid=node_uuid, kind=node_kind,
|
|
204
|
+
node_db_id = node_node.get("db_id")
|
|
205
|
+
node_identifier = NodeIdentifier(uuid=node_uuid, kind=node_kind, db_id=node_db_id)
|
|
206
206
|
node_key = (enriched_root.uuid, node_identifier)
|
|
207
207
|
if node_key in self._diff_node_map:
|
|
208
208
|
return self._diff_node_map[node_key]
|
|
@@ -213,6 +213,7 @@ class EnrichedDiffDeserializer:
|
|
|
213
213
|
label=str(node_node.get("label")),
|
|
214
214
|
changed_at=Timestamp(timestamp_str) if timestamp_str else None,
|
|
215
215
|
action=DiffAction(str(node_node.get("action"))),
|
|
216
|
+
is_node_kind_migration=bool(node_node.get("is_node_kind_migration")),
|
|
216
217
|
path_identifier=str(node_node.get("path_identifier")),
|
|
217
218
|
num_added=int(node_node.get("num_added", 0)),
|
|
218
219
|
num_updated=int(node_node.get("num_updated", 0)),
|
infrahub/core/graph/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
GRAPH_VERSION =
|
|
1
|
+
GRAPH_VERSION = 28
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from infrahub.core import registry
|
|
6
|
+
from infrahub.core.diff.repository.repository import DiffRepository
|
|
7
|
+
from infrahub.core.migrations.shared import MigrationResult
|
|
8
|
+
from infrahub.dependencies.registry import build_component_registry, get_component_registry
|
|
9
|
+
from infrahub.log import get_logger
|
|
10
|
+
|
|
11
|
+
from ..shared import ArbitraryMigration
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from infrahub.database import InfrahubDatabase
|
|
15
|
+
|
|
16
|
+
log = get_logger()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Migration028(ArbitraryMigration):
|
|
20
|
+
"""Delete all diffs because of an update to how we store diff information. All diffs will need to be recalculated"""
|
|
21
|
+
|
|
22
|
+
name: str = "028_diff_delete_bug_fix_update"
|
|
23
|
+
minimum_version: int = 27
|
|
24
|
+
|
|
25
|
+
async def validate_migration(self, db: InfrahubDatabase) -> MigrationResult: # noqa: ARG002
|
|
26
|
+
result = MigrationResult()
|
|
27
|
+
|
|
28
|
+
return result
|
|
29
|
+
|
|
30
|
+
async def execute(self, db: InfrahubDatabase) -> MigrationResult:
|
|
31
|
+
default_branch = registry.get_branch_from_registry()
|
|
32
|
+
build_component_registry()
|
|
33
|
+
component_registry = get_component_registry()
|
|
34
|
+
diff_repo = await component_registry.get_component(DiffRepository, db=db, branch=default_branch)
|
|
35
|
+
|
|
36
|
+
diff_roots = await diff_repo.get_roots_metadata()
|
|
37
|
+
await diff_repo.delete_diff_roots(diff_root_uuids=[d.uuid for d in diff_roots])
|
|
38
|
+
return MigrationResult()
|