infrahub-server 1.3.0b5__py3-none-any.whl → 1.3.0b6__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/core/constraint/node/runner.py +3 -1
- infrahub/core/convert_object_type/conversion.py +2 -0
- infrahub/core/diff/query/delete_query.py +8 -4
- infrahub/core/diff/repository/repository.py +4 -0
- 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/m028_delete_diffs.py +1 -2
- infrahub/core/node/__init__.py +65 -36
- infrahub/core/path.py +14 -0
- 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/definitions/internal.py +8 -1
- infrahub/core/schema/generated/relationship_schema.py +6 -1
- infrahub/core/schema/schema_branch.py +36 -8
- infrahub/core/validators/__init__.py +2 -1
- 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/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_sdk/ctl/generator.py +4 -4
- infrahub_sdk/ctl/repository.py +1 -1
- infrahub_sdk/node/node.py +146 -92
- 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.0b6.dist-info}/METADATA +2 -2
- {infrahub_server-1.3.0b5.dist-info → infrahub_server-1.3.0b6.dist-info}/RECORD +47 -43
- {infrahub_server-1.3.0b5.dist-info → infrahub_server-1.3.0b6.dist-info}/LICENSE.txt +0 -0
- {infrahub_server-1.3.0b5.dist-info → infrahub_server-1.3.0b6.dist-info}/WHEEL +0 -0
- {infrahub_server-1.3.0b5.dist-info → infrahub_server-1.3.0b6.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",
|
|
@@ -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")
|
|
@@ -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)
|
|
@@ -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)
|
|
@@ -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()
|
|
@@ -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()
|
infrahub/core/node/__init__.py
CHANGED
|
@@ -7,6 +7,7 @@ from infrahub_sdk.template import Jinja2Template
|
|
|
7
7
|
from infrahub_sdk.utils import is_valid_uuid
|
|
8
8
|
from infrahub_sdk.uuidt import UUIDT
|
|
9
9
|
|
|
10
|
+
from infrahub import lock
|
|
10
11
|
from infrahub.core import registry
|
|
11
12
|
from infrahub.core.changelog.models import NodeChangelog
|
|
12
13
|
from infrahub.core.constants import (
|
|
@@ -34,6 +35,7 @@ from infrahub.core.schema import (
|
|
|
34
35
|
from infrahub.core.schema.attribute_parameters import NumberPoolParameters
|
|
35
36
|
from infrahub.core.timestamp import Timestamp
|
|
36
37
|
from infrahub.exceptions import InitializationError, NodeNotFoundError, PoolExhaustedError, ValidationError
|
|
38
|
+
from infrahub.pools.models import NumberPoolLockDefinition
|
|
37
39
|
from infrahub.types import ATTRIBUTE_TYPES
|
|
38
40
|
|
|
39
41
|
from ...graphql.constants import KIND_GRAPHQL_FIELD_NAME
|
|
@@ -271,7 +273,7 @@ class Node(BaseNode, metaclass=BaseNodeMeta):
|
|
|
271
273
|
)
|
|
272
274
|
except NodeNotFoundError:
|
|
273
275
|
if number_pool_parameters:
|
|
274
|
-
number_pool = await self.
|
|
276
|
+
number_pool = await self._fetch_or_create_number_pool(
|
|
275
277
|
db=db, attribute=attribute, number_pool_parameters=number_pool_parameters
|
|
276
278
|
)
|
|
277
279
|
|
|
@@ -306,35 +308,49 @@ class Node(BaseNode, metaclass=BaseNodeMeta):
|
|
|
306
308
|
)
|
|
307
309
|
)
|
|
308
310
|
|
|
309
|
-
async def
|
|
311
|
+
async def _fetch_or_create_number_pool(
|
|
310
312
|
self, db: InfrahubDatabase, attribute: BaseAttribute, number_pool_parameters: NumberPoolParameters
|
|
311
313
|
) -> CoreNumberPool:
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
314
|
+
number_pool_from_db: CoreNumberPool | None = None
|
|
315
|
+
lock_definition = NumberPoolLockDefinition(pool_id=str(number_pool_parameters.number_pool_id))
|
|
316
|
+
async with lock.registry.get(
|
|
317
|
+
name=lock_definition.lock_name, namespace=lock_definition.namespace_name, local=False
|
|
318
|
+
):
|
|
319
|
+
try:
|
|
320
|
+
number_pool_from_db = await registry.manager.get_one_by_id_or_default_filter(
|
|
321
|
+
db=db, id=str(number_pool_parameters.number_pool_id), kind=CoreNumberPool
|
|
322
|
+
)
|
|
323
|
+
except NodeNotFoundError:
|
|
324
|
+
schema = db.schema.get_node_schema(name="CoreNumberPool", duplicate=False)
|
|
325
|
+
|
|
326
|
+
pool_node = self._schema.kind
|
|
327
|
+
schema_attribute = self._schema.get_attribute(attribute.schema.name)
|
|
328
|
+
if schema_attribute.inherited:
|
|
329
|
+
for generic_name in self._schema.inherit_from:
|
|
330
|
+
generic_node = db.schema.get_generic_schema(name=generic_name, duplicate=False)
|
|
331
|
+
if attribute.schema.name in generic_node.attribute_names:
|
|
332
|
+
pool_node = generic_node.kind
|
|
333
|
+
break
|
|
334
|
+
|
|
335
|
+
number_pool = await Node.init(db=db, schema=schema, branch=self._branch)
|
|
336
|
+
await number_pool.new(
|
|
337
|
+
db=db,
|
|
338
|
+
id=number_pool_parameters.number_pool_id,
|
|
339
|
+
name=f"{pool_node}.{attribute.schema.name} [{number_pool_parameters.number_pool_id}]",
|
|
340
|
+
node=pool_node,
|
|
341
|
+
node_attribute=attribute.schema.name,
|
|
342
|
+
start_range=number_pool_parameters.start_range,
|
|
343
|
+
end_range=number_pool_parameters.end_range,
|
|
344
|
+
pool_type=NumberPoolType.SCHEMA.value,
|
|
345
|
+
)
|
|
346
|
+
await number_pool.save(db=db)
|
|
347
|
+
|
|
335
348
|
# Do a lookup of the number pool to get the correct mapped type from the registry
|
|
336
349
|
# without this we don't get access to the .get_resource() method.
|
|
337
|
-
|
|
350
|
+
created_pool: CoreNumberPool = number_pool_from_db or await registry.manager.get_one_by_id_or_default_filter(
|
|
351
|
+
db=db, id=number_pool.id, kind=CoreNumberPool
|
|
352
|
+
)
|
|
353
|
+
return created_pool
|
|
338
354
|
|
|
339
355
|
async def handle_object_template(self, fields: dict, db: InfrahubDatabase, errors: list) -> None:
|
|
340
356
|
"""Fill the `fields` parameters with values from an object template if one is in use."""
|
|
@@ -541,17 +557,21 @@ class Node(BaseNode, metaclass=BaseNodeMeta):
|
|
|
541
557
|
relationship_attribute: RelationshipManager = getattr(
|
|
542
558
|
self, attribute_path.active_relationship_schema.name
|
|
543
559
|
)
|
|
544
|
-
peer
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
560
|
+
if peer := await relationship_attribute.get_peer(db=db, raise_on_error=False):
|
|
561
|
+
related_node = await registry.manager.get_one_by_id_or_default_filter(
|
|
562
|
+
db=db,
|
|
563
|
+
id=peer.id,
|
|
564
|
+
kind=attribute_path.active_relationship_schema.peer,
|
|
565
|
+
branch=self._branch.name,
|
|
566
|
+
)
|
|
549
567
|
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
568
|
+
attribute: BaseAttribute = getattr(
|
|
569
|
+
getattr(related_node, attribute_path.active_attribute_schema.name),
|
|
570
|
+
attribute_path.active_attribute_property_name,
|
|
571
|
+
)
|
|
572
|
+
variables[variable] = attribute
|
|
573
|
+
else:
|
|
574
|
+
variables[variable] = None
|
|
555
575
|
|
|
556
576
|
elif attribute_path.is_type_attribute:
|
|
557
577
|
attribute = getattr(
|
|
@@ -962,3 +982,12 @@ class Node(BaseNode, metaclass=BaseNodeMeta):
|
|
|
962
982
|
for name in self._relationships:
|
|
963
983
|
relm: RelationshipManager = getattr(self, name)
|
|
964
984
|
relm.validate()
|
|
985
|
+
|
|
986
|
+
async def get_parent_relationship_peer(self, db: InfrahubDatabase, name: str) -> Node | None:
|
|
987
|
+
"""When a node has a parent relationship of a given name, this method returns the peer of that relationship."""
|
|
988
|
+
relationship = self.get_schema().get_relationship(name=name)
|
|
989
|
+
if relationship.kind != RelationshipKind.PARENT:
|
|
990
|
+
raise ValueError(f"Relationship '{name}' is not of kind 'parent'")
|
|
991
|
+
|
|
992
|
+
relm: RelationshipManager = getattr(self, name)
|
|
993
|
+
return await relm.get_peer(db=db)
|
infrahub/core/path.py
CHANGED
|
@@ -63,6 +63,20 @@ class DataPath(InfrahubPath):
|
|
|
63
63
|
peer_id: str | None = Field(default=None, description="")
|
|
64
64
|
value: Any | None = Field(default=None, description="Optional value of the resource")
|
|
65
65
|
|
|
66
|
+
def __hash__(self) -> int:
|
|
67
|
+
return hash(
|
|
68
|
+
(
|
|
69
|
+
self.branch,
|
|
70
|
+
self.path_type,
|
|
71
|
+
self.node_id,
|
|
72
|
+
self.kind,
|
|
73
|
+
self.field_name,
|
|
74
|
+
self.property_name,
|
|
75
|
+
self.peer_id,
|
|
76
|
+
str(self.value),
|
|
77
|
+
)
|
|
78
|
+
)
|
|
79
|
+
|
|
66
80
|
@property
|
|
67
81
|
def resource_type(self) -> PathResourceType:
|
|
68
82
|
return PathResourceType.DATA
|
|
@@ -3,6 +3,7 @@ from dataclasses import dataclass
|
|
|
3
3
|
from infrahub.core import registry
|
|
4
4
|
from infrahub.core.branch import Branch
|
|
5
5
|
from infrahub.core.constants import RelationshipCardinality, RelationshipDirection
|
|
6
|
+
from infrahub.core.node import Node
|
|
6
7
|
from infrahub.core.query.relationship import RelationshipCountPerNodeQuery
|
|
7
8
|
from infrahub.core.schema import MainSchemaTypes
|
|
8
9
|
from infrahub.database import InfrahubDatabase
|
|
@@ -25,7 +26,7 @@ class RelationshipCountConstraint(RelationshipManagerConstraintInterface):
|
|
|
25
26
|
self.db = db
|
|
26
27
|
self.branch = branch
|
|
27
28
|
|
|
28
|
-
async def check(self, relm: RelationshipManager, node_schema: MainSchemaTypes) -> None: # noqa: ARG002
|
|
29
|
+
async def check(self, relm: RelationshipManager, node_schema: MainSchemaTypes, node: Node) -> None: # noqa: ARG002
|
|
29
30
|
branch = await registry.get_branch(db=self.db) if not self.branch else self.branch
|
|
30
31
|
|
|
31
32
|
# NOTE adding resolve here because we need to retrieve the real ID
|
|
@@ -63,7 +64,7 @@ class RelationshipCountConstraint(RelationshipManagerConstraintInterface):
|
|
|
63
64
|
|
|
64
65
|
query = await RelationshipCountPerNodeQuery.init(
|
|
65
66
|
db=self.db,
|
|
66
|
-
node_ids=[
|
|
67
|
+
node_ids=[n.uuid for n in nodes_to_validate],
|
|
67
68
|
identifier=relm.schema.identifier,
|
|
68
69
|
direction=relm.schema.direction.neighbor_direction,
|
|
69
70
|
branch=branch,
|
|
@@ -74,14 +75,14 @@ class RelationshipCountConstraint(RelationshipManagerConstraintInterface):
|
|
|
74
75
|
# Need to adjust the number based on what we will add / remove
|
|
75
76
|
# +1 for max_count
|
|
76
77
|
# -1 for min_count
|
|
77
|
-
for
|
|
78
|
-
if
|
|
78
|
+
for node_to_validate in nodes_to_validate:
|
|
79
|
+
if node_to_validate.max_count and count_per_peer[node_to_validate.uuid] + 1 > node_to_validate.max_count:
|
|
79
80
|
raise ValidationError(
|
|
80
|
-
f"Node {
|
|
81
|
-
f"for {relm.schema.identifier}, maximum of {
|
|
81
|
+
f"Node {node_to_validate.uuid} has {count_per_peer[node_to_validate.uuid] + 1} peers "
|
|
82
|
+
f"for {relm.schema.identifier}, maximum of {node_to_validate.max_count} allowed",
|
|
82
83
|
)
|
|
83
|
-
if
|
|
84
|
+
if node_to_validate.min_count and count_per_peer[node_to_validate.uuid] - 1 < node_to_validate.min_count:
|
|
84
85
|
raise ValidationError(
|
|
85
|
-
f"Node {
|
|
86
|
-
f"for {relm.schema.identifier}, no fewer than {
|
|
86
|
+
f"Node {node_to_validate.uuid} has {count_per_peer[node_to_validate.uuid] - 1} peers "
|
|
87
|
+
f"for {relm.schema.identifier}, no fewer than {node_to_validate.min_count} allowed",
|
|
87
88
|
)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from abc import ABC, abstractmethod
|
|
2
2
|
|
|
3
|
+
from infrahub.core.node import Node
|
|
3
4
|
from infrahub.core.schema import MainSchemaTypes
|
|
4
5
|
|
|
5
6
|
from ..model import RelationshipManager
|
|
@@ -7,4 +8,4 @@ from ..model import RelationshipManager
|
|
|
7
8
|
|
|
8
9
|
class RelationshipManagerConstraintInterface(ABC):
|
|
9
10
|
@abstractmethod
|
|
10
|
-
async def check(self, relm: RelationshipManager, node_schema: MainSchemaTypes) -> None: ...
|
|
11
|
+
async def check(self, relm: RelationshipManager, node_schema: MainSchemaTypes, node: Node) -> None: ...
|
|
@@ -3,6 +3,7 @@ from dataclasses import dataclass
|
|
|
3
3
|
from infrahub.core import registry
|
|
4
4
|
from infrahub.core.branch import Branch
|
|
5
5
|
from infrahub.core.constants import RelationshipCardinality
|
|
6
|
+
from infrahub.core.node import Node
|
|
6
7
|
from infrahub.core.query.node import NodeListGetInfoQuery
|
|
7
8
|
from infrahub.core.schema import MainSchemaTypes
|
|
8
9
|
from infrahub.core.schema.generic_schema import GenericSchema
|
|
@@ -26,7 +27,7 @@ class RelationshipPeerKindConstraint(RelationshipManagerConstraintInterface):
|
|
|
26
27
|
self.db = db
|
|
27
28
|
self.branch = branch
|
|
28
29
|
|
|
29
|
-
async def check(self, relm: RelationshipManager, node_schema: MainSchemaTypes) -> None: # noqa: ARG002
|
|
30
|
+
async def check(self, relm: RelationshipManager, node_schema: MainSchemaTypes, node: Node) -> None: # noqa: ARG002
|
|
30
31
|
branch = await registry.get_branch(db=self.db) if not self.branch else self.branch
|
|
31
32
|
peer_schema = registry.schema.get(name=relm.schema.peer, branch=branch, duplicate=False)
|
|
32
33
|
if isinstance(peer_schema, GenericSchema):
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Mapping
|
|
4
|
+
|
|
5
|
+
from infrahub.exceptions import ValidationError
|
|
6
|
+
|
|
7
|
+
from .interface import RelationshipManagerConstraintInterface
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from infrahub.core.branch import Branch
|
|
11
|
+
from infrahub.core.node import Node
|
|
12
|
+
from infrahub.core.schema import MainSchemaTypes
|
|
13
|
+
from infrahub.database import InfrahubDatabase
|
|
14
|
+
|
|
15
|
+
from ..model import RelationshipManager
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class RelationshipPeerParentConstraint(RelationshipManagerConstraintInterface):
|
|
19
|
+
def __init__(self, db: InfrahubDatabase, branch: Branch | None = None):
|
|
20
|
+
self.db = db
|
|
21
|
+
self.branch = branch
|
|
22
|
+
|
|
23
|
+
async def _check_relationship_peers_parent(
|
|
24
|
+
self, relm: RelationshipManager, parent_rel_name: str, node: Node, peers: Mapping[str, Node]
|
|
25
|
+
) -> None:
|
|
26
|
+
"""Validate that all peers of a given `relm` have the same parent for the given `relationship_name`."""
|
|
27
|
+
node_parent = await node.get_parent_relationship_peer(db=self.db, name=parent_rel_name)
|
|
28
|
+
if not node_parent:
|
|
29
|
+
# If the schema is properly validated we are not expecting this to happen
|
|
30
|
+
raise ValidationError(f"Node {node.id} ({node.get_kind()}) does not have a parent peer")
|
|
31
|
+
|
|
32
|
+
parents: set[str] = {node_parent.id}
|
|
33
|
+
for peer in peers.values():
|
|
34
|
+
parent = await peer.get_parent_relationship_peer(db=self.db, name=parent_rel_name)
|
|
35
|
+
if not parent:
|
|
36
|
+
# If the schema is properly validated we are not expecting this to happen
|
|
37
|
+
raise ValidationError(f"Peer {peer.id} ({peer.get_kind()}) does not have a parent peer")
|
|
38
|
+
parents.add(parent.id)
|
|
39
|
+
|
|
40
|
+
if len(parents) != 1:
|
|
41
|
+
raise ValidationError(
|
|
42
|
+
f"All the elements of the '{relm.name}' relationship on node {node.id} ({node.get_kind()}) must have the same parent "
|
|
43
|
+
"as the node"
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
async def check(self, relm: RelationshipManager, node_schema: MainSchemaTypes, node: Node) -> None: # noqa: ARG002
|
|
47
|
+
if not relm.schema.common_parent:
|
|
48
|
+
return
|
|
49
|
+
|
|
50
|
+
peers = await relm.get_peers(db=self.db)
|
|
51
|
+
if not peers:
|
|
52
|
+
return
|
|
53
|
+
|
|
54
|
+
await self._check_relationship_peers_parent(
|
|
55
|
+
relm=relm, parent_rel_name=relm.schema.common_parent, node=node, peers=peers
|
|
56
|
+
)
|
|
@@ -58,7 +58,7 @@ class RelationshipPeerRelativesConstraint(RelationshipManagerConstraintInterface
|
|
|
58
58
|
f"for their '{node.schema.kind}.{relationship_name}' relationship"
|
|
59
59
|
)
|
|
60
60
|
|
|
61
|
-
async def check(self, relm: RelationshipManager, node_schema: MainSchemaTypes) -> None:
|
|
61
|
+
async def check(self, relm: RelationshipManager, node_schema: MainSchemaTypes, node: Node) -> None: # noqa: ARG002
|
|
62
62
|
if relm.schema.cardinality != RelationshipCardinality.MANY or not relm.schema.common_relatives:
|
|
63
63
|
return
|
|
64
64
|
|
|
@@ -23,7 +23,7 @@ class RelationshipProfilesKindConstraint(RelationshipManagerConstraintInterface)
|
|
|
23
23
|
self.branch = branch
|
|
24
24
|
self.schema_branch = registry.schema.get_schema_branch(branch.name if branch else registry.default_branch)
|
|
25
25
|
|
|
26
|
-
async def check(self, relm: RelationshipManager, node_schema: MainSchemaTypes) -> None:
|
|
26
|
+
async def check(self, relm: RelationshipManager, node_schema: MainSchemaTypes, node: Node) -> None: # noqa: ARG002
|
|
27
27
|
if relm.name != "profiles" or not isinstance(node_schema, NodeSchema):
|
|
28
28
|
return
|
|
29
29
|
|
|
@@ -754,13 +754,20 @@ relationship_schema = SchemaNode(
|
|
|
754
754
|
optional=True,
|
|
755
755
|
extra={"update": UpdateSupport.VALIDATE_CONSTRAINT},
|
|
756
756
|
),
|
|
757
|
+
SchemaAttribute(
|
|
758
|
+
name="common_parent",
|
|
759
|
+
kind="Text",
|
|
760
|
+
optional=True,
|
|
761
|
+
description="Name of a parent relationship on the peer schema that must share the same related object with the object's parent.",
|
|
762
|
+
extra={"update": UpdateSupport.VALIDATE_CONSTRAINT},
|
|
763
|
+
),
|
|
757
764
|
SchemaAttribute(
|
|
758
765
|
name="common_relatives",
|
|
759
766
|
kind="List",
|
|
760
767
|
internal_kind=str,
|
|
761
768
|
optional=True,
|
|
762
769
|
description="List of relationship names on the peer schema for which all objects must share the same set of peers.",
|
|
763
|
-
extra={"update": UpdateSupport.
|
|
770
|
+
extra={"update": UpdateSupport.ALLOWED},
|
|
764
771
|
),
|
|
765
772
|
SchemaAttribute(
|
|
766
773
|
name="order_weight",
|