infrahub-server 1.3.0a0__py3-none-any.whl → 1.3.0b2__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 -11
- infrahub/branch/__init__.py +0 -0
- infrahub/branch/tasks.py +29 -0
- infrahub/branch/triggers.py +22 -0
- infrahub/cli/db.py +2 -2
- infrahub/computed_attribute/gather.py +3 -1
- infrahub/computed_attribute/tasks.py +23 -29
- infrahub/core/attribute.py +3 -3
- infrahub/core/constants/__init__.py +10 -0
- infrahub/core/constants/database.py +1 -0
- infrahub/core/constants/infrahubkind.py +2 -0
- infrahub/core/convert_object_type/conversion.py +1 -1
- infrahub/core/diff/query/save.py +67 -40
- infrahub/core/diff/query/time_range_query.py +0 -1
- infrahub/core/graph/__init__.py +1 -1
- infrahub/core/migrations/graph/__init__.py +6 -0
- infrahub/core/migrations/graph/m013_convert_git_password_credential.py +0 -2
- infrahub/core/migrations/graph/m029_duplicates_cleanup.py +662 -0
- infrahub/core/migrations/graph/m030_illegal_edges.py +82 -0
- infrahub/core/migrations/query/attribute_add.py +13 -9
- infrahub/core/migrations/query/attribute_rename.py +2 -4
- infrahub/core/migrations/query/delete_element_in_schema.py +16 -11
- infrahub/core/migrations/query/node_duplicate.py +16 -15
- infrahub/core/migrations/query/relationship_duplicate.py +16 -12
- infrahub/core/migrations/schema/node_attribute_remove.py +1 -2
- infrahub/core/migrations/schema/node_remove.py +16 -14
- infrahub/core/node/__init__.py +74 -14
- infrahub/core/node/base.py +1 -1
- infrahub/core/node/resource_manager/ip_address_pool.py +6 -2
- infrahub/core/node/resource_manager/ip_prefix_pool.py +6 -2
- infrahub/core/node/resource_manager/number_pool.py +31 -5
- infrahub/core/node/standard.py +6 -1
- infrahub/core/path.py +1 -1
- infrahub/core/protocols.py +10 -0
- infrahub/core/query/node.py +1 -1
- infrahub/core/query/relationship.py +4 -6
- infrahub/core/query/standard_node.py +19 -5
- infrahub/core/relationship/constraints/peer_relatives.py +72 -0
- infrahub/core/relationship/model.py +1 -1
- infrahub/core/schema/attribute_parameters.py +129 -5
- infrahub/core/schema/attribute_schema.py +62 -14
- infrahub/core/schema/basenode_schema.py +2 -2
- infrahub/core/schema/definitions/core/__init__.py +16 -2
- infrahub/core/schema/definitions/core/group.py +45 -0
- infrahub/core/schema/definitions/core/resource_pool.py +29 -0
- infrahub/core/schema/definitions/internal.py +25 -4
- infrahub/core/schema/generated/attribute_schema.py +12 -5
- infrahub/core/schema/generated/relationship_schema.py +6 -1
- infrahub/core/schema/manager.py +7 -2
- infrahub/core/schema/schema_branch.py +69 -5
- infrahub/core/validators/__init__.py +8 -0
- infrahub/core/validators/attribute/choices.py +0 -1
- infrahub/core/validators/attribute/enum.py +0 -1
- infrahub/core/validators/attribute/kind.py +0 -1
- infrahub/core/validators/attribute/length.py +0 -1
- infrahub/core/validators/attribute/min_max.py +118 -0
- infrahub/core/validators/attribute/number_pool.py +106 -0
- infrahub/core/validators/attribute/optional.py +0 -2
- infrahub/core/validators/attribute/regex.py +0 -1
- infrahub/core/validators/enum.py +5 -0
- infrahub/core/validators/tasks.py +1 -1
- infrahub/database/__init__.py +16 -4
- infrahub/database/validation.py +100 -0
- infrahub/dependencies/builder/constraint/grouped/node_runner.py +2 -0
- infrahub/dependencies/builder/constraint/relationship_manager/peer_relatives.py +8 -0
- infrahub/dependencies/builder/diff/deserializer.py +1 -1
- infrahub/dependencies/registry.py +2 -0
- infrahub/events/models.py +1 -1
- infrahub/git/base.py +5 -3
- infrahub/git/integrator.py +102 -3
- infrahub/graphql/mutations/main.py +1 -1
- infrahub/graphql/mutations/resource_manager.py +54 -6
- infrahub/graphql/queries/resource_manager.py +7 -1
- infrahub/graphql/queries/task.py +10 -0
- infrahub/graphql/resolvers/many_relationship.py +1 -1
- infrahub/graphql/resolvers/resolver.py +2 -2
- infrahub/graphql/resolvers/single_relationship.py +1 -1
- infrahub/graphql/types/task_log.py +3 -2
- infrahub/menu/menu.py +8 -7
- infrahub/message_bus/operations/refresh/registry.py +3 -3
- infrahub/patch/queries/delete_duplicated_edges.py +40 -29
- infrahub/pools/number.py +5 -3
- infrahub/pools/registration.py +22 -0
- infrahub/pools/tasks.py +56 -0
- infrahub/schema/__init__.py +0 -0
- infrahub/schema/tasks.py +27 -0
- infrahub/schema/triggers.py +23 -0
- infrahub/task_manager/task.py +44 -4
- infrahub/trigger/catalogue.py +4 -0
- infrahub/trigger/models.py +5 -4
- infrahub/trigger/setup.py +26 -2
- infrahub/trigger/tasks.py +1 -1
- infrahub/types.py +6 -0
- infrahub/webhook/tasks.py +6 -9
- infrahub/workflows/catalogue.py +27 -1
- infrahub_sdk/client.py +43 -10
- infrahub_sdk/node/__init__.py +39 -0
- infrahub_sdk/node/attribute.py +122 -0
- infrahub_sdk/node/constants.py +21 -0
- infrahub_sdk/{node.py → node/node.py} +50 -749
- infrahub_sdk/node/parsers.py +15 -0
- infrahub_sdk/node/property.py +24 -0
- infrahub_sdk/node/related_node.py +266 -0
- infrahub_sdk/node/relationship.py +302 -0
- infrahub_sdk/protocols.py +112 -0
- infrahub_sdk/protocols_base.py +34 -2
- infrahub_sdk/query_groups.py +13 -2
- infrahub_sdk/schema/main.py +1 -0
- infrahub_sdk/schema/repository.py +16 -0
- infrahub_sdk/spec/object.py +1 -1
- infrahub_sdk/store.py +1 -1
- infrahub_sdk/testing/schemas/car_person.py +1 -0
- {infrahub_server-1.3.0a0.dist-info → infrahub_server-1.3.0b2.dist-info}/METADATA +3 -3
- {infrahub_server-1.3.0a0.dist-info → infrahub_server-1.3.0b2.dist-info}/RECORD +122 -100
- {infrahub_server-1.3.0a0.dist-info → infrahub_server-1.3.0b2.dist-info}/WHEEL +1 -1
- infrahub_testcontainers/container.py +239 -64
- infrahub_testcontainers/docker-compose-cluster.test.yml +321 -0
- infrahub_testcontainers/docker-compose.test.yml +1 -0
- infrahub_testcontainers/helpers.py +15 -1
- infrahub_testcontainers/plugin.py +9 -0
- infrahub/patch/queries/consolidate_duplicated_nodes.py +0 -106
- {infrahub_server-1.3.0a0.dist-info → infrahub_server-1.3.0b2.dist-info}/LICENSE.txt +0 -0
- {infrahub_server-1.3.0a0.dist-info → infrahub_server-1.3.0b2.dist-info}/entry_points.txt +0 -0
infrahub/core/node/__init__.py
CHANGED
|
@@ -16,6 +16,7 @@ from infrahub.core.constants import (
|
|
|
16
16
|
BranchSupportType,
|
|
17
17
|
ComputedAttributeKind,
|
|
18
18
|
InfrahubKind,
|
|
19
|
+
NumberPoolType,
|
|
19
20
|
RelationshipCardinality,
|
|
20
21
|
RelationshipKind,
|
|
21
22
|
)
|
|
@@ -30,6 +31,7 @@ from infrahub.core.schema import (
|
|
|
30
31
|
RelationshipSchema,
|
|
31
32
|
TemplateSchema,
|
|
32
33
|
)
|
|
34
|
+
from infrahub.core.schema.attribute_parameters import NumberPoolParameters
|
|
33
35
|
from infrahub.core.timestamp import Timestamp
|
|
34
36
|
from infrahub.exceptions import InitializationError, NodeNotFoundError, PoolExhaustedError, ValidationError
|
|
35
37
|
from infrahub.types import ATTRIBUTE_TYPES
|
|
@@ -254,6 +256,12 @@ class Node(BaseNode, metaclass=BaseNodeMeta):
|
|
|
254
256
|
within the create code.
|
|
255
257
|
"""
|
|
256
258
|
|
|
259
|
+
number_pool_parameters: NumberPoolParameters | None = None
|
|
260
|
+
if attribute.schema.kind == "NumberPool" and isinstance(attribute.schema.parameters, NumberPoolParameters):
|
|
261
|
+
attribute.from_pool = {"id": attribute.schema.parameters.number_pool_id}
|
|
262
|
+
attribute.is_default = False
|
|
263
|
+
number_pool_parameters = attribute.schema.parameters
|
|
264
|
+
|
|
257
265
|
if not attribute.from_pool:
|
|
258
266
|
return
|
|
259
267
|
|
|
@@ -262,19 +270,25 @@ class Node(BaseNode, metaclass=BaseNodeMeta):
|
|
|
262
270
|
db=db, id=attribute.from_pool["id"], kind=CoreNumberPool
|
|
263
271
|
)
|
|
264
272
|
except NodeNotFoundError:
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
273
|
+
if number_pool_parameters:
|
|
274
|
+
number_pool = await self._create_number_pool(
|
|
275
|
+
db=db, attribute=attribute, number_pool_parameters=number_pool_parameters
|
|
268
276
|
)
|
|
269
|
-
|
|
270
|
-
|
|
277
|
+
|
|
278
|
+
else:
|
|
279
|
+
errors.append(
|
|
280
|
+
ValidationError(
|
|
281
|
+
{f"{attribute.name}.from_pool": f"The pool requested {attribute.from_pool} was not found."}
|
|
282
|
+
)
|
|
283
|
+
)
|
|
284
|
+
return
|
|
271
285
|
|
|
272
286
|
if (
|
|
273
287
|
number_pool.node.value in [self._schema.kind] + self._schema.inherit_from
|
|
274
288
|
and number_pool.node_attribute.value == attribute.name
|
|
275
289
|
):
|
|
276
290
|
try:
|
|
277
|
-
next_free = await number_pool.get_resource(db=db, branch=self._branch, node=self)
|
|
291
|
+
next_free = await number_pool.get_resource(db=db, branch=self._branch, node=self, attribute=attribute)
|
|
278
292
|
except PoolExhaustedError:
|
|
279
293
|
errors.append(
|
|
280
294
|
ValidationError({f"{attribute.name}.from_pool": f"The pool {number_pool.node.value} is exhausted."})
|
|
@@ -292,6 +306,36 @@ class Node(BaseNode, metaclass=BaseNodeMeta):
|
|
|
292
306
|
)
|
|
293
307
|
)
|
|
294
308
|
|
|
309
|
+
async def _create_number_pool(
|
|
310
|
+
self, db: InfrahubDatabase, attribute: BaseAttribute, number_pool_parameters: NumberPoolParameters
|
|
311
|
+
) -> CoreNumberPool:
|
|
312
|
+
schema = db.schema.get_node_schema(name="CoreNumberPool", duplicate=False)
|
|
313
|
+
|
|
314
|
+
pool_node = self._schema.kind
|
|
315
|
+
schema_attribute = self._schema.get_attribute(attribute.schema.name)
|
|
316
|
+
if schema_attribute.inherited:
|
|
317
|
+
for generic_name in self._schema.inherit_from:
|
|
318
|
+
generic_node = db.schema.get_generic_schema(name=generic_name, duplicate=False)
|
|
319
|
+
if attribute.schema.name in generic_node.attribute_names:
|
|
320
|
+
pool_node = generic_node.kind
|
|
321
|
+
break
|
|
322
|
+
|
|
323
|
+
number_pool = await Node.init(db=db, schema=schema, branch=self._branch)
|
|
324
|
+
await number_pool.new(
|
|
325
|
+
db=db,
|
|
326
|
+
id=number_pool_parameters.number_pool_id,
|
|
327
|
+
name=f"{pool_node}.{attribute.schema.name} [{number_pool_parameters.number_pool_id}]",
|
|
328
|
+
node=pool_node,
|
|
329
|
+
node_attribute=attribute.schema.name,
|
|
330
|
+
start_range=number_pool_parameters.start_range,
|
|
331
|
+
end_range=number_pool_parameters.end_range,
|
|
332
|
+
pool_type=NumberPoolType.SCHEMA.value,
|
|
333
|
+
)
|
|
334
|
+
await number_pool.save(db=db)
|
|
335
|
+
# Do a lookup of the number pool to get the correct mapped type from the registry
|
|
336
|
+
# without this we don't get access to the .get_resource() method.
|
|
337
|
+
return await registry.manager.get_one_by_id_or_default_filter(db=db, id=number_pool.id, kind=CoreNumberPool)
|
|
338
|
+
|
|
295
339
|
async def handle_object_template(self, fields: dict, db: InfrahubDatabase, errors: list) -> None:
|
|
296
340
|
"""Fill the `fields` parameters with values from an object template if one is in use."""
|
|
297
341
|
object_template_field = fields.get(OBJECT_TEMPLATE_RELATIONSHIP_NAME)
|
|
@@ -376,6 +420,9 @@ class Node(BaseNode, metaclass=BaseNodeMeta):
|
|
|
376
420
|
self._computed_jinja2_attributes.append(mandatory_attr)
|
|
377
421
|
continue
|
|
378
422
|
|
|
423
|
+
if mandatory_attribute.kind == "NumberPool":
|
|
424
|
+
continue
|
|
425
|
+
|
|
379
426
|
errors.append(
|
|
380
427
|
ValidationError({mandatory_attr: f"{mandatory_attr} is mandatory for {self.get_kind()}"})
|
|
381
428
|
)
|
|
@@ -392,6 +439,21 @@ class Node(BaseNode, metaclass=BaseNodeMeta):
|
|
|
392
439
|
# -------------------------------------------
|
|
393
440
|
# Generate Attribute and Relationship and assign them
|
|
394
441
|
# -------------------------------------------
|
|
442
|
+
errors.extend(await self._process_fields_relationships(fields=fields, db=db))
|
|
443
|
+
errors.extend(await self._process_fields_attributes(fields=fields, db=db))
|
|
444
|
+
|
|
445
|
+
if errors:
|
|
446
|
+
raise ValidationError(errors)
|
|
447
|
+
|
|
448
|
+
# Check if any post processor have been defined
|
|
449
|
+
# A processor can be used for example to assigne a default value
|
|
450
|
+
for name in self._attributes + self._relationships:
|
|
451
|
+
if hasattr(self, f"process_{name}"):
|
|
452
|
+
await getattr(self, f"process_{name}")(db=db)
|
|
453
|
+
|
|
454
|
+
async def _process_fields_relationships(self, fields: dict, db: InfrahubDatabase) -> list[ValidationError]:
|
|
455
|
+
errors: list[ValidationError] = []
|
|
456
|
+
|
|
395
457
|
for rel_schema in self._schema.relationships:
|
|
396
458
|
self._relationships.append(rel_schema.name)
|
|
397
459
|
|
|
@@ -413,6 +475,11 @@ class Node(BaseNode, metaclass=BaseNodeMeta):
|
|
|
413
475
|
except ValidationError as exc:
|
|
414
476
|
errors.append(exc)
|
|
415
477
|
|
|
478
|
+
return errors
|
|
479
|
+
|
|
480
|
+
async def _process_fields_attributes(self, fields: dict, db: InfrahubDatabase) -> list[ValidationError]:
|
|
481
|
+
errors: list[ValidationError] = []
|
|
482
|
+
|
|
416
483
|
for attr_schema in self._schema.attributes:
|
|
417
484
|
self._attributes.append(attr_schema.name)
|
|
418
485
|
if not self._existing and attr_schema.name in self._computed_jinja2_attributes:
|
|
@@ -441,14 +508,7 @@ class Node(BaseNode, metaclass=BaseNodeMeta):
|
|
|
441
508
|
except ValidationError as exc:
|
|
442
509
|
errors.append(exc)
|
|
443
510
|
|
|
444
|
-
|
|
445
|
-
raise ValidationError(errors)
|
|
446
|
-
|
|
447
|
-
# Check if any post processor have been defined
|
|
448
|
-
# A processor can be used for example to assigne a default value
|
|
449
|
-
for name in self._attributes + self._relationships:
|
|
450
|
-
if hasattr(self, f"process_{name}"):
|
|
451
|
-
await getattr(self, f"process_{name}")(db=db)
|
|
511
|
+
return errors
|
|
452
512
|
|
|
453
513
|
async def _process_macros(self, db: InfrahubDatabase) -> None:
|
|
454
514
|
schema_branch = db.schema.get_schema_branch(self._branch.name)
|
infrahub/core/node/base.py
CHANGED
|
@@ -52,7 +52,7 @@ class BaseNodeOptions(BaseOptions):
|
|
|
52
52
|
|
|
53
53
|
|
|
54
54
|
class ObjectNodeMeta(BaseNodeMeta):
|
|
55
|
-
def __new__(mcs, name_, bases, namespace, **options):
|
|
55
|
+
def __new__(mcs, name_, bases, namespace, **options):
|
|
56
56
|
# Note: it's safe to pass options as keyword arguments as they are still type-checked by NodeOptions.
|
|
57
57
|
|
|
58
58
|
# We create this type, to then overload it with the dataclass attrs
|
|
@@ -81,11 +81,15 @@ class CoreIPAddressPool(Node):
|
|
|
81
81
|
return node
|
|
82
82
|
|
|
83
83
|
async def get_next(self, db: InfrahubDatabase, prefixlen: int | None = None) -> IPAddressType:
|
|
84
|
-
# Measure utilization of all prefixes identified as resources
|
|
85
84
|
resources = await self.resources.get_peers(db=db) # type: ignore[attr-defined]
|
|
86
85
|
ip_namespace = await self.ip_namespace.get_peer(db=db) # type: ignore[attr-defined]
|
|
87
86
|
|
|
88
|
-
|
|
87
|
+
try:
|
|
88
|
+
weighted_resources = sorted(resources.values(), key=lambda r: r.allocation_weight.value or 0, reverse=True)
|
|
89
|
+
except AttributeError:
|
|
90
|
+
weighted_resources = list(resources.values())
|
|
91
|
+
|
|
92
|
+
for resource in weighted_resources:
|
|
89
93
|
ip_prefix = ipaddress.ip_network(resource.prefix.value) # type: ignore[attr-defined]
|
|
90
94
|
prefix_length = prefixlen or ip_prefix.prefixlen
|
|
91
95
|
|
|
@@ -88,11 +88,15 @@ class CoreIPPrefixPool(Node):
|
|
|
88
88
|
return node
|
|
89
89
|
|
|
90
90
|
async def get_next(self, db: InfrahubDatabase, prefixlen: int) -> IPNetworkType:
|
|
91
|
-
# Measure utilization of all prefixes identified as resources
|
|
92
91
|
resources = await self.resources.get_peers(db=db) # type: ignore[attr-defined]
|
|
93
92
|
ip_namespace = await self.ip_namespace.get_peer(db=db) # type: ignore[attr-defined]
|
|
94
93
|
|
|
95
|
-
|
|
94
|
+
try:
|
|
95
|
+
weighted_resources = sorted(resources.values(), key=lambda r: r.allocation_weight.value or 0, reverse=True)
|
|
96
|
+
except AttributeError:
|
|
97
|
+
weighted_resources = list(resources.values())
|
|
98
|
+
|
|
99
|
+
for resource in weighted_resources:
|
|
96
100
|
subnets = await get_subnets(
|
|
97
101
|
db=db,
|
|
98
102
|
ip_prefix=ipaddress.ip_network(resource.prefix.value), # type: ignore[attr-defined]
|
|
@@ -2,22 +2,44 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from typing import TYPE_CHECKING
|
|
4
4
|
|
|
5
|
+
from infrahub.core import registry
|
|
5
6
|
from infrahub.core.query.resource_manager import NumberPoolGetReserved, NumberPoolGetUsed, NumberPoolSetReserved
|
|
7
|
+
from infrahub.core.schema.attribute_parameters import NumberAttributeParameters
|
|
6
8
|
from infrahub.exceptions import PoolExhaustedError
|
|
7
9
|
|
|
8
10
|
from .. import Node
|
|
9
11
|
|
|
10
12
|
if TYPE_CHECKING:
|
|
13
|
+
from infrahub.core.attribute import BaseAttribute
|
|
11
14
|
from infrahub.core.branch import Branch
|
|
12
15
|
from infrahub.database import InfrahubDatabase
|
|
13
16
|
|
|
14
17
|
|
|
15
18
|
class CoreNumberPool(Node):
|
|
19
|
+
def get_attribute_nb_excluded_values(self) -> int:
|
|
20
|
+
"""
|
|
21
|
+
Returns the number of excluded values for the attribute of the number pool.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
pool_node = registry.schema.get(name=self.node.value) # type: ignore [attr-defined]
|
|
25
|
+
attribute = [attribute for attribute in pool_node.attributes if attribute.name == self.node_attribute.value][0] # type: ignore [attr-defined]
|
|
26
|
+
if not isinstance(attribute.parameters, NumberAttributeParameters):
|
|
27
|
+
return 0
|
|
28
|
+
|
|
29
|
+
sum_excluded_values = 0
|
|
30
|
+
excluded_ranges = attribute.parameters.get_excluded_ranges()
|
|
31
|
+
for start_range, end_range in excluded_ranges:
|
|
32
|
+
sum_excluded_values += end_range - start_range + 1
|
|
33
|
+
|
|
34
|
+
res = len(attribute.parameters.get_excluded_single_values()) + sum_excluded_values
|
|
35
|
+
return res
|
|
36
|
+
|
|
16
37
|
async def get_resource(
|
|
17
38
|
self,
|
|
18
39
|
db: InfrahubDatabase,
|
|
19
40
|
branch: Branch,
|
|
20
41
|
node: Node,
|
|
42
|
+
attribute: BaseAttribute,
|
|
21
43
|
identifier: str | None = None,
|
|
22
44
|
) -> int:
|
|
23
45
|
identifier = identifier or node.get_id()
|
|
@@ -31,23 +53,24 @@ class CoreNumberPool(Node):
|
|
|
31
53
|
return reservation
|
|
32
54
|
|
|
33
55
|
# If we have not returned a value we need to find one if avaiable
|
|
34
|
-
number = await self.get_next(db=db, branch=branch)
|
|
56
|
+
number = await self.get_next(db=db, branch=branch, attribute=attribute)
|
|
35
57
|
|
|
36
58
|
query_set = await NumberPoolSetReserved.init(
|
|
37
59
|
db=db, pool_id=self.get_id(), identifier=identifier, reserved=number
|
|
38
60
|
)
|
|
39
61
|
await query_set.execute(db=db)
|
|
40
|
-
|
|
41
62
|
return number
|
|
42
63
|
|
|
43
|
-
async def get_next(self, db: InfrahubDatabase, branch: Branch) -> int:
|
|
64
|
+
async def get_next(self, db: InfrahubDatabase, branch: Branch, attribute: BaseAttribute) -> int:
|
|
44
65
|
query = await NumberPoolGetUsed.init(db=db, branch=branch, pool=self, branch_agnostic=True)
|
|
45
66
|
await query.execute(db=db)
|
|
46
67
|
taken = [result.get_as_optional_type("av.value", return_type=int) for result in query.results]
|
|
68
|
+
parameters = attribute.schema.parameters
|
|
47
69
|
next_number = find_next_free(
|
|
48
70
|
start=self.start_range.value, # type: ignore[attr-defined]
|
|
49
71
|
end=self.end_range.value, # type: ignore[attr-defined]
|
|
50
72
|
taken=taken,
|
|
73
|
+
parameters=parameters if isinstance(parameters, NumberAttributeParameters) else None,
|
|
51
74
|
)
|
|
52
75
|
if next_number is None:
|
|
53
76
|
raise PoolExhaustedError("There are no more values available in this pool.")
|
|
@@ -55,12 +78,15 @@ class CoreNumberPool(Node):
|
|
|
55
78
|
return next_number
|
|
56
79
|
|
|
57
80
|
|
|
58
|
-
def find_next_free(
|
|
81
|
+
def find_next_free(
|
|
82
|
+
start: int, end: int, taken: list[int | None], parameters: NumberAttributeParameters | None
|
|
83
|
+
) -> int | None:
|
|
59
84
|
used_numbers = [number for number in taken if number is not None]
|
|
60
85
|
used_set = set(used_numbers)
|
|
61
86
|
|
|
62
87
|
for num in range(start, end + 1):
|
|
63
88
|
if num not in used_set:
|
|
64
|
-
|
|
89
|
+
if parameters is None or parameters.is_valid_value(num):
|
|
90
|
+
return num
|
|
65
91
|
|
|
66
92
|
return None
|
infrahub/core/node/standard.py
CHANGED
|
@@ -210,7 +210,12 @@ class StandardNode(BaseModel):
|
|
|
210
210
|
|
|
211
211
|
@classmethod
|
|
212
212
|
async def get_list(
|
|
213
|
-
cls,
|
|
213
|
+
cls,
|
|
214
|
+
db: InfrahubDatabase,
|
|
215
|
+
limit: int = 1000,
|
|
216
|
+
ids: list[str] | None = None,
|
|
217
|
+
name: str | None = None,
|
|
218
|
+
**kwargs: dict[str, Any],
|
|
214
219
|
) -> list[Self]:
|
|
215
220
|
query: Query = await StandardNodeGetListQuery.init(
|
|
216
221
|
db=db, node_class=cls, ids=ids, node_name=name, limit=limit, **kwargs
|
infrahub/core/path.py
CHANGED
|
@@ -125,7 +125,7 @@ class SchemaPath(InfrahubPath):
|
|
|
125
125
|
if self.field_name:
|
|
126
126
|
identifier += f"/{self.field_name}"
|
|
127
127
|
|
|
128
|
-
if self.property_name and
|
|
128
|
+
if self.property_name and self.path_type != SchemaPathType.NODE:
|
|
129
129
|
identifier += f"/{self.property_name}"
|
|
130
130
|
|
|
131
131
|
return identifier
|
infrahub/core/protocols.py
CHANGED
|
@@ -227,6 +227,10 @@ class CoreWebhook(CoreNode):
|
|
|
227
227
|
validate_certificates: BooleanOptional
|
|
228
228
|
|
|
229
229
|
|
|
230
|
+
class CoreWeightedPoolResource(CoreNode):
|
|
231
|
+
allocation_weight: IntegerOptional
|
|
232
|
+
|
|
233
|
+
|
|
230
234
|
class LineageOwner(CoreNode):
|
|
231
235
|
pass
|
|
232
236
|
|
|
@@ -451,6 +455,7 @@ class CoreNumberPool(CoreResourcePool, LineageSource):
|
|
|
451
455
|
node_attribute: String
|
|
452
456
|
start_range: Integer
|
|
453
457
|
end_range: Integer
|
|
458
|
+
pool_type: Enum
|
|
454
459
|
|
|
455
460
|
|
|
456
461
|
class CoreObjectPermission(CoreBasePermission):
|
|
@@ -493,6 +498,11 @@ class CoreRepository(LineageOwner, LineageSource, CoreGenericRepository, CoreTas
|
|
|
493
498
|
commit: StringOptional
|
|
494
499
|
|
|
495
500
|
|
|
501
|
+
class CoreRepositoryGroup(CoreGroup):
|
|
502
|
+
content: Dropdown
|
|
503
|
+
repository: RelationshipManager
|
|
504
|
+
|
|
505
|
+
|
|
496
506
|
class CoreRepositoryValidator(CoreValidator):
|
|
497
507
|
repository: RelationshipManager
|
|
498
508
|
|
infrahub/core/query/node.py
CHANGED
|
@@ -1469,7 +1469,7 @@ class NodeGetHierarchyQuery(Query):
|
|
|
1469
1469
|
|
|
1470
1470
|
clean_filters = extract_field_filters(field_name=self.direction.value, filters=self.filters)
|
|
1471
1471
|
|
|
1472
|
-
if clean_filters and "id" in clean_filters or "ids" in clean_filters:
|
|
1472
|
+
if (clean_filters and "id" in clean_filters) or "ids" in clean_filters:
|
|
1473
1473
|
where_clause.append("peer.uuid IN $peer_ids")
|
|
1474
1474
|
self.params["peer_ids"] = clean_filters.get("ids", [])
|
|
1475
1475
|
if clean_filters.get("id", None):
|
|
@@ -217,8 +217,7 @@ class RelationshipQuery(Query):
|
|
|
217
217
|
)
|
|
218
218
|
source_query_match = """
|
|
219
219
|
MATCH (s:Node { uuid: $source_id })
|
|
220
|
-
CALL {
|
|
221
|
-
WITH s
|
|
220
|
+
CALL (s) {
|
|
222
221
|
MATCH (s)-[r:IS_PART_OF]->(:Root)
|
|
223
222
|
WHERE %(source_filter)s
|
|
224
223
|
RETURN r.status = "active" AS s_is_active
|
|
@@ -246,8 +245,7 @@ class RelationshipQuery(Query):
|
|
|
246
245
|
)
|
|
247
246
|
destination_query_match = """
|
|
248
247
|
MATCH (d:Node { uuid: $destination_id })
|
|
249
|
-
CALL {
|
|
250
|
-
WITH d
|
|
248
|
+
CALL (d) {
|
|
251
249
|
MATCH (d)-[r:IS_PART_OF]->(:Root)
|
|
252
250
|
WHERE %(destination_filter)s
|
|
253
251
|
RETURN r.status = "active" AS d_is_active
|
|
@@ -678,7 +676,7 @@ class RelationshipGetPeerQuery(Query):
|
|
|
678
676
|
where_clause = ['all(r IN rels WHERE r.status = "active")']
|
|
679
677
|
clean_filters = extract_field_filters(field_name=self.schema.name, filters=self.filters)
|
|
680
678
|
|
|
681
|
-
if clean_filters and "id" in clean_filters or "ids" in clean_filters:
|
|
679
|
+
if (clean_filters and "id" in clean_filters) or "ids" in clean_filters:
|
|
682
680
|
where_clause.append("peer.uuid IN $peer_ids")
|
|
683
681
|
self.params["peer_ids"] = clean_filters.get("ids", [])
|
|
684
682
|
if clean_filters.get("id", None):
|
|
@@ -1037,7 +1035,7 @@ class RelationshipDeleteAllQuery(Query):
|
|
|
1037
1035
|
self.node_id = node_id
|
|
1038
1036
|
super().__init__(**kwargs)
|
|
1039
1037
|
|
|
1040
|
-
async def query_init(self, db: InfrahubDatabase, **kwargs) -> None:
|
|
1038
|
+
async def query_init(self, db: InfrahubDatabase, **kwargs) -> None:
|
|
1041
1039
|
self.params["source_id"] = kwargs["node_id"]
|
|
1042
1040
|
self.params["branch"] = self.branch.name
|
|
1043
1041
|
|
|
@@ -3,28 +3,42 @@ from __future__ import annotations
|
|
|
3
3
|
from typing import TYPE_CHECKING, Any
|
|
4
4
|
|
|
5
5
|
from infrahub.core.query import Query, QueryType
|
|
6
|
+
from infrahub.exceptions import InitializationError
|
|
6
7
|
|
|
7
8
|
if TYPE_CHECKING:
|
|
9
|
+
from uuid import UUID
|
|
10
|
+
|
|
8
11
|
from infrahub.core.node.standard import StandardNode
|
|
9
12
|
from infrahub.database import InfrahubDatabase
|
|
10
13
|
|
|
11
14
|
|
|
12
15
|
class StandardNodeQuery(Query):
|
|
13
16
|
def __init__(
|
|
14
|
-
self,
|
|
15
|
-
|
|
16
|
-
|
|
17
|
+
self,
|
|
18
|
+
node: StandardNode | None = None,
|
|
19
|
+
node_id: UUID | None = None,
|
|
20
|
+
node_db_id: str | None = None,
|
|
21
|
+
**kwargs: Any,
|
|
22
|
+
) -> None:
|
|
23
|
+
self._node = node
|
|
17
24
|
self.node_id = node_id
|
|
18
25
|
self.node_db_id = node_db_id
|
|
19
26
|
|
|
20
|
-
if not self.node_id and self.
|
|
27
|
+
if not self.node_id and self._node:
|
|
21
28
|
self.node_id = self.node.uuid
|
|
22
29
|
|
|
23
|
-
if not self.node_db_id and self.
|
|
30
|
+
if not self.node_db_id and self._node:
|
|
24
31
|
self.node_db_id = self.node.id
|
|
25
32
|
|
|
26
33
|
super().__init__(**kwargs)
|
|
27
34
|
|
|
35
|
+
@property
|
|
36
|
+
def node(self) -> StandardNode:
|
|
37
|
+
if self._node:
|
|
38
|
+
return self._node
|
|
39
|
+
|
|
40
|
+
raise InitializationError("The query is not initialized with a node")
|
|
41
|
+
|
|
28
42
|
|
|
29
43
|
class RootNodeCreateQuery(StandardNodeQuery):
|
|
30
44
|
name = "standard_node_create"
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import TYPE_CHECKING, Mapping
|
|
5
|
+
|
|
6
|
+
from infrahub.core.constants import RelationshipCardinality
|
|
7
|
+
from infrahub.exceptions import ValidationError
|
|
8
|
+
|
|
9
|
+
from .interface import RelationshipManagerConstraintInterface
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from infrahub.core.branch import Branch
|
|
13
|
+
from infrahub.core.node import Node
|
|
14
|
+
from infrahub.core.schema import MainSchemaTypes, NonGenericSchemaTypes
|
|
15
|
+
from infrahub.database import InfrahubDatabase
|
|
16
|
+
|
|
17
|
+
from ..model import RelationshipManager
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class NodeToValidate:
|
|
22
|
+
uuid: str
|
|
23
|
+
relative_uuids: set[str]
|
|
24
|
+
schema: NonGenericSchemaTypes
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class RelationshipPeerRelativesConstraint(RelationshipManagerConstraintInterface):
|
|
28
|
+
def __init__(self, db: InfrahubDatabase, branch: Branch | None = None):
|
|
29
|
+
self.db = db
|
|
30
|
+
self.branch = branch
|
|
31
|
+
|
|
32
|
+
async def _check_relationship_peers_relatives(
|
|
33
|
+
self,
|
|
34
|
+
relm: RelationshipManager,
|
|
35
|
+
node_schema: MainSchemaTypes,
|
|
36
|
+
peers: Mapping[str, Node],
|
|
37
|
+
relationship_name: str,
|
|
38
|
+
) -> None:
|
|
39
|
+
"""Validate that all peers of a given `relm` have the same set of relatives (aka peers) for the given `relationship_name`."""
|
|
40
|
+
nodes_to_validate: list[NodeToValidate] = []
|
|
41
|
+
|
|
42
|
+
for peer in peers.values():
|
|
43
|
+
peer_schema = peer.get_schema()
|
|
44
|
+
peer_relm: RelationshipManager = getattr(peer, relationship_name)
|
|
45
|
+
peer_relm_peers = await peer_relm.get_peers(db=self.db)
|
|
46
|
+
|
|
47
|
+
nodes_to_validate.append(
|
|
48
|
+
NodeToValidate(
|
|
49
|
+
uuid=peer.id, relative_uuids={n.id for n in peer_relm_peers.values()}, schema=peer_schema
|
|
50
|
+
)
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
relative_uuids = nodes_to_validate[0].relative_uuids
|
|
54
|
+
for node in nodes_to_validate[1:]:
|
|
55
|
+
if node.relative_uuids != relative_uuids:
|
|
56
|
+
raise ValidationError(
|
|
57
|
+
f"All the elements of the '{relm.name}' relationship on node {node.uuid} ({node_schema.kind}) must have the same set of peers "
|
|
58
|
+
f"for their '{node.schema.kind}.{relationship_name}' relationship"
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
async def check(self, relm: RelationshipManager, node_schema: MainSchemaTypes) -> None:
|
|
62
|
+
if relm.schema.cardinality != RelationshipCardinality.MANY or not relm.schema.common_relatives:
|
|
63
|
+
return
|
|
64
|
+
|
|
65
|
+
peers = await relm.get_peers(db=self.db)
|
|
66
|
+
if not peers:
|
|
67
|
+
return
|
|
68
|
+
|
|
69
|
+
for rel_name in relm.schema.common_relatives:
|
|
70
|
+
await self._check_relationship_peers_relatives(
|
|
71
|
+
relm=relm, node_schema=node_schema, peers=peers, relationship_name=rel_name
|
|
72
|
+
)
|
|
@@ -494,7 +494,7 @@ class Relationship(FlagPropertyMixin, NodePropertyMixin):
|
|
|
494
494
|
peer_fields = {
|
|
495
495
|
key: value
|
|
496
496
|
for key, value in fields.items()
|
|
497
|
-
if not key.startswith(PREFIX_PROPERTY) or
|
|
497
|
+
if not key.startswith(PREFIX_PROPERTY) or key != "__typename"
|
|
498
498
|
}
|
|
499
499
|
rel_fields = {
|
|
500
500
|
key.replace(PREFIX_PROPERTY, ""): value
|