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
|
@@ -81,7 +81,7 @@ class SchemaDropdownAdd(Mutation):
|
|
|
81
81
|
_validate_schema_permission(graphql_context=graphql_context)
|
|
82
82
|
await apply_external_context(graphql_context=graphql_context, context_input=context)
|
|
83
83
|
|
|
84
|
-
kind = graphql_context.db.schema.get(name=str(data.kind), branch=graphql_context.branch.name)
|
|
84
|
+
kind = graphql_context.db.schema.get(name=str(data.kind), branch=graphql_context.branch.name, duplicate=False)
|
|
85
85
|
attribute = str(data.attribute)
|
|
86
86
|
validate_kind_dropdown(kind=kind, attribute=attribute)
|
|
87
87
|
dropdown = str(data.dropdown)
|
|
@@ -104,7 +104,7 @@ class SchemaDropdownAdd(Mutation):
|
|
|
104
104
|
context=graphql_context.get_context(),
|
|
105
105
|
)
|
|
106
106
|
|
|
107
|
-
kind = graphql_context.db.schema.get(name=str(data.kind), branch=graphql_context.branch.name)
|
|
107
|
+
kind = graphql_context.db.schema.get(name=str(data.kind), branch=graphql_context.branch.name, duplicate=False)
|
|
108
108
|
attrib = kind.get_attribute(attribute)
|
|
109
109
|
dropdown_entry = {}
|
|
110
110
|
success = False
|
|
@@ -141,7 +141,7 @@ class SchemaDropdownRemove(Mutation):
|
|
|
141
141
|
graphql_context: GraphqlContext = info.context
|
|
142
142
|
|
|
143
143
|
_validate_schema_permission(graphql_context=graphql_context)
|
|
144
|
-
kind = graphql_context.db.schema.get(name=str(data.kind), branch=graphql_context.branch.name)
|
|
144
|
+
kind = graphql_context.db.schema.get(name=str(data.kind), branch=graphql_context.branch.name, duplicate=False)
|
|
145
145
|
await apply_external_context(graphql_context=graphql_context, context_input=context)
|
|
146
146
|
|
|
147
147
|
attribute = str(data.attribute)
|
|
@@ -197,7 +197,7 @@ class SchemaEnumAdd(Mutation):
|
|
|
197
197
|
graphql_context: GraphqlContext = info.context
|
|
198
198
|
|
|
199
199
|
_validate_schema_permission(graphql_context=graphql_context)
|
|
200
|
-
kind = graphql_context.db.schema.get(name=str(data.kind), branch=graphql_context.branch.name)
|
|
200
|
+
kind = graphql_context.db.schema.get(name=str(data.kind), branch=graphql_context.branch.name, duplicate=False)
|
|
201
201
|
await apply_external_context(graphql_context=graphql_context, context_input=context)
|
|
202
202
|
|
|
203
203
|
attribute = str(data.attribute)
|
|
@@ -243,7 +243,7 @@ class SchemaEnumRemove(Mutation):
|
|
|
243
243
|
graphql_context: GraphqlContext = info.context
|
|
244
244
|
|
|
245
245
|
_validate_schema_permission(graphql_context=graphql_context)
|
|
246
|
-
kind = graphql_context.db.schema.get(name=str(data.kind), branch=graphql_context.branch.name)
|
|
246
|
+
kind = graphql_context.db.schema.get(name=str(data.kind), branch=graphql_context.branch.name, duplicate=False)
|
|
247
247
|
await apply_external_context(graphql_context=graphql_context, context_input=context)
|
|
248
248
|
|
|
249
249
|
attribute = str(data.attribute)
|
|
@@ -127,7 +127,7 @@ class InfrahubWebhookMutation(InfrahubMutationMixin, Mutation):
|
|
|
127
127
|
|
|
128
128
|
|
|
129
129
|
def _validate_input(graphql_context: GraphqlContext, branch: Branch, input_data: WebhookCreate) -> None:
|
|
130
|
-
if input_data.node_kind:
|
|
130
|
+
if input_data.node_kind and input_data.node_kind.value:
|
|
131
131
|
# Validate that the requested node_kind exists, will raise an error if not
|
|
132
132
|
graphql_context.db.schema.get(name=input_data.node_kind.value, branch=branch, duplicate=False)
|
|
133
133
|
|
|
@@ -242,7 +242,7 @@ class PoolUtilization(ObjectType):
|
|
|
242
242
|
if "kind" in node_fields:
|
|
243
243
|
node_response["kind"] = resource_node.get_kind()
|
|
244
244
|
if "display_label" in node_fields:
|
|
245
|
-
node_response["display_label"] = await resource_node.
|
|
245
|
+
node_response["display_label"] = await resource_node.get_display_label(db=db)
|
|
246
246
|
if "weight" in node_fields:
|
|
247
247
|
node_response["weight"] = await resource_node.get_resource_weight(db=db) # type: ignore[attr-defined]
|
|
248
248
|
if "utilization" in node_fields:
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
from infrahub.core.timestamp import Timestamp
|
|
7
|
+
from infrahub.exceptions import InitializationError
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
import graphene
|
|
11
|
+
|
|
12
|
+
from infrahub.core.branch import Branch
|
|
13
|
+
from infrahub.core.schema.schema_branch import SchemaBranch
|
|
14
|
+
from infrahub.graphql.manager import GraphQLSchemaManager, InterfaceReference
|
|
15
|
+
|
|
16
|
+
from .mutations.main import InfrahubMutation
|
|
17
|
+
from .types import InfrahubObject
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class BranchDetails:
|
|
22
|
+
schema_changed_at: Timestamp
|
|
23
|
+
schema_hash: str
|
|
24
|
+
gql_manager: GraphQLSchemaManager
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class GraphQLSchemaRegistry:
|
|
29
|
+
_branch_details_by_hash: dict[str, BranchDetails] = field(default_factory=dict)
|
|
30
|
+
_branch_name_by_hash: dict[str, set[str]] = field(default_factory=dict)
|
|
31
|
+
_branch_hash_activation_by_branch_name: dict[str, dict[str, str]] = field(default_factory=dict)
|
|
32
|
+
_registered_interface_types: dict[str, type[graphene.Interface]] = field(default_factory=dict)
|
|
33
|
+
_reference_hash_schema_map: dict[str, set[str]] = field(default_factory=dict)
|
|
34
|
+
_registered_edge_types: dict[str, type[InfrahubObject]] = field(default_factory=dict)
|
|
35
|
+
_registered_paginated_types: dict[str, type[InfrahubObject]] = field(default_factory=dict)
|
|
36
|
+
_registered_input_types: dict[str, type[graphene.InputObjectType]] = field(default_factory=dict)
|
|
37
|
+
_registered_object_types: dict[str, type[InfrahubObject]] = field(default_factory=dict)
|
|
38
|
+
_registered_mutation_types: dict[str, type[InfrahubMutation]] = field(default_factory=dict)
|
|
39
|
+
|
|
40
|
+
_manager_class: type[GraphQLSchemaManager] | None = None
|
|
41
|
+
|
|
42
|
+
def _add_branch_hash(self, branch_name: str, schema_hash: str) -> None:
|
|
43
|
+
if schema_hash not in self._branch_name_by_hash:
|
|
44
|
+
self._branch_name_by_hash[schema_hash] = set()
|
|
45
|
+
|
|
46
|
+
self._branch_name_by_hash[schema_hash].add(branch_name)
|
|
47
|
+
|
|
48
|
+
def _register_manager(self, manager: type[GraphQLSchemaManager]) -> None:
|
|
49
|
+
self._manager_class = manager
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def manager(self) -> type[GraphQLSchemaManager]:
|
|
53
|
+
if self._manager_class:
|
|
54
|
+
return self._manager_class
|
|
55
|
+
raise InitializationError
|
|
56
|
+
|
|
57
|
+
def clear_cache(self) -> None:
|
|
58
|
+
"""Clear internal cache stored within this registry."""
|
|
59
|
+
self._branch_details_by_hash = {}
|
|
60
|
+
self._branch_name_by_hash = {}
|
|
61
|
+
self._branch_hash_activation_by_branch_name = {}
|
|
62
|
+
self._registered_interface_types = {}
|
|
63
|
+
self._reference_hash_schema_map = {}
|
|
64
|
+
self._registered_edge_types = {}
|
|
65
|
+
self._registered_paginated_types = {}
|
|
66
|
+
self._registered_input_types = {}
|
|
67
|
+
self._registered_object_types = {}
|
|
68
|
+
self._registered_mutation_types = {}
|
|
69
|
+
|
|
70
|
+
def _add_schema_to_reference_hash(self, reference_hash: str, schema_hash: str) -> None:
|
|
71
|
+
"""Add the schema hash to a map containing the referenced object.
|
|
72
|
+
|
|
73
|
+
The goal of this is to be able to see all schemas that use a given reference type,
|
|
74
|
+
once no schemas use a specific type it's safe to remove the type from the registry.
|
|
75
|
+
"""
|
|
76
|
+
if reference_hash not in self._reference_hash_schema_map:
|
|
77
|
+
self._reference_hash_schema_map[reference_hash] = set()
|
|
78
|
+
self._reference_hash_schema_map[reference_hash].add(schema_hash)
|
|
79
|
+
|
|
80
|
+
def get_edge_type(self, reference_hash: str, schema_hash: str) -> type[InfrahubObject] | None:
|
|
81
|
+
self._add_schema_to_reference_hash(reference_hash=reference_hash, schema_hash=schema_hash)
|
|
82
|
+
return self._registered_edge_types.get(reference_hash)
|
|
83
|
+
|
|
84
|
+
def set_edge_type(self, reference: type[InfrahubObject], reference_hash: str, schema_hash: str) -> None:
|
|
85
|
+
self._add_schema_to_reference_hash(reference_hash=reference_hash, schema_hash=schema_hash)
|
|
86
|
+
self._registered_edge_types[reference_hash] = reference
|
|
87
|
+
|
|
88
|
+
def get_input_type(self, reference_hash: str, schema_hash: str) -> type[graphene.InputObjectType] | None:
|
|
89
|
+
self._add_schema_to_reference_hash(reference_hash=reference_hash, schema_hash=schema_hash)
|
|
90
|
+
return self._registered_input_types.get(reference_hash)
|
|
91
|
+
|
|
92
|
+
def set_input_type(self, reference: type[graphene.InputObjectType], reference_hash: str, schema_hash: str) -> None:
|
|
93
|
+
self._add_schema_to_reference_hash(reference_hash=reference_hash, schema_hash=schema_hash)
|
|
94
|
+
self._registered_input_types[reference_hash] = reference
|
|
95
|
+
|
|
96
|
+
def get_interface_type(self, reference_hash: str, schema_hash: str) -> type[graphene.Interface] | None:
|
|
97
|
+
self._add_schema_to_reference_hash(reference_hash=reference_hash, schema_hash=schema_hash)
|
|
98
|
+
return self._registered_interface_types.get(reference_hash)
|
|
99
|
+
|
|
100
|
+
def set_interface_type(self, reference: InterfaceReference, schema_hash: str) -> None:
|
|
101
|
+
self._add_schema_to_reference_hash(reference_hash=reference.reference_hash, schema_hash=schema_hash)
|
|
102
|
+
self._registered_interface_types[reference.reference_hash] = reference.reference
|
|
103
|
+
|
|
104
|
+
def get_mutation_type(self, reference_hash: str, schema_hash: str) -> type[InfrahubMutation] | None:
|
|
105
|
+
self._add_schema_to_reference_hash(reference_hash=reference_hash, schema_hash=schema_hash)
|
|
106
|
+
return self._registered_mutation_types.get(reference_hash)
|
|
107
|
+
|
|
108
|
+
def set_mutation_type(self, reference: type[InfrahubMutation], reference_hash: str, schema_hash: str) -> None:
|
|
109
|
+
self._add_schema_to_reference_hash(reference_hash=reference_hash, schema_hash=schema_hash)
|
|
110
|
+
self._registered_mutation_types[reference_hash] = reference
|
|
111
|
+
|
|
112
|
+
def get_object_type(self, reference_hash: str, schema_hash: str) -> type[InfrahubObject] | None:
|
|
113
|
+
self._add_schema_to_reference_hash(reference_hash=reference_hash, schema_hash=schema_hash)
|
|
114
|
+
return self._registered_object_types.get(reference_hash)
|
|
115
|
+
|
|
116
|
+
def set_object_type(self, reference: type[InfrahubObject], reference_hash: str, schema_hash: str) -> None:
|
|
117
|
+
self._add_schema_to_reference_hash(reference_hash=reference_hash, schema_hash=schema_hash)
|
|
118
|
+
self._registered_object_types[reference_hash] = reference
|
|
119
|
+
|
|
120
|
+
def get_paginated_type(self, reference_hash: str, schema_hash: str) -> type[InfrahubObject] | None:
|
|
121
|
+
self._add_schema_to_reference_hash(reference_hash=reference_hash, schema_hash=schema_hash)
|
|
122
|
+
return self._registered_paginated_types.get(reference_hash)
|
|
123
|
+
|
|
124
|
+
def set_paginated_type(self, reference: type[InfrahubObject], reference_hash: str, schema_hash: str) -> None:
|
|
125
|
+
self._add_schema_to_reference_hash(reference_hash=reference_hash, schema_hash=schema_hash)
|
|
126
|
+
self._registered_paginated_types[reference_hash] = reference
|
|
127
|
+
|
|
128
|
+
def purge_inactive(self, active_branches: list[str]) -> set[str]:
|
|
129
|
+
"""Return inactive branches that were purged"""
|
|
130
|
+
inactive_branches: set[str] = set()
|
|
131
|
+
for schema_hash in list(self._branch_name_by_hash.keys()):
|
|
132
|
+
branches = list(self._branch_name_by_hash[schema_hash])
|
|
133
|
+
for branch in branches:
|
|
134
|
+
if branch not in active_branches and branch in self._branch_name_by_hash[schema_hash]:
|
|
135
|
+
inactive_branches.add(branch)
|
|
136
|
+
self._branch_name_by_hash[schema_hash].discard(branch)
|
|
137
|
+
|
|
138
|
+
for schema_hash in list(self._branch_name_by_hash.keys()):
|
|
139
|
+
if not self._branch_name_by_hash[schema_hash]:
|
|
140
|
+
# If no remaining branch is using the schema remove it completely
|
|
141
|
+
del self._branch_name_by_hash[schema_hash]
|
|
142
|
+
del self._branch_details_by_hash[schema_hash]
|
|
143
|
+
|
|
144
|
+
return inactive_branches
|
|
145
|
+
|
|
146
|
+
def cache_branch(self, branch: Branch, schema_branch: SchemaBranch, schema_hash: str) -> BranchDetails:
|
|
147
|
+
branch_details = BranchDetails(
|
|
148
|
+
schema_changed_at=Timestamp(branch.schema_changed_at) if branch.schema_changed_at else Timestamp(),
|
|
149
|
+
schema_hash=schema_hash,
|
|
150
|
+
gql_manager=self.manager(schema=schema_branch),
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
self._branch_details_by_hash[schema_hash] = branch_details
|
|
154
|
+
|
|
155
|
+
return branch_details
|
|
156
|
+
|
|
157
|
+
def get_manager_for_branch(self, branch: Branch, schema_branch: SchemaBranch) -> GraphQLSchemaManager:
|
|
158
|
+
if branch.schema_hash:
|
|
159
|
+
schema_hash = branch.schema_hash.main
|
|
160
|
+
else:
|
|
161
|
+
schema_hash = schema_branch.get_hash()
|
|
162
|
+
|
|
163
|
+
if schema_hash in self._branch_details_by_hash:
|
|
164
|
+
branch_details = self._branch_details_by_hash[schema_hash]
|
|
165
|
+
else:
|
|
166
|
+
branch_details = self.cache_branch(branch=branch, schema_branch=schema_branch, schema_hash=schema_hash)
|
|
167
|
+
|
|
168
|
+
self._add_branch_hash(branch_name=branch.name, schema_hash=schema_hash)
|
|
169
|
+
|
|
170
|
+
return branch_details.gql_manager
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
registry = GraphQLSchemaRegistry()
|
|
@@ -166,6 +166,8 @@ async def default_paginated_list_resolver(
|
|
|
166
166
|
|
|
167
167
|
edges = fields.get("edges", {})
|
|
168
168
|
node_fields = edges.get("node", {})
|
|
169
|
+
if "hfid" in node_fields:
|
|
170
|
+
node_fields["human_friendly_id"] = None
|
|
169
171
|
|
|
170
172
|
permission_set: dict[str, Any] | None = None
|
|
171
173
|
permissions = (
|
infrahub/graphql/schema.py
CHANGED
|
@@ -15,11 +15,14 @@ from .mutations.branch import (
|
|
|
15
15
|
BranchUpdate,
|
|
16
16
|
BranchValidate,
|
|
17
17
|
)
|
|
18
|
-
from .mutations.computed_attribute import UpdateComputedAttribute
|
|
18
|
+
from .mutations.computed_attribute import RecomputeComputedAttribute, UpdateComputedAttribute
|
|
19
19
|
from .mutations.convert_object_type import ConvertObjectType
|
|
20
20
|
from .mutations.diff import DiffUpdateMutation
|
|
21
21
|
from .mutations.diff_conflict import ResolveDiffConflict
|
|
22
|
+
from .mutations.display_label import UpdateDisplayLabel
|
|
22
23
|
from .mutations.generator import GeneratorDefinitionRequestRun
|
|
24
|
+
from .mutations.hfid import UpdateHFID
|
|
25
|
+
from .mutations.profile import InfrahubProfilesRefresh
|
|
23
26
|
from .mutations.proposed_change import (
|
|
24
27
|
ProposedChangeCheckForApprovalRevoke,
|
|
25
28
|
ProposedChangeMerge,
|
|
@@ -113,6 +116,9 @@ class InfrahubBaseMutation(ObjectType):
|
|
|
113
116
|
InfrahubRepositoryProcess = ProcessRepository.Field()
|
|
114
117
|
InfrahubRepositoryConnectivity = ValidateRepositoryConnectivity.Field()
|
|
115
118
|
InfrahubUpdateComputedAttribute = UpdateComputedAttribute.Field()
|
|
119
|
+
InfrahubUpdateDisplayLabel = UpdateDisplayLabel.Field()
|
|
120
|
+
InfrahubUpdateHFID = UpdateHFID.Field()
|
|
121
|
+
InfrahubRecomputeComputedAttribute = RecomputeComputedAttribute.Field()
|
|
116
122
|
|
|
117
123
|
RelationshipAdd = RelationshipAdd.Field()
|
|
118
124
|
RelationshipRemove = RelationshipRemove.Field()
|
|
@@ -124,3 +130,4 @@ class InfrahubBaseMutation(ObjectType):
|
|
|
124
130
|
|
|
125
131
|
ConvertObjectType = ConvertObjectType.Field()
|
|
126
132
|
CoreProposedChangeCheckForApprovalRevoke = ProposedChangeCheckForApprovalRevoke.Field()
|
|
133
|
+
InfrahubProfilesRefresh = InfrahubProfilesRefresh.Field()
|
infrahub/groups/tasks.py
CHANGED
|
@@ -20,7 +20,7 @@ async def update_graphql_query_group(model: RequestGraphQLQueryGroupUpdate) -> N
|
|
|
20
20
|
if len(model.subscribers) == 1:
|
|
21
21
|
related_nodes.append(model.subscribers[0])
|
|
22
22
|
|
|
23
|
-
await add_tags(branches=[model.branch], nodes=related_nodes)
|
|
23
|
+
await add_tags(branches=[model.branch], nodes=related_nodes, namespace=False)
|
|
24
24
|
|
|
25
25
|
params_hash = dict_hash(model.params)
|
|
26
26
|
group_name = f"{model.query_name}__{params_hash}"
|
|
File without changes
|
infrahub/hfid/gather.py
ADDED
|
@@ -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 HFIDTriggerDefinition
|
|
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-hfid",
|
|
23
|
+
cache_policy=NONE,
|
|
24
|
+
)
|
|
25
|
+
async def gather_trigger_hfid(
|
|
26
|
+
db: InfrahubDatabase | None = None, # noqa: ARG001 Needed to have a common function signature for gathering functions
|
|
27
|
+
) -> list[HFIDTriggerDefinition]:
|
|
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[HFIDTriggerDefinition] = []
|
|
36
|
+
|
|
37
|
+
for branch in branches_to_process:
|
|
38
|
+
schema_branch = registry.schema.get_schema_branch(name=branch.name)
|
|
39
|
+
branch_triggers = HFIDTriggerDefinition.from_schema_hfids(
|
|
40
|
+
branch=branch.name,
|
|
41
|
+
hfids=schema_branch.hfids,
|
|
42
|
+
branches_out_of_scope=branch.out_of_scope,
|
|
43
|
+
)
|
|
44
|
+
log.info(f"Generating {len(branch_triggers)} HFID trigger for {branch.name} (except {branch.out_of_scope})")
|
|
45
|
+
|
|
46
|
+
triggers.extend(branch_triggers)
|
|
47
|
+
|
|
48
|
+
return triggers
|
infrahub/hfid/models.py
ADDED
|
@@ -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 HFID_PROCESS
|
|
21
|
+
|
|
22
|
+
if TYPE_CHECKING:
|
|
23
|
+
from infrahub.core.schema.schema_branch_hfid import HFIDs, RelationshipTriggers
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class AttributeTarget:
|
|
28
|
+
hash: str
|
|
29
|
+
fields: set[str]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class HFIDTriggerDefinition(TriggerBranchDefinition):
|
|
33
|
+
type: TriggerType = TriggerType.HUMAN_FRIENDLY_ID
|
|
34
|
+
hfid_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.hfid_hash}"
|
|
39
|
+
|
|
40
|
+
@classmethod
|
|
41
|
+
def from_schema_hfids(
|
|
42
|
+
cls,
|
|
43
|
+
branch: str,
|
|
44
|
+
hfids: HFIDs,
|
|
45
|
+
branches_out_of_scope: list[str] | None = None,
|
|
46
|
+
) -> list[HFIDTriggerDefinition]:
|
|
47
|
+
"""
|
|
48
|
+
This function is used to create a trigger definition for a display labels of type Jinja2.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
definitions: list[HFIDTriggerDefinition] = []
|
|
52
|
+
|
|
53
|
+
for node_kind, hfid_definition in hfids.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
|
+
hfid_hash=hfid_definition.get_hash(),
|
|
63
|
+
branches_out_of_scope=branches_out_of_scope,
|
|
64
|
+
)
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
for related_kind, relationship_trigger in hfids.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
|
+
hfids=hfids,
|
|
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
|
+
hfids: HFIDs,
|
|
87
|
+
branches_out_of_scope: list[str] | None = None,
|
|
88
|
+
) -> list[HFIDTriggerDefinition]:
|
|
89
|
+
targets_by_attribute: dict[str, AttributeTarget] = {}
|
|
90
|
+
definitions: list[HFIDTriggerDefinition] = []
|
|
91
|
+
for attribute, relationship_identifiers in relationship_trigger.attributes.items():
|
|
92
|
+
for relationship_identifier in relationship_identifiers:
|
|
93
|
+
actual_node = hfids.get_node_definition(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
|
+
hfid_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
|
+
hfid_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=HFID_PROCESS,
|
|
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
|
+
hfid_hash=hfid_hash,
|
|
159
|
+
branch=branch,
|
|
160
|
+
trigger=event_trigger,
|
|
161
|
+
actions=[workflow],
|
|
162
|
+
target_kind=trigger_definition_target_kind,
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
class HFIDGraphQLResponse(BaseModel):
|
|
167
|
+
node_id: str
|
|
168
|
+
hfid_value: list[str] | None = Field(default=None)
|
|
169
|
+
variables: dict[str, str] = Field(default_factory=dict)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
class HFIDGraphQL(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["hfid"] = None
|
|
181
|
+
query = Query(
|
|
182
|
+
name="HFIDFilter",
|
|
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[HFIDGraphQLResponse]:
|
|
210
|
+
rendered_response: list[HFIDGraphQLResponse] = []
|
|
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]) -> HFIDGraphQLResponse | None:
|
|
219
|
+
if node := node_dict.get("node"):
|
|
220
|
+
node_id = node.get("id")
|
|
221
|
+
else:
|
|
222
|
+
return None
|
|
223
|
+
|
|
224
|
+
hfid = node.get("hfid")
|
|
225
|
+
response = HFIDGraphQLResponse(node_id=node_id, hfid_value=hfid)
|
|
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] = str(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] = str(related_attribute_content.get(related_value, ""))
|
|
239
|
+
|
|
240
|
+
return response
|