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
|
@@ -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
|
|
@@ -89,7 +91,7 @@ class SchemaBranch:
|
|
|
89
91
|
self.templates = data.get("templates", {})
|
|
90
92
|
|
|
91
93
|
@classmethod
|
|
92
|
-
def validate(cls, data: Any) -> Self:
|
|
94
|
+
def validate(cls, data: Any) -> Self:
|
|
93
95
|
if isinstance(data, cls):
|
|
94
96
|
return data
|
|
95
97
|
if isinstance(data, dict):
|
|
@@ -451,7 +453,7 @@ class SchemaBranch:
|
|
|
451
453
|
if isinstance(node, NodeSchema | ProfileSchema | TemplateSchema):
|
|
452
454
|
return node.generate_fields_for_display_label()
|
|
453
455
|
|
|
454
|
-
fields: dict[str, str |
|
|
456
|
+
fields: dict[str, str | dict[str, None] | None] = {}
|
|
455
457
|
if isinstance(node, GenericSchema):
|
|
456
458
|
for child_node_name in node.used_by:
|
|
457
459
|
child_node = self.get(name=child_node_name, duplicate=False)
|
|
@@ -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()
|
|
@@ -994,6 +997,65 @@ class SchemaBranch:
|
|
|
994
997
|
raise ValueError(
|
|
995
998
|
f"{node.kind}: Relationship {rel.name!r} is referring an invalid peer {rel.peer!r}"
|
|
996
999
|
) from None
|
|
1000
|
+
if rel.common_relatives:
|
|
1001
|
+
peer_schema = self.get(name=rel.peer, duplicate=False)
|
|
1002
|
+
for common_relatives_rel_name in rel.common_relatives:
|
|
1003
|
+
if common_relatives_rel_name not in peer_schema.relationship_names:
|
|
1004
|
+
raise ValueError(
|
|
1005
|
+
f"{node.kind}: Relationship {rel.name!r} set 'common_relatives' with invalid relationship from '{rel.peer}'"
|
|
1006
|
+
) from None
|
|
1007
|
+
|
|
1008
|
+
def validate_attribute_parameters(self) -> None:
|
|
1009
|
+
for name in self.generics.keys():
|
|
1010
|
+
generic_schema = self.get_generic(name=name, duplicate=False)
|
|
1011
|
+
for attribute in generic_schema.attributes:
|
|
1012
|
+
if (
|
|
1013
|
+
attribute.kind == "NumberPool"
|
|
1014
|
+
and isinstance(attribute.parameters, NumberPoolParameters)
|
|
1015
|
+
and not attribute.parameters.number_pool_id
|
|
1016
|
+
):
|
|
1017
|
+
attribute.parameters.number_pool_id = str(uuid4())
|
|
1018
|
+
|
|
1019
|
+
for name in self.nodes.keys():
|
|
1020
|
+
node_schema = self.get_node(name=name, duplicate=False)
|
|
1021
|
+
for attribute in node_schema.attributes:
|
|
1022
|
+
if (
|
|
1023
|
+
attribute.kind == "NumberPool"
|
|
1024
|
+
and isinstance(attribute.parameters, NumberPoolParameters)
|
|
1025
|
+
and not attribute.parameters.number_pool_id
|
|
1026
|
+
):
|
|
1027
|
+
self._validate_number_pool_parameters(
|
|
1028
|
+
node_schema=node_schema, attribute=attribute, number_pool_parameters=attribute.parameters
|
|
1029
|
+
)
|
|
1030
|
+
|
|
1031
|
+
def _validate_number_pool_parameters(
|
|
1032
|
+
self, node_schema: NodeSchema, attribute: AttributeSchema, number_pool_parameters: NumberPoolParameters
|
|
1033
|
+
) -> None:
|
|
1034
|
+
if attribute.optional:
|
|
1035
|
+
raise ValidationError(f"{node_schema.kind}.{attribute.name} is a NumberPool it can't be optional")
|
|
1036
|
+
|
|
1037
|
+
if not attribute.read_only:
|
|
1038
|
+
raise ValidationError(
|
|
1039
|
+
f"{node_schema.kind}.{attribute.name} is a NumberPool it has to be a read_only attribute"
|
|
1040
|
+
)
|
|
1041
|
+
|
|
1042
|
+
if attribute.inherited:
|
|
1043
|
+
generics_with_attribute = []
|
|
1044
|
+
for generic_name in node_schema.inherit_from:
|
|
1045
|
+
generic_schema = self.get_generic(name=generic_name, duplicate=False)
|
|
1046
|
+
if attribute.name in generic_schema.attribute_names:
|
|
1047
|
+
generic_attribute = generic_schema.get_attribute(name=attribute.name)
|
|
1048
|
+
generics_with_attribute.append(generic_schema)
|
|
1049
|
+
if isinstance(generic_attribute.parameters, NumberPoolParameters):
|
|
1050
|
+
number_pool_parameters.number_pool_id = generic_attribute.parameters.number_pool_id
|
|
1051
|
+
|
|
1052
|
+
if len(generics_with_attribute) > 1:
|
|
1053
|
+
raise ValidationError(
|
|
1054
|
+
f"{node_schema.kind}.{attribute.name} is a NumberPool inherited from more than one generic"
|
|
1055
|
+
)
|
|
1056
|
+
|
|
1057
|
+
else:
|
|
1058
|
+
number_pool_parameters.number_pool_id = str(uuid4())
|
|
997
1059
|
|
|
998
1060
|
def validate_computed_attributes(self) -> None:
|
|
999
1061
|
self.computed_attributes = ComputedAttributes()
|
|
@@ -1960,7 +2022,11 @@ class SchemaBranch:
|
|
|
1960
2022
|
)
|
|
1961
2023
|
|
|
1962
2024
|
parent_hfid = f"{relationship.name}__template_name__value"
|
|
1963
|
-
if
|
|
2025
|
+
if (
|
|
2026
|
+
not isinstance(template_schema, GenericSchema)
|
|
2027
|
+
and relationship.kind == RelationshipKind.PARENT
|
|
2028
|
+
and parent_hfid not in template_schema.human_friendly_id
|
|
2029
|
+
):
|
|
1964
2030
|
template_schema.human_friendly_id = [parent_hfid] + template_schema.human_friendly_id
|
|
1965
2031
|
template_schema.uniqueness_constraints[0].append(relationship.name)
|
|
1966
2032
|
|
|
@@ -1996,7 +2062,6 @@ class SchemaBranch:
|
|
|
1996
2062
|
include_in_menu=False,
|
|
1997
2063
|
display_labels=["template_name__value"],
|
|
1998
2064
|
human_friendly_id=["template_name__value"],
|
|
1999
|
-
uniqueness_constraints=[["template_name__value"]],
|
|
2000
2065
|
attributes=[template_name_attr],
|
|
2001
2066
|
)
|
|
2002
2067
|
|
|
@@ -2015,7 +2080,6 @@ class SchemaBranch:
|
|
|
2015
2080
|
human_friendly_id=["template_name__value"],
|
|
2016
2081
|
uniqueness_constraints=[["template_name__value"]],
|
|
2017
2082
|
inherit_from=[InfrahubKind.LINEAGESOURCE, InfrahubKind.NODE, core_template_schema.kind],
|
|
2018
|
-
default_filter="template_name__value",
|
|
2019
2083
|
attributes=[template_name_attr],
|
|
2020
2084
|
relationships=[
|
|
2021
2085
|
RelationshipSchema(
|
|
@@ -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"
|
|
@@ -9,7 +9,7 @@ from prefect.logging import get_run_logger
|
|
|
9
9
|
|
|
10
10
|
from infrahub.core.branch import Branch # noqa: TC001
|
|
11
11
|
from infrahub.core.path import SchemaPath # noqa: TC001
|
|
12
|
-
from infrahub.core.schema import GenericSchema, NodeSchema
|
|
12
|
+
from infrahub.core.schema import GenericSchema, NodeSchema
|
|
13
13
|
from infrahub.core.validators.aggregated_checker import AggregatedConstraintChecker
|
|
14
14
|
from infrahub.core.validators.model import (
|
|
15
15
|
SchemaConstraintValidatorRequest,
|
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
|
|
|
@@ -330,7 +339,7 @@ class InfrahubDatabase:
|
|
|
330
339
|
CONNECTION_POOL_USAGE.labels(self._driver._pool.address).set(float(connpool_usage))
|
|
331
340
|
|
|
332
341
|
if config.SETTINGS.database.max_concurrent_queries:
|
|
333
|
-
while connpool_usage > config.SETTINGS.database.max_concurrent_queries:
|
|
342
|
+
while connpool_usage > config.SETTINGS.database.max_concurrent_queries:
|
|
334
343
|
await asyncio.sleep(config.SETTINGS.database.max_concurrent_queries_delay)
|
|
335
344
|
connpool_usage = self._driver._pool.in_use_connection_count(self._driver._pool.address)
|
|
336
345
|
|
|
@@ -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
|
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
from infrahub.database import InfrahubDatabase
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
async def verify_no_duplicate_relationships(db: InfrahubDatabase) -> None:
|
|
5
|
+
"""
|
|
6
|
+
Verify that no duplicate active relationships exist at the database level
|
|
7
|
+
A duplicate is defined as
|
|
8
|
+
- connecting the same two nodes
|
|
9
|
+
- having the same identifier
|
|
10
|
+
- having the same direction (inbound, outbound, bidirectional)
|
|
11
|
+
- having the same branch
|
|
12
|
+
A more thorough check that no duplicates exist at any point in time is possible, but more complex
|
|
13
|
+
"""
|
|
14
|
+
query = """
|
|
15
|
+
MATCH (a:Node)-[e1:IS_RELATED {status: "active"}]-(rel:Relationship)-[e2:IS_RELATED {branch: e1.branch, status: "active"}]-(b:Node)
|
|
16
|
+
WHERE a.uuid <> b.uuid
|
|
17
|
+
AND e1.to IS NULL
|
|
18
|
+
AND e2.to IS NULL
|
|
19
|
+
WITH a, rel.name AS rel_name, b, e1.branch AS branch, CASE
|
|
20
|
+
WHEN startNode(e1) = a AND startNode(e2) = rel THEN "out"
|
|
21
|
+
WHEN startNode(e1) = rel AND startNode(e2) = b THEN "in"
|
|
22
|
+
ELSE "bidir"
|
|
23
|
+
END AS direction, COUNT(*) AS num_duplicates
|
|
24
|
+
WHERE num_duplicates > 1
|
|
25
|
+
RETURN a.uuid AS node_id1, b.uuid AS node_id2, rel_name, branch, direction, num_duplicates
|
|
26
|
+
"""
|
|
27
|
+
results = await db.execute_query(query=query)
|
|
28
|
+
for result in results:
|
|
29
|
+
node_id1 = result.get("node_id1")
|
|
30
|
+
node_id2 = result.get("node_id2")
|
|
31
|
+
rel_name = result.get("rel_name")
|
|
32
|
+
branch = result.get("branch")
|
|
33
|
+
direction = result.get("direction")
|
|
34
|
+
num_duplicates = result.get("num_duplicates")
|
|
35
|
+
raise ValueError(
|
|
36
|
+
f"{num_duplicates} duplicate relationships ({branch=},{direction=}) between nodes '{node_id1}' and '{node_id2}'"
|
|
37
|
+
f" with relationship name '{rel_name}'"
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
async def verify_no_edges_added_after_node_delete(db: InfrahubDatabase) -> None:
|
|
42
|
+
"""
|
|
43
|
+
Verify that no edges are added to a Node after it is deleted on a given branch
|
|
44
|
+
"""
|
|
45
|
+
query = """
|
|
46
|
+
// ------------
|
|
47
|
+
// find deleted nodes
|
|
48
|
+
// ------------
|
|
49
|
+
MATCH (n:Node)-[e:IS_PART_OF]->(:Root)
|
|
50
|
+
WHERE e.status = "deleted" OR e.to IS NOT NULL
|
|
51
|
+
WITH DISTINCT n, e.branch AS delete_branch, e.branch_level AS delete_branch_level, CASE
|
|
52
|
+
WHEN e.status = "deleted" THEN e.from
|
|
53
|
+
ELSE e.to
|
|
54
|
+
END AS delete_time
|
|
55
|
+
// ------------
|
|
56
|
+
// find the edges added to the deleted node after the delete time
|
|
57
|
+
// ------------
|
|
58
|
+
MATCH (n)-[added_e]-(peer)
|
|
59
|
+
WHERE added_e.from > delete_time
|
|
60
|
+
AND type(added_e) <> "IS_PART_OF"
|
|
61
|
+
// if the node was deleted on a branch (delete_branch_level > 1), and then updated on main/global (added_e.branch_level = 1), we can ignore it
|
|
62
|
+
AND added_e.branch_level >= delete_branch_level
|
|
63
|
+
AND (added_e.branch = delete_branch OR delete_branch_level = 1)
|
|
64
|
+
WITH DISTINCT n, delete_branch, delete_time, added_e, peer AS added_peer
|
|
65
|
+
// ------------
|
|
66
|
+
// get the branched_from for the branch on which the node was deleted
|
|
67
|
+
// ------------
|
|
68
|
+
CALL (added_e) {
|
|
69
|
+
MATCH (b:Branch {name: added_e.branch})
|
|
70
|
+
RETURN b.branched_from AS added_e_branched_from
|
|
71
|
+
}
|
|
72
|
+
// ------------
|
|
73
|
+
// account for the following situations, given that the edge update time is after the node delete time
|
|
74
|
+
// - deleted on main/global, updated on branch
|
|
75
|
+
// - illegal if the delete is before branch.branched_from
|
|
76
|
+
// - deleted on branch, updated on branch
|
|
77
|
+
// - illegal
|
|
78
|
+
// ------------
|
|
79
|
+
WITH n, delete_branch, delete_time, added_e, added_peer
|
|
80
|
+
WHERE delete_branch = added_e.branch
|
|
81
|
+
OR delete_time < added_e_branched_from
|
|
82
|
+
RETURN n.uuid AS n_uuid, delete_branch, delete_time, added_e, added_peer
|
|
83
|
+
"""
|
|
84
|
+
results = await db.execute_query(query=query)
|
|
85
|
+
error_messages = []
|
|
86
|
+
for result in results:
|
|
87
|
+
n_uuid = result.get("n_uuid")
|
|
88
|
+
delete_branch = result.get("delete_branch")
|
|
89
|
+
delete_time = result.get("delete_time")
|
|
90
|
+
added_e = result.get("added_e")
|
|
91
|
+
added_e_branch = added_e.get("branch")
|
|
92
|
+
added_e_from = added_e.get("from")
|
|
93
|
+
added_peer = result.get("added_peer")
|
|
94
|
+
message = (
|
|
95
|
+
f"Node {n_uuid} was deleted on {delete_branch} at {delete_time} but has an {added_e.type} edge added on"
|
|
96
|
+
f" branch {added_e_branch} at {added_e_from} to {added_peer.element_id}"
|
|
97
|
+
)
|
|
98
|
+
error_messages.append(message)
|
|
99
|
+
if error_messages:
|
|
100
|
+
raise ValueError(error_messages)
|
|
@@ -4,6 +4,7 @@ from infrahub.dependencies.interface import DependencyBuilder, DependencyBuilder
|
|
|
4
4
|
from ..node.grouped_uniqueness import NodeGroupedUniquenessConstraintDependency
|
|
5
5
|
from ..relationship_manager.count import RelationshipCountConstraintDependency
|
|
6
6
|
from ..relationship_manager.peer_kind import RelationshipPeerKindConstraintDependency
|
|
7
|
+
from ..relationship_manager.peer_relatives import RelationshipPeerRelativesConstraintDependency
|
|
7
8
|
from ..relationship_manager.profiles_kind import RelationshipProfilesKindConstraintDependency
|
|
8
9
|
|
|
9
10
|
|
|
@@ -18,5 +19,6 @@ class NodeConstraintRunnerDependency(DependencyBuilder[NodeConstraintRunner]):
|
|
|
18
19
|
RelationshipPeerKindConstraintDependency.build(context=context),
|
|
19
20
|
RelationshipCountConstraintDependency.build(context=context),
|
|
20
21
|
RelationshipProfilesKindConstraintDependency.build(context=context),
|
|
22
|
+
RelationshipPeerRelativesConstraintDependency.build(context=context),
|
|
21
23
|
],
|
|
22
24
|
)
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
from infrahub.core.relationship.constraints.peer_relatives import RelationshipPeerRelativesConstraint
|
|
2
|
+
from infrahub.dependencies.interface import DependencyBuilder, DependencyBuilderContext
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class RelationshipPeerRelativesConstraintDependency(DependencyBuilder[RelationshipPeerRelativesConstraint]):
|
|
6
|
+
@classmethod
|
|
7
|
+
def build(cls, context: DependencyBuilderContext) -> RelationshipPeerRelativesConstraint:
|
|
8
|
+
return RelationshipPeerRelativesConstraint(db=context.db, branch=context.branch)
|
|
@@ -6,5 +6,5 @@ from .parent_node_adder import DiffParentNodeAdderDependency
|
|
|
6
6
|
|
|
7
7
|
class DiffDeserializerDependency(DependencyBuilder[EnrichedDiffDeserializer]):
|
|
8
8
|
@classmethod
|
|
9
|
-
def build(cls, context: DependencyBuilderContext) -> EnrichedDiffDeserializer:
|
|
9
|
+
def build(cls, context: DependencyBuilderContext) -> EnrichedDiffDeserializer:
|
|
10
10
|
return EnrichedDiffDeserializer(parent_adder=DiffParentNodeAdderDependency.build(context=context))
|