infrahub-server 1.3.0a0__py3-none-any.whl → 1.3.0b1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- infrahub/core/attribute.py +3 -3
- infrahub/core/constants/__init__.py +5 -0
- infrahub/core/constants/infrahubkind.py +2 -0
- 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 -11
- infrahub/core/migrations/schema/node_attribute_remove.py +1 -2
- infrahub/core/migrations/schema/node_remove.py +16 -13
- infrahub/core/node/__init__.py +72 -14
- 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/protocols.py +9 -0
- infrahub/core/query/relationship.py +2 -4
- infrahub/core/schema/attribute_parameters.py +129 -5
- infrahub/core/schema/attribute_schema.py +38 -10
- 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 +20 -0
- infrahub/core/schema/definitions/internal.py +16 -3
- infrahub/core/schema/generated/attribute_schema.py +12 -5
- infrahub/core/schema/manager.py +3 -0
- infrahub/core/schema/schema_branch.py +55 -0
- 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/database/__init__.py +15 -3
- infrahub/git/base.py +5 -3
- infrahub/git/integrator.py +102 -3
- infrahub/graphql/mutations/resource_manager.py +62 -6
- infrahub/graphql/queries/resource_manager.py +7 -1
- infrahub/graphql/queries/task.py +10 -0
- infrahub/graphql/types/task_log.py +3 -2
- infrahub/menu/menu.py +3 -3
- infrahub/pools/number.py +5 -3
- infrahub/task_manager/task.py +44 -4
- infrahub/types.py +6 -0
- 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.0b1.dist-info}/METADATA +3 -3
- {infrahub_server-1.3.0a0.dist-info → infrahub_server-1.3.0b1.dist-info}/RECORD +68 -59
- {infrahub_server-1.3.0a0.dist-info → infrahub_server-1.3.0b1.dist-info}/WHEEL +1 -1
- {infrahub_server-1.3.0a0.dist-info → infrahub_server-1.3.0b1.dist-info}/LICENSE.txt +0 -0
- {infrahub_server-1.3.0a0.dist-info → infrahub_server-1.3.0b1.dist-info}/entry_points.txt +0 -0
|
@@ -5,6 +5,7 @@ import hashlib
|
|
|
5
5
|
from collections import defaultdict
|
|
6
6
|
from itertools import chain, combinations
|
|
7
7
|
from typing import Any
|
|
8
|
+
from uuid import uuid4
|
|
8
9
|
|
|
9
10
|
from infrahub_sdk.template import Jinja2Template
|
|
10
11
|
from infrahub_sdk.template.exceptions import JinjaTemplateError, JinjaTemplateOperationViolationError
|
|
@@ -49,6 +50,7 @@ from infrahub.core.schema import (
|
|
|
49
50
|
SchemaRoot,
|
|
50
51
|
TemplateSchema,
|
|
51
52
|
)
|
|
53
|
+
from infrahub.core.schema.attribute_parameters import NumberPoolParameters
|
|
52
54
|
from infrahub.core.schema.attribute_schema import get_attribute_schema_class_for_kind
|
|
53
55
|
from infrahub.core.schema.definitions.core import core_profile_schema_definition
|
|
54
56
|
from infrahub.core.validators import CONSTRAINT_VALIDATOR_MAP
|
|
@@ -518,6 +520,7 @@ class SchemaBranch:
|
|
|
518
520
|
self.validate_names()
|
|
519
521
|
self.validate_kinds()
|
|
520
522
|
self.validate_computed_attributes()
|
|
523
|
+
self.validate_attribute_parameters()
|
|
521
524
|
self.validate_default_values()
|
|
522
525
|
self.validate_count_against_cardinality()
|
|
523
526
|
self.validate_identifiers()
|
|
@@ -995,6 +998,58 @@ class SchemaBranch:
|
|
|
995
998
|
f"{node.kind}: Relationship {rel.name!r} is referring an invalid peer {rel.peer!r}"
|
|
996
999
|
) from None
|
|
997
1000
|
|
|
1001
|
+
def validate_attribute_parameters(self) -> None:
|
|
1002
|
+
for name in self.generics.keys():
|
|
1003
|
+
generic_schema = self.get_generic(name=name, duplicate=False)
|
|
1004
|
+
for attribute in generic_schema.attributes:
|
|
1005
|
+
if (
|
|
1006
|
+
attribute.kind == "NumberPool"
|
|
1007
|
+
and isinstance(attribute.parameters, NumberPoolParameters)
|
|
1008
|
+
and not attribute.parameters.number_pool_id
|
|
1009
|
+
):
|
|
1010
|
+
attribute.parameters.number_pool_id = str(uuid4())
|
|
1011
|
+
|
|
1012
|
+
for name in self.nodes.keys():
|
|
1013
|
+
node_schema = self.get_node(name=name, duplicate=False)
|
|
1014
|
+
for attribute in node_schema.attributes:
|
|
1015
|
+
if (
|
|
1016
|
+
attribute.kind == "NumberPool"
|
|
1017
|
+
and isinstance(attribute.parameters, NumberPoolParameters)
|
|
1018
|
+
and not attribute.parameters.number_pool_id
|
|
1019
|
+
):
|
|
1020
|
+
self._validate_number_pool_parameters(
|
|
1021
|
+
node_schema=node_schema, attribute=attribute, number_pool_parameters=attribute.parameters
|
|
1022
|
+
)
|
|
1023
|
+
|
|
1024
|
+
def _validate_number_pool_parameters(
|
|
1025
|
+
self, node_schema: NodeSchema, attribute: AttributeSchema, number_pool_parameters: NumberPoolParameters
|
|
1026
|
+
) -> None:
|
|
1027
|
+
if attribute.optional:
|
|
1028
|
+
raise ValidationError(f"{node_schema.kind}.{attribute.name} is a NumberPool it can't be optional")
|
|
1029
|
+
|
|
1030
|
+
if not attribute.read_only:
|
|
1031
|
+
raise ValidationError(
|
|
1032
|
+
f"{node_schema.kind}.{attribute.name} is a NumberPool it has to be a read_only attribute"
|
|
1033
|
+
)
|
|
1034
|
+
|
|
1035
|
+
if attribute.inherited:
|
|
1036
|
+
generics_with_attribute = []
|
|
1037
|
+
for generic_name in node_schema.inherit_from:
|
|
1038
|
+
generic_schema = self.get_generic(name=generic_name, duplicate=False)
|
|
1039
|
+
if attribute.name in generic_schema.attribute_names:
|
|
1040
|
+
generic_attribute = generic_schema.get_attribute(name=attribute.name)
|
|
1041
|
+
generics_with_attribute.append(generic_schema)
|
|
1042
|
+
if isinstance(generic_attribute.parameters, NumberPoolParameters):
|
|
1043
|
+
number_pool_parameters.number_pool_id = generic_attribute.parameters.number_pool_id
|
|
1044
|
+
|
|
1045
|
+
if len(generics_with_attribute) > 1:
|
|
1046
|
+
raise ValidationError(
|
|
1047
|
+
f"{node_schema.kind}.{attribute.name} is a NumberPool inherited from more than one generic"
|
|
1048
|
+
)
|
|
1049
|
+
|
|
1050
|
+
else:
|
|
1051
|
+
number_pool_parameters.number_pool_id = str(uuid4())
|
|
1052
|
+
|
|
998
1053
|
def validate_computed_attributes(self) -> None:
|
|
999
1054
|
self.computed_attributes = ComputedAttributes()
|
|
1000
1055
|
for name in self.nodes.keys():
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
from infrahub.core.validators.attribute.min_max import AttributeNumberChecker
|
|
2
|
+
|
|
1
3
|
from .attribute.choices import AttributeChoicesChecker
|
|
2
4
|
from .attribute.enum import AttributeEnumChecker
|
|
3
5
|
from .attribute.kind import AttributeKindChecker
|
|
4
6
|
from .attribute.length import AttributeLengthChecker
|
|
7
|
+
from .attribute.number_pool import AttributeNumberPoolChecker
|
|
5
8
|
from .attribute.optional import AttributeOptionalChecker
|
|
6
9
|
from .attribute.regex import AttributeRegexChecker
|
|
7
10
|
from .attribute.unique import AttributeUniquenessChecker
|
|
@@ -26,6 +29,11 @@ CONSTRAINT_VALIDATOR_MAP: dict[str, type[ConstraintCheckerInterface] | None] = {
|
|
|
26
29
|
"attribute.max_length.update": AttributeLengthChecker,
|
|
27
30
|
ConstraintIdentifier.ATTRIBUTE_PARAMETERS_MIN_LENGTH_UPDATE.value: AttributeLengthChecker,
|
|
28
31
|
ConstraintIdentifier.ATTRIBUTE_PARAMETERS_MAX_LENGTH_UPDATE.value: AttributeLengthChecker,
|
|
32
|
+
ConstraintIdentifier.ATTRIBUTE_PARAMETERS_MIN_VALUE_UPDATE.value: AttributeNumberChecker,
|
|
33
|
+
ConstraintIdentifier.ATTRIBUTE_PARAMETERS_MAX_VALUE_UPDATE.value: AttributeNumberChecker,
|
|
34
|
+
ConstraintIdentifier.ATTRIBUTE_PARAMETERS_EXCLUDED_VALUES_UPDATE.value: AttributeNumberChecker,
|
|
35
|
+
ConstraintIdentifier.ATTRIBUTE_PARAMETERS_START_RANGE_UPDATE: AttributeNumberPoolChecker,
|
|
36
|
+
ConstraintIdentifier.ATTRIBUTE_PARAMETERS_END_RANGE_UPDATE: AttributeNumberPoolChecker,
|
|
29
37
|
"attribute.unique.update": AttributeUniquenessChecker,
|
|
30
38
|
"attribute.optional.update": AttributeOptionalChecker,
|
|
31
39
|
"attribute.choices.update": AttributeChoicesChecker,
|
|
@@ -42,7 +42,6 @@ class AttributeChoicesUpdateValidatorQuery(AttributeSchemaValidatorQuery):
|
|
|
42
42
|
LIMIT 1
|
|
43
43
|
}
|
|
44
44
|
WITH full_path, node, attribute_value, value_relationship
|
|
45
|
-
WITH full_path, node, attribute_value, value_relationship
|
|
46
45
|
WHERE all(r in relationships(full_path) WHERE r.status = "active")
|
|
47
46
|
AND attribute_value IS NOT NULL
|
|
48
47
|
AND attribute_value <> $null_value
|
|
@@ -41,7 +41,6 @@ class AttributeEnumUpdateValidatorQuery(AttributeSchemaValidatorQuery):
|
|
|
41
41
|
LIMIT 1
|
|
42
42
|
}
|
|
43
43
|
WITH full_path, node, attribute_value, value_relationship
|
|
44
|
-
WITH full_path, node, attribute_value, value_relationship
|
|
45
44
|
WHERE all(r in relationships(full_path) WHERE r.status = "active")
|
|
46
45
|
AND attribute_value IS NOT NULL
|
|
47
46
|
AND attribute_value <> $null_value
|
|
@@ -48,7 +48,6 @@ class AttributeKindUpdateValidatorQuery(AttributeSchemaValidatorQuery):
|
|
|
48
48
|
LIMIT 1
|
|
49
49
|
}
|
|
50
50
|
WITH full_path, node, attribute_value, value_relationship
|
|
51
|
-
WITH full_path, node, attribute_value, value_relationship
|
|
52
51
|
WHERE all(r in relationships(full_path) WHERE r.status = "active")
|
|
53
52
|
AND attribute_value IS NOT NULL
|
|
54
53
|
AND attribute_value <> $null_value
|
|
@@ -40,7 +40,6 @@ class AttributeLengthUpdateValidatorQuery(AttributeSchemaValidatorQuery):
|
|
|
40
40
|
LIMIT 1
|
|
41
41
|
}
|
|
42
42
|
WITH full_path, node, attribute_value, value_relationship
|
|
43
|
-
WITH full_path, node, attribute_value, value_relationship
|
|
44
43
|
WHERE all(r in relationships(full_path) WHERE r.status = "active")
|
|
45
44
|
AND (
|
|
46
45
|
(toInteger($min_length) IS NOT NULL AND size(attribute_value) < toInteger($min_length))
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Any
|
|
4
|
+
|
|
5
|
+
from infrahub.core.constants import PathType
|
|
6
|
+
from infrahub.core.path import DataPath, GroupedDataPaths
|
|
7
|
+
from infrahub.core.schema.attribute_parameters import NumberAttributeParameters
|
|
8
|
+
from infrahub.core.validators.enum import ConstraintIdentifier
|
|
9
|
+
|
|
10
|
+
from ..interface import ConstraintCheckerInterface
|
|
11
|
+
from ..shared import AttributeSchemaValidatorQuery
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from infrahub.core.branch import Branch
|
|
15
|
+
from infrahub.database import InfrahubDatabase
|
|
16
|
+
|
|
17
|
+
from ..model import SchemaConstraintValidatorRequest
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class AttributeNumberUpdateValidatorQuery(AttributeSchemaValidatorQuery):
|
|
21
|
+
name: str = "attribute_constraints_number_validator"
|
|
22
|
+
|
|
23
|
+
async def query_init(self, db: InfrahubDatabase, **kwargs: dict[str, Any]) -> None: # noqa: ARG002
|
|
24
|
+
branch_filter, branch_params = self.branch.get_query_filter_path(at=self.at.to_string())
|
|
25
|
+
self.params.update(branch_params)
|
|
26
|
+
|
|
27
|
+
if not isinstance(self.attribute_schema.parameters, NumberAttributeParameters):
|
|
28
|
+
raise ValueError("attribute parameters are not a NumberAttributeParameters")
|
|
29
|
+
|
|
30
|
+
self.params["attr_name"] = self.attribute_schema.name
|
|
31
|
+
self.params["min_value"] = self.attribute_schema.parameters.min_value
|
|
32
|
+
self.params["max_value"] = self.attribute_schema.parameters.max_value
|
|
33
|
+
self.params["excluded_values"] = self.attribute_schema.parameters.get_excluded_single_values()
|
|
34
|
+
self.params["excluded_ranges"] = self.attribute_schema.parameters.get_excluded_ranges()
|
|
35
|
+
|
|
36
|
+
query = """
|
|
37
|
+
MATCH (n:%(node_kind)s)
|
|
38
|
+
CALL (n) {
|
|
39
|
+
MATCH path = (root:Root)<-[rr:IS_PART_OF]-(n)-[ra:HAS_ATTRIBUTE]-(:Attribute { name: $attr_name } )-[rv:HAS_VALUE]-(av:AttributeValue)
|
|
40
|
+
WHERE all(
|
|
41
|
+
r in relationships(path)
|
|
42
|
+
WHERE %(branch_filter)s
|
|
43
|
+
)
|
|
44
|
+
RETURN path as full_path, n as node, rv as value_relationship, av.value as attribute_value
|
|
45
|
+
ORDER BY rv.branch_level DESC, ra.branch_level DESC, rr.branch_level DESC, rv.from DESC, ra.from DESC, rr.from DESC
|
|
46
|
+
LIMIT 1
|
|
47
|
+
}
|
|
48
|
+
WITH full_path, node, attribute_value, value_relationship
|
|
49
|
+
WHERE all(r in relationships(full_path) WHERE r.status = "active")
|
|
50
|
+
AND (
|
|
51
|
+
(toInteger($min_value) IS NOT NULL AND attribute_value < toInteger($min_value))
|
|
52
|
+
OR (toInteger($max_value) IS NOT NULL AND attribute_value > toInteger($max_value))
|
|
53
|
+
OR (size($excluded_values) > 0 AND attribute_value IN $excluded_values)
|
|
54
|
+
OR (size($excluded_ranges) > 0 AND any(range in $excluded_ranges WHERE attribute_value >= range[0] AND attribute_value <= range[1]))
|
|
55
|
+
)
|
|
56
|
+
""" % {"branch_filter": branch_filter, "node_kind": self.node_schema.kind}
|
|
57
|
+
|
|
58
|
+
self.add_to_query(query)
|
|
59
|
+
self.return_labels = ["node.uuid", "value_relationship", "attribute_value"]
|
|
60
|
+
|
|
61
|
+
async def get_paths(self) -> GroupedDataPaths:
|
|
62
|
+
grouped_data_paths = GroupedDataPaths()
|
|
63
|
+
for result in self.results:
|
|
64
|
+
grouped_data_paths.add_data_path(
|
|
65
|
+
DataPath(
|
|
66
|
+
branch=str(result.get("value_relationship").get("branch")),
|
|
67
|
+
path_type=PathType.ATTRIBUTE,
|
|
68
|
+
node_id=str(result.get("node.uuid")),
|
|
69
|
+
field_name=self.attribute_schema.name,
|
|
70
|
+
kind=self.node_schema.kind,
|
|
71
|
+
value=result.get("attribute_value"),
|
|
72
|
+
),
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
return grouped_data_paths
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class AttributeNumberChecker(ConstraintCheckerInterface):
|
|
79
|
+
query_classes = [AttributeNumberUpdateValidatorQuery]
|
|
80
|
+
|
|
81
|
+
def __init__(self, db: InfrahubDatabase, branch: Branch | None = None):
|
|
82
|
+
self.db = db
|
|
83
|
+
self.branch = branch
|
|
84
|
+
|
|
85
|
+
@property
|
|
86
|
+
def name(self) -> str:
|
|
87
|
+
return "attribute.number.update"
|
|
88
|
+
|
|
89
|
+
def supports(self, request: SchemaConstraintValidatorRequest) -> bool:
|
|
90
|
+
return request.constraint_name in (
|
|
91
|
+
ConstraintIdentifier.ATTRIBUTE_PARAMETERS_MIN_VALUE_UPDATE.value,
|
|
92
|
+
ConstraintIdentifier.ATTRIBUTE_PARAMETERS_MAX_VALUE_UPDATE.value,
|
|
93
|
+
ConstraintIdentifier.ATTRIBUTE_PARAMETERS_EXCLUDED_VALUES_UPDATE.value,
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
async def check(self, request: SchemaConstraintValidatorRequest) -> list[GroupedDataPaths]:
|
|
97
|
+
grouped_data_paths_list: list[GroupedDataPaths] = []
|
|
98
|
+
if not request.schema_path.field_name:
|
|
99
|
+
raise ValueError("field_name is not defined")
|
|
100
|
+
attribute_schema = request.node_schema.get_attribute(name=request.schema_path.field_name)
|
|
101
|
+
if not isinstance(attribute_schema.parameters, NumberAttributeParameters):
|
|
102
|
+
raise ValueError("attribute parameters are not a NumberAttributeParameters")
|
|
103
|
+
|
|
104
|
+
if (
|
|
105
|
+
attribute_schema.parameters.min_value is None
|
|
106
|
+
and attribute_schema.parameters.max_value is None
|
|
107
|
+
and attribute_schema.parameters.excluded_values is None
|
|
108
|
+
):
|
|
109
|
+
return grouped_data_paths_list
|
|
110
|
+
|
|
111
|
+
for query_class in self.query_classes:
|
|
112
|
+
# TODO add exception handling
|
|
113
|
+
query = await query_class.init(
|
|
114
|
+
db=self.db, branch=self.branch, node_schema=request.node_schema, schema_path=request.schema_path
|
|
115
|
+
)
|
|
116
|
+
await query.execute(db=self.db)
|
|
117
|
+
grouped_data_paths_list.append(await query.get_paths())
|
|
118
|
+
return grouped_data_paths_list
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Any
|
|
4
|
+
|
|
5
|
+
from infrahub.core.constants import PathType
|
|
6
|
+
from infrahub.core.path import DataPath, GroupedDataPaths
|
|
7
|
+
from infrahub.core.schema.attribute_parameters import NumberPoolParameters
|
|
8
|
+
from infrahub.core.validators.enum import ConstraintIdentifier
|
|
9
|
+
|
|
10
|
+
from ..interface import ConstraintCheckerInterface
|
|
11
|
+
from ..shared import AttributeSchemaValidatorQuery
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from infrahub.core.branch import Branch
|
|
15
|
+
from infrahub.database import InfrahubDatabase
|
|
16
|
+
|
|
17
|
+
from ..model import SchemaConstraintValidatorRequest
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class AttributeNumberPoolUpdateValidatorQuery(AttributeSchemaValidatorQuery):
|
|
21
|
+
name: str = "attribute_constraints_numberpool_validator"
|
|
22
|
+
|
|
23
|
+
async def query_init(self, db: InfrahubDatabase, **kwargs: dict[str, Any]) -> None: # noqa: ARG002
|
|
24
|
+
branch_filter, branch_params = self.branch.get_query_filter_path(at=self.at.to_string())
|
|
25
|
+
self.params.update(branch_params)
|
|
26
|
+
|
|
27
|
+
if not isinstance(self.attribute_schema.parameters, NumberPoolParameters):
|
|
28
|
+
raise ValueError("attribute parameters are not a NumberPoolParameters")
|
|
29
|
+
|
|
30
|
+
self.params["attr_name"] = self.attribute_schema.name
|
|
31
|
+
self.params["start_range"] = self.attribute_schema.parameters.start_range
|
|
32
|
+
self.params["end_range"] = self.attribute_schema.parameters.end_range
|
|
33
|
+
|
|
34
|
+
query = """
|
|
35
|
+
MATCH (n:%(node_kind)s)
|
|
36
|
+
CALL (n) {
|
|
37
|
+
MATCH path = (root:Root)<-[rr:IS_PART_OF]-(n)-[ra:HAS_ATTRIBUTE]-(:Attribute { name: $attr_name } )-[rv:HAS_VALUE]-(av:AttributeValue)
|
|
38
|
+
WHERE all(
|
|
39
|
+
r in relationships(path)
|
|
40
|
+
WHERE %(branch_filter)s
|
|
41
|
+
)
|
|
42
|
+
RETURN path as full_path, n as node, rv as value_relationship, av.value as attribute_value
|
|
43
|
+
ORDER BY rv.branch_level DESC, ra.branch_level DESC, rr.branch_level DESC, rv.from DESC, ra.from DESC, rr.from DESC
|
|
44
|
+
LIMIT 1
|
|
45
|
+
}
|
|
46
|
+
WITH full_path, node, attribute_value, value_relationship
|
|
47
|
+
WHERE all(r in relationships(full_path) WHERE r.status = "active")
|
|
48
|
+
AND (
|
|
49
|
+
(toInteger($start_range) IS NOT NULL AND attribute_value < toInteger($start_range))
|
|
50
|
+
OR (toInteger($end_range) IS NOT NULL AND attribute_value > toInteger($end_range))
|
|
51
|
+
)
|
|
52
|
+
""" % {"branch_filter": branch_filter, "node_kind": self.node_schema.kind}
|
|
53
|
+
|
|
54
|
+
self.add_to_query(query)
|
|
55
|
+
self.return_labels = ["node.uuid", "value_relationship", "attribute_value"]
|
|
56
|
+
|
|
57
|
+
async def get_paths(self) -> GroupedDataPaths:
|
|
58
|
+
grouped_data_paths = GroupedDataPaths()
|
|
59
|
+
for result in self.results:
|
|
60
|
+
grouped_data_paths.add_data_path(
|
|
61
|
+
DataPath(
|
|
62
|
+
branch=str(result.get("value_relationship").get("branch")),
|
|
63
|
+
path_type=PathType.ATTRIBUTE,
|
|
64
|
+
node_id=str(result.get("node.uuid")),
|
|
65
|
+
field_name=self.attribute_schema.name,
|
|
66
|
+
kind=self.node_schema.kind,
|
|
67
|
+
value=result.get("attribute_value"),
|
|
68
|
+
),
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
return grouped_data_paths
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class AttributeNumberPoolChecker(ConstraintCheckerInterface):
|
|
75
|
+
query_classes = [AttributeNumberPoolUpdateValidatorQuery]
|
|
76
|
+
|
|
77
|
+
def __init__(self, db: InfrahubDatabase, branch: Branch | None = None):
|
|
78
|
+
self.db = db
|
|
79
|
+
self.branch = branch
|
|
80
|
+
|
|
81
|
+
@property
|
|
82
|
+
def name(self) -> str:
|
|
83
|
+
return "attribute.number.update"
|
|
84
|
+
|
|
85
|
+
def supports(self, request: SchemaConstraintValidatorRequest) -> bool:
|
|
86
|
+
return request.constraint_name in (
|
|
87
|
+
ConstraintIdentifier.ATTRIBUTE_PARAMETERS_START_RANGE_UPDATE.value,
|
|
88
|
+
ConstraintIdentifier.ATTRIBUTE_PARAMETERS_END_RANGE_UPDATE.value,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
async def check(self, request: SchemaConstraintValidatorRequest) -> list[GroupedDataPaths]:
|
|
92
|
+
grouped_data_paths_list: list[GroupedDataPaths] = []
|
|
93
|
+
if not request.schema_path.field_name:
|
|
94
|
+
raise ValueError("field_name is not defined")
|
|
95
|
+
attribute_schema = request.node_schema.get_attribute(name=request.schema_path.field_name)
|
|
96
|
+
if not isinstance(attribute_schema.parameters, NumberPoolParameters):
|
|
97
|
+
raise ValueError("attribute parameters are not a NumberPoolParameters")
|
|
98
|
+
|
|
99
|
+
for query_class in self.query_classes:
|
|
100
|
+
# TODO add exception handling
|
|
101
|
+
query = await query_class.init(
|
|
102
|
+
db=self.db, branch=self.branch, node_schema=request.node_schema, schema_path=request.schema_path
|
|
103
|
+
)
|
|
104
|
+
await query.execute(db=self.db)
|
|
105
|
+
grouped_data_paths_list.append(await query.get_paths())
|
|
106
|
+
return grouped_data_paths_list
|
|
@@ -38,7 +38,6 @@ class AttributeOptionalUpdateValidatorQuery(AttributeSchemaValidatorQuery):
|
|
|
38
38
|
LIMIT 1
|
|
39
39
|
}
|
|
40
40
|
WITH full_path, node, attribute_value, value_relationship
|
|
41
|
-
WITH full_path, node, attribute_value, value_relationship
|
|
42
41
|
WHERE all(r in relationships(full_path) WHERE r.status = "active")
|
|
43
42
|
AND (attribute_value IS NULL OR attribute_value = $null_value)
|
|
44
43
|
""" % {"branch_filter": branch_filter, "node_kind": self.node_schema.kind}
|
|
@@ -85,7 +84,6 @@ class AttributeOptionalChecker(ConstraintCheckerInterface):
|
|
|
85
84
|
return grouped_data_paths_list
|
|
86
85
|
|
|
87
86
|
for query_class in self.query_classes:
|
|
88
|
-
# TODO add exception handling
|
|
89
87
|
query = await query_class.init(
|
|
90
88
|
db=self.db, branch=self.branch, node_schema=request.node_schema, schema_path=request.schema_path
|
|
91
89
|
)
|
|
@@ -39,7 +39,6 @@ class AttributeRegexUpdateValidatorQuery(AttributeSchemaValidatorQuery):
|
|
|
39
39
|
LIMIT 1
|
|
40
40
|
}
|
|
41
41
|
WITH full_path, node, attribute_value, value_relationship
|
|
42
|
-
WITH full_path, node, attribute_value, value_relationship
|
|
43
42
|
WHERE all(r in relationships(full_path) WHERE r.status = "active")
|
|
44
43
|
AND attribute_value <> $null_value
|
|
45
44
|
AND NOT attribute_value =~ $attr_value_regex
|
infrahub/core/validators/enum.py
CHANGED
|
@@ -5,3 +5,8 @@ class ConstraintIdentifier(str, Enum):
|
|
|
5
5
|
ATTRIBUTE_PARAMETERS_REGEX_UPDATE = "attribute.parameters.regex.update"
|
|
6
6
|
ATTRIBUTE_PARAMETERS_MIN_LENGTH_UPDATE = "attribute.parameters.min_length.update"
|
|
7
7
|
ATTRIBUTE_PARAMETERS_MAX_LENGTH_UPDATE = "attribute.parameters.max_length.update"
|
|
8
|
+
ATTRIBUTE_PARAMETERS_MIN_VALUE_UPDATE = "attribute.parameters.min_value.update"
|
|
9
|
+
ATTRIBUTE_PARAMETERS_MAX_VALUE_UPDATE = "attribute.parameters.max_value.update"
|
|
10
|
+
ATTRIBUTE_PARAMETERS_EXCLUDED_VALUES_UPDATE = "attribute.parameters.excluded_values.update"
|
|
11
|
+
ATTRIBUTE_PARAMETERS_END_RANGE_UPDATE = "attribute.parameters.end_range.update"
|
|
12
|
+
ATTRIBUTE_PARAMETERS_START_RANGE_UPDATE = "attribute.parameters.start_range.update"
|
infrahub/database/__init__.py
CHANGED
|
@@ -39,7 +39,7 @@ if TYPE_CHECKING:
|
|
|
39
39
|
from types import TracebackType
|
|
40
40
|
|
|
41
41
|
from infrahub.core.branch import Branch
|
|
42
|
-
from infrahub.core.schema import MainSchemaTypes, NodeSchema
|
|
42
|
+
from infrahub.core.schema import GenericSchema, MainSchemaTypes, NodeSchema
|
|
43
43
|
from infrahub.core.schema.schema_branch import SchemaBranch
|
|
44
44
|
|
|
45
45
|
validated_database = {}
|
|
@@ -91,6 +91,15 @@ class DatabaseSchemaManager:
|
|
|
91
91
|
|
|
92
92
|
raise ValueError("The selected node is not of type NodeSchema")
|
|
93
93
|
|
|
94
|
+
def get_generic_schema(
|
|
95
|
+
self, name: str, branch: Branch | str | None = None, duplicate: bool = True
|
|
96
|
+
) -> GenericSchema:
|
|
97
|
+
schema = self.get(name=name, branch=branch, duplicate=duplicate)
|
|
98
|
+
if schema.is_generic_schema:
|
|
99
|
+
return schema
|
|
100
|
+
|
|
101
|
+
raise ValueError("The selected node is not of type GenericSchema")
|
|
102
|
+
|
|
94
103
|
def set(self, name: str, schema: MainSchemaTypes, branch: str | None = None) -> int:
|
|
95
104
|
branch_name = get_branch_name(branch=branch)
|
|
96
105
|
if branch_name not in self._db._schemas:
|
|
@@ -284,7 +293,7 @@ class InfrahubDatabase:
|
|
|
284
293
|
exc_type: type[BaseException] | None,
|
|
285
294
|
exc_value: BaseException | None,
|
|
286
295
|
traceback: TracebackType | None,
|
|
287
|
-
):
|
|
296
|
+
) -> None:
|
|
288
297
|
if self._mode == InfrahubDatabaseMode.SESSION:
|
|
289
298
|
return await self._session.close()
|
|
290
299
|
|
|
@@ -487,7 +496,10 @@ async def get_db(retry: int = 0) -> AsyncDriver:
|
|
|
487
496
|
auth=(config.SETTINGS.database.username, config.SETTINGS.database.password),
|
|
488
497
|
encrypted=config.SETTINGS.database.tls_enabled,
|
|
489
498
|
trusted_certificates=trusted_certificates,
|
|
490
|
-
notifications_disabled_categories=[
|
|
499
|
+
notifications_disabled_categories=[
|
|
500
|
+
NotificationDisabledCategory.UNRECOGNIZED,
|
|
501
|
+
NotificationDisabledCategory.DEPRECATION, # TODO: Remove me with 1.3
|
|
502
|
+
],
|
|
491
503
|
notifications_min_severity=NotificationMinimumSeverity.WARNING,
|
|
492
504
|
)
|
|
493
505
|
|
infrahub/git/base.py
CHANGED
|
@@ -162,6 +162,11 @@ class InfrahubRepositoryBase(BaseModel, ABC):
|
|
|
162
162
|
infrahub_branch_name: str | None = Field(None, description="Infrahub branch on which to sync the remote repository")
|
|
163
163
|
model_config = ConfigDict(arbitrary_types_allowed=True, ignored_types=(Flow, Task))
|
|
164
164
|
|
|
165
|
+
def get_client(self) -> InfrahubClient:
|
|
166
|
+
if self.client is None:
|
|
167
|
+
raise ValueError("Client is not set")
|
|
168
|
+
return self.client
|
|
169
|
+
|
|
165
170
|
@property
|
|
166
171
|
def sdk(self) -> InfrahubClient:
|
|
167
172
|
if self.client:
|
|
@@ -445,9 +450,6 @@ class InfrahubRepositoryBase(BaseModel, ABC):
|
|
|
445
450
|
|
|
446
451
|
return [Worktree.init(response) for response in responses]
|
|
447
452
|
|
|
448
|
-
def get_client(self) -> InfrahubClient:
|
|
449
|
-
return self.sdk
|
|
450
|
-
|
|
451
453
|
def get_location(self) -> str:
|
|
452
454
|
if self.location:
|
|
453
455
|
return self.location
|
infrahub/git/integrator.py
CHANGED
|
@@ -29,10 +29,12 @@ from infrahub_sdk.schema.repository import (
|
|
|
29
29
|
InfrahubPythonTransformConfig,
|
|
30
30
|
InfrahubRepositoryConfig,
|
|
31
31
|
)
|
|
32
|
+
from infrahub_sdk.spec.menu import MenuFile
|
|
33
|
+
from infrahub_sdk.spec.object import ObjectFile
|
|
32
34
|
from infrahub_sdk.template import Jinja2Template
|
|
33
35
|
from infrahub_sdk.template.exceptions import JinjaTemplateError
|
|
34
36
|
from infrahub_sdk.utils import compare_lists
|
|
35
|
-
from infrahub_sdk.yaml import SchemaFile
|
|
37
|
+
from infrahub_sdk.yaml import InfrahubFile, SchemaFile
|
|
36
38
|
from prefect import flow, task
|
|
37
39
|
from prefect.cache_policies import NONE
|
|
38
40
|
from prefect.logging import get_run_logger
|
|
@@ -40,7 +42,7 @@ from pydantic import BaseModel, Field
|
|
|
40
42
|
from pydantic import ValidationError as PydanticValidationError
|
|
41
43
|
from typing_extensions import Self
|
|
42
44
|
|
|
43
|
-
from infrahub.core.constants import ArtifactStatus, ContentType, InfrahubKind, RepositorySyncStatus
|
|
45
|
+
from infrahub.core.constants import ArtifactStatus, ContentType, InfrahubKind, RepositoryObjects, RepositorySyncStatus
|
|
44
46
|
from infrahub.core.registry import registry
|
|
45
47
|
from infrahub.events.artifact_action import ArtifactCreatedEvent, ArtifactUpdatedEvent
|
|
46
48
|
from infrahub.events.models import EventMeta
|
|
@@ -54,6 +56,7 @@ if TYPE_CHECKING:
|
|
|
54
56
|
import types
|
|
55
57
|
|
|
56
58
|
from infrahub_sdk.checks import InfrahubCheck
|
|
59
|
+
from infrahub_sdk.ctl.utils import YamlFileVar
|
|
57
60
|
from infrahub_sdk.schema.repository import InfrahubRepositoryArtifactDefinitionConfig
|
|
58
61
|
from infrahub_sdk.transforms import InfrahubTransform
|
|
59
62
|
|
|
@@ -159,7 +162,7 @@ class InfrahubRepositoryIntegrator(InfrahubRepositoryBase):
|
|
|
159
162
|
async def ensure_location_is_defined(self) -> None:
|
|
160
163
|
if self.location:
|
|
161
164
|
return
|
|
162
|
-
client = self.
|
|
165
|
+
client = self.sdk
|
|
163
166
|
repo = await client.get(
|
|
164
167
|
kind=CoreGenericRepository, name__value=self.name, exclude=["tags", "credential"], raise_when_missing=True
|
|
165
168
|
)
|
|
@@ -179,6 +182,7 @@ class InfrahubRepositoryIntegrator(InfrahubRepositoryBase):
|
|
|
179
182
|
|
|
180
183
|
config_file = await self.get_repository_config(branch_name=infrahub_branch_name, commit=commit) # type: ignore[misc]
|
|
181
184
|
sync_status = RepositorySyncStatus.IN_SYNC if config_file else RepositorySyncStatus.ERROR_IMPORT
|
|
185
|
+
|
|
182
186
|
error: Exception | None = None
|
|
183
187
|
|
|
184
188
|
try:
|
|
@@ -189,6 +193,17 @@ class InfrahubRepositoryIntegrator(InfrahubRepositoryBase):
|
|
|
189
193
|
branch_name=infrahub_branch_name, commit=commit, config_file=config_file
|
|
190
194
|
) # type: ignore[misc]
|
|
191
195
|
|
|
196
|
+
await self.import_objects(
|
|
197
|
+
branch_name=infrahub_branch_name,
|
|
198
|
+
commit=commit,
|
|
199
|
+
config_file=config_file,
|
|
200
|
+
) # type: ignore[misc]
|
|
201
|
+
await self.import_objects(
|
|
202
|
+
branch_name=infrahub_branch_name,
|
|
203
|
+
commit=commit,
|
|
204
|
+
config_file=config_file,
|
|
205
|
+
) # type: ignore[misc]
|
|
206
|
+
|
|
192
207
|
await self.import_all_python_files( # type: ignore[call-overload]
|
|
193
208
|
branch_name=infrahub_branch_name, commit=commit, config_file=config_file
|
|
194
209
|
) # type: ignore[misc]
|
|
@@ -815,6 +830,80 @@ class InfrahubRepositoryIntegrator(InfrahubRepositoryBase):
|
|
|
815
830
|
log.info(f"TransformPython {transform_name!r} not found locally, deleting")
|
|
816
831
|
await transform_definition_in_graph[transform_name].delete()
|
|
817
832
|
|
|
833
|
+
async def _load_yamlfile_from_disk(self, paths: list[Path], file_type: type[YamlFileVar]) -> list[YamlFileVar]:
|
|
834
|
+
data_files = file_type.load_from_disk(paths=paths)
|
|
835
|
+
|
|
836
|
+
for data_file in data_files:
|
|
837
|
+
if not data_file.valid or not data_file.content:
|
|
838
|
+
raise ValueError(f"{data_file.error_message} ({data_file.location})")
|
|
839
|
+
|
|
840
|
+
return data_files
|
|
841
|
+
|
|
842
|
+
async def _load_objects(
|
|
843
|
+
self,
|
|
844
|
+
paths: list[Path],
|
|
845
|
+
branch: str,
|
|
846
|
+
file_type: type[InfrahubFile],
|
|
847
|
+
) -> None:
|
|
848
|
+
"""Load one or multiple objects files into Infrahub."""
|
|
849
|
+
|
|
850
|
+
log = get_run_logger()
|
|
851
|
+
files = await self._load_yamlfile_from_disk(paths=paths, file_type=file_type)
|
|
852
|
+
|
|
853
|
+
for file in files:
|
|
854
|
+
await file.validate_format(client=self.sdk, branch=branch)
|
|
855
|
+
schema = await self.sdk.schema.get(kind=file.spec.kind, branch=branch)
|
|
856
|
+
if not schema.human_friendly_id and not schema.default_filter:
|
|
857
|
+
raise ValueError(
|
|
858
|
+
f"Schemas of objects or menus defined within {file.location} "
|
|
859
|
+
"should have a `human_friendly_id` defined to avoid creating duplicated objects."
|
|
860
|
+
)
|
|
861
|
+
|
|
862
|
+
for file in files:
|
|
863
|
+
log.info(f"Loading objects defined in {file.location}")
|
|
864
|
+
await file.process(client=self.sdk, branch=branch)
|
|
865
|
+
|
|
866
|
+
async def _import_file_paths(
|
|
867
|
+
self, branch_name: str, commit: str, files_pathes: list[Path], object_type: RepositoryObjects
|
|
868
|
+
) -> None:
|
|
869
|
+
branch_wt = self.get_worktree(identifier=commit or branch_name)
|
|
870
|
+
file_pathes = [branch_wt.directory / file_path for file_path in files_pathes]
|
|
871
|
+
|
|
872
|
+
# We currently assume there can't be concurrent imports, but if so, we might need to clone the client before tracking here.
|
|
873
|
+
async with self.sdk.start_tracking(
|
|
874
|
+
identifier=f"group-repo-{object_type.value}-{self.id}",
|
|
875
|
+
delete_unused_nodes=True,
|
|
876
|
+
branch=branch_name,
|
|
877
|
+
group_type="CoreRepositoryGroup",
|
|
878
|
+
group_params={"content": object_type.value, "repository": str(self.id)},
|
|
879
|
+
):
|
|
880
|
+
file_type = repo_object_type_to_file_type(object_type)
|
|
881
|
+
await self._load_objects(
|
|
882
|
+
paths=file_pathes,
|
|
883
|
+
branch=branch_name,
|
|
884
|
+
file_type=file_type,
|
|
885
|
+
)
|
|
886
|
+
|
|
887
|
+
@task(name="import-objects", task_run_name="Import Objects", cache_policy=NONE) # type: ignore[arg-type]
|
|
888
|
+
async def import_objects(
|
|
889
|
+
self,
|
|
890
|
+
branch_name: str,
|
|
891
|
+
commit: str,
|
|
892
|
+
config_file: InfrahubRepositoryConfig,
|
|
893
|
+
) -> None:
|
|
894
|
+
await self._import_file_paths(
|
|
895
|
+
branch_name=branch_name,
|
|
896
|
+
commit=commit,
|
|
897
|
+
files_pathes=config_file.objects,
|
|
898
|
+
object_type=RepositoryObjects.OBJECT,
|
|
899
|
+
)
|
|
900
|
+
await self._import_file_paths(
|
|
901
|
+
branch_name=branch_name,
|
|
902
|
+
commit=commit,
|
|
903
|
+
files_pathes=config_file.menus,
|
|
904
|
+
object_type=RepositoryObjects.MENU,
|
|
905
|
+
)
|
|
906
|
+
|
|
818
907
|
@task(name="check-definition-get", task_run_name="Get Check Definition", cache_policy=NONE) # type: ignore[arg-type]
|
|
819
908
|
async def get_check_definition(
|
|
820
909
|
self,
|
|
@@ -1342,3 +1431,13 @@ class InfrahubRepositoryIntegrator(InfrahubRepositoryBase):
|
|
|
1342
1431
|
|
|
1343
1432
|
await self.service.event.send(event=event)
|
|
1344
1433
|
return ArtifactGenerateResult(changed=True, checksum=checksum, storage_id=storage_id, artifact_id=artifact.id)
|
|
1434
|
+
|
|
1435
|
+
|
|
1436
|
+
def repo_object_type_to_file_type(repo_object: RepositoryObjects) -> type[InfrahubFile]:
|
|
1437
|
+
match repo_object:
|
|
1438
|
+
case RepositoryObjects.OBJECT:
|
|
1439
|
+
return ObjectFile
|
|
1440
|
+
case RepositoryObjects.MENU:
|
|
1441
|
+
return MenuFile
|
|
1442
|
+
case _:
|
|
1443
|
+
raise ValueError(f"Unknown repository object type: {repo_object}")
|