infrahub-server 1.3.0b6__py3-none-any.whl → 1.3.2__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/cli/db.py +7 -5
- infrahub/cli/upgrade.py +6 -1
- infrahub/core/attribute.py +5 -0
- infrahub/core/diff/calculator.py +4 -1
- infrahub/core/diff/coordinator.py +8 -1
- infrahub/core/diff/query/field_specifiers.py +1 -1
- infrahub/core/diff/query/merge.py +2 -2
- infrahub/core/diff/query_parser.py +23 -32
- infrahub/core/graph/__init__.py +1 -1
- infrahub/core/migrations/graph/__init__.py +2 -0
- infrahub/core/migrations/graph/m012_convert_account_generic.py +1 -1
- infrahub/core/migrations/graph/m023_deduplicate_cardinality_one_relationships.py +2 -2
- infrahub/core/migrations/graph/m029_duplicates_cleanup.py +2 -2
- infrahub/core/migrations/graph/m031_check_number_attributes.py +102 -0
- infrahub/core/migrations/query/attribute_rename.py +1 -1
- infrahub/core/node/__init__.py +5 -1
- infrahub/core/node/constraints/grouped_uniqueness.py +88 -132
- infrahub/core/query/delete.py +3 -3
- infrahub/core/schema/attribute_parameters.py +12 -5
- infrahub/core/schema/basenode_schema.py +107 -1
- infrahub/core/schema/schema_branch.py +17 -5
- infrahub/core/validators/attribute/min_max.py +7 -2
- infrahub/core/validators/uniqueness/model.py +17 -0
- infrahub/core/validators/uniqueness/query.py +212 -1
- infrahub/graphql/app.py +5 -1
- infrahub/graphql/mutations/main.py +18 -2
- infrahub/services/adapters/message_bus/nats.py +5 -1
- infrahub/services/scheduler.py +5 -1
- infrahub_sdk/node/__init__.py +2 -0
- infrahub_sdk/node/node.py +33 -2
- infrahub_sdk/node/related_node.py +7 -0
- {infrahub_server-1.3.0b6.dist-info → infrahub_server-1.3.2.dist-info}/METADATA +1 -1
- {infrahub_server-1.3.0b6.dist-info → infrahub_server-1.3.2.dist-info}/RECORD +36 -35
- {infrahub_server-1.3.0b6.dist-info → infrahub_server-1.3.2.dist-info}/LICENSE.txt +0 -0
- {infrahub_server-1.3.0b6.dist-info → infrahub_server-1.3.2.dist-info}/WHEEL +0 -0
- {infrahub_server-1.3.0b6.dist-info → infrahub_server-1.3.2.dist-info}/entry_points.txt +0 -0
infrahub/cli/db.py
CHANGED
|
@@ -287,10 +287,10 @@ async def index(
|
|
|
287
287
|
await dbdriver.close()
|
|
288
288
|
|
|
289
289
|
|
|
290
|
-
async def migrate_database(db: InfrahubDatabase, initialize: bool = False, check: bool = False) ->
|
|
290
|
+
async def migrate_database(db: InfrahubDatabase, initialize: bool = False, check: bool = False) -> bool:
|
|
291
291
|
"""Apply the latest migrations to the database, this function will print the status directly in the console.
|
|
292
292
|
|
|
293
|
-
|
|
293
|
+
Returns a boolean indicating whether a migration failed or if all migrations succeeded.
|
|
294
294
|
|
|
295
295
|
Args:
|
|
296
296
|
db: The database object.
|
|
@@ -306,14 +306,14 @@ async def migrate_database(db: InfrahubDatabase, initialize: bool = False, check
|
|
|
306
306
|
|
|
307
307
|
if not migrations:
|
|
308
308
|
rprint(f"Database up-to-date (v{root_node.graph_version}), no migration to execute.")
|
|
309
|
-
return
|
|
309
|
+
return True
|
|
310
310
|
|
|
311
311
|
rprint(
|
|
312
312
|
f"Database needs to be updated (v{root_node.graph_version} -> v{GRAPH_VERSION}), {len(migrations)} migrations pending"
|
|
313
313
|
)
|
|
314
314
|
|
|
315
315
|
if check:
|
|
316
|
-
return
|
|
316
|
+
return True
|
|
317
317
|
|
|
318
318
|
for migration in migrations:
|
|
319
319
|
execution_result = await migration.execute(db=db)
|
|
@@ -333,7 +333,9 @@ async def migrate_database(db: InfrahubDatabase, initialize: bool = False, check
|
|
|
333
333
|
if validation_result and not validation_result.success:
|
|
334
334
|
for error in validation_result.errors:
|
|
335
335
|
rprint(f" {error}")
|
|
336
|
-
|
|
336
|
+
return False
|
|
337
|
+
|
|
338
|
+
return True
|
|
337
339
|
|
|
338
340
|
|
|
339
341
|
async def initialize_internal_schema() -> None:
|
infrahub/cli/upgrade.py
CHANGED
|
@@ -75,7 +75,12 @@ async def upgrade_cmd(
|
|
|
75
75
|
# -------------------------------------------
|
|
76
76
|
# Upgrade Infrahub Database and Schema
|
|
77
77
|
# -------------------------------------------
|
|
78
|
-
|
|
78
|
+
|
|
79
|
+
if not await migrate_database(db=dbdriver, initialize=False, check=check):
|
|
80
|
+
# A migration failed, stop the upgrade process
|
|
81
|
+
rprint("Upgrade cancelled due to migration failure.")
|
|
82
|
+
await dbdriver.close()
|
|
83
|
+
return
|
|
79
84
|
|
|
80
85
|
await initialize_internal_schema()
|
|
81
86
|
await update_core_schema(db=dbdriver, service=service, initialize=False)
|
infrahub/core/attribute.py
CHANGED
|
@@ -31,6 +31,7 @@ from infrahub.helpers import hash_password
|
|
|
31
31
|
|
|
32
32
|
from ..types import ATTRIBUTE_TYPES, LARGE_ATTRIBUTE_TYPES
|
|
33
33
|
from .constants.relationship_label import RELATIONSHIP_TO_NODE_LABEL, RELATIONSHIP_TO_VALUE_LABEL
|
|
34
|
+
from .schema.attribute_parameters import NumberAttributeParameters
|
|
34
35
|
|
|
35
36
|
if TYPE_CHECKING:
|
|
36
37
|
from infrahub.core.branch import Branch
|
|
@@ -255,6 +256,10 @@ class BaseAttribute(FlagPropertyMixin, NodePropertyMixin):
|
|
|
255
256
|
if len(value) > max_length:
|
|
256
257
|
raise ValidationError({name: f"{value} must have a maximum length of {max_length!r}"})
|
|
257
258
|
|
|
259
|
+
# Some invalid values may exist due to https://github.com/opsmill/infrahub/issues/6714.
|
|
260
|
+
if config.SETTINGS.main.schema_strict_mode and isinstance(schema.parameters, NumberAttributeParameters):
|
|
261
|
+
schema.parameters.check_valid_value(value=value, name=name)
|
|
262
|
+
|
|
258
263
|
if schema.enum:
|
|
259
264
|
try:
|
|
260
265
|
schema.convert_value_to_enum(value)
|
infrahub/core/diff/calculator.py
CHANGED
|
@@ -181,8 +181,11 @@ class DiffCalculator:
|
|
|
181
181
|
log.info("Diff property-level calculation queries for branch complete")
|
|
182
182
|
|
|
183
183
|
if base_branch.name != diff_branch.name:
|
|
184
|
-
current_node_field_specifiers = diff_parser.get_current_node_field_specifiers()
|
|
185
184
|
new_node_field_specifiers = diff_parser.get_new_node_field_specifiers()
|
|
185
|
+
current_node_field_specifiers = None
|
|
186
|
+
if previous_node_specifiers is not None:
|
|
187
|
+
current_node_field_specifiers = previous_node_specifiers - new_node_field_specifiers
|
|
188
|
+
|
|
186
189
|
base_calculation_request = DiffCalculationRequest(
|
|
187
190
|
base_branch=base_branch,
|
|
188
191
|
diff_branch=base_branch,
|
|
@@ -4,7 +4,10 @@ from dataclasses import dataclass, field
|
|
|
4
4
|
from typing import TYPE_CHECKING, Iterable, Literal, Sequence, overload
|
|
5
5
|
from uuid import uuid4
|
|
6
6
|
|
|
7
|
+
from prefect import flow
|
|
8
|
+
|
|
7
9
|
from infrahub import lock
|
|
10
|
+
from infrahub.core.branch import Branch # noqa: TC001
|
|
8
11
|
from infrahub.core.timestamp import Timestamp
|
|
9
12
|
from infrahub.exceptions import ValidationError
|
|
10
13
|
from infrahub.log import get_logger
|
|
@@ -22,7 +25,6 @@ from .model.path import (
|
|
|
22
25
|
)
|
|
23
26
|
|
|
24
27
|
if TYPE_CHECKING:
|
|
25
|
-
from infrahub.core.branch import Branch
|
|
26
28
|
from infrahub.core.node import Node
|
|
27
29
|
|
|
28
30
|
from .calculator import DiffCalculator
|
|
@@ -301,6 +303,11 @@ class DiffCoordinator:
|
|
|
301
303
|
force_branch_refresh: Literal[False] = ...,
|
|
302
304
|
) -> tuple[EnrichedDiffs | EnrichedDiffsMetadata, set[NodeIdentifier]]: ...
|
|
303
305
|
|
|
306
|
+
@flow( # type: ignore[misc]
|
|
307
|
+
name="update-diff",
|
|
308
|
+
flow_run_name="Update diff for {base_branch.name} - {diff_branch.name}: ({from_time}-{to_time}),tracking_id={tracking_id}",
|
|
309
|
+
validate_parameters=False,
|
|
310
|
+
)
|
|
304
311
|
async def _update_diffs(
|
|
305
312
|
self,
|
|
306
313
|
base_branch: Branch,
|
|
@@ -15,7 +15,7 @@ class EnrichedDiffFieldSpecifiersQuery(Query):
|
|
|
15
15
|
async def query_init(self, db: InfrahubDatabase, **kwargs: Any) -> None: # noqa: ARG002
|
|
16
16
|
self.params["diff_id"] = self.diff_id
|
|
17
17
|
query = """
|
|
18
|
-
CALL {
|
|
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
21
|
RETURN node.uuid AS node_uuid, node.kind AS node_kind, attr.name AS field_name
|
|
@@ -710,14 +710,14 @@ class DiffMergeRollbackQuery(Query):
|
|
|
710
710
|
// ---------------------------
|
|
711
711
|
// reset to times on target branch
|
|
712
712
|
// ---------------------------
|
|
713
|
-
CALL {
|
|
713
|
+
CALL () {
|
|
714
714
|
OPTIONAL MATCH ()-[r_to {to: $at, branch: $target_branch}]-()
|
|
715
715
|
SET r_to.to = NULL
|
|
716
716
|
}
|
|
717
717
|
// ---------------------------
|
|
718
718
|
// reset from times on target branch
|
|
719
719
|
// ---------------------------
|
|
720
|
-
CALL {
|
|
720
|
+
CALL () {
|
|
721
721
|
OPTIONAL MATCH ()-[r_from {from: $at, branch: $target_branch}]-()
|
|
722
722
|
DELETE r_from
|
|
723
723
|
}
|
|
@@ -486,6 +486,7 @@ class DiffQueryParser:
|
|
|
486
486
|
self._previous_node_field_specifiers = previous_node_field_specifiers or NodeFieldSpecifierMap()
|
|
487
487
|
self._new_node_field_specifiers: NodeFieldSpecifierMap | None = None
|
|
488
488
|
self._current_node_field_specifiers: NodeFieldSpecifierMap | None = None
|
|
489
|
+
self._diff_node_field_specifiers: NodeFieldSpecifierMap = NodeFieldSpecifierMap()
|
|
489
490
|
|
|
490
491
|
def get_branches(self) -> set[str]:
|
|
491
492
|
return set(self._final_diff_root_by_branch.keys())
|
|
@@ -497,33 +498,17 @@ class DiffQueryParser:
|
|
|
497
498
|
return self._final_diff_root_by_branch[branch]
|
|
498
499
|
return DiffRoot(from_time=self.from_time, to_time=self.to_time, uuid=str(uuid4()), branch=branch, nodes=[])
|
|
499
500
|
|
|
500
|
-
def get_diff_node_field_specifiers(self) -> NodeFieldSpecifierMap:
|
|
501
|
-
node_field_specifiers_map = NodeFieldSpecifierMap()
|
|
502
|
-
if self.diff_branch_name not in self._diff_root_by_branch:
|
|
503
|
-
return node_field_specifiers_map
|
|
504
|
-
diff_root = self._diff_root_by_branch[self.diff_branch_name]
|
|
505
|
-
for node in diff_root.nodes_by_identifier.values():
|
|
506
|
-
for attribute_name in node.attributes_by_name:
|
|
507
|
-
node_field_specifiers_map.add_entry(node_uuid=node.uuid, kind=node.kind, field_name=attribute_name)
|
|
508
|
-
for relationship_diff in node.relationships_by_identifier.values():
|
|
509
|
-
node_field_specifiers_map.add_entry(
|
|
510
|
-
node_uuid=node.uuid, kind=node.kind, field_name=relationship_diff.identifier
|
|
511
|
-
)
|
|
512
|
-
return node_field_specifiers_map
|
|
513
|
-
|
|
514
501
|
def get_new_node_field_specifiers(self) -> NodeFieldSpecifierMap:
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
self.
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
return
|
|
524
|
-
|
|
525
|
-
self._current_node_field_specifiers = self._previous_node_field_specifiers - new_node_field_specifiers
|
|
526
|
-
return self._current_node_field_specifiers
|
|
502
|
+
return self._diff_node_field_specifiers - self._previous_node_field_specifiers
|
|
503
|
+
|
|
504
|
+
def is_new_node_field_specifier(self, node_uuid: str, kind: str, field_name: str) -> bool:
|
|
505
|
+
if not self._diff_node_field_specifiers.has_entry(node_uuid=node_uuid, kind=kind, field_name=field_name):
|
|
506
|
+
return False
|
|
507
|
+
if self._previous_node_field_specifiers and self._previous_node_field_specifiers.has_entry(
|
|
508
|
+
node_uuid=node_uuid, kind=kind, field_name=field_name
|
|
509
|
+
):
|
|
510
|
+
return False
|
|
511
|
+
return True
|
|
527
512
|
|
|
528
513
|
def read_result(self, query_result: QueryResult) -> None:
|
|
529
514
|
try:
|
|
@@ -533,8 +518,6 @@ class DiffQueryParser:
|
|
|
533
518
|
return
|
|
534
519
|
database_path = DatabasePath.from_cypher_path(cypher_path=path)
|
|
535
520
|
self._parse_path(database_path=database_path)
|
|
536
|
-
self._current_node_field_specifiers = None
|
|
537
|
-
self._new_node_field_specifiers = None
|
|
538
521
|
|
|
539
522
|
def parse(self, include_unchanged: bool = False) -> None:
|
|
540
523
|
self._new_node_field_specifiers = None
|
|
@@ -617,11 +600,15 @@ class DiffQueryParser:
|
|
|
617
600
|
branch_name = database_path.deepest_branch
|
|
618
601
|
from_time = self.from_time
|
|
619
602
|
if branch_name == self.base_branch_name:
|
|
620
|
-
|
|
621
|
-
if new_node_field_specifiers.has_entry(
|
|
603
|
+
if self.is_new_node_field_specifier(
|
|
622
604
|
node_uuid=diff_node.uuid, kind=diff_node.kind, field_name=attribute_name
|
|
623
605
|
):
|
|
624
606
|
from_time = self.diff_branched_from_time
|
|
607
|
+
else:
|
|
608
|
+
# Add to diff node field specifiers if this is the diff branch
|
|
609
|
+
self._diff_node_field_specifiers.add_entry(
|
|
610
|
+
node_uuid=diff_node.uuid, kind=diff_node.kind, field_name=attribute_name
|
|
611
|
+
)
|
|
625
612
|
if attribute_name not in diff_node.attributes_by_name:
|
|
626
613
|
diff_node.attributes_by_name[attribute_name] = DiffAttributeIntermediate(
|
|
627
614
|
uuid=database_path.attribute_id,
|
|
@@ -663,11 +650,15 @@ class DiffQueryParser:
|
|
|
663
650
|
branch_name = database_path.deepest_branch
|
|
664
651
|
from_time = self.from_time
|
|
665
652
|
if branch_name == self.base_branch_name:
|
|
666
|
-
|
|
667
|
-
if new_node_field_specifiers.has_entry(
|
|
653
|
+
if self.is_new_node_field_specifier(
|
|
668
654
|
node_uuid=diff_node.uuid, kind=diff_node.kind, field_name=relationship_schema.get_identifier()
|
|
669
655
|
):
|
|
670
656
|
from_time = self.diff_branched_from_time
|
|
657
|
+
else:
|
|
658
|
+
# Add to diff node field specifiers if this is the diff branch
|
|
659
|
+
self._diff_node_field_specifiers.add_entry(
|
|
660
|
+
node_uuid=diff_node.uuid, kind=diff_node.kind, field_name=relationship_schema.get_identifier()
|
|
661
|
+
)
|
|
671
662
|
diff_relationship = DiffRelationshipIntermediate(
|
|
672
663
|
name=relationship_schema.name,
|
|
673
664
|
cardinality=relationship_schema.cardinality,
|
infrahub/core/graph/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
GRAPH_VERSION =
|
|
1
|
+
GRAPH_VERSION = 31
|
|
@@ -32,6 +32,7 @@ from .m027_delete_isolated_nodes import Migration027
|
|
|
32
32
|
from .m028_delete_diffs import Migration028
|
|
33
33
|
from .m029_duplicates_cleanup import Migration029
|
|
34
34
|
from .m030_illegal_edges import Migration030
|
|
35
|
+
from .m031_check_number_attributes import Migration031
|
|
35
36
|
|
|
36
37
|
if TYPE_CHECKING:
|
|
37
38
|
from infrahub.core.root import Root
|
|
@@ -69,6 +70,7 @@ MIGRATIONS: list[type[GraphMigration | InternalSchemaMigration | ArbitraryMigrat
|
|
|
69
70
|
Migration028,
|
|
70
71
|
Migration029,
|
|
71
72
|
Migration030,
|
|
73
|
+
Migration031,
|
|
72
74
|
]
|
|
73
75
|
|
|
74
76
|
|
|
@@ -61,7 +61,7 @@ class Migration012RenameTypeAttributeData(AttributeRenameQuery):
|
|
|
61
61
|
def render_match(self) -> str:
|
|
62
62
|
query = """
|
|
63
63
|
// Find all the active nodes
|
|
64
|
-
CALL {
|
|
64
|
+
CALL () {
|
|
65
65
|
MATCH (node:%(node_kind)s)
|
|
66
66
|
WHERE exists((node)-[:HAS_ATTRIBUTE]-(:Attribute { name: $prev_attr.name }))
|
|
67
67
|
AND NOT exists((node)-[:HAS_ATTRIBUTE]-(:Attribute { name: $new_attr.name }))
|
|
@@ -52,7 +52,7 @@ class DedupCardinalityOneRelsQuery(Query):
|
|
|
52
52
|
# of a one-to-many BIDIR relationship.
|
|
53
53
|
query = """
|
|
54
54
|
|
|
55
|
-
CALL {
|
|
55
|
+
CALL () {
|
|
56
56
|
MATCH (rel_node: Relationship)-[edge:IS_RELATED]->(n: Node)<-[edge_2:IS_RELATED]-(rel_node_2: Relationship {name: rel_node.name})
|
|
57
57
|
WHERE rel_node.name in $rel_one_identifiers_inbound[n.kind]
|
|
58
58
|
AND edge.branch = edge_2.branch
|
|
@@ -64,7 +64,7 @@ class DedupCardinalityOneRelsQuery(Query):
|
|
|
64
64
|
DETACH DELETE rel_node
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
CALL {
|
|
67
|
+
CALL () {
|
|
68
68
|
MATCH (rel_node_3: Relationship)<-[edge_3:IS_RELATED]-(n: Node)-[edge_4:IS_RELATED]->(rel_node_4: Relationship {name: rel_node_3.name})
|
|
69
69
|
WHERE rel_node_3.name in $rel_one_identifiers_outbound[n.kind]
|
|
70
70
|
AND edge_3.branch = edge_4.branch
|
|
@@ -535,12 +535,12 @@ class PerformHardDeletes(Query):
|
|
|
535
535
|
|
|
536
536
|
async def query_init(self, db: InfrahubDatabase, **kwargs: dict[str, Any]) -> None: # noqa: ARG002
|
|
537
537
|
query = """
|
|
538
|
-
CALL {
|
|
538
|
+
CALL () {
|
|
539
539
|
MATCH (n)
|
|
540
540
|
WHERE n.to_delete = TRUE
|
|
541
541
|
DETACH DELETE n
|
|
542
542
|
}
|
|
543
|
-
CALL {
|
|
543
|
+
CALL () {
|
|
544
544
|
MATCH ()-[e]-()
|
|
545
545
|
WHERE e.to_delete = TRUE
|
|
546
546
|
DELETE e
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Sequence
|
|
4
|
+
|
|
5
|
+
from infrahub import config
|
|
6
|
+
from infrahub.core import registry
|
|
7
|
+
from infrahub.core.branch import Branch
|
|
8
|
+
from infrahub.core.constants import SchemaPathType
|
|
9
|
+
from infrahub.core.initialization import initialization
|
|
10
|
+
from infrahub.core.migrations.shared import InternalSchemaMigration, MigrationResult, SchemaMigration
|
|
11
|
+
from infrahub.core.path import SchemaPath
|
|
12
|
+
from infrahub.core.schema import GenericSchema, NodeSchema
|
|
13
|
+
from infrahub.core.schema.attribute_parameters import NumberAttributeParameters
|
|
14
|
+
from infrahub.core.validators.attribute.min_max import AttributeNumberChecker
|
|
15
|
+
from infrahub.core.validators.enum import ConstraintIdentifier
|
|
16
|
+
from infrahub.core.validators.model import SchemaConstraintValidatorRequest
|
|
17
|
+
from infrahub.lock import initialize_lock
|
|
18
|
+
from infrahub.log import get_logger
|
|
19
|
+
from infrahub.types import Number
|
|
20
|
+
|
|
21
|
+
if TYPE_CHECKING:
|
|
22
|
+
from infrahub.database import InfrahubDatabase
|
|
23
|
+
|
|
24
|
+
log = get_logger()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class Migration031(InternalSchemaMigration):
|
|
28
|
+
"""
|
|
29
|
+
Some nodes with invalid number attributes may have been created as min/max/excluded_values were not working properly.
|
|
30
|
+
This migration indicates corrupted nodes. If strict mode is disabled, both this migration and min/max/excludes_values constraints are disabled,
|
|
31
|
+
so that users can carry one with their corrupted data without any failure.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
name: str = "031_check_number_attributes"
|
|
35
|
+
minimum_version: int = 30
|
|
36
|
+
migrations: Sequence[SchemaMigration] = []
|
|
37
|
+
|
|
38
|
+
async def execute(self, db: InfrahubDatabase) -> MigrationResult:
|
|
39
|
+
"""Retrieve all number attributes that have a min/max/excluded_values
|
|
40
|
+
For any of these attributes, check if corresponding existing nodes are valid."""
|
|
41
|
+
|
|
42
|
+
if not config.SETTINGS.main.schema_strict_mode:
|
|
43
|
+
return MigrationResult()
|
|
44
|
+
|
|
45
|
+
# load schemas from database into registry
|
|
46
|
+
initialize_lock()
|
|
47
|
+
await initialization(db=db)
|
|
48
|
+
|
|
49
|
+
node_id_to_error_message = {}
|
|
50
|
+
|
|
51
|
+
branches = await Branch.get_list(db=db)
|
|
52
|
+
for branch in branches: # noqa
|
|
53
|
+
schema_branch = await registry.schema.load_schema_from_db(db=db, branch=branch)
|
|
54
|
+
for node_schema_kind in schema_branch.node_names:
|
|
55
|
+
schema = schema_branch.get_node(name=node_schema_kind, duplicate=False)
|
|
56
|
+
if not isinstance(schema, (NodeSchema, GenericSchema)):
|
|
57
|
+
continue
|
|
58
|
+
|
|
59
|
+
for attr in schema.attributes:
|
|
60
|
+
if attr.kind != Number.label:
|
|
61
|
+
continue
|
|
62
|
+
|
|
63
|
+
# Check if the attribute has a min/max/excluded_values being violated
|
|
64
|
+
if isinstance(attr.parameters, NumberAttributeParameters) and (
|
|
65
|
+
attr.parameters.min_value is not None
|
|
66
|
+
or attr.parameters.max_value is not None
|
|
67
|
+
or attr.parameters.excluded_values
|
|
68
|
+
):
|
|
69
|
+
request = SchemaConstraintValidatorRequest(
|
|
70
|
+
branch=branch,
|
|
71
|
+
constraint_name=ConstraintIdentifier.ATTRIBUTE_PARAMETERS_MIN_VALUE_UPDATE.value,
|
|
72
|
+
node_schema=schema,
|
|
73
|
+
schema_path=SchemaPath(
|
|
74
|
+
path_type=SchemaPathType.ATTRIBUTE, schema_kind=schema.kind, field_name=attr.name
|
|
75
|
+
),
|
|
76
|
+
schema_branch=db.schema.get_schema_branch(name=registry.default_branch),
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
constraint_checker = AttributeNumberChecker(db=db, branch=branch)
|
|
80
|
+
grouped_data_paths = await constraint_checker.check(request)
|
|
81
|
+
data_paths = grouped_data_paths[0].get_all_data_paths()
|
|
82
|
+
for data_path in data_paths:
|
|
83
|
+
# Avoid having duplicated error messages for nodes present on multiple branches.
|
|
84
|
+
if data_path.node_id not in node_id_to_error_message:
|
|
85
|
+
node_id_to_error_message[data_path.node_id] = (
|
|
86
|
+
f"Node {data_path.node_id} on branch {branch.name} "
|
|
87
|
+
f"has an invalid Number attribute {data_path.field_name}: {data_path.value}"
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
if len(node_id_to_error_message) == 0:
|
|
91
|
+
return MigrationResult()
|
|
92
|
+
|
|
93
|
+
error_str = (
|
|
94
|
+
"Following nodes attributes values must be updated to not violate corresponding min_value, "
|
|
95
|
+
"max_value or excluded_values schema constraints"
|
|
96
|
+
)
|
|
97
|
+
errors_messages = list(node_id_to_error_message.values())
|
|
98
|
+
return MigrationResult(errors=[error_str] + errors_messages)
|
|
99
|
+
|
|
100
|
+
async def validate_migration(self, db: InfrahubDatabase) -> MigrationResult: # noqa: ARG002
|
|
101
|
+
result = MigrationResult()
|
|
102
|
+
return result
|
|
@@ -38,7 +38,7 @@ class AttributeRenameQuery(Query):
|
|
|
38
38
|
def render_match(self) -> str:
|
|
39
39
|
query = """
|
|
40
40
|
// Find all the active nodes
|
|
41
|
-
CALL {
|
|
41
|
+
CALL () {
|
|
42
42
|
MATCH (node:%(node_kind)s)
|
|
43
43
|
WHERE exists((node)-[:HAS_ATTRIBUTE]-(:Attribute { name: $prev_attr.name }))
|
|
44
44
|
RETURN node
|
infrahub/core/node/__init__.py
CHANGED
|
@@ -70,7 +70,9 @@ log = get_logger()
|
|
|
70
70
|
|
|
71
71
|
class Node(BaseNode, metaclass=BaseNodeMeta):
|
|
72
72
|
@classmethod
|
|
73
|
-
def __init_subclass_with_meta__(
|
|
73
|
+
def __init_subclass_with_meta__(
|
|
74
|
+
cls, _meta: BaseNodeOptions | None = None, default_filter: None = None, **options: dict[str, Any]
|
|
75
|
+
) -> None:
|
|
74
76
|
if not _meta:
|
|
75
77
|
_meta = BaseNodeOptions(cls)
|
|
76
78
|
|
|
@@ -959,6 +961,8 @@ class Node(BaseNode, metaclass=BaseNodeMeta):
|
|
|
959
961
|
if relationship.kind == RelationshipKind.PARENT:
|
|
960
962
|
return relationship.name
|
|
961
963
|
|
|
964
|
+
return None
|
|
965
|
+
|
|
962
966
|
async def get_object_template(self, db: InfrahubDatabase) -> CoreObjectTemplate | None:
|
|
963
967
|
object_template: RelationshipManager = getattr(self, OBJECT_TEMPLATE_RELATIONSHIP_NAME, None)
|
|
964
968
|
return (
|