infrahub-server 1.5.0b1__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/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 +83 -102
- 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 +10 -2
- infrahub/core/branch/enums.py +1 -1
- infrahub/core/branch/models.py +7 -3
- infrahub/core/branch/tasks.py +68 -7
- infrahub/core/constants/__init__.py +3 -0
- 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/migrations/__init__.py +3 -0
- infrahub/core/migrations/exceptions.py +4 -0
- infrahub/core/migrations/graph/__init__.py +10 -13
- 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 +30 -2
- 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 +48 -14
- infrahub/core/node/__init__.py +16 -11
- infrahub/core/node/create.py +46 -63
- infrahub/core/node/lock_utils.py +70 -44
- 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/query/attribute.py +55 -0
- infrahub/core/query/ipam.py +1 -0
- infrahub/core/query/node.py +9 -3
- infrahub/core/query/relationship.py +1 -0
- infrahub/core/schema/__init__.py +56 -0
- infrahub/core/schema/attribute_schema.py +4 -0
- infrahub/core/schema/definitions/internal.py +2 -2
- infrahub/core/schema/generated/attribute_schema.py +2 -2
- infrahub/core/schema/manager.py +22 -1
- infrahub/core/schema/schema_branch.py +180 -22
- infrahub/database/graph.py +21 -0
- infrahub/display_labels/tasks.py +13 -7
- infrahub/events/branch_action.py +27 -1
- infrahub/generators/tasks.py +3 -7
- infrahub/git/base.py +4 -1
- infrahub/git/integrator.py +1 -1
- 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/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 +17 -10
- infrahub/graphql/mutations/hfid.py +17 -10
- infrahub/graphql/mutations/ipam.py +54 -35
- infrahub/graphql/mutations/main.py +27 -28
- infrahub/graphql/schema_sort.py +170 -0
- infrahub/graphql/types/branch.py +4 -1
- infrahub/graphql/types/enums.py +3 -0
- infrahub/hfid/tasks.py +13 -7
- infrahub/lock.py +52 -12
- infrahub/message_bus/types.py +2 -1
- infrahub/permissions/constants.py +2 -0
- infrahub/proposed_change/tasks.py +25 -16
- infrahub/server.py +6 -2
- infrahub/services/__init__.py +2 -2
- 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/setup.py +13 -4
- infrahub/trigger/tasks.py +3 -0
- infrahub/workers/dependencies.py +10 -1
- infrahub/workers/infrahub_async.py +10 -2
- infrahub/workflows/catalogue.py +8 -0
- infrahub/workflows/initialization.py +5 -0
- infrahub/workflows/utils.py +2 -1
- infrahub_sdk/client.py +13 -10
- infrahub_sdk/config.py +29 -2
- infrahub_sdk/ctl/schema.py +22 -7
- infrahub_sdk/schema/__init__.py +32 -4
- infrahub_sdk/spec/models.py +7 -0
- infrahub_sdk/spec/object.py +37 -102
- 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_server-1.5.0b1.dist-info → infrahub_server-1.5.0b2.dist-info}/METADATA +3 -1
- {infrahub_server-1.5.0b1.dist-info → infrahub_server-1.5.0b2.dist-info}/RECORD +115 -101
- infrahub_testcontainers/container.py +114 -2
- infrahub_testcontainers/docker-compose-cluster.test.yml +5 -0
- infrahub_testcontainers/docker-compose.test.yml +5 -0
- infrahub/core/migrations/graph/m040_profile_attrs_in_db.py +0 -166
- infrahub/core/migrations/graph/m041_create_hfid_display_label_in_db.py +0 -97
- infrahub/core/migrations/graph/m042_backfill_hfid_display_label_in_db.py +0 -86
- {infrahub_server-1.5.0b1.dist-info → infrahub_server-1.5.0b2.dist-info}/LICENSE.txt +0 -0
- {infrahub_server-1.5.0b1.dist-info → infrahub_server-1.5.0b2.dist-info}/WHEEL +0 -0
- {infrahub_server-1.5.0b1.dist-info → infrahub_server-1.5.0b2.dist-info}/entry_points.txt +0 -0
infrahub/core/schema/__init__.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import uuid
|
|
4
|
+
from enum import Enum
|
|
4
5
|
from typing import Any, TypeAlias
|
|
5
6
|
|
|
6
7
|
from infrahub_sdk.utils import deep_merge_dict
|
|
@@ -44,6 +45,21 @@ class SchemaExtension(HashableModel):
|
|
|
44
45
|
nodes: list[NodeExtensionSchema] = Field(default_factory=list)
|
|
45
46
|
|
|
46
47
|
|
|
48
|
+
class SchemaWarningType(Enum):
|
|
49
|
+
DEPRECATION = "deprecation"
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class SchemaWarningKind(BaseModel):
|
|
53
|
+
kind: str = Field(..., description="The kind impacted by the warning")
|
|
54
|
+
field: str | None = Field(default=None, description="The attribute or relationship impacted by the warning")
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class SchemaWarning(BaseModel):
|
|
58
|
+
type: SchemaWarningType = Field(..., description="The type of warning")
|
|
59
|
+
kinds: list[SchemaWarningKind] = Field(default_factory=list, description="The kinds impacted by the warning")
|
|
60
|
+
message: str = Field(..., description="The message that describes the warning")
|
|
61
|
+
|
|
62
|
+
|
|
47
63
|
class SchemaRoot(BaseModel):
|
|
48
64
|
model_config = ConfigDict(extra="forbid")
|
|
49
65
|
|
|
@@ -80,6 +96,46 @@ class SchemaRoot(BaseModel):
|
|
|
80
96
|
|
|
81
97
|
return errors
|
|
82
98
|
|
|
99
|
+
def gather_warnings(self) -> list[SchemaWarning]:
|
|
100
|
+
models = self.nodes + self.generics
|
|
101
|
+
warnings: list[SchemaWarning] = []
|
|
102
|
+
for model in models:
|
|
103
|
+
if model.display_labels is not None:
|
|
104
|
+
warnings.append(
|
|
105
|
+
SchemaWarning(
|
|
106
|
+
type=SchemaWarningType.DEPRECATION,
|
|
107
|
+
kinds=[SchemaWarningKind(kind=model.kind)],
|
|
108
|
+
message="display_labels are deprecated, use display_label instead",
|
|
109
|
+
)
|
|
110
|
+
)
|
|
111
|
+
if model.default_filter is not None:
|
|
112
|
+
warnings.append(
|
|
113
|
+
SchemaWarning(
|
|
114
|
+
type=SchemaWarningType.DEPRECATION,
|
|
115
|
+
kinds=[SchemaWarningKind(kind=model.kind)],
|
|
116
|
+
message="default_filter is deprecated",
|
|
117
|
+
)
|
|
118
|
+
)
|
|
119
|
+
for attribute in model.attributes:
|
|
120
|
+
if attribute.max_length is not None:
|
|
121
|
+
warnings.append(
|
|
122
|
+
SchemaWarning(
|
|
123
|
+
type=SchemaWarningType.DEPRECATION,
|
|
124
|
+
kinds=[SchemaWarningKind(kind=model.kind, field=attribute.name)],
|
|
125
|
+
message="Use of 'max_length' on attributes is deprecated, use parameters instead",
|
|
126
|
+
)
|
|
127
|
+
)
|
|
128
|
+
if attribute.min_length is not None:
|
|
129
|
+
warnings.append(
|
|
130
|
+
SchemaWarning(
|
|
131
|
+
type=SchemaWarningType.DEPRECATION,
|
|
132
|
+
kinds=[SchemaWarningKind(kind=model.kind, field=attribute.name)],
|
|
133
|
+
message="Use of 'min_length' on attributes is deprecated, use parameters instead",
|
|
134
|
+
)
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
return warnings
|
|
138
|
+
|
|
83
139
|
def generate_uuid(self) -> None:
|
|
84
140
|
"""Generate UUID for all nodes, attributes & relationships
|
|
85
141
|
Mainly useful during unit tests."""
|
|
@@ -68,6 +68,10 @@ class AttributeSchema(GeneratedAttributeSchema):
|
|
|
68
68
|
def is_deprecated(self) -> bool:
|
|
69
69
|
return bool(self.deprecation)
|
|
70
70
|
|
|
71
|
+
@property
|
|
72
|
+
def support_profiles(self) -> bool:
|
|
73
|
+
return self.read_only is False and self.optional is True
|
|
74
|
+
|
|
71
75
|
def get_id(self) -> str:
|
|
72
76
|
if self.id is None:
|
|
73
77
|
raise InitializationError("The attribute schema has not been saved yet and doesn't have an id")
|
|
@@ -568,7 +568,7 @@ attribute_schema = SchemaNode(
|
|
|
568
568
|
"Mainly relevant for internal object.",
|
|
569
569
|
default_value=False,
|
|
570
570
|
optional=True,
|
|
571
|
-
extra={"update": UpdateSupport.
|
|
571
|
+
extra={"update": UpdateSupport.MIGRATION_REQUIRED},
|
|
572
572
|
),
|
|
573
573
|
SchemaAttribute(
|
|
574
574
|
name="unique",
|
|
@@ -585,7 +585,7 @@ attribute_schema = SchemaNode(
|
|
|
585
585
|
default_value=False,
|
|
586
586
|
override_default_value=False,
|
|
587
587
|
optional=True,
|
|
588
|
-
extra={"update": UpdateSupport.
|
|
588
|
+
extra={"update": UpdateSupport.MIGRATION_REQUIRED},
|
|
589
589
|
),
|
|
590
590
|
SchemaAttribute(
|
|
591
591
|
name="branch",
|
|
@@ -78,7 +78,7 @@ class GeneratedAttributeSchema(HashableModel):
|
|
|
78
78
|
read_only: bool = Field(
|
|
79
79
|
default=False,
|
|
80
80
|
description="Set the attribute as Read-Only, users won't be able to change its value. Mainly relevant for internal object.",
|
|
81
|
-
json_schema_extra={"update": "
|
|
81
|
+
json_schema_extra={"update": "migration_required"},
|
|
82
82
|
)
|
|
83
83
|
unique: bool = Field(
|
|
84
84
|
default=False,
|
|
@@ -88,7 +88,7 @@ class GeneratedAttributeSchema(HashableModel):
|
|
|
88
88
|
optional: bool = Field(
|
|
89
89
|
default=False,
|
|
90
90
|
description="Indicate if this attribute is mandatory or optional.",
|
|
91
|
-
json_schema_extra={"update": "
|
|
91
|
+
json_schema_extra={"update": "migration_required"},
|
|
92
92
|
)
|
|
93
93
|
branch: BranchSupportType | None = Field(
|
|
94
94
|
default=None,
|
infrahub/core/schema/manager.py
CHANGED
|
@@ -2,6 +2,9 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from typing import TYPE_CHECKING, Any
|
|
4
4
|
|
|
5
|
+
from cachetools import LRUCache
|
|
6
|
+
from infrahub_sdk.schema import BranchSchema as SDKBranchSchema
|
|
7
|
+
|
|
5
8
|
from infrahub import lock
|
|
6
9
|
from infrahub.core.manager import NodeManager
|
|
7
10
|
from infrahub.core.models import (
|
|
@@ -40,6 +43,8 @@ class SchemaManager(NodeManager):
|
|
|
40
43
|
def __init__(self) -> None:
|
|
41
44
|
self._cache: dict[int, Any] = {}
|
|
42
45
|
self._branches: dict[str, SchemaBranch] = {}
|
|
46
|
+
self._branch_hash_by_name: dict[str, str] = {}
|
|
47
|
+
self._sdk_branches: LRUCache[str, SDKBranchSchema] = LRUCache(maxsize=10)
|
|
43
48
|
|
|
44
49
|
def _get_from_cache(self, key: int) -> Any:
|
|
45
50
|
return self._cache[key]
|
|
@@ -140,12 +145,26 @@ class SchemaManager(NodeManager):
|
|
|
140
145
|
if name in self._branches:
|
|
141
146
|
return self._branches[name]
|
|
142
147
|
|
|
143
|
-
self.
|
|
148
|
+
self.set_schema_branch(name, schema=SchemaBranch(cache=self._cache, name=name))
|
|
144
149
|
return self._branches[name]
|
|
145
150
|
|
|
151
|
+
def get_sdk_schema_branch(self, name: str) -> SDKBranchSchema:
|
|
152
|
+
schema_hash = self._branch_hash_by_name[name]
|
|
153
|
+
branch_schema = self._sdk_branches.get(schema_hash)
|
|
154
|
+
if not branch_schema:
|
|
155
|
+
self._sdk_branches[schema_hash] = SDKBranchSchema.from_api_response(
|
|
156
|
+
data=self._branches[name].to_dict_api_schema_object()
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
return self._sdk_branches[schema_hash]
|
|
160
|
+
|
|
146
161
|
def set_schema_branch(self, name: str, schema: SchemaBranch) -> None:
|
|
147
162
|
schema.name = name
|
|
148
163
|
self._branches[name] = schema
|
|
164
|
+
self._branch_hash_by_name[name] = schema.get_hash()
|
|
165
|
+
|
|
166
|
+
def has_schema_branch(self, name: str) -> bool:
|
|
167
|
+
return name in self._branches
|
|
149
168
|
|
|
150
169
|
def process_schema_branch(self, name: str) -> None:
|
|
151
170
|
schema_branch = self.get_schema_branch(name=name)
|
|
@@ -764,6 +783,8 @@ class SchemaManager(NodeManager):
|
|
|
764
783
|
for branch_name in list(self._branches.keys()):
|
|
765
784
|
if branch_name not in active_branches:
|
|
766
785
|
del self._branches[branch_name]
|
|
786
|
+
if branch_name in self._branch_hash_by_name:
|
|
787
|
+
del self._branch_hash_by_name[branch_name]
|
|
767
788
|
removed_branches.append(branch_name)
|
|
768
789
|
|
|
769
790
|
for hash_key in list(self._cache.keys()):
|
|
@@ -169,6 +169,14 @@ class SchemaBranch:
|
|
|
169
169
|
"templates": {name: self.get(name, duplicate=duplicate) for name in self.templates},
|
|
170
170
|
}
|
|
171
171
|
|
|
172
|
+
def to_dict_api_schema_object(self) -> dict[str, list[dict]]:
|
|
173
|
+
return {
|
|
174
|
+
"nodes": [self.get(name, duplicate=False).model_dump() for name in self.nodes],
|
|
175
|
+
"profiles": [self.get(name, duplicate=False).model_dump() for name in self.profiles],
|
|
176
|
+
"generics": [self.get(name, duplicate=False).model_dump() for name in self.generics],
|
|
177
|
+
"templates": [self.get(name, duplicate=False).model_dump() for name in self.templates],
|
|
178
|
+
}
|
|
179
|
+
|
|
172
180
|
@classmethod
|
|
173
181
|
def from_dict_schema_object(cls, data: dict) -> Self:
|
|
174
182
|
type_mapping = {
|
|
@@ -320,14 +328,23 @@ class SchemaBranch:
|
|
|
320
328
|
elif name in self.templates:
|
|
321
329
|
key = self.templates[name]
|
|
322
330
|
|
|
323
|
-
if key
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
331
|
+
if not key:
|
|
332
|
+
raise SchemaNotFoundError(
|
|
333
|
+
branch_name=self.name, identifier=name, message=f"Unable to find the schema {name!r} in the registry"
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
schema: MainSchemaTypes | None = None
|
|
337
|
+
try:
|
|
338
|
+
schema = self._cache[key]
|
|
339
|
+
except KeyError:
|
|
340
|
+
pass
|
|
327
341
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
342
|
+
if not schema:
|
|
343
|
+
raise ValueError(f"Schema {name!r} on branch {self.name} has incorrect hash: {key!r}")
|
|
344
|
+
|
|
345
|
+
if duplicate:
|
|
346
|
+
return schema.duplicate()
|
|
347
|
+
return schema
|
|
331
348
|
|
|
332
349
|
def get_node(self, name: str, duplicate: bool = True) -> NodeSchema:
|
|
333
350
|
"""Access a specific NodeSchema, defined by its kind."""
|
|
@@ -540,13 +557,14 @@ class SchemaBranch:
|
|
|
540
557
|
self.validate_identifiers()
|
|
541
558
|
self.sync_uniqueness_constraints_and_unique_attributes()
|
|
542
559
|
self.validate_uniqueness_constraints()
|
|
543
|
-
self.validate_display_label()
|
|
544
560
|
self.validate_display_labels()
|
|
561
|
+
self.validate_display_label()
|
|
545
562
|
self.validate_order_by()
|
|
546
563
|
self.validate_default_filters()
|
|
547
564
|
self.validate_parent_component()
|
|
548
565
|
self.validate_human_friendly_id()
|
|
549
566
|
self.validate_required_relationships()
|
|
567
|
+
self.validate_inherited_relationships_fields()
|
|
550
568
|
|
|
551
569
|
def process_post_validation(self) -> None:
|
|
552
570
|
self.cleanup_inherited_elements()
|
|
@@ -556,6 +574,7 @@ class SchemaBranch:
|
|
|
556
574
|
self.process_dropdowns()
|
|
557
575
|
self.process_relationships()
|
|
558
576
|
self.process_human_friendly_id()
|
|
577
|
+
self.register_human_friendly_id()
|
|
559
578
|
|
|
560
579
|
def _generate_identifier_string(self, node_kind: str, peer_kind: str) -> str:
|
|
561
580
|
return "__".join(sorted([node_kind, peer_kind])).lower()
|
|
@@ -774,20 +793,21 @@ class SchemaBranch:
|
|
|
774
793
|
if len(node_schema.display_labels) == 1:
|
|
775
794
|
# If the previous display_labels consist of a single attribute convert
|
|
776
795
|
# it to an attribute based display label
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
# display_label, if this is the case we need to append '__value'
|
|
781
|
-
converted_display_label = f"{converted_display_label}__value"
|
|
782
|
-
update_candidate.display_label = converted_display_label
|
|
796
|
+
update_candidate.display_label = _format_display_label_component(
|
|
797
|
+
component=node_schema.display_labels[0]
|
|
798
|
+
)
|
|
783
799
|
else:
|
|
784
800
|
# If the previous display label consists of multiple attributes
|
|
785
801
|
# convert it to a Jinja2 based display label
|
|
786
802
|
update_candidate.display_label = " ".join(
|
|
787
|
-
[
|
|
803
|
+
[
|
|
804
|
+
f"{{{{ {_format_display_label_component(component=display_label)} }}}}"
|
|
805
|
+
for display_label in node_schema.display_labels
|
|
806
|
+
]
|
|
788
807
|
)
|
|
789
808
|
self.set(name=name, schema=update_candidate)
|
|
790
809
|
|
|
810
|
+
node_schema = self.get(name=name, duplicate=False)
|
|
791
811
|
if not node_schema.display_label:
|
|
792
812
|
continue
|
|
793
813
|
|
|
@@ -890,7 +910,6 @@ class SchemaBranch:
|
|
|
890
910
|
return False
|
|
891
911
|
|
|
892
912
|
def validate_human_friendly_id(self) -> None:
|
|
893
|
-
self.hfids = HFIDs()
|
|
894
913
|
for name in self.generic_names_without_templates + self.node_names:
|
|
895
914
|
node_schema = self.get(name=name, duplicate=False)
|
|
896
915
|
|
|
@@ -924,11 +943,6 @@ class SchemaBranch:
|
|
|
924
943
|
rel_schemas_to_paths[rel_identifier] = (schema_path.related_schema, [])
|
|
925
944
|
rel_schemas_to_paths[rel_identifier][1].append(schema_path.attribute_path_as_str)
|
|
926
945
|
|
|
927
|
-
if node_schema.is_node_schema and node_schema.namespace not in ["Schema", "Internal"]:
|
|
928
|
-
self.hfids.register_hfid_schema_path(
|
|
929
|
-
kind=node_schema.kind, schema_path=schema_path, hfid=node_schema.human_friendly_id
|
|
930
|
-
)
|
|
931
|
-
|
|
932
946
|
if config.SETTINGS.main.schema_strict_mode:
|
|
933
947
|
# For every relationship referred within hfid, check whether the combination of attributes is unique is the peer schema node
|
|
934
948
|
for related_schema, attrs_paths in rel_schemas_to_paths.values():
|
|
@@ -1327,6 +1341,81 @@ class SchemaBranch:
|
|
|
1327
1341
|
f"{node.kind}: Relationship {rel.name!r} max_count must be 0 or greater than 1 when cardinality is MANY"
|
|
1328
1342
|
)
|
|
1329
1343
|
|
|
1344
|
+
def validate_inherited_relationships_fields(self) -> None:
|
|
1345
|
+
for name in self.node_names:
|
|
1346
|
+
node_schema = self.get(name=name, duplicate=False)
|
|
1347
|
+
if not node_schema.inherit_from:
|
|
1348
|
+
continue
|
|
1349
|
+
|
|
1350
|
+
self.validate_node_inherited_relationship_fields(node_schema)
|
|
1351
|
+
|
|
1352
|
+
def validate_node_inherited_relationship_fields(self, node_schema: NodeSchema) -> None:
|
|
1353
|
+
generics = [self.get(name=node_name, duplicate=False) for node_name in node_schema.inherit_from]
|
|
1354
|
+
relationship_names = [node.relationship_names for node in generics]
|
|
1355
|
+
related_relationship_names = set().union(
|
|
1356
|
+
*[
|
|
1357
|
+
set(relationship_name_a) & set(relationship_name_b)
|
|
1358
|
+
for index, relationship_name_a in enumerate(relationship_names)
|
|
1359
|
+
for relationship_name_b in relationship_names[index + 1 :]
|
|
1360
|
+
]
|
|
1361
|
+
)
|
|
1362
|
+
# Check that the relationship properties match
|
|
1363
|
+
# for every generic node in generics list having related relationship names
|
|
1364
|
+
for index, generic_a in enumerate(generics):
|
|
1365
|
+
for generic_b in generics[index + 1 :]:
|
|
1366
|
+
for relationship_name in related_relationship_names:
|
|
1367
|
+
try:
|
|
1368
|
+
relationship_a = generic_a.get_relationship(name=relationship_name)
|
|
1369
|
+
relationship_b = generic_b.get_relationship(name=relationship_name)
|
|
1370
|
+
except ValueError:
|
|
1371
|
+
continue
|
|
1372
|
+
|
|
1373
|
+
matched, _property = self._check_relationship_properties_match(
|
|
1374
|
+
relationship_a=relationship_a, relationship_b=relationship_b
|
|
1375
|
+
)
|
|
1376
|
+
if not matched:
|
|
1377
|
+
raise ValueError(
|
|
1378
|
+
f"{node_schema.kind} inherits from '{generic_a.kind}' & '{generic_b.kind}'"
|
|
1379
|
+
f" with different '{_property}' on the '{relationship_name}' relationship"
|
|
1380
|
+
)
|
|
1381
|
+
|
|
1382
|
+
def _check_relationship_properties_match(
|
|
1383
|
+
self, relationship_a: RelationshipSchema, relationship_b: RelationshipSchema
|
|
1384
|
+
) -> tuple[bool, str | None]:
|
|
1385
|
+
compulsorily_matching_properties = (
|
|
1386
|
+
"name",
|
|
1387
|
+
"peer",
|
|
1388
|
+
"kind",
|
|
1389
|
+
"identifier",
|
|
1390
|
+
"cardinality",
|
|
1391
|
+
"min_count",
|
|
1392
|
+
"max_count",
|
|
1393
|
+
"common_parent",
|
|
1394
|
+
"common_relatives",
|
|
1395
|
+
"optional",
|
|
1396
|
+
"branch",
|
|
1397
|
+
"direction",
|
|
1398
|
+
"on_delete",
|
|
1399
|
+
"read_only",
|
|
1400
|
+
"hierarchical",
|
|
1401
|
+
"allow_override",
|
|
1402
|
+
)
|
|
1403
|
+
for _property in compulsorily_matching_properties:
|
|
1404
|
+
if not hasattr(relationship_a, _property) or not hasattr(relationship_b, _property):
|
|
1405
|
+
continue
|
|
1406
|
+
|
|
1407
|
+
equal_delete_actions = (None, RelationshipDeleteBehavior.NO_ACTION)
|
|
1408
|
+
if (
|
|
1409
|
+
_property == "on_delete"
|
|
1410
|
+
and getattr(relationship_a, _property) in equal_delete_actions
|
|
1411
|
+
and getattr(relationship_b, _property) in equal_delete_actions
|
|
1412
|
+
):
|
|
1413
|
+
continue
|
|
1414
|
+
|
|
1415
|
+
if getattr(relationship_a, _property) != getattr(relationship_b, _property):
|
|
1416
|
+
return False, _property
|
|
1417
|
+
return True, None
|
|
1418
|
+
|
|
1330
1419
|
def process_dropdowns(self) -> None:
|
|
1331
1420
|
for name in self.all_names:
|
|
1332
1421
|
node = self.get(name=name, duplicate=False)
|
|
@@ -1455,6 +1544,34 @@ class SchemaBranch:
|
|
|
1455
1544
|
node.uniqueness_constraints = [hfid_uniqueness_constraint]
|
|
1456
1545
|
self.set(name=node.kind, schema=node)
|
|
1457
1546
|
|
|
1547
|
+
def register_human_friendly_id(self) -> None:
|
|
1548
|
+
"""Register HFID automations
|
|
1549
|
+
|
|
1550
|
+
Register the HFIDs after all processing and validation has been done.
|
|
1551
|
+
"""
|
|
1552
|
+
|
|
1553
|
+
self.hfids = HFIDs()
|
|
1554
|
+
for name in self.generic_names_without_templates + self.node_names:
|
|
1555
|
+
node_schema = self.get(name=name, duplicate=False)
|
|
1556
|
+
|
|
1557
|
+
if not node_schema.human_friendly_id:
|
|
1558
|
+
continue
|
|
1559
|
+
|
|
1560
|
+
allowed_types = SchemaElementPathType.ATTR_WITH_PROP | SchemaElementPathType.REL_ONE_MANDATORY_ATTR
|
|
1561
|
+
|
|
1562
|
+
for hfid_path in node_schema.human_friendly_id:
|
|
1563
|
+
schema_path = self.validate_schema_path(
|
|
1564
|
+
node_schema=node_schema,
|
|
1565
|
+
path=hfid_path,
|
|
1566
|
+
allowed_path_types=allowed_types,
|
|
1567
|
+
element_name="human_friendly_id",
|
|
1568
|
+
)
|
|
1569
|
+
|
|
1570
|
+
if node_schema.is_node_schema and node_schema.namespace not in ["Schema", "Internal"]:
|
|
1571
|
+
self.hfids.register_hfid_schema_path(
|
|
1572
|
+
kind=node_schema.kind, schema_path=schema_path, hfid=node_schema.human_friendly_id
|
|
1573
|
+
)
|
|
1574
|
+
|
|
1458
1575
|
def process_hierarchy(self) -> None:
|
|
1459
1576
|
for name in self.nodes.keys():
|
|
1460
1577
|
node = self.get_node(name=name, duplicate=False)
|
|
@@ -1750,6 +1867,8 @@ class SchemaBranch:
|
|
|
1750
1867
|
"""Generate order_weight for all generic schemas."""
|
|
1751
1868
|
for name in self.generic_names:
|
|
1752
1869
|
node = self.get(name=name, duplicate=False)
|
|
1870
|
+
if node.namespace == "Template":
|
|
1871
|
+
continue
|
|
1753
1872
|
|
|
1754
1873
|
items_to_update = [item for item in node.attributes + node.relationships if not item.order_weight]
|
|
1755
1874
|
|
|
@@ -1813,10 +1932,36 @@ class SchemaBranch:
|
|
|
1813
1932
|
|
|
1814
1933
|
self.set(name=name, schema=template)
|
|
1815
1934
|
|
|
1935
|
+
def _generate_generics_templates_weight(self) -> None:
|
|
1936
|
+
"""Generate order_weight for generic templates.
|
|
1937
|
+
|
|
1938
|
+
The order of the fields for the template must respect the order of the node.
|
|
1939
|
+
"""
|
|
1940
|
+
for name in self.generic_names:
|
|
1941
|
+
generic_node = self.get(name=name, duplicate=False)
|
|
1942
|
+
try:
|
|
1943
|
+
template = self.get(name=self._get_object_template_kind(node_kind=generic_node.kind), duplicate=True)
|
|
1944
|
+
except SchemaNotFoundError:
|
|
1945
|
+
continue
|
|
1946
|
+
|
|
1947
|
+
generic_node_weights = {
|
|
1948
|
+
item.name: item.order_weight
|
|
1949
|
+
for item in generic_node.attributes + generic_node.relationships
|
|
1950
|
+
if item.order_weight is not None
|
|
1951
|
+
}
|
|
1952
|
+
|
|
1953
|
+
for item in template.attributes + template.relationships:
|
|
1954
|
+
if item.order_weight:
|
|
1955
|
+
continue
|
|
1956
|
+
item.order_weight = generic_node_weights[item.name] if item.name in generic_node_weights else None
|
|
1957
|
+
|
|
1958
|
+
self.set(name=template.kind, schema=template)
|
|
1959
|
+
|
|
1816
1960
|
def generate_weight(self) -> None:
|
|
1817
1961
|
self._generate_weight_generics()
|
|
1818
1962
|
self._generate_weight_nodes_profiles()
|
|
1819
1963
|
self._generate_weight_templates()
|
|
1964
|
+
self._generate_generics_templates_weight()
|
|
1820
1965
|
|
|
1821
1966
|
def cleanup_inherited_elements(self) -> None:
|
|
1822
1967
|
for name in self.node_names:
|
|
@@ -2142,7 +2287,7 @@ class SchemaBranch:
|
|
|
2142
2287
|
)
|
|
2143
2288
|
|
|
2144
2289
|
for node_attr in node.attributes:
|
|
2145
|
-
if
|
|
2290
|
+
if not node_attr.support_profiles:
|
|
2146
2291
|
continue
|
|
2147
2292
|
attr_schema_class = get_attribute_schema_class_for_kind(kind=node_attr.kind)
|
|
2148
2293
|
attr = attr_schema_class(
|
|
@@ -2447,3 +2592,16 @@ class SchemaBranch:
|
|
|
2447
2592
|
updated_used_by_node = set(chain(template_schema_kinds, set(core_node_schema.used_by)))
|
|
2448
2593
|
core_node_schema.used_by = sorted(updated_used_by_node)
|
|
2449
2594
|
self.set(name=InfrahubKind.NODE, schema=core_node_schema)
|
|
2595
|
+
|
|
2596
|
+
|
|
2597
|
+
def _format_display_label_component(component: str) -> str:
|
|
2598
|
+
"""Return correct format for display_label.
|
|
2599
|
+
|
|
2600
|
+
Previously both the format of 'name' and 'name__value' was
|
|
2601
|
+
supported this function ensures that the proper 'name__value'
|
|
2602
|
+
format is used
|
|
2603
|
+
"""
|
|
2604
|
+
if "__" in component:
|
|
2605
|
+
return component
|
|
2606
|
+
|
|
2607
|
+
return f"{component}__value"
|
|
@@ -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
|
+
)
|
infrahub/display_labels/tasks.py
CHANGED
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from typing import cast
|
|
4
4
|
|
|
5
|
+
from infrahub_sdk.exceptions import URLNotFoundError
|
|
5
6
|
from infrahub_sdk.template import Jinja2Template
|
|
6
7
|
from prefect import flow
|
|
7
8
|
from prefect.logging import get_run_logger
|
|
@@ -53,12 +54,17 @@ async def display_label_jinja2_update_value(
|
|
|
53
54
|
log.debug(f"Ignoring to update {obj} with existing value on display_label={value}")
|
|
54
55
|
return
|
|
55
56
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
+
)
|
|
62
68
|
|
|
63
69
|
|
|
64
70
|
@flow(
|
|
@@ -152,7 +158,7 @@ async def display_labels_setup_jinja2(
|
|
|
152
158
|
|
|
153
159
|
@flow(
|
|
154
160
|
name="trigger-update-display-labels",
|
|
155
|
-
flow_run_name="Trigger updates for display labels for kind",
|
|
161
|
+
flow_run_name="Trigger updates for display labels for {kind}",
|
|
156
162
|
)
|
|
157
163
|
async def trigger_update_display_labels(
|
|
158
164
|
branch_name: str,
|
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/generators/tasks.py
CHANGED
|
@@ -23,6 +23,7 @@ from infrahub.generators.models import (
|
|
|
23
23
|
)
|
|
24
24
|
from infrahub.git.base import extract_repo_file_information
|
|
25
25
|
from infrahub.git.repository import get_initialized_repo
|
|
26
|
+
from infrahub.git.utils import fetch_proposed_change_generator_definition_targets
|
|
26
27
|
from infrahub.workers.dependencies import get_client, get_workflow
|
|
27
28
|
from infrahub.workflows.catalogue import REQUEST_GENERATOR_DEFINITION_RUN, REQUEST_GENERATOR_RUN
|
|
28
29
|
from infrahub.workflows.utils import add_tags
|
|
@@ -195,14 +196,9 @@ async def request_generator_definition_run(
|
|
|
195
196
|
branch=model.branch,
|
|
196
197
|
)
|
|
197
198
|
|
|
198
|
-
group = await
|
|
199
|
-
|
|
200
|
-
prefetch_relationships=True,
|
|
201
|
-
populate_store=True,
|
|
202
|
-
id=model.generator_definition.group_id,
|
|
203
|
-
branch=model.branch,
|
|
199
|
+
group = await fetch_proposed_change_generator_definition_targets(
|
|
200
|
+
client=client, branch=model.branch, definition=model.generator_definition
|
|
204
201
|
)
|
|
205
|
-
await group.members.fetch()
|
|
206
202
|
|
|
207
203
|
instance_by_member = {}
|
|
208
204
|
for instance in existing_instances:
|
infrahub/git/base.py
CHANGED
|
@@ -941,7 +941,10 @@ class InfrahubRepositoryBase(BaseModel, ABC):
|
|
|
941
941
|
def _raise_enriched_error_static(
|
|
942
942
|
error: GitCommandError, name: str, location: str, branch_name: str | None = None
|
|
943
943
|
) -> NoReturn:
|
|
944
|
-
if
|
|
944
|
+
if any(
|
|
945
|
+
err in error.stderr
|
|
946
|
+
for err in ("Repository not found", "does not appear to be a git", "Failed to connect to")
|
|
947
|
+
):
|
|
945
948
|
raise RepositoryConnectionError(identifier=name) from error
|
|
946
949
|
|
|
947
950
|
if "error: pathspec" in error.stderr:
|
infrahub/git/integrator.py
CHANGED
|
@@ -1371,7 +1371,7 @@ class InfrahubRepositoryIntegrator(InfrahubRepositoryBase):
|
|
|
1371
1371
|
message: CheckArtifactCreate | RequestArtifactGenerate,
|
|
1372
1372
|
) -> ArtifactGenerateResult:
|
|
1373
1373
|
response = await self.sdk.query_gql_query(
|
|
1374
|
-
name=message.
|
|
1374
|
+
name=message.query_id,
|
|
1375
1375
|
variables=message.variables,
|
|
1376
1376
|
update_group=True,
|
|
1377
1377
|
subscribers=[artifact.id],
|
infrahub/git/models.py
CHANGED
|
@@ -38,7 +38,8 @@ class RequestArtifactGenerate(BaseModel):
|
|
|
38
38
|
target_kind: str = Field(..., description="The kind of the target object for this artifact")
|
|
39
39
|
target_name: str = Field(..., description="Name of the artifact target")
|
|
40
40
|
artifact_id: str | None = Field(default=None, description="The id of the artifact if it previously existed")
|
|
41
|
-
query: str = Field(..., description="The name of the query to use when collecting data")
|
|
41
|
+
query: str = Field(..., description="The name of the query to use when collecting data") # Deprecated
|
|
42
|
+
query_id: str = Field(..., description="The id of the query to use when collecting data")
|
|
42
43
|
timeout: int = Field(..., description="Timeout for requests used to generate this artifact")
|
|
43
44
|
variables: dict = Field(..., description="Input variables when generating the artifact")
|
|
44
45
|
context: InfrahubContext = Field(..., description="The context of the task")
|