infrahub-server 1.3.0b5__py3-none-any.whl → 1.3.1__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 +36 -79
- infrahub/actions/schema.py +2 -0
- infrahub/cli/db.py +7 -5
- infrahub/cli/upgrade.py +6 -1
- infrahub/core/attribute.py +5 -0
- infrahub/core/constraint/node/runner.py +3 -1
- infrahub/core/convert_object_type/conversion.py +2 -0
- infrahub/core/diff/coordinator.py +8 -1
- infrahub/core/diff/query/delete_query.py +8 -4
- infrahub/core/diff/query/field_specifiers.py +1 -1
- infrahub/core/diff/query/merge.py +2 -2
- infrahub/core/diff/repository/repository.py +4 -0
- 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/m015_diff_format_update.py +1 -2
- infrahub/core/migrations/graph/m016_diff_delete_bug_fix.py +1 -2
- infrahub/core/migrations/graph/m023_deduplicate_cardinality_one_relationships.py +2 -2
- infrahub/core/migrations/graph/m028_delete_diffs.py +1 -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 +70 -37
- infrahub/core/path.py +14 -0
- infrahub/core/query/delete.py +3 -3
- infrahub/core/relationship/constraints/count.py +10 -9
- infrahub/core/relationship/constraints/interface.py +2 -1
- infrahub/core/relationship/constraints/peer_kind.py +2 -1
- infrahub/core/relationship/constraints/peer_parent.py +56 -0
- infrahub/core/relationship/constraints/peer_relatives.py +1 -1
- infrahub/core/relationship/constraints/profiles_kind.py +1 -1
- infrahub/core/schema/attribute_parameters.py +12 -5
- infrahub/core/schema/basenode_schema.py +107 -1
- infrahub/core/schema/definitions/internal.py +8 -1
- infrahub/core/schema/generated/relationship_schema.py +6 -1
- infrahub/core/schema/schema_branch.py +53 -13
- infrahub/core/validators/__init__.py +2 -1
- infrahub/core/validators/attribute/min_max.py +7 -2
- infrahub/core/validators/relationship/peer.py +174 -4
- infrahub/database/__init__.py +0 -1
- infrahub/dependencies/builder/constraint/grouped/node_runner.py +2 -0
- infrahub/dependencies/builder/constraint/relationship_manager/peer_parent.py +8 -0
- infrahub/dependencies/builder/constraint/schema/aggregated.py +2 -0
- infrahub/dependencies/builder/constraint/schema/relationship_peer.py +8 -0
- infrahub/dependencies/registry.py +2 -0
- infrahub/git/tasks.py +1 -0
- infrahub/graphql/app.py +5 -1
- infrahub/graphql/mutations/convert_object_type.py +16 -7
- infrahub/graphql/mutations/relationship.py +32 -0
- infrahub/graphql/queries/convert_object_type_mapping.py +3 -5
- infrahub/message_bus/operations/refresh/registry.py +3 -6
- infrahub/pools/models.py +14 -0
- infrahub/pools/tasks.py +71 -1
- infrahub/services/adapters/message_bus/nats.py +5 -1
- infrahub/services/scheduler.py +5 -1
- infrahub_sdk/ctl/generator.py +4 -4
- infrahub_sdk/ctl/repository.py +1 -1
- infrahub_sdk/node/__init__.py +2 -0
- infrahub_sdk/node/node.py +166 -93
- infrahub_sdk/pytest_plugin/items/python_transform.py +2 -1
- infrahub_sdk/query_groups.py +4 -3
- infrahub_sdk/utils.py +7 -20
- infrahub_sdk/yaml.py +6 -5
- {infrahub_server-1.3.0b5.dist-info → infrahub_server-1.3.1.dist-info}/METADATA +2 -2
- {infrahub_server-1.3.0b5.dist-info → infrahub_server-1.3.1.dist-info}/RECORD +68 -63
- {infrahub_server-1.3.0b5.dist-info → infrahub_server-1.3.1.dist-info}/LICENSE.txt +0 -0
- {infrahub_server-1.3.0b5.dist-info → infrahub_server-1.3.1.dist-info}/WHEEL +0 -0
- {infrahub_server-1.3.0b5.dist-info → infrahub_server-1.3.1.dist-info}/entry_points.txt +0 -0
infrahub/actions/constants.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from enum import Enum
|
|
4
|
+
from typing import Self
|
|
4
5
|
|
|
5
6
|
from infrahub.core.constants import InfrahubKind
|
|
6
7
|
from infrahub.core.schema.dropdown import DropdownChoice
|
|
@@ -9,165 +10,121 @@ from infrahub.utils import InfrahubStringEnum
|
|
|
9
10
|
|
|
10
11
|
class NodeAction(InfrahubStringEnum):
|
|
11
12
|
CREATED = "created"
|
|
12
|
-
DELETED = "deleted"
|
|
13
13
|
UPDATED = "updated"
|
|
14
14
|
|
|
15
15
|
|
|
16
|
-
class
|
|
16
|
+
class DropdownEnum(Enum):
|
|
17
|
+
@classmethod
|
|
18
|
+
def available_types(cls) -> list[DropdownChoice]:
|
|
19
|
+
return [cls.__members__[member].value for member in list(cls.__members__)]
|
|
20
|
+
|
|
21
|
+
@classmethod
|
|
22
|
+
def from_value(cls, value: str) -> Self:
|
|
23
|
+
for member in cls.__members__:
|
|
24
|
+
if value == cls.__members__[member].value.name:
|
|
25
|
+
return cls.__members__[member]
|
|
26
|
+
|
|
27
|
+
raise NotImplementedError(f"The defined value {value} doesn't match a value of {cls.__class__.__name__}")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class BranchScope(DropdownEnum):
|
|
17
31
|
ALL_BRANCHES = DropdownChoice(
|
|
18
32
|
name="all_branches",
|
|
19
33
|
label="All Branches",
|
|
20
34
|
description="All branches",
|
|
21
|
-
color="#
|
|
35
|
+
color="#4cd964",
|
|
22
36
|
)
|
|
23
37
|
DEFAULT_BRANCH = DropdownChoice(
|
|
24
38
|
name="default_branch",
|
|
25
39
|
label="Default Branch",
|
|
26
40
|
description="Only the default branch",
|
|
27
|
-
color="#
|
|
41
|
+
color="#5ac8fa",
|
|
28
42
|
)
|
|
29
43
|
OTHER_BRANCHES = DropdownChoice(
|
|
30
44
|
name="other_branches",
|
|
31
45
|
label="Other Branches",
|
|
32
46
|
description="All branches except the default branch",
|
|
33
|
-
color="#
|
|
47
|
+
color="#ff2d55",
|
|
34
48
|
)
|
|
35
49
|
|
|
36
|
-
@classmethod
|
|
37
|
-
def available_types(cls) -> list[DropdownChoice]:
|
|
38
|
-
return [cls.__members__[member].value for member in list(cls.__members__)]
|
|
39
50
|
|
|
40
|
-
|
|
41
|
-
def from_value(cls, value: str) -> BranchScope:
|
|
42
|
-
for member in cls.__members__:
|
|
43
|
-
if value == cls.__members__[member].value.name:
|
|
44
|
-
return cls.__members__[member]
|
|
45
|
-
|
|
46
|
-
raise NotImplementedError(f"The defined value {value} doesn't match a branch scope")
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
class MemberAction(Enum):
|
|
51
|
+
class MemberAction(DropdownEnum):
|
|
50
52
|
ADD_MEMBER = DropdownChoice(
|
|
51
53
|
name="add_member",
|
|
52
54
|
label="Add member",
|
|
53
55
|
description="Add impacted member to the selected group",
|
|
54
|
-
color="#
|
|
56
|
+
color="#4cd964",
|
|
55
57
|
)
|
|
56
58
|
REMOVE_MEMBER = DropdownChoice(
|
|
57
59
|
name="remove_member",
|
|
58
60
|
label="Remove member",
|
|
59
61
|
description="Remove impacted member from the selected group",
|
|
60
|
-
color="#
|
|
62
|
+
color="#ff2d55",
|
|
61
63
|
)
|
|
62
64
|
|
|
63
|
-
@classmethod
|
|
64
|
-
def available_types(cls) -> list[DropdownChoice]:
|
|
65
|
-
return [cls.__members__[member].value for member in list(cls.__members__)]
|
|
66
|
-
|
|
67
|
-
@classmethod
|
|
68
|
-
def from_value(cls, value: str) -> MemberAction:
|
|
69
|
-
for member in cls.__members__:
|
|
70
|
-
if value == cls.__members__[member].value.name:
|
|
71
|
-
return cls.__members__[member]
|
|
72
|
-
|
|
73
|
-
raise NotImplementedError(f"The defined value {value} doesn't match a member action")
|
|
74
|
-
|
|
75
65
|
|
|
76
|
-
class MemberUpdate(
|
|
66
|
+
class MemberUpdate(DropdownEnum):
|
|
77
67
|
ADDED = DropdownChoice(
|
|
78
68
|
name="added",
|
|
79
69
|
label="Added",
|
|
80
70
|
description="Trigger when members are added to this group",
|
|
81
|
-
color="#
|
|
71
|
+
color="#4cd964",
|
|
82
72
|
)
|
|
83
73
|
REMOVED = DropdownChoice(
|
|
84
74
|
name="removed",
|
|
85
75
|
label="Removed",
|
|
86
76
|
description="Trigger when members are removed from this group",
|
|
87
|
-
color="#
|
|
77
|
+
color="#ff2d55",
|
|
88
78
|
)
|
|
89
79
|
|
|
90
|
-
@classmethod
|
|
91
|
-
def available_types(cls) -> list[DropdownChoice]:
|
|
92
|
-
return [cls.__members__[member].value for member in list(cls.__members__)]
|
|
93
|
-
|
|
94
|
-
@classmethod
|
|
95
|
-
def from_value(cls, value: str) -> MemberUpdate:
|
|
96
|
-
for member in cls.__members__:
|
|
97
|
-
if value == cls.__members__[member].value.name:
|
|
98
|
-
return cls.__members__[member]
|
|
99
80
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
class RelationshipMatch(Enum):
|
|
81
|
+
class RelationshipMatch(DropdownEnum):
|
|
104
82
|
ADDED = DropdownChoice(
|
|
105
83
|
name="added",
|
|
106
84
|
label="Added",
|
|
107
85
|
description="Check if the selected relationship was added",
|
|
108
|
-
color="#
|
|
86
|
+
color="#4cd964",
|
|
109
87
|
)
|
|
110
88
|
REMOVED = DropdownChoice(
|
|
111
89
|
name="removed",
|
|
112
90
|
label="Removed",
|
|
113
91
|
description="Check if the selected relationship was removed",
|
|
114
|
-
color="#
|
|
92
|
+
color="#ff2d55",
|
|
115
93
|
)
|
|
116
94
|
UPDATED = DropdownChoice(
|
|
117
95
|
name="updated",
|
|
118
96
|
label="Updated",
|
|
119
97
|
description="Check if the selected relationship was updated, added or removed.",
|
|
120
|
-
color="#
|
|
98
|
+
color="#5ac8fa",
|
|
121
99
|
)
|
|
122
100
|
|
|
123
|
-
@classmethod
|
|
124
|
-
def available_types(cls) -> list[DropdownChoice]:
|
|
125
|
-
return [cls.__members__[member].value for member in list(cls.__members__)]
|
|
126
|
-
|
|
127
|
-
@classmethod
|
|
128
|
-
def from_value(cls, value: str) -> RelationshipMatch:
|
|
129
|
-
for member in cls.__members__:
|
|
130
|
-
if value == cls.__members__[member].value.name:
|
|
131
|
-
return cls.__members__[member]
|
|
132
|
-
|
|
133
|
-
raise NotImplementedError(f"The defined value {value} doesn't match a RelationshipMatch")
|
|
134
101
|
|
|
135
|
-
|
|
136
|
-
class ValueMatch(Enum):
|
|
102
|
+
class ValueMatch(DropdownEnum):
|
|
137
103
|
VALUE = DropdownChoice(
|
|
138
104
|
name="value",
|
|
139
105
|
label="Value",
|
|
140
106
|
description="Match against the current value",
|
|
141
|
-
color="#
|
|
107
|
+
color="#4cd964",
|
|
142
108
|
)
|
|
143
109
|
VALUE_PREVIOUS = DropdownChoice(
|
|
144
110
|
name="value_previous",
|
|
145
111
|
label="Value Previous",
|
|
146
112
|
description="Match against the previous value",
|
|
147
|
-
color="#
|
|
113
|
+
color="#ff2d55",
|
|
148
114
|
)
|
|
149
115
|
VALUE_FULL = DropdownChoice(
|
|
150
116
|
name="value_full",
|
|
151
117
|
label="Full value match",
|
|
152
118
|
description="Match against both the current and previous values",
|
|
153
|
-
color="#
|
|
119
|
+
color="#5ac8fa",
|
|
154
120
|
)
|
|
155
121
|
|
|
156
|
-
@classmethod
|
|
157
|
-
def available_types(cls) -> list[DropdownChoice]:
|
|
158
|
-
return [cls.__members__[member].value for member in list(cls.__members__)]
|
|
159
|
-
|
|
160
|
-
@classmethod
|
|
161
|
-
def from_value(cls, value: str) -> ValueMatch:
|
|
162
|
-
for member in cls.__members__:
|
|
163
|
-
if value == cls.__members__[member].value.name:
|
|
164
|
-
return cls.__members__[member]
|
|
165
|
-
|
|
166
|
-
raise NotImplementedError(f"The defined value {value} doesn't match a ValueMatch")
|
|
167
|
-
|
|
168
122
|
|
|
169
123
|
NODES_THAT_TRIGGER_ACTION_RULES_SETUP = [
|
|
124
|
+
InfrahubKind.GENERATORACTION,
|
|
170
125
|
InfrahubKind.GROUPACTION,
|
|
171
126
|
InfrahubKind.GROUPTRIGGERRULE,
|
|
172
127
|
InfrahubKind.NODETRIGGERRULE,
|
|
128
|
+
InfrahubKind.NODETRIGGERATTRIBUTEMATCH,
|
|
129
|
+
InfrahubKind.NODETRIGGERRELATIONSHIPMATCH,
|
|
173
130
|
]
|
infrahub/actions/schema.py
CHANGED
|
@@ -271,6 +271,7 @@ core_node_trigger_attribute_match = NodeSchema(
|
|
|
271
271
|
branch=BranchSupportType.AGNOSTIC,
|
|
272
272
|
generate_profile=False,
|
|
273
273
|
inherit_from=["CoreNodeTriggerMatch"],
|
|
274
|
+
display_labels=["attribute_name__value"],
|
|
274
275
|
attributes=[
|
|
275
276
|
Attr(
|
|
276
277
|
name="attribute_name",
|
|
@@ -319,6 +320,7 @@ core_node_trigger_relationship_match = NodeSchema(
|
|
|
319
320
|
branch=BranchSupportType.AGNOSTIC,
|
|
320
321
|
generate_profile=False,
|
|
321
322
|
inherit_from=["CoreNodeTriggerMatch"],
|
|
323
|
+
display_labels=["relationship_name__value", "modification_type__value"],
|
|
322
324
|
attributes=[
|
|
323
325
|
Attr(
|
|
324
326
|
name="relationship_name",
|
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)
|
|
@@ -38,4 +38,6 @@ class NodeConstraintRunner:
|
|
|
38
38
|
relationship_manager: RelationshipManager = getattr(node, relationship_name)
|
|
39
39
|
await relationship_manager.fetch_relationship_ids(db=db, force_refresh=True)
|
|
40
40
|
for relationship_constraint in self.relationship_manager_constraints:
|
|
41
|
-
await relationship_constraint.check(
|
|
41
|
+
await relationship_constraint.check(
|
|
42
|
+
relm=relationship_manager, node_schema=node.get_schema(), node=node
|
|
43
|
+
)
|
|
@@ -101,6 +101,8 @@ async def convert_object_type(
|
|
|
101
101
|
deleted_node_out_rels_peer_ids = await get_out_rels_peers_ids(node=node, db=dbt)
|
|
102
102
|
deleted_node_unidir_rels_peer_ids = await get_unidirectional_rels_peers_ids(node=node, db=dbt, branch=branch)
|
|
103
103
|
|
|
104
|
+
# Delete the node, so we delete relationships with peers as well, which might temporarily break cardinality constraints
|
|
105
|
+
# but they should be restored when creating the new node.
|
|
104
106
|
deleted_nodes = await NodeManager.delete(db=dbt, branch=branch, nodes=[node], cascade_delete=False)
|
|
105
107
|
if len(deleted_nodes) != 1:
|
|
106
108
|
raise ValueError(f"Deleted {len(deleted_nodes)} nodes instead of 1")
|
|
@@ -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,
|
|
@@ -9,17 +9,21 @@ class EnrichedDiffDeleteQuery(Query):
|
|
|
9
9
|
type = QueryType.WRITE
|
|
10
10
|
insert_return = False
|
|
11
11
|
|
|
12
|
-
def __init__(self, enriched_diff_root_uuids: list[str], **kwargs: Any) -> None:
|
|
12
|
+
def __init__(self, enriched_diff_root_uuids: list[str] | None = None, **kwargs: Any) -> None:
|
|
13
13
|
super().__init__(**kwargs)
|
|
14
14
|
self.enriched_diff_root_uuids = enriched_diff_root_uuids
|
|
15
15
|
|
|
16
16
|
async def query_init(self, db: InfrahubDatabase, **kwargs: Any) -> None: # noqa: ARG002
|
|
17
|
-
|
|
17
|
+
diff_filter = ""
|
|
18
|
+
if self.enriched_diff_root_uuids:
|
|
19
|
+
self.params = {"diff_root_uuids": self.enriched_diff_root_uuids}
|
|
20
|
+
diff_filter = "WHERE d_root.uuid IN $diff_root_uuids"
|
|
21
|
+
|
|
18
22
|
query = """
|
|
19
23
|
MATCH (d_root:DiffRoot)
|
|
20
|
-
|
|
24
|
+
%(diff_filter)s
|
|
21
25
|
OPTIONAL MATCH (d_root)-[*]->(diff_thing)
|
|
22
26
|
DETACH DELETE diff_thing
|
|
23
27
|
DETACH DELETE d_root
|
|
24
|
-
"""
|
|
28
|
+
""" % {"diff_filter": diff_filter}
|
|
25
29
|
self.add_to_query(query=query)
|
|
@@ -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
|
}
|
|
@@ -377,6 +377,10 @@ class DiffRepository:
|
|
|
377
377
|
await query.execute(db=self.db)
|
|
378
378
|
return query.get_summary()
|
|
379
379
|
|
|
380
|
+
async def delete_all_diff_roots(self) -> None:
|
|
381
|
+
query = await EnrichedDiffDeleteQuery.init(db=self.db)
|
|
382
|
+
await query.execute(db=self.db)
|
|
383
|
+
|
|
380
384
|
async def delete_diff_roots(self, diff_root_uuids: list[str]) -> None:
|
|
381
385
|
query = await EnrichedDiffDeleteQuery.init(db=self.db, enriched_diff_root_uuids=diff_root_uuids)
|
|
382
386
|
await query.execute(db=self.db)
|
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 }))
|
|
@@ -31,6 +31,5 @@ class Migration015(ArbitraryMigration):
|
|
|
31
31
|
component_registry = get_component_registry()
|
|
32
32
|
diff_repo = await component_registry.get_component(DiffRepository, db=db, branch=default_branch)
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
await diff_repo.delete_diff_roots(diff_root_uuids=[d.uuid for d in diff_roots])
|
|
34
|
+
await diff_repo.delete_all_diff_roots()
|
|
36
35
|
return MigrationResult()
|
|
@@ -31,6 +31,5 @@ class Migration016(ArbitraryMigration):
|
|
|
31
31
|
component_registry = get_component_registry()
|
|
32
32
|
diff_repo = await component_registry.get_component(DiffRepository, db=db, branch=default_branch)
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
await diff_repo.delete_diff_roots(diff_root_uuids=[d.uuid for d in diff_roots])
|
|
34
|
+
await diff_repo.delete_all_diff_roots()
|
|
36
35
|
return MigrationResult()
|
|
@@ -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
|
|
@@ -33,6 +33,5 @@ class Migration028(ArbitraryMigration):
|
|
|
33
33
|
component_registry = get_component_registry()
|
|
34
34
|
diff_repo = await component_registry.get_component(DiffRepository, db=db, branch=default_branch)
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
await diff_repo.delete_diff_roots(diff_root_uuids=[d.uuid for d in diff_roots])
|
|
36
|
+
await diff_repo.delete_all_diff_roots()
|
|
38
37
|
return MigrationResult()
|
|
@@ -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
|