infrahub-server 1.4.10__py3-none-any.whl → 1.5.0b1__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/tasks.py +208 -16
- infrahub/api/artifact.py +3 -0
- infrahub/api/diff/diff.py +1 -1
- infrahub/api/query.py +2 -0
- infrahub/api/schema.py +3 -0
- infrahub/auth.py +5 -5
- infrahub/cli/db.py +26 -2
- infrahub/cli/db_commands/clean_duplicate_schema_fields.py +212 -0
- infrahub/config.py +7 -2
- infrahub/core/attribute.py +25 -22
- infrahub/core/branch/models.py +2 -2
- infrahub/core/branch/needs_rebase_status.py +11 -0
- infrahub/core/branch/tasks.py +4 -3
- infrahub/core/changelog/models.py +4 -12
- infrahub/core/constants/__init__.py +1 -0
- infrahub/core/constants/infrahubkind.py +1 -0
- infrahub/core/convert_object_type/object_conversion.py +201 -0
- infrahub/core/convert_object_type/repository_conversion.py +89 -0
- infrahub/core/convert_object_type/schema_mapping.py +27 -3
- infrahub/core/diff/model/path.py +4 -0
- infrahub/core/diff/payload_builder.py +1 -1
- infrahub/core/diff/query/artifact.py +1 -1
- infrahub/core/graph/__init__.py +1 -1
- infrahub/core/initialization.py +2 -2
- infrahub/core/ipam/utilization.py +1 -1
- infrahub/core/manager.py +9 -84
- infrahub/core/migrations/graph/__init__.py +6 -0
- infrahub/core/migrations/graph/m040_profile_attrs_in_db.py +166 -0
- infrahub/core/migrations/graph/m041_create_hfid_display_label_in_db.py +97 -0
- infrahub/core/migrations/graph/m042_backfill_hfid_display_label_in_db.py +86 -0
- infrahub/core/migrations/schema/node_attribute_add.py +5 -2
- infrahub/core/migrations/shared.py +5 -6
- infrahub/core/node/__init__.py +165 -42
- infrahub/core/node/constraints/attribute_uniqueness.py +3 -1
- infrahub/core/node/create.py +67 -35
- infrahub/core/node/lock_utils.py +98 -0
- infrahub/core/node/node_property_attribute.py +230 -0
- infrahub/core/node/standard.py +1 -1
- infrahub/core/property.py +11 -0
- infrahub/core/protocols.py +8 -1
- infrahub/core/query/attribute.py +27 -15
- infrahub/core/query/node.py +61 -185
- infrahub/core/query/relationship.py +43 -26
- infrahub/core/query/subquery.py +0 -8
- infrahub/core/registry.py +2 -2
- infrahub/core/relationship/constraints/count.py +1 -1
- infrahub/core/relationship/model.py +60 -20
- infrahub/core/schema/attribute_schema.py +0 -2
- infrahub/core/schema/basenode_schema.py +42 -2
- infrahub/core/schema/definitions/core/__init__.py +2 -0
- infrahub/core/schema/definitions/core/generator.py +2 -0
- infrahub/core/schema/definitions/core/group.py +16 -2
- infrahub/core/schema/definitions/core/repository.py +7 -0
- infrahub/core/schema/definitions/internal.py +14 -1
- infrahub/core/schema/generated/base_node_schema.py +6 -1
- infrahub/core/schema/node_schema.py +5 -2
- infrahub/core/schema/relationship_schema.py +0 -1
- infrahub/core/schema/schema_branch.py +137 -2
- infrahub/core/schema/schema_branch_display.py +123 -0
- infrahub/core/schema/schema_branch_hfid.py +114 -0
- infrahub/core/validators/aggregated_checker.py +1 -1
- infrahub/core/validators/determiner.py +12 -1
- infrahub/core/validators/relationship/peer.py +1 -1
- infrahub/core/validators/tasks.py +1 -1
- infrahub/display_labels/__init__.py +0 -0
- infrahub/display_labels/gather.py +48 -0
- infrahub/display_labels/models.py +240 -0
- infrahub/display_labels/tasks.py +186 -0
- infrahub/display_labels/triggers.py +22 -0
- infrahub/events/group_action.py +1 -1
- infrahub/events/node_action.py +1 -1
- infrahub/generators/constants.py +7 -0
- infrahub/generators/models.py +38 -12
- infrahub/generators/tasks.py +34 -16
- infrahub/git/base.py +38 -1
- infrahub/git/integrator.py +22 -14
- infrahub/graphql/analyzer.py +1 -1
- infrahub/graphql/api/dependencies.py +2 -4
- infrahub/graphql/api/endpoints.py +2 -2
- infrahub/graphql/app.py +2 -4
- infrahub/graphql/initialization.py +2 -3
- infrahub/graphql/manager.py +212 -137
- infrahub/graphql/middleware.py +12 -0
- infrahub/graphql/mutations/branch.py +11 -0
- infrahub/graphql/mutations/computed_attribute.py +110 -3
- infrahub/graphql/mutations/convert_object_type.py +34 -13
- infrahub/graphql/mutations/display_label.py +111 -0
- infrahub/graphql/mutations/generator.py +25 -7
- infrahub/graphql/mutations/hfid.py +118 -0
- infrahub/graphql/mutations/ipam.py +21 -8
- infrahub/graphql/mutations/main.py +37 -153
- infrahub/graphql/mutations/profile.py +195 -0
- infrahub/graphql/mutations/proposed_change.py +2 -1
- infrahub/graphql/mutations/relationship.py +2 -2
- infrahub/graphql/mutations/repository.py +22 -83
- infrahub/graphql/mutations/resource_manager.py +2 -2
- infrahub/graphql/mutations/schema.py +5 -5
- infrahub/graphql/mutations/webhook.py +1 -1
- infrahub/graphql/queries/resource_manager.py +1 -1
- infrahub/graphql/registry.py +173 -0
- infrahub/graphql/resolvers/resolver.py +2 -0
- infrahub/graphql/schema.py +8 -1
- infrahub/groups/tasks.py +1 -1
- infrahub/hfid/__init__.py +0 -0
- infrahub/hfid/gather.py +48 -0
- infrahub/hfid/models.py +240 -0
- infrahub/hfid/tasks.py +185 -0
- infrahub/hfid/triggers.py +22 -0
- infrahub/lock.py +67 -30
- infrahub/locks/__init__.py +0 -0
- infrahub/locks/tasks.py +37 -0
- infrahub/middleware.py +26 -1
- infrahub/patch/plan_writer.py +2 -2
- infrahub/profiles/__init__.py +0 -0
- infrahub/profiles/node_applier.py +101 -0
- infrahub/profiles/queries/__init__.py +0 -0
- infrahub/profiles/queries/get_profile_data.py +99 -0
- infrahub/profiles/tasks.py +63 -0
- infrahub/proposed_change/tasks.py +10 -1
- infrahub/repositories/__init__.py +0 -0
- infrahub/repositories/create_repository.py +113 -0
- infrahub/server.py +16 -3
- infrahub/services/__init__.py +8 -5
- infrahub/tasks/registry.py +6 -4
- infrahub/trigger/catalogue.py +4 -0
- infrahub/trigger/models.py +2 -0
- infrahub/trigger/tasks.py +3 -0
- infrahub/webhook/models.py +1 -1
- infrahub/workflows/catalogue.py +110 -3
- infrahub/workflows/initialization.py +16 -0
- infrahub/workflows/models.py +17 -2
- infrahub_sdk/branch.py +5 -8
- infrahub_sdk/checks.py +1 -1
- infrahub_sdk/client.py +364 -84
- infrahub_sdk/convert_object_type.py +61 -0
- infrahub_sdk/ctl/check.py +2 -3
- infrahub_sdk/ctl/cli_commands.py +18 -12
- infrahub_sdk/ctl/config.py +8 -2
- infrahub_sdk/ctl/generator.py +6 -3
- infrahub_sdk/ctl/graphql.py +184 -0
- infrahub_sdk/ctl/repository.py +39 -1
- infrahub_sdk/ctl/schema.py +18 -3
- infrahub_sdk/ctl/utils.py +4 -0
- infrahub_sdk/ctl/validate.py +5 -3
- infrahub_sdk/diff.py +4 -5
- infrahub_sdk/exceptions.py +2 -0
- infrahub_sdk/generator.py +7 -1
- infrahub_sdk/graphql/__init__.py +12 -0
- infrahub_sdk/graphql/constants.py +1 -0
- infrahub_sdk/graphql/plugin.py +85 -0
- infrahub_sdk/graphql/query.py +77 -0
- infrahub_sdk/{graphql.py → graphql/renderers.py} +88 -75
- infrahub_sdk/graphql/utils.py +40 -0
- infrahub_sdk/node/attribute.py +2 -0
- infrahub_sdk/node/node.py +28 -20
- infrahub_sdk/playback.py +1 -2
- infrahub_sdk/protocols.py +54 -6
- infrahub_sdk/pytest_plugin/plugin.py +7 -4
- infrahub_sdk/pytest_plugin/utils.py +40 -0
- infrahub_sdk/repository.py +1 -2
- infrahub_sdk/schema/__init__.py +38 -0
- infrahub_sdk/schema/main.py +1 -0
- infrahub_sdk/schema/repository.py +8 -0
- infrahub_sdk/spec/object.py +120 -7
- infrahub_sdk/spec/range_expansion.py +118 -0
- infrahub_sdk/timestamp.py +18 -6
- infrahub_sdk/transforms.py +1 -1
- {infrahub_server-1.4.10.dist-info → infrahub_server-1.5.0b1.dist-info}/METADATA +9 -11
- {infrahub_server-1.4.10.dist-info → infrahub_server-1.5.0b1.dist-info}/RECORD +177 -134
- infrahub_testcontainers/container.py +1 -1
- infrahub_testcontainers/docker-compose-cluster.test.yml +1 -1
- infrahub_testcontainers/docker-compose.test.yml +1 -1
- infrahub_testcontainers/models.py +2 -2
- infrahub_testcontainers/performance_test.py +4 -4
- infrahub/core/convert_object_type/conversion.py +0 -134
- {infrahub_server-1.4.10.dist-info → infrahub_server-1.5.0b1.dist-info}/LICENSE.txt +0 -0
- {infrahub_server-1.4.10.dist-info → infrahub_server-1.5.0b1.dist-info}/WHEEL +0 -0
- {infrahub_server-1.4.10.dist-info → infrahub_server-1.5.0b1.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from abc import abstractmethod
|
|
4
|
+
from enum import Enum
|
|
5
|
+
from typing import TYPE_CHECKING, Any, Generic, TypeVar
|
|
6
|
+
|
|
7
|
+
from infrahub_sdk.template import Jinja2Template
|
|
8
|
+
|
|
9
|
+
from infrahub.core.query.node import AttributeFromDB
|
|
10
|
+
from infrahub.core.schema import NodeSchema, ProfileSchema, TemplateSchema
|
|
11
|
+
|
|
12
|
+
from ..attribute import BaseAttribute, ListAttributeOptional, StringOptional
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from infrahub.core.node import Node
|
|
16
|
+
from infrahub.core.schema import NodeSchema, ProfileSchema, TemplateSchema
|
|
17
|
+
from infrahub.core.schema.attribute_schema import AttributeSchema
|
|
18
|
+
from infrahub.core.timestamp import Timestamp
|
|
19
|
+
from infrahub.database import InfrahubDatabase
|
|
20
|
+
|
|
21
|
+
T = TypeVar("T")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class NodePropertyAttribute(Generic[T]):
|
|
25
|
+
"""A node property attribute is a construct that seats between a property and an attribute.
|
|
26
|
+
|
|
27
|
+
View it as a property, set at the node level but stored in the database as an attribute. It usually is something computed from other components of
|
|
28
|
+
a node, such as its attributes and its relationships.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __init__(
|
|
32
|
+
self,
|
|
33
|
+
node_schema: NodeSchema | ProfileSchema | TemplateSchema,
|
|
34
|
+
template: T | None,
|
|
35
|
+
value: AttributeFromDB | T | None = None,
|
|
36
|
+
) -> None:
|
|
37
|
+
self.node_schema = node_schema
|
|
38
|
+
|
|
39
|
+
self.node_attributes: list[str] = []
|
|
40
|
+
self.node_relationships: list[str] = []
|
|
41
|
+
|
|
42
|
+
self.template = template
|
|
43
|
+
self._value = value
|
|
44
|
+
self._manually_assigned = False
|
|
45
|
+
|
|
46
|
+
self.schema: AttributeSchema
|
|
47
|
+
|
|
48
|
+
self.analyze_variables()
|
|
49
|
+
|
|
50
|
+
def needs_update(self, fields: list[str] | None) -> bool:
|
|
51
|
+
"""Tell if this node property attribute must be recomputed given a list of updated fields of a node."""
|
|
52
|
+
if self._manually_assigned or not fields:
|
|
53
|
+
return True
|
|
54
|
+
for field in fields:
|
|
55
|
+
if field in self.node_attributes or field in self.node_relationships:
|
|
56
|
+
return True
|
|
57
|
+
|
|
58
|
+
return False
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def attribute_value(self) -> AttributeFromDB | dict[str, T | None]:
|
|
62
|
+
if isinstance(self._value, AttributeFromDB):
|
|
63
|
+
return self._value
|
|
64
|
+
return {"value": self._value}
|
|
65
|
+
|
|
66
|
+
def set_value(self, value: T | None, manually_assigned: bool = False) -> None:
|
|
67
|
+
"""Force the value of the node property attribute to the given one."""
|
|
68
|
+
if isinstance(self._value, AttributeFromDB):
|
|
69
|
+
self._value.value = value
|
|
70
|
+
else:
|
|
71
|
+
self._value = value
|
|
72
|
+
|
|
73
|
+
if manually_assigned:
|
|
74
|
+
self._manually_assigned = True
|
|
75
|
+
|
|
76
|
+
def get_value(self, node: Node, at: Timestamp) -> T | None:
|
|
77
|
+
if isinstance(self._value, AttributeFromDB):
|
|
78
|
+
attr = self.get_node_attribute(node=node, at=at)
|
|
79
|
+
return attr.value # type: ignore
|
|
80
|
+
|
|
81
|
+
return self._value
|
|
82
|
+
|
|
83
|
+
@abstractmethod
|
|
84
|
+
def analyze_variables(self) -> None: ...
|
|
85
|
+
|
|
86
|
+
@abstractmethod
|
|
87
|
+
async def compute(self, db: InfrahubDatabase, node: Node) -> None: ...
|
|
88
|
+
|
|
89
|
+
@abstractmethod
|
|
90
|
+
def get_node_attribute(self, node: Node, at: Timestamp) -> BaseAttribute: ...
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class DisplayLabel(NodePropertyAttribute[str]):
|
|
94
|
+
def __init__(
|
|
95
|
+
self,
|
|
96
|
+
node_schema: NodeSchema | ProfileSchema | TemplateSchema,
|
|
97
|
+
template: str | None,
|
|
98
|
+
value: AttributeFromDB | str | None = None,
|
|
99
|
+
) -> None:
|
|
100
|
+
super().__init__(node_schema=node_schema, template=template, value=value)
|
|
101
|
+
|
|
102
|
+
self.schema = node_schema.get_attribute(name="display_label")
|
|
103
|
+
|
|
104
|
+
@property
|
|
105
|
+
def is_jinja2_template(self) -> bool:
|
|
106
|
+
if self.template is None:
|
|
107
|
+
return False
|
|
108
|
+
|
|
109
|
+
return any(c in self.template for c in "{}")
|
|
110
|
+
|
|
111
|
+
def _analyze_plain_value(self) -> None:
|
|
112
|
+
if self.template is None or "__" not in self.template:
|
|
113
|
+
return
|
|
114
|
+
|
|
115
|
+
items = self.template.split("__", maxsplit=1)
|
|
116
|
+
if items[0] not in self.node_schema.attribute_names:
|
|
117
|
+
raise ValueError(f"{items[0]} is not an attribute of {self.node_schema.kind}")
|
|
118
|
+
|
|
119
|
+
self.node_attributes.append(items[0])
|
|
120
|
+
|
|
121
|
+
def _analyze_jinja2_value(self) -> None:
|
|
122
|
+
if self.template is None or not self.is_jinja2_template:
|
|
123
|
+
return
|
|
124
|
+
|
|
125
|
+
tpl = Jinja2Template(template=self.template)
|
|
126
|
+
for variable in tpl.get_variables():
|
|
127
|
+
items = variable.split("__", maxsplit=1)
|
|
128
|
+
if items[0] in self.node_schema.attribute_names:
|
|
129
|
+
self.node_attributes.append(items[0])
|
|
130
|
+
elif items[0] in self.node_schema.relationship_names:
|
|
131
|
+
self.node_relationships.append(items[0])
|
|
132
|
+
else:
|
|
133
|
+
raise ValueError(f"{items[0]} is neither an attribute or a relationship of {self.node_schema.kind}")
|
|
134
|
+
|
|
135
|
+
def analyze_variables(self) -> None:
|
|
136
|
+
"""Look at variables used in the display label and record attributes and relationships required to compute it."""
|
|
137
|
+
if not self.is_jinja2_template:
|
|
138
|
+
self._analyze_plain_value()
|
|
139
|
+
else:
|
|
140
|
+
self._analyze_jinja2_value()
|
|
141
|
+
|
|
142
|
+
async def compute(self, db: InfrahubDatabase, node: Node) -> None:
|
|
143
|
+
"""Update the display label value by recomputing it from the template."""
|
|
144
|
+
if self.template is None or self._manually_assigned:
|
|
145
|
+
return
|
|
146
|
+
|
|
147
|
+
if node.get_schema() != self.node_schema:
|
|
148
|
+
raise ValueError(
|
|
149
|
+
f"display_label for schema {self.node_schema.kind} cannot be rendered for node {node.get_schema().kind} {node.id}"
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
if not self.is_jinja2_template:
|
|
153
|
+
path_value = await node.get_path_value(db=db, path=self.template)
|
|
154
|
+
# Use .value for enum to keep compat with old style display label
|
|
155
|
+
self.set_value(value=str(path_value if not isinstance(path_value, Enum) else path_value.value))
|
|
156
|
+
return
|
|
157
|
+
|
|
158
|
+
jinja2_template = Jinja2Template(template=self.template)
|
|
159
|
+
|
|
160
|
+
variables: dict[str, Any] = {}
|
|
161
|
+
for variable in jinja2_template.get_variables():
|
|
162
|
+
variables[variable] = await node.get_path_value(db=db, path=variable)
|
|
163
|
+
|
|
164
|
+
self.set_value(value=await jinja2_template.render(variables=variables))
|
|
165
|
+
|
|
166
|
+
def get_node_attribute(self, node: Node, at: Timestamp) -> StringOptional:
|
|
167
|
+
"""Return a node attribute that can be stored in the database for this display label and node."""
|
|
168
|
+
return StringOptional(
|
|
169
|
+
name="display_label",
|
|
170
|
+
schema=self.schema,
|
|
171
|
+
branch=node.get_branch(),
|
|
172
|
+
at=at,
|
|
173
|
+
node=node,
|
|
174
|
+
data=self.attribute_value,
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
class HumanFriendlyIdentifier(NodePropertyAttribute[list[str]]):
|
|
179
|
+
def __init__(
|
|
180
|
+
self,
|
|
181
|
+
node_schema: NodeSchema | ProfileSchema | TemplateSchema,
|
|
182
|
+
template: list[str] | None,
|
|
183
|
+
value: AttributeFromDB | list[str] | None = None,
|
|
184
|
+
) -> None:
|
|
185
|
+
super().__init__(node_schema=node_schema, template=template, value=value)
|
|
186
|
+
|
|
187
|
+
self.schema = node_schema.get_attribute(name="human_friendly_id")
|
|
188
|
+
|
|
189
|
+
def _analyze_single_variable(self, value: str) -> None:
|
|
190
|
+
items = value.split("__", maxsplit=1)
|
|
191
|
+
if items[0] in self.node_schema.attribute_names:
|
|
192
|
+
self.node_attributes.append(items[0])
|
|
193
|
+
elif items[0] in self.node_schema.relationship_names:
|
|
194
|
+
self.node_relationships.append(items[0])
|
|
195
|
+
else:
|
|
196
|
+
raise ValueError(f"{items[0]} is neither an attribute or a relationship of {self.node_schema.kind}")
|
|
197
|
+
|
|
198
|
+
def analyze_variables(self) -> None:
|
|
199
|
+
"""Look at variables used in the HFID and record attributes and relationships required to compute it."""
|
|
200
|
+
for item in self.template or []:
|
|
201
|
+
self._analyze_single_variable(value=item)
|
|
202
|
+
|
|
203
|
+
async def compute(self, db: InfrahubDatabase, node: Node) -> None:
|
|
204
|
+
"""Update the HFID value by recomputing it from the template."""
|
|
205
|
+
if self.template is None or self._manually_assigned:
|
|
206
|
+
return
|
|
207
|
+
|
|
208
|
+
if node.get_schema() != self.node_schema:
|
|
209
|
+
raise ValueError(
|
|
210
|
+
f"human_friendly_id for schema {self.node_schema.kind} cannot be computed for node {node.get_schema().kind} {node.id}"
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
value: list[str] = []
|
|
214
|
+
for path in self.template:
|
|
215
|
+
path_value = await node.get_path_value(db=db, path=path)
|
|
216
|
+
# Use .value for enum to be consistent with display label
|
|
217
|
+
value.append(path_value if not isinstance(path_value, Enum) else path_value.value)
|
|
218
|
+
|
|
219
|
+
self.set_value(value=value)
|
|
220
|
+
|
|
221
|
+
def get_node_attribute(self, node: Node, at: Timestamp) -> ListAttributeOptional:
|
|
222
|
+
"""Return a node attribute that can be stored in the database for this HFID and node."""
|
|
223
|
+
return ListAttributeOptional(
|
|
224
|
+
name="human_friendly_id",
|
|
225
|
+
schema=self.schema,
|
|
226
|
+
branch=node.get_branch(),
|
|
227
|
+
at=at,
|
|
228
|
+
node=node,
|
|
229
|
+
data=self.attribute_value,
|
|
230
|
+
)
|
infrahub/core/node/standard.py
CHANGED
infrahub/core/property.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from enum import Enum
|
|
3
4
|
from typing import TYPE_CHECKING
|
|
4
5
|
from uuid import UUID
|
|
5
6
|
|
|
@@ -26,6 +27,10 @@ class NodePropertyData(BaseModel):
|
|
|
26
27
|
peer_id: str
|
|
27
28
|
|
|
28
29
|
|
|
30
|
+
class ClearValue(Enum):
|
|
31
|
+
CLEAR = "clear"
|
|
32
|
+
|
|
33
|
+
|
|
29
34
|
class FlagPropertyMixin:
|
|
30
35
|
_flag_properties: list[str] = [v.value for v in FlagProperty]
|
|
31
36
|
|
|
@@ -51,6 +56,7 @@ class NodePropertyMixin:
|
|
|
51
56
|
for node in self._node_properties:
|
|
52
57
|
setattr(self, f"_{node}", None)
|
|
53
58
|
setattr(self, f"{node}_id", None)
|
|
59
|
+
setattr(self, f"_clear_{node}", False)
|
|
54
60
|
|
|
55
61
|
if not kwargs:
|
|
56
62
|
return
|
|
@@ -79,12 +85,14 @@ class NodePropertyMixin:
|
|
|
79
85
|
|
|
80
86
|
def clear_owner(self) -> None:
|
|
81
87
|
self._set_node_property(name="owner", value=None)
|
|
88
|
+
self._clear_owner = True
|
|
82
89
|
|
|
83
90
|
async def get_source(self, db: InfrahubDatabase) -> Node | None:
|
|
84
91
|
return await self._get_node_property(name="source", db=db)
|
|
85
92
|
|
|
86
93
|
def clear_source(self) -> None:
|
|
87
94
|
self._set_node_property(name="source", value=None)
|
|
95
|
+
self._clear_source = True
|
|
88
96
|
|
|
89
97
|
def set_source(self, value: str | Node | UUID) -> None:
|
|
90
98
|
self._set_node_property(name="source", value=value)
|
|
@@ -95,6 +103,9 @@ class NodePropertyMixin:
|
|
|
95
103
|
def set_owner(self, value: str | Node | UUID) -> None:
|
|
96
104
|
self._set_node_property(name="owner", value=value)
|
|
97
105
|
|
|
106
|
+
def is_clear(self, name: str) -> bool:
|
|
107
|
+
return getattr(self, f"_clear_{name}", False)
|
|
108
|
+
|
|
98
109
|
def _get_node_property_from_cache(self, name: str) -> Node:
|
|
99
110
|
"""Return the node attribute if it's already present locally,
|
|
100
111
|
Otherwise raise an exception
|
infrahub/core/protocols.py
CHANGED
|
@@ -4,7 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
from typing import TYPE_CHECKING
|
|
6
6
|
|
|
7
|
-
from .protocols_base import CoreNode
|
|
7
|
+
from infrahub.core.protocols_base import CoreNode
|
|
8
8
|
|
|
9
9
|
if TYPE_CHECKING:
|
|
10
10
|
from enum import Enum
|
|
@@ -125,6 +125,7 @@ class CoreGenericRepository(CoreNode):
|
|
|
125
125
|
queries: RelationshipManager
|
|
126
126
|
checks: RelationshipManager
|
|
127
127
|
generators: RelationshipManager
|
|
128
|
+
groups_objects: RelationshipManager
|
|
128
129
|
|
|
129
130
|
|
|
130
131
|
class CoreGroup(CoreNode):
|
|
@@ -349,6 +350,10 @@ class CoreGeneratorAction(CoreAction):
|
|
|
349
350
|
generator: RelationshipManager
|
|
350
351
|
|
|
351
352
|
|
|
353
|
+
class CoreGeneratorAwareGroup(CoreGroup):
|
|
354
|
+
pass
|
|
355
|
+
|
|
356
|
+
|
|
352
357
|
class CoreGeneratorCheck(CoreCheck):
|
|
353
358
|
instance: String
|
|
354
359
|
|
|
@@ -360,6 +365,8 @@ class CoreGeneratorDefinition(CoreTaskTarget):
|
|
|
360
365
|
file_path: String
|
|
361
366
|
class_name: String
|
|
362
367
|
convert_query_response: BooleanOptional
|
|
368
|
+
execute_in_proposed_change: BooleanOptional
|
|
369
|
+
execute_after_merge: BooleanOptional
|
|
363
370
|
query: RelationshipManager
|
|
364
371
|
repository: RelationshipManager
|
|
365
372
|
targets: RelationshipManager
|
infrahub/core/query/attribute.py
CHANGED
|
@@ -133,7 +133,7 @@ class AttributeUpdateNodePropertyQuery(AttributeQuery):
|
|
|
133
133
|
def __init__(
|
|
134
134
|
self,
|
|
135
135
|
prop_name: str,
|
|
136
|
-
prop_id: str,
|
|
136
|
+
prop_id: str | None = None,
|
|
137
137
|
**kwargs: Any,
|
|
138
138
|
):
|
|
139
139
|
self.prop_name = prop_name
|
|
@@ -144,6 +144,8 @@ class AttributeUpdateNodePropertyQuery(AttributeQuery):
|
|
|
144
144
|
async def query_init(self, db: InfrahubDatabase, **kwargs: dict[str, Any]) -> None: # noqa: ARG002
|
|
145
145
|
at = self.at or self.attr.at
|
|
146
146
|
|
|
147
|
+
branch_filter, branch_params = self.branch.get_query_filter_path(at=at)
|
|
148
|
+
self.params.update(branch_params)
|
|
147
149
|
self.params["attr_uuid"] = self.attr.id
|
|
148
150
|
self.params["branch"] = self.branch.name
|
|
149
151
|
self.params["branch_level"] = self.branch.hierarchy_level
|
|
@@ -151,18 +153,34 @@ class AttributeUpdateNodePropertyQuery(AttributeQuery):
|
|
|
151
153
|
self.params["prop_name"] = self.prop_name
|
|
152
154
|
self.params["prop_id"] = self.prop_id
|
|
153
155
|
|
|
154
|
-
|
|
156
|
+
rel_label = f"HAS_{self.prop_name.upper()}"
|
|
155
157
|
|
|
156
|
-
|
|
158
|
+
if self.branch.is_default or self.branch.is_global:
|
|
159
|
+
node_query = """
|
|
160
|
+
MATCH (np:Node { uuid: $prop_id })-[r:IS_PART_OF]->(:Root)
|
|
161
|
+
WHERE r.branch IN $branch0
|
|
162
|
+
AND r.status = "active"
|
|
163
|
+
AND r.from <= $at AND (r.to IS NULL OR r.to > $at)
|
|
164
|
+
WITH np
|
|
165
|
+
LIMIT 1
|
|
157
166
|
"""
|
|
167
|
+
else:
|
|
168
|
+
node_query = """
|
|
169
|
+
MATCH (np:Node { uuid: $prop_id })-[r:IS_PART_OF]->(:Root)
|
|
170
|
+
WHERE %(branch_filter)s
|
|
171
|
+
ORDER BY r.branch_level DESC, r.from DESC, r.status ASC
|
|
172
|
+
LIMIT 1
|
|
173
|
+
WITH np
|
|
174
|
+
WHERE r.status = "active"
|
|
175
|
+
""" % {"branch_filter": branch_filter}
|
|
176
|
+
self.add_to_query(node_query)
|
|
177
|
+
|
|
178
|
+
attr_query = """
|
|
158
179
|
MATCH (a:Attribute { uuid: $attr_uuid })
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
% rel_name
|
|
163
|
-
)
|
|
180
|
+
CREATE (a)-[r:%(rel_label)s { branch: $branch, branch_level: $branch_level, status: "active", from: $at }]->(np)
|
|
181
|
+
""" % {"rel_label": rel_label}
|
|
182
|
+
self.add_to_query(attr_query)
|
|
164
183
|
|
|
165
|
-
self.add_to_query(query)
|
|
166
184
|
self.return_labels = ["a", "np", "r"]
|
|
167
185
|
|
|
168
186
|
|
|
@@ -204,7 +222,6 @@ async def default_attribute_query_filter(
|
|
|
204
222
|
param_prefix: str | None = None,
|
|
205
223
|
db: InfrahubDatabase | None = None, # noqa: ARG001
|
|
206
224
|
partial_match: bool = False,
|
|
207
|
-
support_profiles: bool = False,
|
|
208
225
|
) -> tuple[list[QueryElement], dict[str, Any], list[str]]:
|
|
209
226
|
"""Generate Query String Snippet to filter the right node."""
|
|
210
227
|
attribute_value_label = GraphAttributeValueNode.get_default_label()
|
|
@@ -251,9 +268,6 @@ async def default_attribute_query_filter(
|
|
|
251
268
|
query_where.append(f"toString(av.{filter_name}) =~ ${param_prefix}_{filter_name}")
|
|
252
269
|
elif filter_name == "isnull":
|
|
253
270
|
query_filter.append(QueryNode(name="av", labels=[attribute_value_label]))
|
|
254
|
-
elif support_profiles:
|
|
255
|
-
query_filter.append(QueryNode(name="av", labels=[attribute_value_label]))
|
|
256
|
-
query_where.append(f"(av.{filter_name} = ${param_prefix}_{filter_name} OR av.is_default)")
|
|
257
271
|
else:
|
|
258
272
|
query_filter.append(
|
|
259
273
|
QueryNode(
|
|
@@ -271,8 +285,6 @@ async def default_attribute_query_filter(
|
|
|
271
285
|
if attribute_kind and attribute_kind == "List":
|
|
272
286
|
query_params[f"{param_prefix}_{filter_name}"] = build_regex_attrs(values=filter_value)
|
|
273
287
|
query_where.append(f"toString(av.value) =~ ${param_prefix}_{filter_name}")
|
|
274
|
-
elif support_profiles:
|
|
275
|
-
query_where.append(f"(av.value IN ${param_prefix}_value OR av.is_default)")
|
|
276
288
|
else:
|
|
277
289
|
query_where.append(f"av.value IN ${param_prefix}_value")
|
|
278
290
|
query_params[f"{param_prefix}_value"] = filter_value
|