infrahub-server 1.5.0b0__py3-none-any.whl → 1.5.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 +8 -0
- infrahub/api/diff/diff.py +1 -1
- infrahub/api/internal.py +2 -0
- infrahub/api/oauth2.py +13 -19
- infrahub/api/oidc.py +15 -21
- infrahub/api/schema.py +24 -3
- infrahub/artifacts/models.py +2 -1
- infrahub/auth.py +137 -3
- infrahub/cli/__init__.py +2 -0
- infrahub/cli/db.py +103 -98
- infrahub/cli/db_commands/clean_duplicate_schema_fields.py +212 -0
- infrahub/cli/dev.py +118 -0
- infrahub/cli/tasks.py +46 -0
- infrahub/cli/upgrade.py +30 -3
- infrahub/computed_attribute/tasks.py +20 -8
- infrahub/core/attribute.py +13 -5
- infrahub/core/branch/enums.py +1 -1
- infrahub/core/branch/models.py +7 -3
- infrahub/core/branch/tasks.py +70 -8
- infrahub/core/changelog/models.py +4 -12
- infrahub/core/constants/__init__.py +3 -0
- infrahub/core/constants/infrahubkind.py +1 -0
- infrahub/core/diff/model/path.py +4 -0
- infrahub/core/diff/payload_builder.py +1 -1
- infrahub/core/diff/query/artifact.py +1 -0
- infrahub/core/diff/query/field_summary.py +1 -0
- infrahub/core/graph/__init__.py +1 -1
- infrahub/core/initialization.py +5 -2
- infrahub/core/ipam/utilization.py +1 -1
- infrahub/core/manager.py +6 -3
- infrahub/core/migrations/__init__.py +3 -0
- infrahub/core/migrations/exceptions.py +4 -0
- infrahub/core/migrations/graph/__init__.py +12 -11
- infrahub/core/migrations/graph/load_schema_branch.py +21 -0
- infrahub/core/migrations/graph/m013_convert_git_password_credential.py +1 -1
- infrahub/core/migrations/graph/m040_duplicated_attributes.py +81 -0
- infrahub/core/migrations/graph/m041_profile_attrs_in_db.py +145 -0
- infrahub/core/migrations/graph/m042_create_hfid_display_label_in_db.py +164 -0
- infrahub/core/migrations/graph/m043_backfill_hfid_display_label_in_db.py +866 -0
- infrahub/core/migrations/query/__init__.py +7 -8
- infrahub/core/migrations/query/attribute_add.py +8 -6
- infrahub/core/migrations/query/attribute_remove.py +134 -0
- infrahub/core/migrations/runner.py +54 -0
- infrahub/core/migrations/schema/attribute_kind_update.py +9 -3
- infrahub/core/migrations/schema/attribute_supports_profile.py +90 -0
- infrahub/core/migrations/schema/node_attribute_add.py +35 -4
- infrahub/core/migrations/schema/node_attribute_remove.py +13 -109
- infrahub/core/migrations/schema/node_kind_update.py +2 -1
- infrahub/core/migrations/schema/node_remove.py +2 -1
- infrahub/core/migrations/schema/placeholder_dummy.py +3 -2
- infrahub/core/migrations/shared.py +52 -19
- infrahub/core/node/__init__.py +158 -51
- infrahub/core/node/constraints/attribute_uniqueness.py +3 -1
- infrahub/core/node/create.py +46 -63
- infrahub/core/node/lock_utils.py +70 -44
- infrahub/core/node/node_property_attribute.py +230 -0
- infrahub/core/node/resource_manager/ip_address_pool.py +2 -1
- infrahub/core/node/resource_manager/ip_prefix_pool.py +2 -1
- infrahub/core/node/resource_manager/number_pool.py +2 -1
- infrahub/core/node/standard.py +1 -1
- infrahub/core/protocols.py +7 -1
- infrahub/core/query/attribute.py +55 -0
- infrahub/core/query/ipam.py +1 -0
- infrahub/core/query/node.py +23 -4
- infrahub/core/query/relationship.py +1 -0
- infrahub/core/registry.py +2 -2
- infrahub/core/relationship/constraints/count.py +1 -1
- infrahub/core/relationship/model.py +1 -1
- infrahub/core/schema/__init__.py +56 -0
- infrahub/core/schema/attribute_schema.py +4 -0
- 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/internal.py +16 -3
- infrahub/core/schema/generated/attribute_schema.py +2 -2
- infrahub/core/schema/generated/base_node_schema.py +6 -1
- infrahub/core/schema/manager.py +22 -1
- infrahub/core/schema/node_schema.py +5 -2
- infrahub/core/schema/schema_branch.py +300 -8
- 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/database/graph.py +21 -0
- 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 +192 -0
- infrahub/display_labels/triggers.py +22 -0
- infrahub/events/branch_action.py +27 -1
- infrahub/events/group_action.py +1 -1
- infrahub/events/node_action.py +1 -1
- infrahub/generators/constants.py +7 -0
- infrahub/generators/models.py +7 -0
- infrahub/generators/tasks.py +34 -22
- infrahub/git/base.py +4 -1
- infrahub/git/integrator.py +23 -15
- infrahub/git/models.py +2 -1
- infrahub/git/repository.py +22 -5
- infrahub/git/tasks.py +66 -10
- infrahub/git/utils.py +123 -1
- infrahub/graphql/analyzer.py +1 -1
- infrahub/graphql/api/endpoints.py +14 -4
- infrahub/graphql/manager.py +4 -9
- infrahub/graphql/mutations/convert_object_type.py +11 -1
- infrahub/graphql/mutations/display_label.py +118 -0
- infrahub/graphql/mutations/generator.py +25 -7
- infrahub/graphql/mutations/hfid.py +125 -0
- infrahub/graphql/mutations/ipam.py +54 -35
- infrahub/graphql/mutations/main.py +27 -28
- infrahub/graphql/mutations/relationship.py +2 -2
- infrahub/graphql/mutations/resource_manager.py +2 -2
- infrahub/graphql/mutations/schema.py +5 -5
- infrahub/graphql/queries/resource_manager.py +1 -1
- infrahub/graphql/resolvers/resolver.py +2 -0
- infrahub/graphql/schema.py +4 -0
- infrahub/graphql/schema_sort.py +170 -0
- infrahub/graphql/types/branch.py +4 -1
- infrahub/graphql/types/enums.py +3 -0
- 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 +191 -0
- infrahub/hfid/triggers.py +22 -0
- infrahub/lock.py +67 -16
- infrahub/message_bus/types.py +2 -1
- infrahub/middleware.py +26 -1
- infrahub/permissions/constants.py +2 -0
- infrahub/proposed_change/tasks.py +35 -17
- infrahub/server.py +21 -4
- infrahub/services/__init__.py +8 -5
- infrahub/services/adapters/http/__init__.py +5 -0
- infrahub/services/adapters/workflow/worker.py +14 -3
- infrahub/task_manager/event.py +5 -0
- infrahub/task_manager/models.py +7 -0
- infrahub/task_manager/task.py +73 -0
- infrahub/trigger/catalogue.py +4 -0
- infrahub/trigger/models.py +2 -0
- infrahub/trigger/setup.py +13 -4
- infrahub/trigger/tasks.py +6 -0
- infrahub/workers/dependencies.py +10 -1
- infrahub/workers/infrahub_async.py +10 -2
- infrahub/workflows/catalogue.py +80 -0
- infrahub/workflows/initialization.py +21 -0
- infrahub/workflows/utils.py +2 -1
- infrahub_sdk/checks.py +1 -1
- infrahub_sdk/client.py +13 -10
- infrahub_sdk/config.py +29 -2
- infrahub_sdk/ctl/cli_commands.py +2 -0
- infrahub_sdk/ctl/generator.py +4 -0
- infrahub_sdk/ctl/graphql.py +184 -0
- infrahub_sdk/ctl/schema.py +28 -9
- 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} +81 -73
- infrahub_sdk/graphql/utils.py +40 -0
- infrahub_sdk/protocols.py +14 -0
- infrahub_sdk/schema/__init__.py +70 -4
- infrahub_sdk/schema/repository.py +8 -0
- infrahub_sdk/spec/models.py +7 -0
- infrahub_sdk/spec/object.py +53 -44
- infrahub_sdk/spec/processors/__init__.py +0 -0
- infrahub_sdk/spec/processors/data_processor.py +10 -0
- infrahub_sdk/spec/processors/factory.py +34 -0
- infrahub_sdk/spec/processors/range_expand_processor.py +56 -0
- infrahub_sdk/spec/range_expansion.py +1 -1
- infrahub_sdk/transforms.py +1 -1
- {infrahub_server-1.5.0b0.dist-info → infrahub_server-1.5.0b2.dist-info}/METADATA +7 -4
- {infrahub_server-1.5.0b0.dist-info → infrahub_server-1.5.0b2.dist-info}/RECORD +182 -143
- infrahub_testcontainers/container.py +115 -3
- infrahub_testcontainers/docker-compose-cluster.test.yml +6 -1
- infrahub_testcontainers/docker-compose.test.yml +6 -1
- infrahub/core/migrations/graph/m040_profile_attrs_in_db.py +0 -166
- {infrahub_server-1.5.0b0.dist-info → infrahub_server-1.5.0b2.dist-info}/LICENSE.txt +0 -0
- {infrahub_server-1.5.0b0.dist-info → infrahub_server-1.5.0b2.dist-info}/WHEEL +0 -0
- {infrahub_server-1.5.0b0.dist-info → infrahub_server-1.5.0b2.dist-info}/entry_points.txt +0 -0
|
@@ -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(
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from infrahub.core.graph import GRAPH_VERSION
|
|
6
|
+
from infrahub.core.initialization import get_root_node
|
|
7
|
+
from infrahub.log import get_logger
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from infrahub.database import InfrahubDatabase
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
log = get_logger()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
async def validate_graph_version(db: InfrahubDatabase) -> None:
|
|
17
|
+
root = await get_root_node(db=db)
|
|
18
|
+
if root.graph_version != GRAPH_VERSION:
|
|
19
|
+
log.warning(
|
|
20
|
+
f"Expected database graph version {GRAPH_VERSION} but got {root.graph_version}, possibly 'infrahub upgrade' has not been executed"
|
|
21
|
+
)
|
|
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
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import cast
|
|
4
|
+
|
|
5
|
+
from infrahub_sdk.exceptions import URLNotFoundError
|
|
6
|
+
from infrahub_sdk.template import Jinja2Template
|
|
7
|
+
from prefect import flow
|
|
8
|
+
from prefect.logging import get_run_logger
|
|
9
|
+
|
|
10
|
+
from infrahub.context import InfrahubContext # noqa: TC001 needed for prefect flow
|
|
11
|
+
from infrahub.core.registry import registry
|
|
12
|
+
from infrahub.events import BranchDeletedEvent
|
|
13
|
+
from infrahub.trigger.models import TriggerSetupReport, TriggerType
|
|
14
|
+
from infrahub.trigger.setup import setup_triggers_specific
|
|
15
|
+
from infrahub.workers.dependencies import get_client, get_component, get_database, get_workflow
|
|
16
|
+
from infrahub.workflows.catalogue import DISPLAY_LABELS_PROCESS_JINJA2, TRIGGER_UPDATE_DISPLAY_LABELS
|
|
17
|
+
from infrahub.workflows.utils import add_tags, wait_for_schema_to_converge
|
|
18
|
+
|
|
19
|
+
from .gather import gather_trigger_display_labels_jinja2
|
|
20
|
+
from .models import DisplayLabelJinja2GraphQL, DisplayLabelJinja2GraphQLResponse, DisplayLabelTriggerDefinition
|
|
21
|
+
|
|
22
|
+
UPDATE_DISPLAY_LABEL = """
|
|
23
|
+
mutation UpdateDisplayLabel(
|
|
24
|
+
$id: String!,
|
|
25
|
+
$kind: String!,
|
|
26
|
+
$value: String!
|
|
27
|
+
) {
|
|
28
|
+
InfrahubUpdateDisplayLabel(
|
|
29
|
+
data: {id: $id, value: $value, kind: $kind}
|
|
30
|
+
) {
|
|
31
|
+
ok
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@flow(
|
|
38
|
+
name="display-label-jinja2-update-value",
|
|
39
|
+
flow_run_name="Update value for display_label on {node_kind}",
|
|
40
|
+
)
|
|
41
|
+
async def display_label_jinja2_update_value(
|
|
42
|
+
branch_name: str,
|
|
43
|
+
obj: DisplayLabelJinja2GraphQLResponse,
|
|
44
|
+
node_kind: str,
|
|
45
|
+
template: Jinja2Template,
|
|
46
|
+
) -> None:
|
|
47
|
+
log = get_run_logger()
|
|
48
|
+
client = get_client()
|
|
49
|
+
|
|
50
|
+
await add_tags(branches=[branch_name], nodes=[obj.node_id], db_change=True)
|
|
51
|
+
|
|
52
|
+
value = await template.render(variables=obj.variables)
|
|
53
|
+
if value == obj.display_label_value:
|
|
54
|
+
log.debug(f"Ignoring to update {obj} with existing value on display_label={value}")
|
|
55
|
+
return
|
|
56
|
+
|
|
57
|
+
try:
|
|
58
|
+
await client.execute_graphql(
|
|
59
|
+
query=UPDATE_DISPLAY_LABEL,
|
|
60
|
+
variables={"id": obj.node_id, "kind": node_kind, "value": value},
|
|
61
|
+
branch_name=branch_name,
|
|
62
|
+
)
|
|
63
|
+
log.info(f"Updating {node_kind}.display_label='{value}' ({obj.node_id})")
|
|
64
|
+
except URLNotFoundError:
|
|
65
|
+
log.warning(
|
|
66
|
+
f"Updating {node_kind}.display_label='{value}' ({obj.node_id}) failed for branch {branch_name} (branch not found)"
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@flow(
|
|
71
|
+
name="display-label-process-jinja2",
|
|
72
|
+
flow_run_name="Process display_labels for {target_kind}",
|
|
73
|
+
)
|
|
74
|
+
async def process_display_label(
|
|
75
|
+
branch_name: str,
|
|
76
|
+
node_kind: str,
|
|
77
|
+
object_id: str,
|
|
78
|
+
target_kind: str,
|
|
79
|
+
context: InfrahubContext, # noqa: ARG001
|
|
80
|
+
) -> None:
|
|
81
|
+
log = get_run_logger()
|
|
82
|
+
client = get_client()
|
|
83
|
+
|
|
84
|
+
await add_tags(branches=[branch_name])
|
|
85
|
+
|
|
86
|
+
target_schema = branch_name if branch_name in registry.get_altered_schema_branches() else registry.default_branch
|
|
87
|
+
schema_branch = registry.schema.get_schema_branch(name=target_schema)
|
|
88
|
+
node_schema = schema_branch.get_node(name=target_kind, duplicate=False)
|
|
89
|
+
|
|
90
|
+
if node_kind == target_kind:
|
|
91
|
+
display_label_template = schema_branch.display_labels.get_template_node(kind=node_kind)
|
|
92
|
+
else:
|
|
93
|
+
display_label_template = schema_branch.display_labels.get_related_template(
|
|
94
|
+
related_kind=node_kind, target_kind=target_kind
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
jinja_template = Jinja2Template(template=display_label_template.template)
|
|
98
|
+
variables = jinja_template.get_variables()
|
|
99
|
+
display_label_graphql = DisplayLabelJinja2GraphQL(
|
|
100
|
+
node_schema=node_schema, variables=variables, filter_key=display_label_template.filter_key
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
query = display_label_graphql.render_graphql_query(filter_id=object_id)
|
|
104
|
+
response = await client.execute_graphql(query=query, branch_name=branch_name)
|
|
105
|
+
update_candidates = display_label_graphql.parse_response(response=response)
|
|
106
|
+
|
|
107
|
+
if not update_candidates:
|
|
108
|
+
log.debug("No nodes found that requires updates")
|
|
109
|
+
return
|
|
110
|
+
|
|
111
|
+
batch = await client.create_batch()
|
|
112
|
+
for node in update_candidates:
|
|
113
|
+
batch.add(
|
|
114
|
+
task=display_label_jinja2_update_value,
|
|
115
|
+
branch_name=branch_name,
|
|
116
|
+
obj=node,
|
|
117
|
+
node_kind=node_schema.kind,
|
|
118
|
+
template=jinja_template,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
_ = [response async for _, response in batch.execute()]
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@flow(name="display-labels-setup-jinja2", flow_run_name="Setup display labels in task-manager")
|
|
125
|
+
async def display_labels_setup_jinja2(
|
|
126
|
+
context: InfrahubContext, branch_name: str | None = None, event_name: str | None = None
|
|
127
|
+
) -> None:
|
|
128
|
+
database = await get_database()
|
|
129
|
+
async with database.start_session() as db:
|
|
130
|
+
log = get_run_logger()
|
|
131
|
+
|
|
132
|
+
if branch_name:
|
|
133
|
+
await add_tags(branches=[branch_name])
|
|
134
|
+
component = await get_component()
|
|
135
|
+
await wait_for_schema_to_converge(branch_name=branch_name, component=component, db=db, log=log)
|
|
136
|
+
|
|
137
|
+
report: TriggerSetupReport = await setup_triggers_specific(
|
|
138
|
+
gatherer=gather_trigger_display_labels_jinja2, trigger_type=TriggerType.DISPLAY_LABEL_JINJA2
|
|
139
|
+
) # type: ignore[misc]
|
|
140
|
+
|
|
141
|
+
# Configure all DisplayLabelTriggerDefinitions in Prefect
|
|
142
|
+
display_reports = [cast(DisplayLabelTriggerDefinition, entry) for entry in report.updated + report.created]
|
|
143
|
+
direct_target_triggers = [display_report for display_report in display_reports if display_report.target_kind]
|
|
144
|
+
|
|
145
|
+
for display_report in direct_target_triggers:
|
|
146
|
+
if event_name != BranchDeletedEvent.event_name and display_report.branch == branch_name:
|
|
147
|
+
await get_workflow().submit_workflow(
|
|
148
|
+
workflow=TRIGGER_UPDATE_DISPLAY_LABELS,
|
|
149
|
+
context=context,
|
|
150
|
+
parameters={
|
|
151
|
+
"branch_name": display_report.branch,
|
|
152
|
+
"kind": display_report.target_kind,
|
|
153
|
+
},
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
log.info(f"{report.in_use_count} Display labels for Jinja2 automation configuration completed")
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
@flow(
|
|
160
|
+
name="trigger-update-display-labels",
|
|
161
|
+
flow_run_name="Trigger updates for display labels for {kind}",
|
|
162
|
+
)
|
|
163
|
+
async def trigger_update_display_labels(
|
|
164
|
+
branch_name: str,
|
|
165
|
+
kind: str,
|
|
166
|
+
context: InfrahubContext,
|
|
167
|
+
) -> None:
|
|
168
|
+
await add_tags(branches=[branch_name])
|
|
169
|
+
|
|
170
|
+
client = get_client()
|
|
171
|
+
|
|
172
|
+
# NOTE we only need the id of the nodes, this query will still query for the HFID
|
|
173
|
+
node_schema = registry.schema.get_node_schema(name=kind, branch=branch_name)
|
|
174
|
+
nodes = await client.all(
|
|
175
|
+
kind=kind,
|
|
176
|
+
branch=branch_name,
|
|
177
|
+
exclude=node_schema.attribute_names + node_schema.relationship_names,
|
|
178
|
+
populate_store=False,
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
for node in nodes:
|
|
182
|
+
await get_workflow().submit_workflow(
|
|
183
|
+
workflow=DISPLAY_LABELS_PROCESS_JINJA2,
|
|
184
|
+
context=context,
|
|
185
|
+
parameters={
|
|
186
|
+
"branch_name": branch_name,
|
|
187
|
+
"node_kind": kind,
|
|
188
|
+
"target_kind": kind,
|
|
189
|
+
"object_id": node.id,
|
|
190
|
+
"context": context,
|
|
191
|
+
},
|
|
192
|
+
)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from infrahub.events.branch_action import BranchDeletedEvent
|
|
2
|
+
from infrahub.events.schema_action import SchemaUpdatedEvent
|
|
3
|
+
from infrahub.trigger.models import BuiltinTriggerDefinition, EventTrigger, ExecuteWorkflow
|
|
4
|
+
from infrahub.workflows.catalogue import DISPLAY_LABELS_SETUP_JINJA2
|
|
5
|
+
|
|
6
|
+
TRIGGER_DISPLAY_LABELS_ALL_SCHEMA = BuiltinTriggerDefinition(
|
|
7
|
+
name="display-labels-setup-all",
|
|
8
|
+
trigger=EventTrigger(events={SchemaUpdatedEvent.event_name, BranchDeletedEvent.event_name}),
|
|
9
|
+
actions=[
|
|
10
|
+
ExecuteWorkflow(
|
|
11
|
+
workflow=DISPLAY_LABELS_SETUP_JINJA2,
|
|
12
|
+
parameters={
|
|
13
|
+
"branch_name": "{{ event.resource['infrahub.branch.name'] }}",
|
|
14
|
+
"event_name": "{{ event.event }}",
|
|
15
|
+
"context": {
|
|
16
|
+
"__prefect_kind": "json",
|
|
17
|
+
"value": {"__prefect_kind": "jinja", "template": "{{ event.payload['context'] | tojson }}"},
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
),
|
|
21
|
+
],
|
|
22
|
+
)
|
infrahub/events/branch_action.py
CHANGED
|
@@ -109,7 +109,7 @@ class BranchRebasedEvent(InfrahubEvent):
|
|
|
109
109
|
|
|
110
110
|
event_name: ClassVar[str] = f"{EVENT_NAMESPACE}.branch.rebased"
|
|
111
111
|
|
|
112
|
-
branch_id: str = Field(..., description="The ID of the
|
|
112
|
+
branch_id: str = Field(..., description="The ID of the branch")
|
|
113
113
|
branch_name: str = Field(..., description="The name of the branch")
|
|
114
114
|
|
|
115
115
|
def get_resource(self) -> dict[str, str]:
|
|
@@ -128,3 +128,29 @@ class BranchRebasedEvent(InfrahubEvent):
|
|
|
128
128
|
RefreshRegistryRebasedBranch(branch=self.branch_name),
|
|
129
129
|
]
|
|
130
130
|
return events
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
class BranchMigratedEvent(InfrahubEvent):
|
|
134
|
+
"""Event generated when a branch has been migrated"""
|
|
135
|
+
|
|
136
|
+
event_name: ClassVar[str] = f"{EVENT_NAMESPACE}.branch.migrated"
|
|
137
|
+
|
|
138
|
+
branch_id: str = Field(..., description="The ID of the branch")
|
|
139
|
+
branch_name: str = Field(..., description="The name of the branch")
|
|
140
|
+
|
|
141
|
+
def get_resource(self) -> dict[str, str]:
|
|
142
|
+
return {
|
|
143
|
+
"prefect.resource.id": f"infrahub.branch.{self.branch_name}",
|
|
144
|
+
"infrahub.branch.id": self.branch_id,
|
|
145
|
+
"infrahub.branch.name": self.branch_name,
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
def get_messages(self) -> list[InfrahubMessage]:
|
|
149
|
+
events: list[InfrahubMessage] = [
|
|
150
|
+
# EventBranchMigrated(
|
|
151
|
+
# branch=self.branch,
|
|
152
|
+
# meta=self.get_message_meta(),
|
|
153
|
+
# ),
|
|
154
|
+
RefreshRegistryRebasedBranch(branch=self.branch_name),
|
|
155
|
+
]
|
|
156
|
+
return events
|
infrahub/events/group_action.py
CHANGED
|
@@ -22,7 +22,7 @@ class GroupMutatedEvent(InfrahubEvent):
|
|
|
22
22
|
def get_related(self) -> list[dict[str, str]]:
|
|
23
23
|
related = super().get_related()
|
|
24
24
|
|
|
25
|
-
if self.kind in [InfrahubKind.GENERATORGROUP, InfrahubKind.GRAPHQLQUERYGROUP]:
|
|
25
|
+
if self.kind in [InfrahubKind.GENERATORGROUP, InfrahubKind.GENERATORAWAREGROUP, InfrahubKind.GRAPHQLQUERYGROUP]:
|
|
26
26
|
# Temporary workaround to avoid too large payloads for the related field
|
|
27
27
|
return related
|
|
28
28
|
|
infrahub/events/node_action.py
CHANGED
|
@@ -24,7 +24,7 @@ class NodeMutatedEvent(InfrahubEvent):
|
|
|
24
24
|
|
|
25
25
|
def get_related(self) -> list[dict[str, str]]:
|
|
26
26
|
related = super().get_related()
|
|
27
|
-
if self.kind in [InfrahubKind.GENERATORGROUP, InfrahubKind.GRAPHQLQUERYGROUP]:
|
|
27
|
+
if self.kind in [InfrahubKind.GENERATORGROUP, InfrahubKind.GENERATORAWAREGROUP, InfrahubKind.GRAPHQLQUERYGROUP]:
|
|
28
28
|
# Temporary workaround to avoid too large payloads for the related field
|
|
29
29
|
return related
|
|
30
30
|
|
infrahub/generators/models.py
CHANGED
|
@@ -44,6 +44,11 @@ class GeneratorDefinitionModel(BaseModel):
|
|
|
44
44
|
group_id: str = Field(..., description="The group to target when running this generator")
|
|
45
45
|
parameters: dict = Field(..., description="The input parameters required to run this check")
|
|
46
46
|
|
|
47
|
+
execute_in_proposed_change: bool = Field(
|
|
48
|
+
..., description="Indicates if the generator should execute in a proposed change."
|
|
49
|
+
)
|
|
50
|
+
execute_after_merge: bool = Field(..., description="Indicates if the generator should execute after a merge.")
|
|
51
|
+
|
|
47
52
|
@classmethod
|
|
48
53
|
def from_pc_generator_definition(cls, model: ProposedChangeGeneratorDefinition) -> GeneratorDefinitionModel:
|
|
49
54
|
return GeneratorDefinitionModel(
|
|
@@ -55,6 +60,8 @@ class GeneratorDefinitionModel(BaseModel):
|
|
|
55
60
|
file_path=model.file_path,
|
|
56
61
|
group_id=model.group_id,
|
|
57
62
|
parameters=model.parameters,
|
|
63
|
+
execute_in_proposed_change=model.execute_in_proposed_change,
|
|
64
|
+
execute_after_merge=model.execute_after_merge,
|
|
58
65
|
)
|
|
59
66
|
|
|
60
67
|
|