infrahub-server 1.4.10__py3-none-any.whl → 1.5.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/actions/tasks.py +208 -16
- infrahub/api/artifact.py +3 -0
- infrahub/api/diff/diff.py +1 -1
- infrahub/api/query.py +2 -0
- infrahub/api/schema.py +3 -0
- infrahub/auth.py +5 -5
- infrahub/cli/db.py +26 -2
- infrahub/cli/db_commands/clean_duplicate_schema_fields.py +212 -0
- infrahub/config.py +7 -2
- infrahub/core/attribute.py +25 -22
- infrahub/core/branch/models.py +2 -2
- infrahub/core/branch/needs_rebase_status.py +11 -0
- infrahub/core/branch/tasks.py +4 -3
- infrahub/core/changelog/models.py +4 -12
- infrahub/core/constants/__init__.py +1 -0
- infrahub/core/constants/infrahubkind.py +1 -0
- infrahub/core/convert_object_type/object_conversion.py +201 -0
- infrahub/core/convert_object_type/repository_conversion.py +89 -0
- infrahub/core/convert_object_type/schema_mapping.py +27 -3
- infrahub/core/diff/model/path.py +4 -0
- infrahub/core/diff/payload_builder.py +1 -1
- infrahub/core/diff/query/artifact.py +1 -1
- infrahub/core/graph/__init__.py +1 -1
- infrahub/core/initialization.py +2 -2
- infrahub/core/ipam/utilization.py +1 -1
- infrahub/core/manager.py +9 -84
- infrahub/core/migrations/graph/__init__.py +6 -0
- infrahub/core/migrations/graph/m040_profile_attrs_in_db.py +166 -0
- infrahub/core/migrations/graph/m041_create_hfid_display_label_in_db.py +97 -0
- infrahub/core/migrations/graph/m042_backfill_hfid_display_label_in_db.py +86 -0
- infrahub/core/migrations/schema/node_attribute_add.py +5 -2
- infrahub/core/migrations/shared.py +5 -6
- infrahub/core/node/__init__.py +165 -42
- infrahub/core/node/constraints/attribute_uniqueness.py +3 -1
- infrahub/core/node/create.py +67 -35
- infrahub/core/node/lock_utils.py +98 -0
- infrahub/core/node/node_property_attribute.py +230 -0
- infrahub/core/node/standard.py +1 -1
- infrahub/core/property.py +11 -0
- infrahub/core/protocols.py +8 -1
- infrahub/core/query/attribute.py +27 -15
- infrahub/core/query/node.py +61 -185
- infrahub/core/query/relationship.py +43 -26
- infrahub/core/query/subquery.py +0 -8
- infrahub/core/registry.py +2 -2
- infrahub/core/relationship/constraints/count.py +1 -1
- infrahub/core/relationship/model.py +60 -20
- infrahub/core/schema/attribute_schema.py +0 -2
- infrahub/core/schema/basenode_schema.py +42 -2
- infrahub/core/schema/definitions/core/__init__.py +2 -0
- infrahub/core/schema/definitions/core/generator.py +2 -0
- infrahub/core/schema/definitions/core/group.py +16 -2
- infrahub/core/schema/definitions/core/repository.py +7 -0
- infrahub/core/schema/definitions/internal.py +14 -1
- infrahub/core/schema/generated/base_node_schema.py +6 -1
- infrahub/core/schema/node_schema.py +5 -2
- infrahub/core/schema/relationship_schema.py +0 -1
- infrahub/core/schema/schema_branch.py +137 -2
- infrahub/core/schema/schema_branch_display.py +123 -0
- infrahub/core/schema/schema_branch_hfid.py +114 -0
- infrahub/core/validators/aggregated_checker.py +1 -1
- infrahub/core/validators/determiner.py +12 -1
- infrahub/core/validators/relationship/peer.py +1 -1
- infrahub/core/validators/tasks.py +1 -1
- infrahub/display_labels/__init__.py +0 -0
- infrahub/display_labels/gather.py +48 -0
- infrahub/display_labels/models.py +240 -0
- infrahub/display_labels/tasks.py +186 -0
- infrahub/display_labels/triggers.py +22 -0
- infrahub/events/group_action.py +1 -1
- infrahub/events/node_action.py +1 -1
- infrahub/generators/constants.py +7 -0
- infrahub/generators/models.py +38 -12
- infrahub/generators/tasks.py +34 -16
- infrahub/git/base.py +38 -1
- infrahub/git/integrator.py +22 -14
- infrahub/graphql/analyzer.py +1 -1
- infrahub/graphql/api/dependencies.py +2 -4
- infrahub/graphql/api/endpoints.py +2 -2
- infrahub/graphql/app.py +2 -4
- infrahub/graphql/initialization.py +2 -3
- infrahub/graphql/manager.py +212 -137
- infrahub/graphql/middleware.py +12 -0
- infrahub/graphql/mutations/branch.py +11 -0
- infrahub/graphql/mutations/computed_attribute.py +110 -3
- infrahub/graphql/mutations/convert_object_type.py +34 -13
- infrahub/graphql/mutations/display_label.py +111 -0
- infrahub/graphql/mutations/generator.py +25 -7
- infrahub/graphql/mutations/hfid.py +118 -0
- infrahub/graphql/mutations/ipam.py +21 -8
- infrahub/graphql/mutations/main.py +37 -153
- infrahub/graphql/mutations/profile.py +195 -0
- infrahub/graphql/mutations/proposed_change.py +2 -1
- infrahub/graphql/mutations/relationship.py +2 -2
- infrahub/graphql/mutations/repository.py +22 -83
- infrahub/graphql/mutations/resource_manager.py +2 -2
- infrahub/graphql/mutations/schema.py +5 -5
- infrahub/graphql/mutations/webhook.py +1 -1
- infrahub/graphql/queries/resource_manager.py +1 -1
- infrahub/graphql/registry.py +173 -0
- infrahub/graphql/resolvers/resolver.py +2 -0
- infrahub/graphql/schema.py +8 -1
- infrahub/groups/tasks.py +1 -1
- infrahub/hfid/__init__.py +0 -0
- infrahub/hfid/gather.py +48 -0
- infrahub/hfid/models.py +240 -0
- infrahub/hfid/tasks.py +185 -0
- infrahub/hfid/triggers.py +22 -0
- infrahub/lock.py +67 -30
- infrahub/locks/__init__.py +0 -0
- infrahub/locks/tasks.py +37 -0
- infrahub/middleware.py +26 -1
- infrahub/patch/plan_writer.py +2 -2
- infrahub/profiles/__init__.py +0 -0
- infrahub/profiles/node_applier.py +101 -0
- infrahub/profiles/queries/__init__.py +0 -0
- infrahub/profiles/queries/get_profile_data.py +99 -0
- infrahub/profiles/tasks.py +63 -0
- infrahub/proposed_change/tasks.py +10 -1
- infrahub/repositories/__init__.py +0 -0
- infrahub/repositories/create_repository.py +113 -0
- infrahub/server.py +16 -3
- infrahub/services/__init__.py +8 -5
- infrahub/tasks/registry.py +6 -4
- infrahub/trigger/catalogue.py +4 -0
- infrahub/trigger/models.py +2 -0
- infrahub/trigger/tasks.py +3 -0
- infrahub/webhook/models.py +1 -1
- infrahub/workflows/catalogue.py +110 -3
- infrahub/workflows/initialization.py +16 -0
- infrahub/workflows/models.py +17 -2
- infrahub_sdk/branch.py +5 -8
- infrahub_sdk/checks.py +1 -1
- infrahub_sdk/client.py +364 -84
- infrahub_sdk/convert_object_type.py +61 -0
- infrahub_sdk/ctl/check.py +2 -3
- infrahub_sdk/ctl/cli_commands.py +18 -12
- infrahub_sdk/ctl/config.py +8 -2
- infrahub_sdk/ctl/generator.py +6 -3
- infrahub_sdk/ctl/graphql.py +184 -0
- infrahub_sdk/ctl/repository.py +39 -1
- infrahub_sdk/ctl/schema.py +18 -3
- infrahub_sdk/ctl/utils.py +4 -0
- infrahub_sdk/ctl/validate.py +5 -3
- infrahub_sdk/diff.py +4 -5
- infrahub_sdk/exceptions.py +2 -0
- infrahub_sdk/generator.py +7 -1
- infrahub_sdk/graphql/__init__.py +12 -0
- infrahub_sdk/graphql/constants.py +1 -0
- infrahub_sdk/graphql/plugin.py +85 -0
- infrahub_sdk/graphql/query.py +77 -0
- infrahub_sdk/{graphql.py → graphql/renderers.py} +88 -75
- infrahub_sdk/graphql/utils.py +40 -0
- infrahub_sdk/node/attribute.py +2 -0
- infrahub_sdk/node/node.py +28 -20
- infrahub_sdk/playback.py +1 -2
- infrahub_sdk/protocols.py +54 -6
- infrahub_sdk/pytest_plugin/plugin.py +7 -4
- infrahub_sdk/pytest_plugin/utils.py +40 -0
- infrahub_sdk/repository.py +1 -2
- infrahub_sdk/schema/__init__.py +38 -0
- infrahub_sdk/schema/main.py +1 -0
- infrahub_sdk/schema/repository.py +8 -0
- infrahub_sdk/spec/object.py +120 -7
- infrahub_sdk/spec/range_expansion.py +118 -0
- infrahub_sdk/timestamp.py +18 -6
- infrahub_sdk/transforms.py +1 -1
- {infrahub_server-1.4.10.dist-info → infrahub_server-1.5.0b1.dist-info}/METADATA +9 -11
- {infrahub_server-1.4.10.dist-info → infrahub_server-1.5.0b1.dist-info}/RECORD +177 -134
- infrahub_testcontainers/container.py +1 -1
- infrahub_testcontainers/docker-compose-cluster.test.yml +1 -1
- infrahub_testcontainers/docker-compose.test.yml +1 -1
- infrahub_testcontainers/models.py +2 -2
- infrahub_testcontainers/performance_test.py +4 -4
- infrahub/core/convert_object_type/conversion.py +0 -134
- {infrahub_server-1.4.10.dist-info → infrahub_server-1.5.0b1.dist-info}/LICENSE.txt +0 -0
- {infrahub_server-1.4.10.dist-info → infrahub_server-1.5.0b1.dist-info}/WHEEL +0 -0
- {infrahub_server-1.4.10.dist-info → infrahub_server-1.5.0b1.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
from copy import deepcopy
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from infrahub.core.schema import SchemaAttributePath
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class TemplateLabel:
|
|
14
|
+
template: str
|
|
15
|
+
attributes: set[str] = field(default_factory=set)
|
|
16
|
+
relationships: set[str] = field(default_factory=set)
|
|
17
|
+
filter_key: str = "ids"
|
|
18
|
+
|
|
19
|
+
@property
|
|
20
|
+
def fields(self) -> list[str]:
|
|
21
|
+
return sorted(list(self.attributes) + list(self.relationships))
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def has_related_components(self) -> bool:
|
|
25
|
+
"""Indicate if the associated template use variables from relationships"""
|
|
26
|
+
return len(self.relationships) > 0
|
|
27
|
+
|
|
28
|
+
def get_hash(self) -> str:
|
|
29
|
+
return hashlib.md5(self.template.encode(), usedforsecurity=False).hexdigest()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class RelationshipIdentifier:
|
|
34
|
+
kind: str
|
|
35
|
+
filter_key: str
|
|
36
|
+
template: str
|
|
37
|
+
|
|
38
|
+
def __hash__(self) -> int:
|
|
39
|
+
return hash(f"{self.kind}::{self.filter_key}::{self.template}")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass
|
|
43
|
+
class RelationshipTriggers:
|
|
44
|
+
attributes: dict[str, set[RelationshipIdentifier]] = field(default_factory=dict)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class DisplayLabels:
|
|
48
|
+
def __init__(
|
|
49
|
+
self,
|
|
50
|
+
template_based_display_labels: dict[str, TemplateLabel] | None = None,
|
|
51
|
+
template_relationship_triggers: dict[str, RelationshipTriggers] | None = None,
|
|
52
|
+
) -> None:
|
|
53
|
+
self._template_based_display_labels: dict[str, TemplateLabel] = template_based_display_labels or {}
|
|
54
|
+
self._template_relationship_triggers: dict[str, RelationshipTriggers] = template_relationship_triggers or {}
|
|
55
|
+
|
|
56
|
+
def duplicate(self) -> DisplayLabels:
|
|
57
|
+
"""Clone the current object."""
|
|
58
|
+
return self.__class__(
|
|
59
|
+
template_based_display_labels=deepcopy(self._template_based_display_labels),
|
|
60
|
+
template_relationship_triggers=deepcopy(self._template_relationship_triggers),
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
def register_attribute_based_display_label(self, kind: str, attribute_name: str) -> None:
|
|
64
|
+
"""Register nodes where the display label consists of a single defined attribute name."""
|
|
65
|
+
self._template_based_display_labels[kind] = TemplateLabel(
|
|
66
|
+
template=f"{{{{ {attribute_name}__value }}}}", attributes={attribute_name}
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
def register_template_schema_path(self, kind: str, schema_path: SchemaAttributePath, template: str) -> None:
|
|
70
|
+
"""Register Jinja2 template based display labels using the schema path of each impacted variable in the node."""
|
|
71
|
+
|
|
72
|
+
if kind not in self._template_based_display_labels:
|
|
73
|
+
self._template_based_display_labels[kind] = TemplateLabel(template=template)
|
|
74
|
+
|
|
75
|
+
if schema_path.is_type_attribute:
|
|
76
|
+
self._template_based_display_labels[kind].attributes.add(schema_path.active_attribute_schema.name)
|
|
77
|
+
elif schema_path.is_type_relationship and schema_path.related_schema:
|
|
78
|
+
self._template_based_display_labels[kind].relationships.add(schema_path.active_relationship_schema.name)
|
|
79
|
+
if schema_path.related_schema.kind not in self._template_relationship_triggers:
|
|
80
|
+
self._template_relationship_triggers[schema_path.related_schema.kind] = RelationshipTriggers()
|
|
81
|
+
if (
|
|
82
|
+
schema_path.active_attribute_schema.name
|
|
83
|
+
not in self._template_relationship_triggers[schema_path.related_schema.kind].attributes
|
|
84
|
+
):
|
|
85
|
+
self._template_relationship_triggers[schema_path.related_schema.kind].attributes[
|
|
86
|
+
schema_path.active_attribute_schema.name
|
|
87
|
+
] = set()
|
|
88
|
+
self._template_relationship_triggers[schema_path.related_schema.kind].attributes[
|
|
89
|
+
schema_path.active_attribute_schema.name
|
|
90
|
+
].add(
|
|
91
|
+
RelationshipIdentifier(
|
|
92
|
+
kind=kind, filter_key=f"{schema_path.active_relationship_schema.name}__ids", template=template
|
|
93
|
+
)
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
def targets_node(self, kind: str) -> bool:
|
|
97
|
+
"""Indicates if there is a display_label defined for the targeted node"""
|
|
98
|
+
return kind in self._template_based_display_labels
|
|
99
|
+
|
|
100
|
+
def get_template_node(self, kind: str) -> TemplateLabel:
|
|
101
|
+
"""Return node kinds together with their template definitions."""
|
|
102
|
+
return self._template_based_display_labels[kind]
|
|
103
|
+
|
|
104
|
+
def get_template_nodes(self) -> dict[str, TemplateLabel]:
|
|
105
|
+
"""Return node kinds together with their template definitions."""
|
|
106
|
+
return self._template_based_display_labels
|
|
107
|
+
|
|
108
|
+
def get_related_trigger_nodes(self) -> dict[str, RelationshipTriggers]:
|
|
109
|
+
"""Return node kinds that other nodes use within their templates for display_labels."""
|
|
110
|
+
return self._template_relationship_triggers
|
|
111
|
+
|
|
112
|
+
def get_related_template(self, related_kind: str, target_kind: str) -> TemplateLabel:
|
|
113
|
+
relationship_trigger = self._template_relationship_triggers[related_kind]
|
|
114
|
+
for applicable_kinds in relationship_trigger.attributes.values():
|
|
115
|
+
for relationship_identifier in applicable_kinds:
|
|
116
|
+
if target_kind == relationship_identifier.kind:
|
|
117
|
+
template_label = self.get_template_node(kind=target_kind)
|
|
118
|
+
template_label.filter_key = relationship_identifier.filter_key
|
|
119
|
+
return template_label
|
|
120
|
+
|
|
121
|
+
raise ValueError(
|
|
122
|
+
f"Unable to find registered template for {target_kind} registered on related node {related_kind}"
|
|
123
|
+
)
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
from copy import deepcopy
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from infrahub.core.schema import SchemaAttributePath
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class HFIDDefinition:
|
|
14
|
+
hfid: list[str]
|
|
15
|
+
attributes: set[str] = field(default_factory=set)
|
|
16
|
+
relationships: set[str] = field(default_factory=set)
|
|
17
|
+
filter_key: str = "ids"
|
|
18
|
+
|
|
19
|
+
@property
|
|
20
|
+
def fields(self) -> list[str]:
|
|
21
|
+
return sorted(list(self.attributes) + list(self.relationships))
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def has_related_components(self) -> bool:
|
|
25
|
+
"""Indicate if the associated template use variables from relationships"""
|
|
26
|
+
return len(self.relationships) > 0
|
|
27
|
+
|
|
28
|
+
def get_hash(self) -> str:
|
|
29
|
+
return hashlib.md5("::".join(self.hfid).encode(), usedforsecurity=False).hexdigest()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class RelationshipIdentifier:
|
|
34
|
+
kind: str
|
|
35
|
+
hfid: list[str]
|
|
36
|
+
filter_key: str
|
|
37
|
+
|
|
38
|
+
def __hash__(self) -> int:
|
|
39
|
+
return hash(f"{self.kind}::{'::'.join(self.hfid)}::{self.filter_key}")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass
|
|
43
|
+
class RelationshipTriggers:
|
|
44
|
+
attributes: dict[str, set[RelationshipIdentifier]] = field(default_factory=dict)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class HFIDs:
|
|
48
|
+
def __init__(
|
|
49
|
+
self,
|
|
50
|
+
node_level_hfids: dict[str, HFIDDefinition] | None = None,
|
|
51
|
+
relationship_triggers: dict[str, RelationshipTriggers] | None = None,
|
|
52
|
+
) -> None:
|
|
53
|
+
self._node_level_hfids: dict[str, HFIDDefinition] = node_level_hfids or {}
|
|
54
|
+
self._relationship_triggers: dict[str, RelationshipTriggers] = relationship_triggers or {}
|
|
55
|
+
|
|
56
|
+
def duplicate(self) -> HFIDs:
|
|
57
|
+
return self.__class__(
|
|
58
|
+
node_level_hfids=deepcopy(self._node_level_hfids),
|
|
59
|
+
relationship_triggers=deepcopy(self._relationship_triggers),
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
def register_hfid_schema_path(self, kind: str, schema_path: SchemaAttributePath, hfid: list[str]) -> None:
|
|
63
|
+
"""Register HFID using the schema path of each impacted schema path in use."""
|
|
64
|
+
if kind not in self._node_level_hfids:
|
|
65
|
+
self._node_level_hfids[kind] = HFIDDefinition(hfid=hfid)
|
|
66
|
+
if schema_path.is_type_attribute:
|
|
67
|
+
self._node_level_hfids[kind].attributes.add(schema_path.active_attribute_schema.name)
|
|
68
|
+
elif schema_path.is_type_relationship and schema_path.related_schema:
|
|
69
|
+
self._node_level_hfids[kind].relationships.add(schema_path.active_relationship_schema.name)
|
|
70
|
+
if schema_path.related_schema.kind not in self._relationship_triggers:
|
|
71
|
+
self._relationship_triggers[schema_path.related_schema.kind] = RelationshipTriggers()
|
|
72
|
+
if (
|
|
73
|
+
schema_path.active_attribute_schema.name
|
|
74
|
+
not in self._relationship_triggers[schema_path.related_schema.kind].attributes
|
|
75
|
+
):
|
|
76
|
+
self._relationship_triggers[schema_path.related_schema.kind].attributes[
|
|
77
|
+
schema_path.active_attribute_schema.name
|
|
78
|
+
] = set()
|
|
79
|
+
self._relationship_triggers[schema_path.related_schema.kind].attributes[
|
|
80
|
+
schema_path.active_attribute_schema.name
|
|
81
|
+
].add(
|
|
82
|
+
RelationshipIdentifier(
|
|
83
|
+
kind=kind, filter_key=f"{schema_path.active_relationship_schema.name}__ids", hfid=hfid
|
|
84
|
+
)
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
def targets_node(self, kind: str) -> bool:
|
|
88
|
+
"""Indicates if there is a human_friendly_id defined for the targeted node"""
|
|
89
|
+
return kind in self._node_level_hfids
|
|
90
|
+
|
|
91
|
+
def get_node_definition(self, kind: str) -> HFIDDefinition:
|
|
92
|
+
"""Return node kinds together with their template definitions."""
|
|
93
|
+
return self._node_level_hfids[kind]
|
|
94
|
+
|
|
95
|
+
def get_template_nodes(self) -> dict[str, HFIDDefinition]:
|
|
96
|
+
"""Return node kinds together with their template definitions."""
|
|
97
|
+
return self._node_level_hfids
|
|
98
|
+
|
|
99
|
+
def get_related_trigger_nodes(self) -> dict[str, RelationshipTriggers]:
|
|
100
|
+
"""Return node kinds that other nodes use within their templates for display_labels."""
|
|
101
|
+
return self._relationship_triggers
|
|
102
|
+
|
|
103
|
+
def get_related_definition(self, related_kind: str, target_kind: str) -> HFIDDefinition:
|
|
104
|
+
relationship_trigger = self._relationship_triggers[related_kind]
|
|
105
|
+
for applicable_kinds in relationship_trigger.attributes.values():
|
|
106
|
+
for relationship_identifier in applicable_kinds:
|
|
107
|
+
if target_kind == relationship_identifier.kind:
|
|
108
|
+
template_label = self.get_node_definition(kind=target_kind)
|
|
109
|
+
template_label.filter_key = relationship_identifier.filter_key
|
|
110
|
+
return template_label
|
|
111
|
+
|
|
112
|
+
raise ValueError(
|
|
113
|
+
f"Unable to find registered template for {target_kind} registered on related node {related_kind}"
|
|
114
|
+
)
|
|
@@ -49,7 +49,7 @@ class AggregatedConstraintChecker:
|
|
|
49
49
|
node_display_label = None
|
|
50
50
|
display_label = None
|
|
51
51
|
if node:
|
|
52
|
-
node_display_label = await node.
|
|
52
|
+
node_display_label = await node.get_display_label(db=self.db)
|
|
53
53
|
if node_display_label:
|
|
54
54
|
if request.node_schema.display_labels and node:
|
|
55
55
|
display_label = f"Node {node_display_label} ({node.get_kind()}: {path.node_id})"
|
|
@@ -10,6 +10,7 @@ from infrahub.core.schema.attribute_parameters import AttributeParameters
|
|
|
10
10
|
from infrahub.core.schema.relationship_schema import RelationshipSchema
|
|
11
11
|
from infrahub.core.schema.schema_branch import SchemaBranch
|
|
12
12
|
from infrahub.core.validators import CONSTRAINT_VALIDATOR_MAP
|
|
13
|
+
from infrahub.exceptions import SchemaNotFoundError
|
|
13
14
|
from infrahub.log import get_logger
|
|
14
15
|
|
|
15
16
|
if TYPE_CHECKING:
|
|
@@ -81,7 +82,17 @@ class ConstraintValidatorDeterminer:
|
|
|
81
82
|
|
|
82
83
|
async def _get_all_property_constraints(self) -> list[SchemaUpdateConstraintInfo]:
|
|
83
84
|
constraints: list[SchemaUpdateConstraintInfo] = []
|
|
84
|
-
|
|
85
|
+
schemas = list(self.schema_branch.get_all(duplicate=False).values())
|
|
86
|
+
# added here to check their uniqueness constraints
|
|
87
|
+
try:
|
|
88
|
+
schemas.append(self.schema_branch.get_node(name="SchemaAttribute", duplicate=False))
|
|
89
|
+
except SchemaNotFoundError:
|
|
90
|
+
pass
|
|
91
|
+
try:
|
|
92
|
+
schemas.append(self.schema_branch.get_node(name="SchemaRelationship", duplicate=False))
|
|
93
|
+
except SchemaNotFoundError:
|
|
94
|
+
pass
|
|
95
|
+
for schema in schemas:
|
|
85
96
|
constraints.extend(await self._get_property_constraints_for_one_schema(schema=schema))
|
|
86
97
|
return constraints
|
|
87
98
|
|
|
@@ -22,7 +22,7 @@ class RelationshipPeerUpdateValidatorQuery(RelationshipSchemaValidatorQuery):
|
|
|
22
22
|
name = "relationship_constraints_peer_validator"
|
|
23
23
|
|
|
24
24
|
async def query_init(self, db: InfrahubDatabase, **kwargs: dict[str, Any]) -> None: # noqa: ARG002
|
|
25
|
-
peer_schema = db.schema.get(name=self.relationship_schema.peer, branch=self.branch)
|
|
25
|
+
peer_schema = db.schema.get(name=self.relationship_schema.peer, branch=self.branch, duplicate=False)
|
|
26
26
|
allowed_peer_kinds = [peer_schema.kind]
|
|
27
27
|
if isinstance(peer_schema, GenericSchema):
|
|
28
28
|
allowed_peer_kinds += peer_schema.used_by
|
|
@@ -36,7 +36,7 @@ async def schema_validate_migrations(message: SchemaValidateMigrationData) -> li
|
|
|
36
36
|
log.info(f"{len(message.constraints)} constraint(s) to validate")
|
|
37
37
|
# NOTE this task is a good candidate to add a progress bar
|
|
38
38
|
for constraint in message.constraints:
|
|
39
|
-
schema = message.schema_branch.get(name=constraint.path.schema_kind)
|
|
39
|
+
schema = message.schema_branch.get(name=constraint.path.schema_kind, duplicate=False)
|
|
40
40
|
if not isinstance(schema, GenericSchema | NodeSchema):
|
|
41
41
|
continue
|
|
42
42
|
batch.add(
|
|
File without changes
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
|
|
5
|
+
from prefect import task
|
|
6
|
+
from prefect.cache_policies import NONE
|
|
7
|
+
from prefect.logging import get_run_logger
|
|
8
|
+
|
|
9
|
+
from infrahub.core.registry import registry
|
|
10
|
+
from infrahub.database import InfrahubDatabase # noqa: TC001 needed for prefect flow
|
|
11
|
+
|
|
12
|
+
from .models import DisplayLabelTriggerDefinition
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class BranchScope:
|
|
17
|
+
name: str
|
|
18
|
+
out_of_scope: list[str] = field(default_factory=list)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@task(
|
|
22
|
+
name="gather-trigger-display-labels-jinja2",
|
|
23
|
+
cache_policy=NONE,
|
|
24
|
+
)
|
|
25
|
+
async def gather_trigger_display_labels_jinja2(
|
|
26
|
+
db: InfrahubDatabase | None = None, # noqa: ARG001 Needed to have a common function signature for gathering functions
|
|
27
|
+
) -> list[DisplayLabelTriggerDefinition]:
|
|
28
|
+
log = get_run_logger()
|
|
29
|
+
|
|
30
|
+
# Build a list of all branches to process based on which branch is different from main
|
|
31
|
+
branches_with_diff_from_main = registry.get_altered_schema_branches()
|
|
32
|
+
branches_to_process: list[BranchScope] = [BranchScope(name=branch) for branch in branches_with_diff_from_main]
|
|
33
|
+
branches_to_process.append(BranchScope(name=registry.default_branch, out_of_scope=branches_with_diff_from_main))
|
|
34
|
+
|
|
35
|
+
triggers: list[DisplayLabelTriggerDefinition] = []
|
|
36
|
+
|
|
37
|
+
for branch in branches_to_process:
|
|
38
|
+
schema_branch = registry.schema.get_schema_branch(name=branch.name)
|
|
39
|
+
branch_triggers = DisplayLabelTriggerDefinition.from_schema_display_labels(
|
|
40
|
+
branch=branch.name,
|
|
41
|
+
display_labels=schema_branch.display_labels,
|
|
42
|
+
branches_out_of_scope=branch.out_of_scope,
|
|
43
|
+
)
|
|
44
|
+
log.info(f"Generating {len(branch_triggers)} Jinja2 trigger for {branch.name} (except {branch.out_of_scope})")
|
|
45
|
+
|
|
46
|
+
triggers.extend(branch_triggers)
|
|
47
|
+
|
|
48
|
+
return triggers
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import TYPE_CHECKING, Any, Self
|
|
5
|
+
|
|
6
|
+
from infrahub_sdk.graphql import Query
|
|
7
|
+
from pydantic import BaseModel, Field
|
|
8
|
+
|
|
9
|
+
from infrahub.core.constants import RelationshipCardinality
|
|
10
|
+
from infrahub.core.registry import registry
|
|
11
|
+
from infrahub.core.schema import NodeSchema # noqa: TC001
|
|
12
|
+
from infrahub.events import NodeUpdatedEvent
|
|
13
|
+
from infrahub.trigger.constants import NAME_SEPARATOR
|
|
14
|
+
from infrahub.trigger.models import (
|
|
15
|
+
EventTrigger,
|
|
16
|
+
ExecuteWorkflow,
|
|
17
|
+
TriggerBranchDefinition,
|
|
18
|
+
TriggerType,
|
|
19
|
+
)
|
|
20
|
+
from infrahub.workflows.catalogue import DISPLAY_LABELS_PROCESS_JINJA2
|
|
21
|
+
|
|
22
|
+
if TYPE_CHECKING:
|
|
23
|
+
from infrahub.core.schema.schema_branch_display import DisplayLabels, RelationshipTriggers
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class AttributeTarget:
|
|
28
|
+
hash: str
|
|
29
|
+
fields: set[str]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class DisplayLabelTriggerDefinition(TriggerBranchDefinition):
|
|
33
|
+
type: TriggerType = TriggerType.DISPLAY_LABEL_JINJA2
|
|
34
|
+
template_hash: str
|
|
35
|
+
target_kind: str | None = Field(default=None)
|
|
36
|
+
|
|
37
|
+
def get_description(self) -> str:
|
|
38
|
+
return f"{super().get_description()} | hash:{self.template_hash}"
|
|
39
|
+
|
|
40
|
+
@classmethod
|
|
41
|
+
def from_schema_display_labels(
|
|
42
|
+
cls,
|
|
43
|
+
branch: str,
|
|
44
|
+
display_labels: DisplayLabels,
|
|
45
|
+
branches_out_of_scope: list[str] | None = None,
|
|
46
|
+
) -> list[DisplayLabelTriggerDefinition]:
|
|
47
|
+
"""
|
|
48
|
+
This function is used to create a trigger definition for a display labels of type Jinja2.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
definitions: list[DisplayLabelTriggerDefinition] = []
|
|
52
|
+
|
|
53
|
+
for node_kind, template_label in display_labels.get_template_nodes().items():
|
|
54
|
+
definitions.append(
|
|
55
|
+
cls.new(
|
|
56
|
+
branch=branch,
|
|
57
|
+
node_kind=node_kind,
|
|
58
|
+
target_kind=node_kind,
|
|
59
|
+
fields=[
|
|
60
|
+
"_trigger_placeholder"
|
|
61
|
+
], # Triggers for the nodes themselves are only used to determine if all nodes should be regenerated
|
|
62
|
+
template_hash=template_label.get_hash(),
|
|
63
|
+
branches_out_of_scope=branches_out_of_scope,
|
|
64
|
+
)
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
for related_kind, relationship_trigger in display_labels.get_related_trigger_nodes().items():
|
|
68
|
+
definitions.extend(
|
|
69
|
+
cls.from_related_node(
|
|
70
|
+
branch=branch,
|
|
71
|
+
related_kind=related_kind,
|
|
72
|
+
relationship_trigger=relationship_trigger,
|
|
73
|
+
display_labels=display_labels,
|
|
74
|
+
branches_out_of_scope=branches_out_of_scope,
|
|
75
|
+
)
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
return definitions
|
|
79
|
+
|
|
80
|
+
@classmethod
|
|
81
|
+
def from_related_node(
|
|
82
|
+
cls,
|
|
83
|
+
branch: str,
|
|
84
|
+
related_kind: str,
|
|
85
|
+
relationship_trigger: RelationshipTriggers,
|
|
86
|
+
display_labels: DisplayLabels,
|
|
87
|
+
branches_out_of_scope: list[str] | None = None,
|
|
88
|
+
) -> list[DisplayLabelTriggerDefinition]:
|
|
89
|
+
targets_by_attribute: dict[str, AttributeTarget] = {}
|
|
90
|
+
definitions: list[DisplayLabelTriggerDefinition] = []
|
|
91
|
+
for attribute, relationship_identifiers in relationship_trigger.attributes.items():
|
|
92
|
+
for relationship_identifier in relationship_identifiers:
|
|
93
|
+
actual_node = display_labels.get_template_node(kind=relationship_identifier.kind)
|
|
94
|
+
if relationship_identifier.kind not in targets_by_attribute:
|
|
95
|
+
targets_by_attribute[relationship_identifier.kind] = AttributeTarget(
|
|
96
|
+
actual_node.get_hash(), fields=set()
|
|
97
|
+
)
|
|
98
|
+
targets_by_attribute[relationship_identifier.kind].fields.add(attribute)
|
|
99
|
+
|
|
100
|
+
for target_kind, attribute_target in targets_by_attribute.items():
|
|
101
|
+
definitions.append(
|
|
102
|
+
cls.new(
|
|
103
|
+
branch=branch,
|
|
104
|
+
node_kind=related_kind,
|
|
105
|
+
target_kind=target_kind,
|
|
106
|
+
fields=sorted(attribute_target.fields),
|
|
107
|
+
template_hash=attribute_target.hash,
|
|
108
|
+
branches_out_of_scope=branches_out_of_scope,
|
|
109
|
+
)
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
return definitions
|
|
113
|
+
|
|
114
|
+
@classmethod
|
|
115
|
+
def new(
|
|
116
|
+
cls,
|
|
117
|
+
branch: str,
|
|
118
|
+
node_kind: str,
|
|
119
|
+
target_kind: str,
|
|
120
|
+
template_hash: str,
|
|
121
|
+
fields: list[str],
|
|
122
|
+
branches_out_of_scope: list[str] | None = None,
|
|
123
|
+
) -> Self:
|
|
124
|
+
event_trigger = EventTrigger()
|
|
125
|
+
event_trigger.events.add(NodeUpdatedEvent.event_name)
|
|
126
|
+
event_trigger.match = {"infrahub.node.kind": node_kind}
|
|
127
|
+
if branches_out_of_scope:
|
|
128
|
+
event_trigger.match["infrahub.branch.name"] = [f"!{branch}" for branch in branches_out_of_scope]
|
|
129
|
+
elif not branches_out_of_scope and branch != registry.default_branch:
|
|
130
|
+
event_trigger.match["infrahub.branch.name"] = branch
|
|
131
|
+
|
|
132
|
+
event_trigger.match_related = {
|
|
133
|
+
"prefect.resource.role": ["infrahub.node.attribute_update", "infrahub.node.relationship_update"],
|
|
134
|
+
"infrahub.field.name": fields,
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
workflow = ExecuteWorkflow(
|
|
138
|
+
workflow=DISPLAY_LABELS_PROCESS_JINJA2,
|
|
139
|
+
parameters={
|
|
140
|
+
"branch_name": "{{ event.resource['infrahub.branch.name'] }}",
|
|
141
|
+
"node_kind": node_kind,
|
|
142
|
+
"object_id": "{{ event.resource['infrahub.node.id'] }}",
|
|
143
|
+
"target_kind": target_kind,
|
|
144
|
+
"context": {
|
|
145
|
+
"__prefect_kind": "json",
|
|
146
|
+
"value": {
|
|
147
|
+
"__prefect_kind": "jinja",
|
|
148
|
+
"template": "{{ event.payload['context'] | tojson }}",
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
trigger_definition_target_kind = target_kind if target_kind == node_kind else None
|
|
155
|
+
|
|
156
|
+
return cls(
|
|
157
|
+
name=f"{target_kind}{NAME_SEPARATOR}by{NAME_SEPARATOR}{node_kind}",
|
|
158
|
+
template_hash=template_hash,
|
|
159
|
+
branch=branch,
|
|
160
|
+
trigger=event_trigger,
|
|
161
|
+
actions=[workflow],
|
|
162
|
+
target_kind=trigger_definition_target_kind,
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
class DisplayLabelJinja2GraphQLResponse(BaseModel):
|
|
167
|
+
node_id: str
|
|
168
|
+
display_label_value: str | None
|
|
169
|
+
variables: dict[str, Any] = Field(default_factory=dict)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
class DisplayLabelJinja2GraphQL(BaseModel):
|
|
173
|
+
filter_key: str
|
|
174
|
+
node_schema: NodeSchema = Field(..., description="The node kind where the computed attribute is defined")
|
|
175
|
+
variables: list[str] = Field(..., description="The list of variable names used within the computed attribute")
|
|
176
|
+
|
|
177
|
+
def render_graphql_query(self, filter_id: str) -> str:
|
|
178
|
+
query_fields = self.query_fields
|
|
179
|
+
query_fields["id"] = None
|
|
180
|
+
query_fields["display_label"] = None
|
|
181
|
+
query = Query(
|
|
182
|
+
name="DisplayLabelFilter",
|
|
183
|
+
query={
|
|
184
|
+
self.node_schema.kind: {
|
|
185
|
+
"@filters": {self.filter_key: filter_id},
|
|
186
|
+
"edges": {"node": query_fields},
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
return query.render()
|
|
192
|
+
|
|
193
|
+
@property
|
|
194
|
+
def query_fields(self) -> dict[str, Any]:
|
|
195
|
+
output: dict[str, Any] = {}
|
|
196
|
+
for variable in self.variables:
|
|
197
|
+
field_name, remainder = variable.split("__", maxsplit=1)
|
|
198
|
+
if field_name in self.node_schema.attribute_names:
|
|
199
|
+
output[field_name] = {remainder: None}
|
|
200
|
+
elif field_name in self.node_schema.relationship_names:
|
|
201
|
+
related_attribute, related_value = remainder.split("__", maxsplit=1)
|
|
202
|
+
relationship = self.node_schema.get_relationship(name=field_name)
|
|
203
|
+
if relationship.cardinality == RelationshipCardinality.ONE:
|
|
204
|
+
if field_name not in output:
|
|
205
|
+
output[field_name] = {"node": {}}
|
|
206
|
+
output[field_name]["node"][related_attribute] = {related_value: None}
|
|
207
|
+
return output
|
|
208
|
+
|
|
209
|
+
def parse_response(self, response: dict[str, Any]) -> list[DisplayLabelJinja2GraphQLResponse]:
|
|
210
|
+
rendered_response: list[DisplayLabelJinja2GraphQLResponse] = []
|
|
211
|
+
if kind_payload := response.get(self.node_schema.kind):
|
|
212
|
+
edges = kind_payload.get("edges", [])
|
|
213
|
+
for node in edges:
|
|
214
|
+
if node_response := self.to_node_response(node_dict=node):
|
|
215
|
+
rendered_response.append(node_response)
|
|
216
|
+
return rendered_response
|
|
217
|
+
|
|
218
|
+
def to_node_response(self, node_dict: dict[str, Any]) -> DisplayLabelJinja2GraphQLResponse | None:
|
|
219
|
+
if node := node_dict.get("node"):
|
|
220
|
+
node_id = node.get("id")
|
|
221
|
+
else:
|
|
222
|
+
return None
|
|
223
|
+
|
|
224
|
+
display_label = node.get("display_label")
|
|
225
|
+
response = DisplayLabelJinja2GraphQLResponse(node_id=node_id, display_label_value=display_label)
|
|
226
|
+
for variable in self.variables:
|
|
227
|
+
field_name, remainder = variable.split("__", maxsplit=1)
|
|
228
|
+
response.variables[variable] = None
|
|
229
|
+
if field_content := node.get(field_name):
|
|
230
|
+
if field_name in self.node_schema.attribute_names:
|
|
231
|
+
response.variables[variable] = field_content.get(remainder)
|
|
232
|
+
elif field_name in self.node_schema.relationship_names:
|
|
233
|
+
relationship = self.node_schema.get_relationship(name=field_name)
|
|
234
|
+
if relationship.cardinality == RelationshipCardinality.ONE:
|
|
235
|
+
related_attribute, related_value = remainder.split("__", maxsplit=1)
|
|
236
|
+
node_content = field_content.get("node") or {}
|
|
237
|
+
related_attribute_content = node_content.get(related_attribute) or {}
|
|
238
|
+
response.variables[variable] = related_attribute_content.get(related_value)
|
|
239
|
+
|
|
240
|
+
return response
|