infrahub-server 1.6.3__py3-none-any.whl → 1.7.0b0__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 +4 -2
- infrahub/api/schema.py +3 -1
- infrahub/artifacts/tasks.py +1 -0
- infrahub/auth.py +2 -2
- infrahub/cli/db.py +6 -6
- infrahub/computed_attribute/gather.py +3 -4
- infrahub/computed_attribute/tasks.py +23 -6
- infrahub/config.py +8 -0
- infrahub/constants/enums.py +12 -0
- infrahub/core/account.py +5 -8
- infrahub/core/attribute.py +106 -108
- infrahub/core/branch/models.py +44 -71
- infrahub/core/branch/tasks.py +5 -3
- infrahub/core/changelog/diff.py +1 -20
- infrahub/core/changelog/models.py +0 -7
- infrahub/core/constants/__init__.py +17 -0
- infrahub/core/constants/database.py +0 -1
- infrahub/core/constants/schema.py +0 -1
- infrahub/core/convert_object_type/repository_conversion.py +3 -4
- infrahub/core/diff/data_check_synchronizer.py +3 -2
- infrahub/core/diff/enricher/cardinality_one.py +1 -1
- infrahub/core/diff/merger/merger.py +27 -1
- infrahub/core/diff/merger/serializer.py +3 -10
- infrahub/core/diff/model/diff.py +1 -1
- infrahub/core/diff/query/merge.py +376 -135
- infrahub/core/graph/__init__.py +1 -1
- infrahub/core/graph/constraints.py +2 -2
- infrahub/core/graph/schema.py +2 -12
- infrahub/core/manager.py +132 -126
- infrahub/core/metadata/__init__.py +0 -0
- infrahub/core/metadata/interface.py +37 -0
- infrahub/core/metadata/model.py +31 -0
- infrahub/core/metadata/query/__init__.py +0 -0
- infrahub/core/metadata/query/node_metadata.py +301 -0
- infrahub/core/migrations/graph/__init__.py +4 -0
- infrahub/core/migrations/graph/m013_convert_git_password_credential.py +3 -8
- infrahub/core/migrations/graph/m017_add_core_profile.py +5 -2
- infrahub/core/migrations/graph/m018_uniqueness_nulls.py +2 -1
- infrahub/core/migrations/graph/m019_restore_rels_to_time.py +0 -10
- infrahub/core/migrations/graph/m020_duplicate_edges.py +0 -8
- infrahub/core/migrations/graph/m025_uniqueness_nulls.py +2 -1
- infrahub/core/migrations/graph/m026_0000_prefix_fix.py +2 -1
- infrahub/core/migrations/graph/m029_duplicates_cleanup.py +0 -1
- infrahub/core/migrations/graph/m031_check_number_attributes.py +2 -2
- infrahub/core/migrations/graph/m038_redo_0000_prefix_fix.py +2 -1
- infrahub/core/migrations/graph/m049_remove_is_visible_relationship.py +38 -0
- infrahub/core/migrations/graph/m050_backfill_vertex_metadata.py +168 -0
- infrahub/core/migrations/query/attribute_add.py +17 -6
- infrahub/core/migrations/query/attribute_remove.py +19 -5
- infrahub/core/migrations/query/attribute_rename.py +21 -5
- infrahub/core/migrations/query/node_duplicate.py +19 -4
- infrahub/core/migrations/schema/attribute_kind_update.py +25 -7
- infrahub/core/migrations/schema/attribute_supports_profile.py +3 -1
- infrahub/core/migrations/schema/models.py +3 -0
- infrahub/core/migrations/schema/node_attribute_add.py +4 -1
- infrahub/core/migrations/schema/node_remove.py +24 -2
- infrahub/core/migrations/schema/tasks.py +4 -1
- infrahub/core/migrations/shared.py +13 -6
- infrahub/core/models.py +6 -6
- infrahub/core/node/__init__.py +156 -57
- infrahub/core/node/create.py +7 -3
- infrahub/core/node/standard.py +100 -14
- infrahub/core/property.py +0 -1
- infrahub/core/protocols_base.py +6 -2
- infrahub/core/query/__init__.py +6 -7
- infrahub/core/query/attribute.py +161 -46
- infrahub/core/query/branch.py +57 -69
- infrahub/core/query/diff.py +4 -4
- infrahub/core/query/node.py +618 -180
- infrahub/core/query/relationship.py +449 -300
- infrahub/core/query/standard_node.py +25 -5
- infrahub/core/query/utils.py +2 -4
- infrahub/core/relationship/constraints/profiles_removal.py +168 -0
- infrahub/core/relationship/model.py +293 -139
- infrahub/core/schema/attribute_parameters.py +1 -28
- infrahub/core/schema/attribute_schema.py +17 -11
- infrahub/core/schema/manager.py +63 -43
- infrahub/core/schema/relationship_schema.py +6 -2
- infrahub/core/schema/schema_branch.py +48 -76
- infrahub/core/task/task.py +4 -2
- infrahub/core/utils.py +0 -22
- infrahub/core/validators/attribute/kind.py +2 -5
- infrahub/core/validators/determiner.py +3 -3
- infrahub/database/__init__.py +3 -3
- infrahub/dependencies/builder/constraint/grouped/node_runner.py +2 -0
- infrahub/dependencies/builder/constraint/relationship_manager/profiles_removal.py +8 -0
- infrahub/dependencies/registry.py +2 -0
- infrahub/display_labels/tasks.py +12 -3
- infrahub/git/integrator.py +18 -18
- infrahub/git/tasks.py +1 -1
- infrahub/graphql/app.py +2 -2
- infrahub/graphql/constants.py +3 -0
- infrahub/graphql/context.py +1 -1
- infrahub/graphql/initialization.py +11 -0
- infrahub/graphql/loaders/account.py +134 -0
- infrahub/graphql/loaders/node.py +5 -12
- infrahub/graphql/loaders/peers.py +5 -7
- infrahub/graphql/manager.py +158 -18
- infrahub/graphql/metadata.py +91 -0
- infrahub/graphql/models.py +33 -3
- infrahub/graphql/mutations/account.py +5 -5
- infrahub/graphql/mutations/attribute.py +0 -2
- infrahub/graphql/mutations/branch.py +9 -5
- infrahub/graphql/mutations/computed_attribute.py +1 -1
- infrahub/graphql/mutations/display_label.py +1 -1
- infrahub/graphql/mutations/hfid.py +1 -1
- infrahub/graphql/mutations/ipam.py +4 -6
- infrahub/graphql/mutations/main.py +9 -4
- infrahub/graphql/mutations/profile.py +16 -22
- infrahub/graphql/mutations/proposed_change.py +4 -4
- infrahub/graphql/mutations/relationship.py +40 -10
- infrahub/graphql/mutations/repository.py +14 -12
- infrahub/graphql/mutations/schema.py +2 -2
- infrahub/graphql/queries/branch.py +62 -6
- infrahub/graphql/queries/diff/tree.py +5 -5
- infrahub/graphql/resolvers/account_metadata.py +84 -0
- infrahub/graphql/resolvers/ipam.py +6 -8
- infrahub/graphql/resolvers/many_relationship.py +77 -35
- infrahub/graphql/resolvers/resolver.py +16 -12
- infrahub/graphql/resolvers/single_relationship.py +87 -23
- infrahub/graphql/subscription/graphql_query.py +2 -0
- infrahub/graphql/types/__init__.py +0 -1
- infrahub/graphql/types/attribute.py +10 -5
- infrahub/graphql/types/branch.py +40 -53
- infrahub/graphql/types/enums.py +3 -0
- infrahub/graphql/types/metadata.py +28 -0
- infrahub/graphql/types/node.py +22 -2
- infrahub/graphql/types/relationship.py +10 -2
- infrahub/graphql/types/standard_node.py +4 -3
- infrahub/hfid/tasks.py +12 -3
- infrahub/profiles/gather.py +56 -0
- infrahub/profiles/mandatory_fields_checker.py +116 -0
- infrahub/profiles/models.py +66 -0
- infrahub/profiles/node_applier.py +153 -12
- infrahub/profiles/queries/get_profile_data.py +143 -31
- infrahub/profiles/tasks.py +79 -27
- infrahub/profiles/triggers.py +22 -0
- infrahub/proposed_change/tasks.py +4 -1
- infrahub/tasks/artifact.py +1 -0
- infrahub/transformations/tasks.py +2 -2
- infrahub/trigger/catalogue.py +2 -0
- infrahub/trigger/models.py +1 -0
- infrahub/trigger/setup.py +3 -3
- infrahub/trigger/tasks.py +3 -0
- infrahub/validators/tasks.py +1 -0
- infrahub/webhook/models.py +1 -1
- infrahub/webhook/tasks.py +1 -1
- infrahub/workers/dependencies.py +9 -3
- infrahub/workers/infrahub_async.py +13 -4
- infrahub/workflows/catalogue.py +19 -0
- infrahub_sdk/node/constants.py +1 -0
- infrahub_sdk/node/related_node.py +13 -4
- infrahub_sdk/node/relationship.py +8 -0
- {infrahub_server-1.6.3.dist-info → infrahub_server-1.7.0b0.dist-info}/METADATA +17 -16
- {infrahub_server-1.6.3.dist-info → infrahub_server-1.7.0b0.dist-info}/RECORD +161 -143
- infrahub_testcontainers/container.py +3 -3
- infrahub_testcontainers/docker-compose-cluster.test.yml +7 -7
- infrahub_testcontainers/docker-compose.test.yml +13 -5
- {infrahub_server-1.6.3.dist-info → infrahub_server-1.7.0b0.dist-info}/WHEEL +0 -0
- {infrahub_server-1.6.3.dist-info → infrahub_server-1.7.0b0.dist-info}/entry_points.txt +0 -0
- {infrahub_server-1.6.3.dist-info → infrahub_server-1.7.0b0.dist-info}/licenses/LICENSE.txt +0 -0
infrahub/core/node/standard.py
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import inspect
|
|
4
|
+
from dataclasses import dataclass, field
|
|
4
5
|
from enum import Enum
|
|
5
6
|
from typing import TYPE_CHECKING, Any, Optional, Union, get_args, get_origin
|
|
6
7
|
from uuid import UUID
|
|
7
8
|
|
|
8
9
|
import ujson
|
|
9
10
|
from infrahub_sdk.uuidt import UUIDT
|
|
10
|
-
from pydantic import BaseModel
|
|
11
|
+
from pydantic import BaseModel, Field, field_validator
|
|
11
12
|
|
|
12
|
-
from infrahub.
|
|
13
|
+
from infrahub.constants.enums import OrderByField, OrderDirection
|
|
14
|
+
from infrahub.core.constants import NULL_VALUE, SYSTEM_USER_ID, InfrahubKind
|
|
13
15
|
from infrahub.core.query.standard_node import (
|
|
14
16
|
StandardNodeCreateQuery,
|
|
15
17
|
StandardNodeDeleteQuery,
|
|
@@ -18,6 +20,7 @@ from infrahub.core.query.standard_node import (
|
|
|
18
20
|
StandardNodeQuery,
|
|
19
21
|
StandardNodeUpdateQuery,
|
|
20
22
|
)
|
|
23
|
+
from infrahub.core.timestamp import Timestamp, current_timestamp
|
|
21
24
|
from infrahub.exceptions import Error, InitializationError
|
|
22
25
|
|
|
23
26
|
if TYPE_CHECKING:
|
|
@@ -29,9 +32,25 @@ if TYPE_CHECKING:
|
|
|
29
32
|
from infrahub.database import InfrahubDatabase
|
|
30
33
|
|
|
31
34
|
|
|
35
|
+
@dataclass
|
|
36
|
+
class StandardNodeOrdering:
|
|
37
|
+
order_by: OrderByField = field(default=OrderByField.ID)
|
|
38
|
+
direction: OrderDirection = field(default=OrderDirection.ASC)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass(slots=True)
|
|
42
|
+
class StandardNodeQueryFields:
|
|
43
|
+
node: dict[str, Any] = field(default_factory=dict)
|
|
44
|
+
node_metadata: dict[str, Any] = field(default_factory=dict)
|
|
45
|
+
|
|
46
|
+
|
|
32
47
|
class StandardNode(BaseModel):
|
|
33
48
|
id: Optional[str] = None
|
|
34
49
|
uuid: Optional[UUID] = None
|
|
50
|
+
created_at: Optional[str] = Field(default=None, validate_default=True)
|
|
51
|
+
created_by: str = Field(default=SYSTEM_USER_ID)
|
|
52
|
+
updated_by: Optional[str] = Field(default=None)
|
|
53
|
+
updated_at: Optional[str] = Field(default=None, validate_default=True)
|
|
35
54
|
|
|
36
55
|
_query: type[StandardNodeQuery] = StandardNodeCreateQuery
|
|
37
56
|
_exclude_attrs: list[str] = ["id", "uuid", "_query"]
|
|
@@ -45,6 +64,11 @@ class StandardNode(BaseModel):
|
|
|
45
64
|
raise ValueError("id isn't defined yet")
|
|
46
65
|
return self.id
|
|
47
66
|
|
|
67
|
+
@field_validator("created_at", mode="before")
|
|
68
|
+
@classmethod
|
|
69
|
+
def set_created_at(cls, value: str) -> str:
|
|
70
|
+
return Timestamp(value).to_string()
|
|
71
|
+
|
|
48
72
|
@staticmethod
|
|
49
73
|
def guess_field_type(field: FieldInfo) -> Any:
|
|
50
74
|
"""Return the type of a Pydantic model field.
|
|
@@ -68,7 +92,14 @@ class StandardNode(BaseModel):
|
|
|
68
92
|
|
|
69
93
|
raise InitializationError("The root node has not been initialized with a uuid")
|
|
70
94
|
|
|
71
|
-
async def
|
|
95
|
+
async def to_graphql_flat(self, fields: dict) -> dict:
|
|
96
|
+
"""Returns the GraphQL representation of the object with only top-level fields.
|
|
97
|
+
|
|
98
|
+
This method does not handle nested fields and is only used for the old `Branch` query which
|
|
99
|
+
will be deprecated in the future and replaced by the `InfrahubBranch` query.
|
|
100
|
+
It's also used for the old style of Branch mutations that will be deprecated in the future,
|
|
101
|
+
when we introduce InfrahubBranch muations for consistency.
|
|
102
|
+
"""
|
|
72
103
|
response: dict[str, Any] = {"id": self.uuid}
|
|
73
104
|
|
|
74
105
|
for field_name in fields.keys():
|
|
@@ -77,21 +108,71 @@ class StandardNode(BaseModel):
|
|
|
77
108
|
if field_name == "__typename":
|
|
78
109
|
response[field_name] = self.get_type()
|
|
79
110
|
continue
|
|
80
|
-
field = getattr(self, field_name)
|
|
111
|
+
field = getattr(self, field_name, None)
|
|
81
112
|
if field is None:
|
|
82
113
|
response[field_name] = None
|
|
83
114
|
continue
|
|
115
|
+
if isinstance(fields.get(field_name), dict):
|
|
116
|
+
result = {}
|
|
117
|
+
for nested_field in fields.get(field_name, {}).keys():
|
|
118
|
+
if nested_field == "value":
|
|
119
|
+
result[nested_field] = field
|
|
120
|
+
continue
|
|
121
|
+
response[field_name] = result
|
|
122
|
+
continue
|
|
123
|
+
|
|
84
124
|
response[field_name] = field
|
|
85
125
|
|
|
86
126
|
return response
|
|
87
127
|
|
|
88
|
-
async def
|
|
128
|
+
async def to_graphql(self, fields: StandardNodeQueryFields) -> dict:
|
|
129
|
+
node_response: dict[str, Any] = {}
|
|
130
|
+
meta_response: dict[str, Any] = {}
|
|
131
|
+
|
|
132
|
+
for field_name in fields.node.keys():
|
|
133
|
+
if field_name == "id":
|
|
134
|
+
node_response["id"] = self.uuid
|
|
135
|
+
continue
|
|
136
|
+
if field_name == "__typename":
|
|
137
|
+
node_response[field_name] = self.get_type()
|
|
138
|
+
continue
|
|
139
|
+
field = getattr(self, field_name, None)
|
|
140
|
+
if field is None:
|
|
141
|
+
node_response[field_name] = None
|
|
142
|
+
continue
|
|
143
|
+
if isinstance(fields.node.get(field_name), dict):
|
|
144
|
+
result = {}
|
|
145
|
+
for nested_field in fields.node.get(field_name, {}).keys():
|
|
146
|
+
if nested_field == "value":
|
|
147
|
+
result[nested_field] = field
|
|
148
|
+
continue
|
|
149
|
+
node_response[field_name] = result
|
|
150
|
+
continue
|
|
151
|
+
|
|
152
|
+
node_response[field_name] = field
|
|
153
|
+
|
|
154
|
+
for field_name in fields.node_metadata.keys():
|
|
155
|
+
match field_name:
|
|
156
|
+
case "created_at":
|
|
157
|
+
meta_response["created_at"] = Timestamp(self.created_at).to_datetime() if self.created_at else None
|
|
158
|
+
case "updated_at":
|
|
159
|
+
meta_response["updated_at"] = Timestamp(self.updated_at).to_datetime() if self.updated_at else None
|
|
160
|
+
case "created_by":
|
|
161
|
+
if self.created_by and self.created_by != SYSTEM_USER_ID:
|
|
162
|
+
meta_response["created_by"] = {"id": self.created_by, "__kind__": InfrahubKind.ACCOUNT}
|
|
163
|
+
case "updated_by":
|
|
164
|
+
if self.updated_by and self.updated_by != SYSTEM_USER_ID:
|
|
165
|
+
meta_response["updated_by"] = {"id": self.updated_by, "__kind__": InfrahubKind.ACCOUNT}
|
|
166
|
+
|
|
167
|
+
return {"node": node_response, "node_metadata": meta_response}
|
|
168
|
+
|
|
169
|
+
async def save(self, db: InfrahubDatabase, user_id: str = SYSTEM_USER_ID) -> bool:
|
|
89
170
|
"""Create or Update the Node in the database."""
|
|
90
171
|
|
|
91
172
|
if self.id:
|
|
92
|
-
return await self.update(db=db)
|
|
173
|
+
return await self.update(db=db, user_id=user_id)
|
|
93
174
|
|
|
94
|
-
return await self.create(db=db)
|
|
175
|
+
return await self.create(db=db, user_id=user_id)
|
|
95
176
|
|
|
96
177
|
async def delete(self, db: InfrahubDatabase) -> None:
|
|
97
178
|
"""Delete the Node in the database."""
|
|
@@ -99,9 +180,11 @@ class StandardNode(BaseModel):
|
|
|
99
180
|
query: Query = await StandardNodeDeleteQuery.init(db=db, node=self)
|
|
100
181
|
await query.execute(db=db)
|
|
101
182
|
|
|
102
|
-
async def create(self, db: InfrahubDatabase) -> bool:
|
|
183
|
+
async def create(self, db: InfrahubDatabase, user_id: str = SYSTEM_USER_ID) -> bool:
|
|
103
184
|
"""Create a new node in the database."""
|
|
104
|
-
|
|
185
|
+
self.created_by = user_id
|
|
186
|
+
self.updated_by = self.created_by
|
|
187
|
+
self.updated_at = self.created_at
|
|
105
188
|
query: Query = await self._query.init(db=db, node=self)
|
|
106
189
|
await query.execute(db=db)
|
|
107
190
|
|
|
@@ -115,9 +198,10 @@ class StandardNode(BaseModel):
|
|
|
115
198
|
|
|
116
199
|
return True
|
|
117
200
|
|
|
118
|
-
async def update(self, db: InfrahubDatabase) -> bool:
|
|
201
|
+
async def update(self, db: InfrahubDatabase, user_id: str = SYSTEM_USER_ID) -> bool:
|
|
119
202
|
"""Update the node in the database if needed."""
|
|
120
|
-
|
|
203
|
+
self.updated_by = user_id
|
|
204
|
+
self.updated_at = current_timestamp()
|
|
121
205
|
query: Query = await StandardNodeUpdateQuery.init(db=db, node=self)
|
|
122
206
|
await query.execute(db=db)
|
|
123
207
|
result = query.get_result()
|
|
@@ -187,7 +271,7 @@ class StandardNode(BaseModel):
|
|
|
187
271
|
else:
|
|
188
272
|
data["uuid"] = str(self.uuid)
|
|
189
273
|
|
|
190
|
-
for attr_name,
|
|
274
|
+
for attr_name, field_info in self.__class__.model_fields.items():
|
|
191
275
|
if attr_name in self._exclude_attrs:
|
|
192
276
|
continue
|
|
193
277
|
|
|
@@ -195,7 +279,7 @@ class StandardNode(BaseModel):
|
|
|
195
279
|
if isinstance(attr_value, Enum):
|
|
196
280
|
attr_value = attr_value.value
|
|
197
281
|
|
|
198
|
-
field_type = self.guess_field_type(
|
|
282
|
+
field_type = self.guess_field_type(field_info)
|
|
199
283
|
|
|
200
284
|
if attr_value is None:
|
|
201
285
|
data[attr_name] = NULL_VALUE
|
|
@@ -219,10 +303,12 @@ class StandardNode(BaseModel):
|
|
|
219
303
|
limit: int = 1000,
|
|
220
304
|
ids: list[str] | None = None,
|
|
221
305
|
name: str | None = None,
|
|
306
|
+
node_ordering: StandardNodeOrdering | None = None,
|
|
222
307
|
**kwargs: dict[str, Any],
|
|
223
308
|
) -> list[Self]:
|
|
309
|
+
node_ordering = node_ordering or StandardNodeOrdering()
|
|
224
310
|
query: Query = await StandardNodeGetListQuery.init(
|
|
225
|
-
db=db, node_class=cls, ids=ids, node_name=name, limit=limit, **kwargs
|
|
311
|
+
db=db, node_class=cls, ids=ids, node_name=name, limit=limit, node_ordering=node_ordering, **kwargs
|
|
226
312
|
)
|
|
227
313
|
await query.execute(db=db)
|
|
228
314
|
|
infrahub/core/property.py
CHANGED
infrahub/core/protocols_base.py
CHANGED
|
@@ -4,6 +4,8 @@ from typing import TYPE_CHECKING, Any, Protocol, runtime_checkable
|
|
|
4
4
|
|
|
5
5
|
from typing_extensions import Self
|
|
6
6
|
|
|
7
|
+
from infrahub.core.constants import SYSTEM_USER_ID
|
|
8
|
+
|
|
7
9
|
if TYPE_CHECKING:
|
|
8
10
|
from neo4j import AsyncResult, AsyncSession, AsyncTransaction, Record
|
|
9
11
|
|
|
@@ -82,8 +84,10 @@ class CoreNode(Protocol):
|
|
|
82
84
|
at: Timestamp | str | None = None,
|
|
83
85
|
) -> Self: ...
|
|
84
86
|
async def new(self, db: InfrahubDatabase, id: str | None = None, **kwargs: Any) -> Self: ...
|
|
85
|
-
async def save(self, db: InfrahubDatabase, at: Timestamp | None = None) -> Self: ...
|
|
86
|
-
async def delete(
|
|
87
|
+
async def save(self, db: InfrahubDatabase, at: Timestamp | None = None, user_id: str = SYSTEM_USER_ID) -> Self: ...
|
|
88
|
+
async def delete(
|
|
89
|
+
self, db: InfrahubDatabase, at: Timestamp | None = None, user_id: str = SYSTEM_USER_ID
|
|
90
|
+
) -> None: ...
|
|
87
91
|
async def load(
|
|
88
92
|
self,
|
|
89
93
|
db: InfrahubDatabase,
|
infrahub/core/query/__init__.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
3
4
|
from collections import defaultdict
|
|
4
5
|
from dataclasses import dataclass, field
|
|
5
6
|
from enum import Enum
|
|
@@ -12,7 +13,7 @@ from neo4j.graph import Relationship as Neo4jRelationship
|
|
|
12
13
|
from opentelemetry import trace
|
|
13
14
|
|
|
14
15
|
from infrahub import config
|
|
15
|
-
from infrahub.core.constants import PermissionLevel
|
|
16
|
+
from infrahub.core.constants import SYSTEM_USER_ID, PermissionLevel
|
|
16
17
|
from infrahub.core.timestamp import Timestamp
|
|
17
18
|
from infrahub.exceptions import QueryError
|
|
18
19
|
|
|
@@ -335,7 +336,7 @@ class QueryStat:
|
|
|
335
336
|
return cls(**data)
|
|
336
337
|
|
|
337
338
|
|
|
338
|
-
class Query:
|
|
339
|
+
class Query(ABC):
|
|
339
340
|
name: str = "base-query"
|
|
340
341
|
type: QueryType
|
|
341
342
|
|
|
@@ -351,6 +352,7 @@ class Query:
|
|
|
351
352
|
offset: int | None = None,
|
|
352
353
|
order_by: list[str] | None = None,
|
|
353
354
|
branch_agnostic: bool = False,
|
|
355
|
+
user_id: str = SYSTEM_USER_ID,
|
|
354
356
|
):
|
|
355
357
|
if branch:
|
|
356
358
|
self.branch = branch
|
|
@@ -366,6 +368,7 @@ class Query:
|
|
|
366
368
|
self.limit = limit
|
|
367
369
|
self.offset = offset
|
|
368
370
|
self.order_by = order_by
|
|
371
|
+
self.user_id = user_id
|
|
369
372
|
|
|
370
373
|
# Initialize internal variables
|
|
371
374
|
self.params: dict = {}
|
|
@@ -402,12 +405,8 @@ class Query:
|
|
|
402
405
|
|
|
403
406
|
return query
|
|
404
407
|
|
|
408
|
+
@abstractmethod
|
|
405
409
|
async def query_init(self, db: InfrahubDatabase, **kwargs: Any) -> None:
|
|
406
|
-
# Avoid using this method for new queries and look at migrating older queries. The
|
|
407
|
-
# problem here is that we loose so much information with the `**kwargs` we should instead
|
|
408
|
-
# populate this information via the constructor and anything done within the existing query_init methods
|
|
409
|
-
# could either be handled within __init__ or via dedicated methods within each Query class where appropriate,
|
|
410
|
-
# i.e. things might need to happend in a certain order or we just want to separate the logic better.
|
|
411
410
|
raise NotImplementedError
|
|
412
411
|
|
|
413
412
|
def get_context(self) -> dict[str, str]:
|
infrahub/core/query/attribute.py
CHANGED
|
@@ -48,13 +48,14 @@ class AttributeQuery(Query):
|
|
|
48
48
|
class AttributeUpdateValueQuery(AttributeQuery):
|
|
49
49
|
name = "attribute_update_value"
|
|
50
50
|
type: QueryType = QueryType.WRITE
|
|
51
|
-
|
|
52
|
-
raise_error_if_empty: bool =
|
|
51
|
+
insert_return: bool = False
|
|
52
|
+
raise_error_if_empty: bool = False
|
|
53
53
|
|
|
54
54
|
async def query_init(self, db: InfrahubDatabase, **kwargs: dict[str, Any]) -> None: # noqa: ARG002
|
|
55
55
|
at = self.at or self.attr.at
|
|
56
56
|
|
|
57
57
|
self.params["attr_uuid"] = self.attr.id
|
|
58
|
+
self.params["user_id"] = self.user_id
|
|
58
59
|
self.params["branch"] = self.branch.name
|
|
59
60
|
self.params["branch_level"] = self.branch.hierarchy_level
|
|
60
61
|
self.params["at"] = at.to_string()
|
|
@@ -73,29 +74,46 @@ class AttributeUpdateValueQuery(AttributeQuery):
|
|
|
73
74
|
labels.append(GraphAttributeIPNetworkNode.get_default_label())
|
|
74
75
|
|
|
75
76
|
query = """
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
77
|
+
MATCH (a:Attribute { uuid: $attr_uuid })
|
|
78
|
+
MERGE (av:%(labels)s { %(props)s } )
|
|
79
|
+
WITH av, a
|
|
80
|
+
LIMIT 1
|
|
81
|
+
// ----------
|
|
82
|
+
// find the existing HAS_VALUE edge, if it exists, and set the to time and user_id
|
|
83
|
+
// ---------
|
|
84
|
+
OPTIONAL MATCH (a)-[existing_active_r:%(rel_label)s { branch: $branch, status: "active" }]->()
|
|
85
|
+
WHERE existing_active_r.to IS NULL
|
|
86
|
+
SET existing_active_r.to = $at, existing_active_r.to_user_id = $user_id
|
|
87
|
+
WITH av, a
|
|
88
|
+
LIMIT 1
|
|
89
|
+
// ----------
|
|
90
|
+
// create the new HAS_VALUE edge
|
|
91
|
+
// ---------
|
|
92
|
+
CREATE (a)-[r:%(rel_label)s { branch: $branch, branch_level: $branch_level, status: "active", from: $at, from_user_id: $user_id }]->(av)
|
|
93
|
+
// ----------
|
|
94
|
+
// update the Attribute node with the new timestamp and user id if we are on the default or global branch
|
|
95
|
+
// ---------
|
|
96
|
+
WITH a
|
|
97
|
+
WHERE $branch_level = 1
|
|
98
|
+
LIMIT 1
|
|
99
|
+
SET a.updated_at = $at, a.updated_by = $user_id
|
|
81
100
|
""" % {"rel_label": self.attr._rel_to_value_label, "labels": ":".join(labels), "props": ", ".join(prop_list)}
|
|
82
101
|
|
|
83
102
|
self.add_to_query(query)
|
|
84
|
-
self.return_labels = ["a", "av", "r"]
|
|
85
103
|
|
|
86
104
|
|
|
87
105
|
class AttributeUpdateFlagQuery(AttributeQuery):
|
|
88
106
|
name = "attribute_update_flag"
|
|
89
107
|
type: QueryType = QueryType.WRITE
|
|
90
|
-
|
|
91
|
-
raise_error_if_empty: bool =
|
|
108
|
+
insert_return: bool = False
|
|
109
|
+
raise_error_if_empty: bool = False
|
|
92
110
|
|
|
93
111
|
def __init__(
|
|
94
112
|
self,
|
|
95
113
|
flag_name: str,
|
|
96
114
|
**kwargs: Any,
|
|
97
115
|
) -> None:
|
|
98
|
-
SUPPORTED_FLAGS = ["
|
|
116
|
+
SUPPORTED_FLAGS = ["is_protected"]
|
|
99
117
|
|
|
100
118
|
if flag_name not in SUPPORTED_FLAGS:
|
|
101
119
|
raise ValueError(f"Only {SUPPORTED_FLAGS} are supported for now.")
|
|
@@ -108,6 +126,7 @@ class AttributeUpdateFlagQuery(AttributeQuery):
|
|
|
108
126
|
at = self.at or self.attr.at
|
|
109
127
|
|
|
110
128
|
self.params["attr_uuid"] = self.attr.id
|
|
129
|
+
self.params["user_id"] = self.user_id
|
|
111
130
|
self.params["branch"] = self.branch.name
|
|
112
131
|
self.params["branch_level"] = self.branch.hierarchy_level
|
|
113
132
|
self.params["at"] = at.to_string()
|
|
@@ -115,20 +134,37 @@ class AttributeUpdateFlagQuery(AttributeQuery):
|
|
|
115
134
|
self.params["flag_type"] = self.attr.get_kind()
|
|
116
135
|
|
|
117
136
|
query = """
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
137
|
+
MATCH (a:Attribute { uuid: $attr_uuid })
|
|
138
|
+
MERGE (flag:Boolean { value: $flag_value })
|
|
139
|
+
WITH flag, a
|
|
140
|
+
LIMIT 1
|
|
141
|
+
// ----------
|
|
142
|
+
// find the existing property edge, if it exists, and set the to time and user_id
|
|
143
|
+
// ---------
|
|
144
|
+
OPTIONAL MATCH (a)-[existing_active_r:%(flag_type)s { branch: $branch, status: "active" }]->()
|
|
145
|
+
WHERE existing_active_r.to IS NULL
|
|
146
|
+
SET existing_active_r.to = $at, existing_active_r.to_user_id = $user_id
|
|
147
|
+
// ----------
|
|
148
|
+
// create the new property edge
|
|
149
|
+
// ---------
|
|
150
|
+
WITH a, flag
|
|
151
|
+
CREATE (a)-[r:%(flag_type)s { branch: $branch, branch_level: $branch_level, status: "active", from: $at, from_user_id: $user_id }]->(flag)
|
|
152
|
+
// ----------
|
|
153
|
+
// update the Attribute node with the new timestamp and user id if we are on the default or global branch
|
|
154
|
+
// ---------
|
|
155
|
+
WITH a
|
|
156
|
+
WHERE $branch_level = 1
|
|
157
|
+
LIMIT 1
|
|
158
|
+
SET a.updated_at = $at, a.updated_by = $user_id
|
|
159
|
+
""" % {"flag_type": self.flag_name.upper()}
|
|
123
160
|
self.add_to_query(query)
|
|
124
|
-
self.return_labels = ["a", "flag", "r"]
|
|
125
161
|
|
|
126
162
|
|
|
127
163
|
class AttributeUpdateNodePropertyQuery(AttributeQuery):
|
|
128
164
|
name = "attribute_update_node_property"
|
|
129
165
|
type: QueryType = QueryType.WRITE
|
|
130
|
-
|
|
131
|
-
raise_error_if_empty: bool =
|
|
166
|
+
insert_return: bool = False
|
|
167
|
+
raise_error_if_empty: bool = False
|
|
132
168
|
|
|
133
169
|
def __init__(
|
|
134
170
|
self,
|
|
@@ -147,6 +183,7 @@ class AttributeUpdateNodePropertyQuery(AttributeQuery):
|
|
|
147
183
|
branch_filter, branch_params = self.branch.get_query_filter_path(at=at)
|
|
148
184
|
self.params.update(branch_params)
|
|
149
185
|
self.params["attr_uuid"] = self.attr.id
|
|
186
|
+
self.params["user_id"] = self.user_id
|
|
150
187
|
self.params["branch"] = self.branch.name
|
|
151
188
|
self.params["branch_level"] = self.branch.hierarchy_level
|
|
152
189
|
self.params["at"] = at.to_string()
|
|
@@ -176,13 +213,29 @@ class AttributeUpdateNodePropertyQuery(AttributeQuery):
|
|
|
176
213
|
self.add_to_query(node_query)
|
|
177
214
|
|
|
178
215
|
attr_query = """
|
|
179
|
-
|
|
180
|
-
|
|
216
|
+
MATCH (a:Attribute { uuid: $attr_uuid })
|
|
217
|
+
// ----------
|
|
218
|
+
// find the existing property edge, if it exists, and set the to time and user_id
|
|
219
|
+
// ---------
|
|
220
|
+
OPTIONAL MATCH (a)-[existing_active_r:%(rel_label)s { branch: $branch, status: "active" }]->()
|
|
221
|
+
WHERE existing_active_r.to IS NULL
|
|
222
|
+
SET existing_active_r.to = $at, existing_active_r.to_user_id = $user_id
|
|
223
|
+
// ----------
|
|
224
|
+
// create the new property edge
|
|
225
|
+
// ---------
|
|
226
|
+
WITH a, np
|
|
227
|
+
LIMIT 1
|
|
228
|
+
CREATE (a)-[r:%(rel_label)s { branch: $branch, branch_level: $branch_level, status: "active", from: $at, from_user_id: $user_id }]->(np)
|
|
229
|
+
// ----------
|
|
230
|
+
// update the Attribute node with the new timestamp and user id if we are on the default or global branch
|
|
231
|
+
// ---------
|
|
232
|
+
WITH a
|
|
233
|
+
WHERE $branch_level = 1
|
|
234
|
+
LIMIT 1
|
|
235
|
+
SET a.updated_at = $at, a.updated_by = $user_id
|
|
181
236
|
""" % {"rel_label": rel_label}
|
|
182
237
|
self.add_to_query(attr_query)
|
|
183
238
|
|
|
184
|
-
self.return_labels = ["a", "np", "r"]
|
|
185
|
-
|
|
186
239
|
|
|
187
240
|
class AttributeClearNodePropertyQuery(AttributeQuery):
|
|
188
241
|
name = "attribute_clear_node_property"
|
|
@@ -192,11 +245,9 @@ class AttributeClearNodePropertyQuery(AttributeQuery):
|
|
|
192
245
|
def __init__(
|
|
193
246
|
self,
|
|
194
247
|
prop_name: str,
|
|
195
|
-
prop_id: str | None = None,
|
|
196
248
|
**kwargs: Any,
|
|
197
249
|
):
|
|
198
250
|
self.prop_name = prop_name
|
|
199
|
-
self.prop_id = prop_id
|
|
200
251
|
|
|
201
252
|
super().__init__(**kwargs)
|
|
202
253
|
|
|
@@ -206,15 +257,14 @@ class AttributeClearNodePropertyQuery(AttributeQuery):
|
|
|
206
257
|
branch_filter, branch_params = self.branch.get_query_filter_path(at=at)
|
|
207
258
|
self.params.update(branch_params)
|
|
208
259
|
self.params["attr_uuid"] = self.attr.id
|
|
260
|
+
self.params["user_id"] = self.user_id
|
|
209
261
|
self.params["branch"] = self.branch.name
|
|
210
262
|
self.params["branch_level"] = self.branch.hierarchy_level
|
|
211
263
|
self.params["at"] = at.to_string()
|
|
212
|
-
self.params["prop_name"] = self.prop_name
|
|
213
|
-
self.params["prop_id"] = self.prop_id
|
|
214
264
|
|
|
215
265
|
rel_label = f"HAS_{self.prop_name.upper()}"
|
|
216
266
|
query = """
|
|
217
|
-
MATCH (a:Attribute { uuid: $attr_uuid })-[r:%(rel_label)s]->(np:Node
|
|
267
|
+
MATCH (a:Attribute { uuid: $attr_uuid })-[r:%(rel_label)s]->(np:Node)
|
|
218
268
|
WITH DISTINCT a, np
|
|
219
269
|
CALL (a, np) {
|
|
220
270
|
MATCH (a)-[r:%(rel_label)s]->(np)
|
|
@@ -228,42 +278,107 @@ WHERE property_edge.status = "active"
|
|
|
228
278
|
CALL (property_edge) {
|
|
229
279
|
WITH property_edge
|
|
230
280
|
WHERE property_edge.branch = $branch
|
|
231
|
-
SET property_edge.to = $at
|
|
281
|
+
SET property_edge.to = $at, property_edge.to_user_id = $user_id
|
|
232
282
|
}
|
|
233
283
|
CALL (a, np, property_edge) {
|
|
234
284
|
WITH property_edge
|
|
235
285
|
WHERE property_edge.branch_level < $branch_level
|
|
236
|
-
CREATE (a)-[r:%(rel_label)s { branch: $branch, branch_level: $branch_level, status: "deleted", from: $at }]->(np)
|
|
286
|
+
CREATE (a)-[r:%(rel_label)s { branch: $branch, branch_level: $branch_level, status: "deleted", from: $at, from_user_id: $user_id }]->(np)
|
|
287
|
+
}
|
|
288
|
+
CALL (a) {
|
|
289
|
+
WITH a
|
|
290
|
+
WHERE $branch_level = 1
|
|
291
|
+
LIMIT 1
|
|
292
|
+
SET a.updated_at = $at, a.updated_by = $user_id
|
|
237
293
|
}
|
|
238
294
|
""" % {"branch_filter": branch_filter, "rel_label": rel_label}
|
|
239
295
|
self.add_to_query(query)
|
|
240
296
|
|
|
241
297
|
|
|
242
|
-
class
|
|
243
|
-
name = "
|
|
244
|
-
type: QueryType = QueryType.
|
|
298
|
+
class AttributeDeleteQuery(AttributeQuery):
|
|
299
|
+
name = "attribute_delete"
|
|
300
|
+
type: QueryType = QueryType.WRITE
|
|
301
|
+
insert_return: bool = False
|
|
245
302
|
|
|
246
303
|
async def query_init(self, db: InfrahubDatabase, **kwargs: Any) -> None: # noqa: ARG002
|
|
247
304
|
self.params["attr_uuid"] = self.attr.id
|
|
248
|
-
self.params["
|
|
249
|
-
|
|
305
|
+
self.params["user_id"] = self.user_id
|
|
306
|
+
self.params["branch"] = self.branch.name
|
|
307
|
+
self.params["branch_level"] = self.branch.hierarchy_level
|
|
308
|
+
self.params["branched_from"] = self.branch.get_branched_from()
|
|
250
309
|
self.params["at"] = self.at.to_string()
|
|
251
310
|
|
|
252
|
-
|
|
253
|
-
self.params.update(
|
|
311
|
+
branch_filter, branch_params = self.branch.get_query_filter_path(at=self.at)
|
|
312
|
+
self.params.update(branch_params)
|
|
254
313
|
|
|
255
|
-
query =
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
314
|
+
query = """
|
|
315
|
+
MATCH (a:Attribute { uuid: $attr_uuid })
|
|
316
|
+
CALL (a) {
|
|
317
|
+
WITH a
|
|
318
|
+
WHERE $branch_level = 1
|
|
319
|
+
LIMIT 1
|
|
320
|
+
SET a.updated_at = $at, a.updated_by = $user_id
|
|
321
|
+
}
|
|
263
322
|
|
|
323
|
+
UNWIND [
|
|
324
|
+
["HAS_ATTRIBUTE", "in"],
|
|
325
|
+
["HAS_VALUE", "out"],
|
|
326
|
+
["IS_PROTECTED", "out"],
|
|
327
|
+
["HAS_SOURCE", "out"],
|
|
328
|
+
["HAS_OWNER", "out"]
|
|
329
|
+
] AS edge_details
|
|
330
|
+
WITH a, edge_details[0] AS property_type, edge_details[1] AS direction
|
|
331
|
+
CALL (a, property_type, direction) {
|
|
332
|
+
MATCH (a)-[r]-(attr_peer)
|
|
333
|
+
WHERE type(r) = property_type
|
|
334
|
+
AND (
|
|
335
|
+
(direction = "in" AND startNode(r) = attr_peer)
|
|
336
|
+
OR (direction = "out" AND startNode(r) = a)
|
|
337
|
+
)
|
|
338
|
+
AND %(branch_filter)s
|
|
339
|
+
RETURN r AS property_edge, attr_peer
|
|
340
|
+
ORDER BY r.branch_level DESC, r.from DESC, r.status ASC
|
|
341
|
+
LIMIT 1
|
|
342
|
+
}
|
|
343
|
+
CALL (property_edge) {
|
|
344
|
+
WITH property_edge
|
|
345
|
+
WHERE property_edge.status = "active"
|
|
346
|
+
AND property_edge.branch = $branch
|
|
347
|
+
AND property_edge.to IS NULL
|
|
348
|
+
SET property_edge.to = $at, property_edge.to_user_id = $user_id
|
|
349
|
+
}
|
|
350
|
+
WITH a, property_edge, property_type, attr_peer, direction
|
|
351
|
+
CALL (a, property_type, attr_peer, direction) {
|
|
352
|
+
WITH direction
|
|
353
|
+
WHERE direction = "out"
|
|
354
|
+
CREATE (a)
|
|
355
|
+
-[r:$(property_type) { branch: $branch, branch_level: $branch_level, status: "deleted", from: $at, from_user_id: $user_id }]
|
|
356
|
+
->(attr_peer)
|
|
357
|
+
}
|
|
358
|
+
CALL (a, property_type, attr_peer, direction) {
|
|
359
|
+
WITH direction
|
|
360
|
+
WHERE direction = "in"
|
|
361
|
+
CREATE (a)
|
|
362
|
+
<-[r:$(property_type) { branch: $branch, branch_level: $branch_level, status: "deleted", from: $at, from_user_id: $user_id }]
|
|
363
|
+
-(attr_peer)
|
|
364
|
+
}
|
|
365
|
+
WITH CASE
|
|
366
|
+
WHEN property_type = "HAS_VALUE" THEN attr_peer.value
|
|
367
|
+
ELSE NULL
|
|
368
|
+
END AS property_value
|
|
369
|
+
WITH property_value
|
|
370
|
+
RETURN property_value
|
|
371
|
+
ORDER BY property_value ASC
|
|
372
|
+
LIMIT 1
|
|
373
|
+
""" % {"branch_filter": branch_filter}
|
|
264
374
|
self.add_to_query(query)
|
|
375
|
+
self.return_labels = ["property_value"]
|
|
265
376
|
|
|
266
|
-
|
|
377
|
+
def get_previous_property_value(self) -> Any:
|
|
378
|
+
result = self.get_result()
|
|
379
|
+
if result:
|
|
380
|
+
return result.get(label="property_value")
|
|
381
|
+
return None
|
|
267
382
|
|
|
268
383
|
|
|
269
384
|
async def default_attribute_query_filter(
|