infrahub-server 1.6.2__py3-none-any.whl → 1.7.0__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/exceptions.py +2 -2
- infrahub/api/schema.py +3 -1
- infrahub/artifacts/tasks.py +1 -0
- infrahub/auth.py +2 -2
- infrahub/cli/db.py +54 -28
- 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 +12 -9
- 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/branch_differ.py +1 -1
- infrahub/core/diff/conflict_transferer.py +1 -1
- infrahub/core/diff/data_check_synchronizer.py +4 -3
- infrahub/core/diff/enricher/cardinality_one.py +2 -2
- infrahub/core/diff/enricher/hierarchy.py +1 -1
- infrahub/core/diff/enricher/labels.py +1 -1
- infrahub/core/diff/merger/merger.py +28 -2
- 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/diff/repository/repository.py +3 -1
- infrahub/core/graph/__init__.py +1 -1
- infrahub/core/graph/constraints.py +3 -3
- infrahub/core/graph/schema.py +2 -12
- infrahub/core/ipam/reconciler.py +8 -6
- infrahub/core/ipam/utilization.py +8 -15
- infrahub/core/manager.py +133 -152
- infrahub/core/merge.py +1 -1
- 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/m012_convert_account_generic.py +12 -12
- infrahub/core/migrations/graph/m013_convert_git_password_credential.py +7 -12
- 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/m041_deleted_dup_edges.py +1 -1
- infrahub/core/migrations/graph/m049_remove_is_visible_relationship.py +53 -0
- infrahub/core/migrations/graph/m050_backfill_vertex_metadata.py +168 -0
- infrahub/core/migrations/query/__init__.py +2 -2
- 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/query/schema_attribute_update.py +1 -1
- infrahub/core/migrations/schema/attribute_kind_update.py +26 -6
- infrahub/core/migrations/schema/attribute_name_update.py +1 -1
- infrahub/core/migrations/schema/attribute_supports_profile.py +5 -3
- infrahub/core/migrations/schema/models.py +3 -0
- infrahub/core/migrations/schema/node_attribute_add.py +5 -2
- infrahub/core/migrations/schema/node_attribute_remove.py +1 -1
- infrahub/core/migrations/schema/node_kind_update.py +1 -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 +157 -58
- infrahub/core/node/base.py +9 -5
- infrahub/core/node/create.py +7 -3
- infrahub/core/node/delete_validator.py +1 -1
- infrahub/core/node/standard.py +100 -14
- infrahub/core/order.py +30 -0
- infrahub/core/property.py +0 -1
- infrahub/core/protocols.py +1 -0
- infrahub/core/protocols_base.py +10 -2
- infrahub/core/query/__init__.py +11 -6
- infrahub/core/query/attribute.py +164 -49
- infrahub/core/query/branch.py +58 -70
- infrahub/core/query/delete.py +1 -1
- infrahub/core/query/diff.py +7 -7
- infrahub/core/query/ipam.py +104 -43
- infrahub/core/query/node.py +1072 -281
- infrahub/core/query/relationship.py +531 -325
- infrahub/core/query/resource_manager.py +107 -18
- infrahub/core/query/standard_node.py +25 -5
- infrahub/core/query/utils.py +2 -4
- infrahub/core/relationship/constraints/count.py +1 -1
- infrahub/core/relationship/constraints/peer_kind.py +1 -1
- infrahub/core/relationship/constraints/peer_parent.py +1 -1
- infrahub/core/relationship/constraints/peer_relatives.py +1 -1
- infrahub/core/relationship/constraints/profiles_kind.py +1 -1
- infrahub/core/relationship/constraints/profiles_removal.py +168 -0
- infrahub/core/relationship/model.py +293 -139
- infrahub/core/schema/attribute_parameters.py +28 -1
- infrahub/core/schema/attribute_schema.py +11 -17
- infrahub/core/schema/basenode_schema.py +3 -0
- infrahub/core/schema/definitions/core/__init__.py +8 -2
- infrahub/core/schema/definitions/core/account.py +10 -10
- infrahub/core/schema/definitions/core/artifact.py +14 -8
- infrahub/core/schema/definitions/core/check.py +10 -4
- infrahub/core/schema/definitions/core/generator.py +26 -6
- infrahub/core/schema/definitions/core/graphql_query.py +1 -1
- infrahub/core/schema/definitions/core/group.py +9 -2
- infrahub/core/schema/definitions/core/ipam.py +80 -10
- infrahub/core/schema/definitions/core/menu.py +41 -7
- infrahub/core/schema/definitions/core/permission.py +16 -2
- infrahub/core/schema/definitions/core/profile.py +16 -2
- infrahub/core/schema/definitions/core/propose_change.py +24 -4
- infrahub/core/schema/definitions/core/propose_change_comment.py +23 -11
- infrahub/core/schema/definitions/core/propose_change_validator.py +50 -21
- infrahub/core/schema/definitions/core/repository.py +10 -0
- infrahub/core/schema/definitions/core/resource_pool.py +8 -1
- infrahub/core/schema/definitions/core/template.py +19 -2
- infrahub/core/schema/definitions/core/transform.py +11 -5
- infrahub/core/schema/definitions/core/webhook.py +27 -9
- infrahub/core/schema/manager.py +63 -43
- infrahub/core/schema/relationship_schema.py +6 -2
- infrahub/core/schema/schema_branch.py +115 -11
- infrahub/core/task/task.py +4 -2
- infrahub/core/utils.py +3 -25
- infrahub/core/validators/aggregated_checker.py +1 -1
- infrahub/core/validators/attribute/choices.py +1 -1
- infrahub/core/validators/attribute/enum.py +1 -1
- infrahub/core/validators/attribute/kind.py +6 -3
- infrahub/core/validators/attribute/length.py +1 -1
- infrahub/core/validators/attribute/min_max.py +1 -1
- infrahub/core/validators/attribute/number_pool.py +1 -1
- infrahub/core/validators/attribute/optional.py +1 -1
- infrahub/core/validators/attribute/regex.py +1 -1
- infrahub/core/validators/determiner.py +3 -3
- infrahub/core/validators/node/attribute.py +1 -1
- infrahub/core/validators/node/relationship.py +1 -1
- infrahub/core/validators/relationship/peer.py +1 -1
- infrahub/database/__init__.py +4 -4
- 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/git/utils.py +1 -1
- infrahub/graphql/app.py +2 -2
- infrahub/graphql/constants.py +3 -0
- infrahub/graphql/context.py +1 -1
- infrahub/graphql/field_extractor.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 +175 -21
- infrahub/graphql/metadata.py +91 -0
- infrahub/graphql/mutations/account.py +6 -6
- 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/order.py +14 -0
- infrahub/graphql/queries/branch.py +62 -6
- infrahub/graphql/queries/diff/tree.py +5 -5
- infrahub/graphql/queries/resource_manager.py +25 -24
- 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 +59 -14
- 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 +12 -7
- infrahub/hfid/tasks.py +12 -3
- infrahub/lock.py +7 -0
- infrahub/menu/repository.py +1 -1
- infrahub/patch/queries/base.py +1 -1
- infrahub/pools/number.py +1 -8
- 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 +154 -13
- infrahub/profiles/queries/get_profile_data.py +143 -31
- infrahub/profiles/tasks.py +79 -27
- infrahub/profiles/triggers.py +22 -0
- infrahub/proposed_change/action_checker.py +1 -1
- infrahub/proposed_change/tasks.py +4 -1
- infrahub/services/__init__.py +1 -1
- infrahub/services/adapters/cache/nats.py +1 -1
- infrahub/services/adapters/cache/redis.py +7 -0
- 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/gather.py +1 -1
- infrahub/webhook/models.py +1 -1
- infrahub/webhook/tasks.py +23 -7
- infrahub/workers/dependencies.py +9 -3
- infrahub/workers/infrahub_async.py +13 -4
- infrahub/workflows/catalogue.py +19 -0
- infrahub_sdk/analyzer.py +2 -2
- infrahub_sdk/branch.py +12 -39
- infrahub_sdk/checks.py +4 -4
- infrahub_sdk/client.py +36 -0
- infrahub_sdk/ctl/cli_commands.py +2 -1
- infrahub_sdk/ctl/graphql.py +15 -4
- infrahub_sdk/ctl/utils.py +2 -2
- infrahub_sdk/enums.py +6 -0
- infrahub_sdk/graphql/renderers.py +21 -0
- infrahub_sdk/graphql/utils.py +85 -0
- infrahub_sdk/node/attribute.py +12 -2
- infrahub_sdk/node/constants.py +12 -0
- infrahub_sdk/node/metadata.py +69 -0
- infrahub_sdk/node/node.py +65 -14
- infrahub_sdk/node/property.py +3 -0
- infrahub_sdk/node/related_node.py +37 -5
- infrahub_sdk/node/relationship.py +18 -1
- infrahub_sdk/operation.py +2 -2
- infrahub_sdk/schema/repository.py +1 -2
- infrahub_sdk/transforms.py +2 -2
- infrahub_sdk/types.py +18 -2
- {infrahub_server-1.6.2.dist-info → infrahub_server-1.7.0.dist-info}/METADATA +17 -16
- {infrahub_server-1.6.2.dist-info → infrahub_server-1.7.0.dist-info}/RECORD +252 -231
- infrahub_testcontainers/container.py +3 -3
- infrahub_testcontainers/docker-compose-cluster.test.yml +7 -7
- infrahub_testcontainers/docker-compose.test.yml +13 -5
- infrahub_testcontainers/models.py +3 -3
- infrahub_testcontainers/performance_test.py +1 -1
- infrahub/graphql/models.py +0 -6
- {infrahub_server-1.6.2.dist-info → infrahub_server-1.7.0.dist-info}/WHEEL +0 -0
- {infrahub_server-1.6.2.dist-info → infrahub_server-1.7.0.dist-info}/entry_points.txt +0 -0
- {infrahub_server-1.6.2.dist-info → infrahub_server-1.7.0.dist-info}/licenses/LICENSE.txt +0 -0
|
@@ -1,24 +1,74 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from dataclasses import dataclass
|
|
3
4
|
from typing import TYPE_CHECKING, Any, Generator
|
|
4
5
|
|
|
5
|
-
from pydantic import BaseModel, ConfigDict
|
|
6
|
-
|
|
7
6
|
from infrahub.core import registry
|
|
8
7
|
from infrahub.core.constants import InfrahubKind, RelationshipStatus
|
|
9
|
-
from infrahub.core.query import Query, QueryType
|
|
8
|
+
from infrahub.core.query import Query, QueryResult, QueryType
|
|
10
9
|
|
|
11
10
|
if TYPE_CHECKING:
|
|
12
11
|
from infrahub.core.protocols import CoreNumberPool
|
|
13
12
|
from infrahub.database import InfrahubDatabase
|
|
14
13
|
|
|
15
14
|
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
@dataclass(frozen=True)
|
|
16
|
+
class NumberPoolIdentifierData:
|
|
17
|
+
"""Result containing a pool reservation value and its identifier."""
|
|
18
18
|
|
|
19
19
|
value: int
|
|
20
20
|
identifier: str
|
|
21
21
|
|
|
22
|
+
@classmethod
|
|
23
|
+
def from_db(cls, result: QueryResult) -> NumberPoolIdentifierData:
|
|
24
|
+
"""Convert raw QueryResult to typed dataclass."""
|
|
25
|
+
return cls(
|
|
26
|
+
value=result.get_as_type("value", return_type=int),
|
|
27
|
+
identifier=result.get_as_type("identifier", return_type=str),
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass(frozen=True)
|
|
32
|
+
class PoolIdentifierResult:
|
|
33
|
+
"""Result from pool identifier queries containing allocation and identifier data."""
|
|
34
|
+
|
|
35
|
+
allocated_uuid: str
|
|
36
|
+
"""UUID of the allocated resource (address or prefix)."""
|
|
37
|
+
|
|
38
|
+
identifier: str
|
|
39
|
+
"""Identifier used for the reservation."""
|
|
40
|
+
|
|
41
|
+
@classmethod
|
|
42
|
+
def from_db(cls, result: QueryResult) -> PoolIdentifierResult:
|
|
43
|
+
"""Convert raw QueryResult to typed dataclass."""
|
|
44
|
+
return cls(
|
|
45
|
+
allocated_uuid=result.get_as_type("allocated_uuid", str),
|
|
46
|
+
identifier=result.get_as_type("identifier", str),
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@dataclass(frozen=True)
|
|
51
|
+
class NumberPoolAllocatedResult:
|
|
52
|
+
"""Result from NumberPoolGetAllocated containing allocated number info."""
|
|
53
|
+
|
|
54
|
+
id: str
|
|
55
|
+
"""UUID of the node with the allocated number."""
|
|
56
|
+
|
|
57
|
+
branch: str
|
|
58
|
+
"""Branch where the allocation exists."""
|
|
59
|
+
|
|
60
|
+
value: int
|
|
61
|
+
"""The allocated number value."""
|
|
62
|
+
|
|
63
|
+
@classmethod
|
|
64
|
+
def from_db(cls, result: QueryResult) -> NumberPoolAllocatedResult:
|
|
65
|
+
"""Convert raw QueryResult to typed dataclass."""
|
|
66
|
+
return cls(
|
|
67
|
+
id=result.get_as_type("id", str),
|
|
68
|
+
branch=result.get_as_type("branch", str),
|
|
69
|
+
value=result.get_as_type("value", int),
|
|
70
|
+
)
|
|
71
|
+
|
|
22
72
|
|
|
23
73
|
class IPAddressPoolGetIdentifiers(Query):
|
|
24
74
|
name = "ipaddresspool_get_identifiers"
|
|
@@ -44,7 +94,15 @@ class IPAddressPoolGetIdentifiers(Query):
|
|
|
44
94
|
WHERE allocated.uuid in $addresses
|
|
45
95
|
""" % {"ipaddress_pool": InfrahubKind.IPADDRESSPOOL}
|
|
46
96
|
self.add_to_query(query)
|
|
47
|
-
self.return_labels = ["allocated", "reservation"]
|
|
97
|
+
self.return_labels = ["allocated.uuid AS allocated_uuid", "reservation.identifier AS identifier"]
|
|
98
|
+
|
|
99
|
+
def get_data(self) -> list[PoolIdentifierResult]:
|
|
100
|
+
"""Return results as typed dataclass instances.
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
List of PoolIdentifierResult containing allocation and identifier data.
|
|
104
|
+
"""
|
|
105
|
+
return [PoolIdentifierResult.from_db(result) for result in self.get_results()]
|
|
48
106
|
|
|
49
107
|
|
|
50
108
|
class IPAddressPoolGetReserved(Query):
|
|
@@ -159,6 +217,14 @@ class NumberPoolGetAllocated(Query):
|
|
|
159
217
|
self.return_labels = ["n.uuid as id", "hv.branch as branch", "av.value as value"]
|
|
160
218
|
self.order_by = ["av.value"]
|
|
161
219
|
|
|
220
|
+
def get_data(self) -> list[NumberPoolAllocatedResult]:
|
|
221
|
+
"""Return results as typed dataclass instances.
|
|
222
|
+
|
|
223
|
+
Returns:
|
|
224
|
+
List of NumberPoolAllocatedResult containing allocated number info.
|
|
225
|
+
"""
|
|
226
|
+
return [NumberPoolAllocatedResult.from_db(result) for result in self.get_results()]
|
|
227
|
+
|
|
162
228
|
|
|
163
229
|
class NumberPoolGetReserved(Query):
|
|
164
230
|
name = "numberpool_get_reserved"
|
|
@@ -205,17 +271,31 @@ class NumberPoolGetReserved(Query):
|
|
|
205
271
|
self.return_labels = ["reservation.value AS value", "r.identifier AS identifier"]
|
|
206
272
|
|
|
207
273
|
def get_reservation(self) -> int | None:
|
|
274
|
+
"""Return the reserved value for a single identifier.
|
|
275
|
+
|
|
276
|
+
Returns:
|
|
277
|
+
The reserved integer value, or None if no reservation exists.
|
|
278
|
+
"""
|
|
208
279
|
result = self.get_result()
|
|
209
280
|
if result:
|
|
210
281
|
return result.get_as_optional_type("value", return_type=int)
|
|
211
282
|
return None
|
|
212
283
|
|
|
284
|
+
def get_data(self) -> list[NumberPoolIdentifierData]:
|
|
285
|
+
"""Return all reservations as typed dataclass instances.
|
|
286
|
+
|
|
287
|
+
Returns:
|
|
288
|
+
List of NumberPoolIdentifierData containing value and identifier.
|
|
289
|
+
"""
|
|
290
|
+
return [NumberPoolIdentifierData.from_db(result) for result in self.get_results()]
|
|
291
|
+
|
|
213
292
|
def get_reservations(self) -> Generator[NumberPoolIdentifierData]:
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
293
|
+
"""Yield reservations as typed dataclass instances.
|
|
294
|
+
|
|
295
|
+
Yields:
|
|
296
|
+
NumberPoolIdentifierData for each reservation.
|
|
297
|
+
"""
|
|
298
|
+
yield from self.get_data()
|
|
219
299
|
|
|
220
300
|
|
|
221
301
|
class PoolChangeReserved(Query):
|
|
@@ -282,7 +362,6 @@ This will be especially important as we want to support upsert with NumberPool
|
|
|
282
362
|
class NumberPoolGetUsed(Query):
|
|
283
363
|
name = "number_pool_get_used"
|
|
284
364
|
type = QueryType.READ
|
|
285
|
-
return_model = NumberPoolIdentifierData
|
|
286
365
|
|
|
287
366
|
def __init__(
|
|
288
367
|
self,
|
|
@@ -331,11 +410,13 @@ class NumberPoolGetUsed(Query):
|
|
|
331
410
|
self.order_by = ["value"]
|
|
332
411
|
|
|
333
412
|
def iter_results(self) -> Generator[NumberPoolIdentifierData]:
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
413
|
+
"""Yield used pool values as typed dataclass instances.
|
|
414
|
+
|
|
415
|
+
Yields:
|
|
416
|
+
NumberPoolIdentifierData for each used value in the pool.
|
|
417
|
+
"""
|
|
418
|
+
for result in self.get_results():
|
|
419
|
+
yield NumberPoolIdentifierData.from_db(result)
|
|
339
420
|
|
|
340
421
|
|
|
341
422
|
class NumberPoolSetReserved(Query):
|
|
@@ -405,7 +486,15 @@ class PrefixPoolGetIdentifiers(Query):
|
|
|
405
486
|
WHERE allocated.uuid in $prefixes
|
|
406
487
|
""" % {"ipaddress_pool": InfrahubKind.IPPREFIXPOOL}
|
|
407
488
|
self.add_to_query(query)
|
|
408
|
-
self.return_labels = ["allocated", "reservation"]
|
|
489
|
+
self.return_labels = ["allocated.uuid AS allocated_uuid", "reservation.identifier AS identifier"]
|
|
490
|
+
|
|
491
|
+
def get_data(self) -> list[PoolIdentifierResult]:
|
|
492
|
+
"""Return results as typed dataclass instances.
|
|
493
|
+
|
|
494
|
+
Returns:
|
|
495
|
+
List of PoolIdentifierResult containing allocation and identifier data.
|
|
496
|
+
"""
|
|
497
|
+
return [PoolIdentifierResult.from_db(result) for result in self.get_results()]
|
|
409
498
|
|
|
410
499
|
|
|
411
500
|
class PrefixPoolGetReserved(Query):
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import TYPE_CHECKING, Any
|
|
3
|
+
from typing import TYPE_CHECKING, Any, assert_never
|
|
4
4
|
|
|
5
|
+
from infrahub.constants.enums import OrderByField
|
|
5
6
|
from infrahub.core.query import Query, QueryType
|
|
6
7
|
from infrahub.exceptions import InitializationError
|
|
7
8
|
|
|
8
9
|
if TYPE_CHECKING:
|
|
9
10
|
from uuid import UUID
|
|
10
11
|
|
|
11
|
-
from infrahub.core.node.standard import StandardNode
|
|
12
|
+
from infrahub.core.node.standard import StandardNode, StandardNodeOrdering
|
|
12
13
|
from infrahub.database import InfrahubDatabase
|
|
13
14
|
|
|
14
15
|
|
|
@@ -135,11 +136,19 @@ class StandardNodeGetListQuery(Query):
|
|
|
135
136
|
raw_filter: str | None = None
|
|
136
137
|
|
|
137
138
|
def __init__(
|
|
138
|
-
self,
|
|
139
|
+
self,
|
|
140
|
+
node_class: StandardNode,
|
|
141
|
+
node_ordering: StandardNodeOrdering,
|
|
142
|
+
ids: list[str] | None = None,
|
|
143
|
+
node_name: str | None = None,
|
|
144
|
+
partial_match: bool = False,
|
|
145
|
+
**kwargs: Any,
|
|
139
146
|
) -> None:
|
|
140
147
|
self.ids = ids
|
|
141
148
|
self.node_name = node_name
|
|
142
149
|
self.node_class = node_class
|
|
150
|
+
self.partial_match = partial_match
|
|
151
|
+
self.node_ordering = node_ordering
|
|
143
152
|
|
|
144
153
|
super().__init__(**kwargs)
|
|
145
154
|
|
|
@@ -148,9 +157,12 @@ class StandardNodeGetListQuery(Query):
|
|
|
148
157
|
if self.ids:
|
|
149
158
|
filters.append("n.uuid in $ids_value")
|
|
150
159
|
self.params["ids_value"] = self.ids
|
|
151
|
-
if self.node_name:
|
|
160
|
+
if self.node_name and not self.partial_match:
|
|
152
161
|
filters.append("n.name = $name")
|
|
153
162
|
self.params["name"] = self.node_name
|
|
163
|
+
if self.node_name and self.partial_match:
|
|
164
|
+
filters.append("toLower(toString(n.name)) CONTAINS toLower(toString($name))")
|
|
165
|
+
self.params["name"] = self.node_name
|
|
154
166
|
if self.raw_filter:
|
|
155
167
|
filters.append(self.raw_filter)
|
|
156
168
|
|
|
@@ -169,4 +181,12 @@ class StandardNodeGetListQuery(Query):
|
|
|
169
181
|
self.add_to_query(query)
|
|
170
182
|
|
|
171
183
|
self.return_labels = ["n"]
|
|
172
|
-
self.order_by
|
|
184
|
+
match self.node_ordering.order_by:
|
|
185
|
+
case OrderByField.ID:
|
|
186
|
+
self.order_by = [f"{db.get_id_function_name()}(n)"]
|
|
187
|
+
case OrderByField.CREATED_AT:
|
|
188
|
+
self.order_by = [f"n.created_at {self.node_ordering.direction.value}"]
|
|
189
|
+
case OrderByField.UPDATED_AT:
|
|
190
|
+
self.order_by = [f"n.updated_at {self.node_ordering.direction.value}"]
|
|
191
|
+
case _:
|
|
192
|
+
assert_never(self.node_ordering.order_by)
|
infrahub/core/query/utils.py
CHANGED
|
@@ -5,16 +5,14 @@ from typing import TYPE_CHECKING
|
|
|
5
5
|
from infrahub.core.schema import NodeSchema, ProfileSchema, TemplateSchema
|
|
6
6
|
|
|
7
7
|
if TYPE_CHECKING:
|
|
8
|
-
from neo4j.graph import Node as Neo4jNode
|
|
9
|
-
|
|
10
8
|
from infrahub.core.branch import Branch
|
|
11
9
|
from infrahub.database import InfrahubDatabase
|
|
12
10
|
|
|
13
11
|
|
|
14
12
|
def find_node_schema(
|
|
15
|
-
db: InfrahubDatabase,
|
|
13
|
+
db: InfrahubDatabase, branch: Branch | str, labels: list[str], duplicate: bool = False
|
|
16
14
|
) -> NodeSchema | ProfileSchema | TemplateSchema | None:
|
|
17
|
-
for label in
|
|
15
|
+
for label in labels:
|
|
18
16
|
if db.schema.has(name=label, branch=branch):
|
|
19
17
|
schema = db.schema.get(name=label, branch=branch, duplicate=duplicate)
|
|
20
18
|
if isinstance(schema, NodeSchema | ProfileSchema | TemplateSchema):
|
|
@@ -22,7 +22,7 @@ class NodeToValidate:
|
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
class RelationshipCountConstraint(RelationshipManagerConstraintInterface):
|
|
25
|
-
def __init__(self, db: InfrahubDatabase, branch: Branch | None = None):
|
|
25
|
+
def __init__(self, db: InfrahubDatabase, branch: Branch | None = None) -> None:
|
|
26
26
|
self.db = db
|
|
27
27
|
self.branch = branch
|
|
28
28
|
|
|
@@ -23,7 +23,7 @@ class NodeToValidate:
|
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
class RelationshipPeerKindConstraint(RelationshipManagerConstraintInterface):
|
|
26
|
-
def __init__(self, db: InfrahubDatabase, branch: Branch | None = None):
|
|
26
|
+
def __init__(self, db: InfrahubDatabase, branch: Branch | None = None) -> None:
|
|
27
27
|
self.db = db
|
|
28
28
|
self.branch = branch
|
|
29
29
|
|
|
@@ -16,7 +16,7 @@ if TYPE_CHECKING:
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
class RelationshipPeerParentConstraint(RelationshipManagerConstraintInterface):
|
|
19
|
-
def __init__(self, db: InfrahubDatabase, branch: Branch | None = None):
|
|
19
|
+
def __init__(self, db: InfrahubDatabase, branch: Branch | None = None) -> None:
|
|
20
20
|
self.db = db
|
|
21
21
|
self.branch = branch
|
|
22
22
|
|
|
@@ -25,7 +25,7 @@ class NodeToValidate:
|
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
class RelationshipPeerRelativesConstraint(RelationshipManagerConstraintInterface):
|
|
28
|
-
def __init__(self, db: InfrahubDatabase, branch: Branch | None = None):
|
|
28
|
+
def __init__(self, db: InfrahubDatabase, branch: Branch | None = None) -> None:
|
|
29
29
|
self.db = db
|
|
30
30
|
self.branch = branch
|
|
31
31
|
|
|
@@ -18,7 +18,7 @@ if TYPE_CHECKING:
|
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
class RelationshipProfilesKindConstraint(RelationshipManagerConstraintInterface):
|
|
21
|
-
def __init__(self, db: InfrahubDatabase, branch: Branch | None = None):
|
|
21
|
+
def __init__(self, db: InfrahubDatabase, branch: Branch | None = None) -> None:
|
|
22
22
|
self.db = db
|
|
23
23
|
self.branch = branch
|
|
24
24
|
self.schema_branch = registry.schema.get_schema_branch(branch.name if branch else registry.default_branch)
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from infrahub.core import registry
|
|
6
|
+
from infrahub.core.constants import MetadataOptions
|
|
7
|
+
from infrahub.core.manager import NodeManager
|
|
8
|
+
from infrahub.core.schema import NodeSchema, ProfileSchema
|
|
9
|
+
from infrahub.exceptions import ValidationError
|
|
10
|
+
|
|
11
|
+
from .interface import RelationshipManagerConstraintInterface
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from infrahub.core.branch import Branch
|
|
15
|
+
from infrahub.core.node import Node
|
|
16
|
+
from infrahub.core.relationship.model import Relationship, RelationshipManager
|
|
17
|
+
from infrahub.core.schema import MainSchemaTypes
|
|
18
|
+
from infrahub.database import InfrahubDatabase
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class RelationshipProfileRemovalConstraint(RelationshipManagerConstraintInterface):
|
|
22
|
+
"""Constraint that validates removing profiles from a node doesn't violate required relationships.
|
|
23
|
+
|
|
24
|
+
This runs in two cases:
|
|
25
|
+
1. When a node's `profiles` relationship is changed.
|
|
26
|
+
2. When a profile's `related_nodes` relationship is changed.
|
|
27
|
+
|
|
28
|
+
In both cases, it will perform checks only if peers are being removed from the relationship being changed.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __init__(self, db: InfrahubDatabase, branch: Branch | None = None) -> None:
|
|
32
|
+
self.db = db
|
|
33
|
+
self.branch = branch
|
|
34
|
+
self.schema_branch = registry.schema.get_schema_branch(branch.name if branch else registry.default_branch)
|
|
35
|
+
|
|
36
|
+
def _get_required_attributes_names(self, schema: NodeSchema) -> set[str]:
|
|
37
|
+
attr_names: set[str] = set()
|
|
38
|
+
for attr_schema in schema.attributes:
|
|
39
|
+
if attr_schema.support_profiles and not attr_schema.optional:
|
|
40
|
+
attr_names.add(attr_schema.name)
|
|
41
|
+
return attr_names
|
|
42
|
+
|
|
43
|
+
def _get_required_relationship_names(self, schema: NodeSchema) -> set[str]:
|
|
44
|
+
rel_names: set[str] = set()
|
|
45
|
+
for rel_schema in schema.relationships:
|
|
46
|
+
if rel_schema.support_profiles and not rel_schema.optional:
|
|
47
|
+
rel_names.add(rel_schema.name)
|
|
48
|
+
return rel_names
|
|
49
|
+
|
|
50
|
+
async def _validate_profile_removal(
|
|
51
|
+
self, node: Node, profile_id: str, required_attr_names: set[str], required_rel_names: set[str]
|
|
52
|
+
) -> None:
|
|
53
|
+
for attr_name in required_attr_names:
|
|
54
|
+
attr = node.get_attribute(name=attr_name)
|
|
55
|
+
if attr.is_from_profile:
|
|
56
|
+
source = await attr.get_source(db=self.db)
|
|
57
|
+
if source and source.id == profile_id:
|
|
58
|
+
node_display_label = await node.get_display_label(db=self.db)
|
|
59
|
+
node_reference = f"node '{node_display_label}' (ID: {node.get_id()})"
|
|
60
|
+
raise ValidationError(
|
|
61
|
+
f"Cannot remove profile '{profile_id}' because {node_reference} "
|
|
62
|
+
f"inherits required attribute '{attr_name}' from this profile."
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
for rel_name in required_rel_names:
|
|
66
|
+
rel_manager = node.get_relationship(name=rel_name)
|
|
67
|
+
|
|
68
|
+
relationships: list[Relationship] = await rel_manager.get_relationships(db=self.db)
|
|
69
|
+
for rel in relationships:
|
|
70
|
+
if rel.is_from_profile:
|
|
71
|
+
source = await rel.get_source(db=self.db)
|
|
72
|
+
if source and source.id == profile_id:
|
|
73
|
+
node_display_label = await node.get_display_label(db=self.db)
|
|
74
|
+
node_reference = f"node '{node_display_label}' (ID: {node.get_id()})"
|
|
75
|
+
raise ValidationError(
|
|
76
|
+
f"Cannot remove profile '{profile_id}' because {node_reference} "
|
|
77
|
+
f"inherits required relationship '{rel_name}' from this profile."
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
async def _check_node_profiles_removal(
|
|
81
|
+
self, relm: RelationshipManager, node_schema: NodeSchema, node: Node
|
|
82
|
+
) -> None:
|
|
83
|
+
required_attr_names = self._get_required_attributes_names(schema=node_schema)
|
|
84
|
+
required_rel_names = self._get_required_relationship_names(schema=node_schema)
|
|
85
|
+
if not required_attr_names and not required_rel_names:
|
|
86
|
+
return
|
|
87
|
+
|
|
88
|
+
relm_update_details = await relm.fetch_relationship_ids(db=self.db, force_refresh=False)
|
|
89
|
+
if not relm_update_details.peer_ids_present_database_only:
|
|
90
|
+
return
|
|
91
|
+
|
|
92
|
+
if required_attr_names:
|
|
93
|
+
# Required to get source for attributes
|
|
94
|
+
node = await NodeManager.get_one(
|
|
95
|
+
db=self.db, branch=self.branch, id=node.get_id(), include_metadata=MetadataOptions.SOURCE
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
for profile_id in relm_update_details.peer_ids_present_database_only:
|
|
99
|
+
await self._validate_profile_removal(
|
|
100
|
+
node=node,
|
|
101
|
+
profile_id=profile_id,
|
|
102
|
+
required_attr_names=required_attr_names,
|
|
103
|
+
required_rel_names=required_rel_names,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
async def _check_profile_related_nodes_removal(
|
|
107
|
+
self, relm: RelationshipManager, profile_schema: ProfileSchema, profile: Node
|
|
108
|
+
) -> None:
|
|
109
|
+
relm_update_details = await relm.fetch_relationship_ids(db=self.db, force_refresh=False)
|
|
110
|
+
if not relm_update_details.peer_ids_present_database_only:
|
|
111
|
+
return
|
|
112
|
+
|
|
113
|
+
target_kind = profile_schema.get_relationship(name="related_nodes").peer
|
|
114
|
+
target_schema = self.schema_branch.get_node(name=target_kind, duplicate=False)
|
|
115
|
+
|
|
116
|
+
required_attr_names = self._get_required_attributes_names(schema=target_schema)
|
|
117
|
+
required_rel_names = self._get_required_relationship_names(schema=target_schema)
|
|
118
|
+
if not required_attr_names and not required_rel_names:
|
|
119
|
+
return
|
|
120
|
+
|
|
121
|
+
nodes = await NodeManager.get_many(
|
|
122
|
+
db=self.db,
|
|
123
|
+
branch=self.branch,
|
|
124
|
+
ids=relm_update_details.peer_ids_present_database_only,
|
|
125
|
+
include_metadata=MetadataOptions.SOURCE,
|
|
126
|
+
)
|
|
127
|
+
for node in nodes.values():
|
|
128
|
+
await self._validate_profile_removal(
|
|
129
|
+
node=node,
|
|
130
|
+
profile_id=profile.get_id(),
|
|
131
|
+
required_attr_names=required_attr_names,
|
|
132
|
+
required_rel_names=required_rel_names,
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
async def check(self, relm: RelationshipManager, node_schema: MainSchemaTypes, node: Node) -> None:
|
|
136
|
+
if relm.name == "profiles" and isinstance(node_schema, NodeSchema):
|
|
137
|
+
await self._check_node_profiles_removal(relm=relm, node_schema=node_schema, node=node)
|
|
138
|
+
return
|
|
139
|
+
|
|
140
|
+
if relm.name == "related_nodes" and isinstance(node_schema, ProfileSchema):
|
|
141
|
+
await self._check_profile_related_nodes_removal(relm=relm, profile_schema=node_schema, profile=node)
|
|
142
|
+
return
|
|
143
|
+
|
|
144
|
+
async def validate_profile_deletion(self, profile: Node, profile_schema: ProfileSchema) -> None:
|
|
145
|
+
related_nodes_rels = await profile.related_nodes.get_relationships(db=self.db) # type: ignore[attr-defined]
|
|
146
|
+
related_node_ids = [rel.peer_id for rel in related_nodes_rels if rel.peer_id]
|
|
147
|
+
|
|
148
|
+
if not related_node_ids:
|
|
149
|
+
return
|
|
150
|
+
|
|
151
|
+
target_kind = profile_schema.get_relationship(name="related_nodes").peer
|
|
152
|
+
target_schema = self.schema_branch.get_node(name=target_kind, duplicate=False)
|
|
153
|
+
|
|
154
|
+
required_attr_names = self._get_required_attributes_names(schema=target_schema)
|
|
155
|
+
required_rel_names = self._get_required_relationship_names(schema=target_schema)
|
|
156
|
+
if not required_attr_names and not required_rel_names:
|
|
157
|
+
return
|
|
158
|
+
|
|
159
|
+
nodes = await NodeManager.get_many(
|
|
160
|
+
db=self.db, branch=self.branch, ids=related_node_ids, include_metadata=MetadataOptions.SOURCE
|
|
161
|
+
)
|
|
162
|
+
for node in nodes.values():
|
|
163
|
+
await self._validate_profile_removal(
|
|
164
|
+
node=node,
|
|
165
|
+
profile_id=profile.get_id(),
|
|
166
|
+
required_attr_names=required_attr_names,
|
|
167
|
+
required_rel_names=required_rel_names,
|
|
168
|
+
)
|