infrahub-server 1.6.3__py3-none-any.whl → 1.7.0b0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- infrahub/actions/tasks.py +4 -2
- infrahub/api/schema.py +3 -1
- infrahub/artifacts/tasks.py +1 -0
- infrahub/auth.py +2 -2
- infrahub/cli/db.py +6 -6
- infrahub/computed_attribute/gather.py +3 -4
- infrahub/computed_attribute/tasks.py +23 -6
- infrahub/config.py +8 -0
- infrahub/constants/enums.py +12 -0
- infrahub/core/account.py +5 -8
- infrahub/core/attribute.py +106 -108
- infrahub/core/branch/models.py +44 -71
- infrahub/core/branch/tasks.py +5 -3
- infrahub/core/changelog/diff.py +1 -20
- infrahub/core/changelog/models.py +0 -7
- infrahub/core/constants/__init__.py +17 -0
- infrahub/core/constants/database.py +0 -1
- infrahub/core/constants/schema.py +0 -1
- infrahub/core/convert_object_type/repository_conversion.py +3 -4
- infrahub/core/diff/data_check_synchronizer.py +3 -2
- infrahub/core/diff/enricher/cardinality_one.py +1 -1
- infrahub/core/diff/merger/merger.py +27 -1
- infrahub/core/diff/merger/serializer.py +3 -10
- infrahub/core/diff/model/diff.py +1 -1
- infrahub/core/diff/query/merge.py +376 -135
- infrahub/core/graph/__init__.py +1 -1
- infrahub/core/graph/constraints.py +2 -2
- infrahub/core/graph/schema.py +2 -12
- infrahub/core/manager.py +132 -126
- infrahub/core/metadata/__init__.py +0 -0
- infrahub/core/metadata/interface.py +37 -0
- infrahub/core/metadata/model.py +31 -0
- infrahub/core/metadata/query/__init__.py +0 -0
- infrahub/core/metadata/query/node_metadata.py +301 -0
- infrahub/core/migrations/graph/__init__.py +4 -0
- infrahub/core/migrations/graph/m013_convert_git_password_credential.py +3 -8
- infrahub/core/migrations/graph/m017_add_core_profile.py +5 -2
- infrahub/core/migrations/graph/m018_uniqueness_nulls.py +2 -1
- infrahub/core/migrations/graph/m019_restore_rels_to_time.py +0 -10
- infrahub/core/migrations/graph/m020_duplicate_edges.py +0 -8
- infrahub/core/migrations/graph/m025_uniqueness_nulls.py +2 -1
- infrahub/core/migrations/graph/m026_0000_prefix_fix.py +2 -1
- infrahub/core/migrations/graph/m029_duplicates_cleanup.py +0 -1
- infrahub/core/migrations/graph/m031_check_number_attributes.py +2 -2
- infrahub/core/migrations/graph/m038_redo_0000_prefix_fix.py +2 -1
- infrahub/core/migrations/graph/m049_remove_is_visible_relationship.py +38 -0
- infrahub/core/migrations/graph/m050_backfill_vertex_metadata.py +168 -0
- infrahub/core/migrations/query/attribute_add.py +17 -6
- infrahub/core/migrations/query/attribute_remove.py +19 -5
- infrahub/core/migrations/query/attribute_rename.py +21 -5
- infrahub/core/migrations/query/node_duplicate.py +19 -4
- infrahub/core/migrations/schema/attribute_kind_update.py +25 -7
- infrahub/core/migrations/schema/attribute_supports_profile.py +3 -1
- infrahub/core/migrations/schema/models.py +3 -0
- infrahub/core/migrations/schema/node_attribute_add.py +4 -1
- infrahub/core/migrations/schema/node_remove.py +24 -2
- infrahub/core/migrations/schema/tasks.py +4 -1
- infrahub/core/migrations/shared.py +13 -6
- infrahub/core/models.py +6 -6
- infrahub/core/node/__init__.py +156 -57
- infrahub/core/node/create.py +7 -3
- infrahub/core/node/standard.py +100 -14
- infrahub/core/property.py +0 -1
- infrahub/core/protocols_base.py +6 -2
- infrahub/core/query/__init__.py +6 -7
- infrahub/core/query/attribute.py +161 -46
- infrahub/core/query/branch.py +57 -69
- infrahub/core/query/diff.py +4 -4
- infrahub/core/query/node.py +618 -180
- infrahub/core/query/relationship.py +449 -300
- infrahub/core/query/standard_node.py +25 -5
- infrahub/core/query/utils.py +2 -4
- infrahub/core/relationship/constraints/profiles_removal.py +168 -0
- infrahub/core/relationship/model.py +293 -139
- infrahub/core/schema/attribute_parameters.py +1 -28
- infrahub/core/schema/attribute_schema.py +17 -11
- infrahub/core/schema/manager.py +63 -43
- infrahub/core/schema/relationship_schema.py +6 -2
- infrahub/core/schema/schema_branch.py +48 -76
- infrahub/core/task/task.py +4 -2
- infrahub/core/utils.py +0 -22
- infrahub/core/validators/attribute/kind.py +2 -5
- infrahub/core/validators/determiner.py +3 -3
- infrahub/database/__init__.py +3 -3
- infrahub/dependencies/builder/constraint/grouped/node_runner.py +2 -0
- infrahub/dependencies/builder/constraint/relationship_manager/profiles_removal.py +8 -0
- infrahub/dependencies/registry.py +2 -0
- infrahub/display_labels/tasks.py +12 -3
- infrahub/git/integrator.py +18 -18
- infrahub/git/tasks.py +1 -1
- infrahub/graphql/app.py +2 -2
- infrahub/graphql/constants.py +3 -0
- infrahub/graphql/context.py +1 -1
- infrahub/graphql/initialization.py +11 -0
- infrahub/graphql/loaders/account.py +134 -0
- infrahub/graphql/loaders/node.py +5 -12
- infrahub/graphql/loaders/peers.py +5 -7
- infrahub/graphql/manager.py +158 -18
- infrahub/graphql/metadata.py +91 -0
- infrahub/graphql/models.py +33 -3
- infrahub/graphql/mutations/account.py +5 -5
- infrahub/graphql/mutations/attribute.py +0 -2
- infrahub/graphql/mutations/branch.py +9 -5
- infrahub/graphql/mutations/computed_attribute.py +1 -1
- infrahub/graphql/mutations/display_label.py +1 -1
- infrahub/graphql/mutations/hfid.py +1 -1
- infrahub/graphql/mutations/ipam.py +4 -6
- infrahub/graphql/mutations/main.py +9 -4
- infrahub/graphql/mutations/profile.py +16 -22
- infrahub/graphql/mutations/proposed_change.py +4 -4
- infrahub/graphql/mutations/relationship.py +40 -10
- infrahub/graphql/mutations/repository.py +14 -12
- infrahub/graphql/mutations/schema.py +2 -2
- infrahub/graphql/queries/branch.py +62 -6
- infrahub/graphql/queries/diff/tree.py +5 -5
- infrahub/graphql/resolvers/account_metadata.py +84 -0
- infrahub/graphql/resolvers/ipam.py +6 -8
- infrahub/graphql/resolvers/many_relationship.py +77 -35
- infrahub/graphql/resolvers/resolver.py +16 -12
- infrahub/graphql/resolvers/single_relationship.py +87 -23
- infrahub/graphql/subscription/graphql_query.py +2 -0
- infrahub/graphql/types/__init__.py +0 -1
- infrahub/graphql/types/attribute.py +10 -5
- infrahub/graphql/types/branch.py +40 -53
- infrahub/graphql/types/enums.py +3 -0
- infrahub/graphql/types/metadata.py +28 -0
- infrahub/graphql/types/node.py +22 -2
- infrahub/graphql/types/relationship.py +10 -2
- infrahub/graphql/types/standard_node.py +4 -3
- infrahub/hfid/tasks.py +12 -3
- infrahub/profiles/gather.py +56 -0
- infrahub/profiles/mandatory_fields_checker.py +116 -0
- infrahub/profiles/models.py +66 -0
- infrahub/profiles/node_applier.py +153 -12
- infrahub/profiles/queries/get_profile_data.py +143 -31
- infrahub/profiles/tasks.py +79 -27
- infrahub/profiles/triggers.py +22 -0
- infrahub/proposed_change/tasks.py +4 -1
- infrahub/tasks/artifact.py +1 -0
- infrahub/transformations/tasks.py +2 -2
- infrahub/trigger/catalogue.py +2 -0
- infrahub/trigger/models.py +1 -0
- infrahub/trigger/setup.py +3 -3
- infrahub/trigger/tasks.py +3 -0
- infrahub/validators/tasks.py +1 -0
- infrahub/webhook/models.py +1 -1
- infrahub/webhook/tasks.py +1 -1
- infrahub/workers/dependencies.py +9 -3
- infrahub/workers/infrahub_async.py +13 -4
- infrahub/workflows/catalogue.py +19 -0
- infrahub_sdk/node/constants.py +1 -0
- infrahub_sdk/node/related_node.py +13 -4
- infrahub_sdk/node/relationship.py +8 -0
- {infrahub_server-1.6.3.dist-info → infrahub_server-1.7.0b0.dist-info}/METADATA +17 -16
- {infrahub_server-1.6.3.dist-info → infrahub_server-1.7.0b0.dist-info}/RECORD +161 -143
- infrahub_testcontainers/container.py +3 -3
- infrahub_testcontainers/docker-compose-cluster.test.yml +7 -7
- infrahub_testcontainers/docker-compose.test.yml +13 -5
- {infrahub_server-1.6.3.dist-info → infrahub_server-1.7.0b0.dist-info}/WHEEL +0 -0
- {infrahub_server-1.6.3.dist-info → infrahub_server-1.7.0b0.dist-info}/entry_points.txt +0 -0
- {infrahub_server-1.6.3.dist-info → infrahub_server-1.7.0b0.dist-info}/licenses/LICENSE.txt +0 -0
infrahub/graphql/types/branch.py
CHANGED
|
@@ -2,13 +2,14 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from typing import TYPE_CHECKING, Any
|
|
4
4
|
|
|
5
|
-
from graphene import Boolean, Field, Int, List, NonNull, String
|
|
5
|
+
from graphene import Boolean, Field, Int, List, NonNull, ObjectType, String
|
|
6
6
|
|
|
7
7
|
from infrahub.core.branch import Branch
|
|
8
|
-
from infrahub.core.
|
|
8
|
+
from infrahub.core.node.standard import StandardNodeQueryFields
|
|
9
9
|
|
|
10
10
|
from ...exceptions import BranchNotFoundError
|
|
11
11
|
from .enums import InfrahubBranchStatus
|
|
12
|
+
from .metadata import InfrahubStandardNodeMetaData
|
|
12
13
|
from .standard_node import InfrahubObjectType
|
|
13
14
|
|
|
14
15
|
if TYPE_CHECKING:
|
|
@@ -34,56 +35,35 @@ class BranchType(InfrahubObjectType):
|
|
|
34
35
|
name = "Branch"
|
|
35
36
|
model = Branch
|
|
36
37
|
|
|
37
|
-
@staticmethod
|
|
38
|
-
async def _map_fields_to_graphql(objs: list[Branch], fields: dict) -> list[dict[str, Any]]:
|
|
39
|
-
return [await obj.to_graphql(fields=fields) for obj in objs]
|
|
40
|
-
|
|
41
38
|
@classmethod
|
|
42
39
|
async def get_list(
|
|
43
40
|
cls,
|
|
44
|
-
fields:
|
|
41
|
+
fields: StandardNodeQueryFields,
|
|
45
42
|
graphql_context: GraphqlContext,
|
|
46
43
|
**kwargs: Any,
|
|
47
44
|
) -> list[dict[str, Any]]:
|
|
48
45
|
async with graphql_context.db.start_session(read_only=True) as db:
|
|
49
46
|
objs = await Branch.get_list(db=db, **kwargs)
|
|
47
|
+
return [await obj.to_graphql_flat(fields=fields.node) for obj in objs]
|
|
50
48
|
|
|
51
|
-
if not objs:
|
|
52
|
-
return []
|
|
53
|
-
|
|
54
|
-
return await cls._map_fields_to_graphql(objs=objs, fields=fields)
|
|
55
49
|
|
|
56
|
-
|
|
57
|
-
async def get_by_name(
|
|
58
|
-
cls,
|
|
59
|
-
fields: dict,
|
|
60
|
-
graphql_context: GraphqlContext,
|
|
61
|
-
name: str,
|
|
62
|
-
) -> dict[str, Any]:
|
|
63
|
-
branch_responses = await cls.get_list(fields=fields, graphql_context=graphql_context, name=name)
|
|
64
|
-
|
|
65
|
-
if branch_responses:
|
|
66
|
-
return branch_responses[0]
|
|
67
|
-
raise BranchNotFoundError(f"Branch with name '{name}' not found")
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
class RequiredStringValueField(InfrahubObjectType):
|
|
50
|
+
class RequiredStringValueField(ObjectType):
|
|
71
51
|
value = String(required=True)
|
|
72
52
|
|
|
73
53
|
|
|
74
|
-
class NonRequiredStringValueField(
|
|
54
|
+
class NonRequiredStringValueField(ObjectType):
|
|
75
55
|
value = String(required=False)
|
|
76
56
|
|
|
77
57
|
|
|
78
|
-
class NonRequiredIntValueField(
|
|
58
|
+
class NonRequiredIntValueField(ObjectType):
|
|
79
59
|
value = Int(required=False)
|
|
80
60
|
|
|
81
61
|
|
|
82
|
-
class NonRequiredBooleanValueField(
|
|
62
|
+
class NonRequiredBooleanValueField(ObjectType):
|
|
83
63
|
value = Boolean(required=False)
|
|
84
64
|
|
|
85
65
|
|
|
86
|
-
class StatusField(
|
|
66
|
+
class StatusField(ObjectType):
|
|
87
67
|
value = InfrahubBranchStatus(required=True)
|
|
88
68
|
|
|
89
69
|
|
|
@@ -101,36 +81,43 @@ class InfrahubBranch(BranchType):
|
|
|
101
81
|
)
|
|
102
82
|
has_schema_changes = Field(NonRequiredBooleanValueField, required=False)
|
|
103
83
|
|
|
84
|
+
@classmethod
|
|
85
|
+
async def get_list(
|
|
86
|
+
cls,
|
|
87
|
+
fields: StandardNodeQueryFields,
|
|
88
|
+
graphql_context: GraphqlContext,
|
|
89
|
+
**kwargs: Any,
|
|
90
|
+
) -> list[dict[str, Any]]:
|
|
91
|
+
async with graphql_context.db.start_session(read_only=True) as db:
|
|
92
|
+
objs = await Branch.get_list(db=db, **kwargs)
|
|
93
|
+
return [await obj.to_graphql(fields=fields) for obj in objs]
|
|
94
|
+
|
|
95
|
+
@classmethod
|
|
96
|
+
async def get_by_name(
|
|
97
|
+
cls,
|
|
98
|
+
fields: dict,
|
|
99
|
+
graphql_context: GraphqlContext,
|
|
100
|
+
name: str,
|
|
101
|
+
) -> dict[str, Any]:
|
|
102
|
+
branch_responses = await cls.get_list(
|
|
103
|
+
fields=StandardNodeQueryFields(node=fields), graphql_context=graphql_context, name=name
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
if branch_responses:
|
|
107
|
+
return branch_responses[0]
|
|
108
|
+
raise BranchNotFoundError(f"Branch with name '{name}' not found")
|
|
109
|
+
|
|
104
110
|
class Meta:
|
|
105
111
|
description = "InfrahubBranch"
|
|
106
112
|
name = "InfrahubBranch"
|
|
107
113
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
field_keys = fields.keys()
|
|
111
|
-
result: list[dict[str, Any]] = []
|
|
112
|
-
for obj in objs:
|
|
113
|
-
if obj.name == GLOBAL_BRANCH_NAME:
|
|
114
|
-
continue
|
|
115
|
-
data: dict[str, Any] = {}
|
|
116
|
-
for field in field_keys:
|
|
117
|
-
if field == "id":
|
|
118
|
-
data["id"] = obj.uuid
|
|
119
|
-
continue
|
|
120
|
-
value = getattr(obj, field, None)
|
|
121
|
-
if isinstance(fields.get(field), dict):
|
|
122
|
-
data[field] = {"value": value}
|
|
123
|
-
else:
|
|
124
|
-
data[field] = value
|
|
125
|
-
result.append(data)
|
|
126
|
-
return result
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
class InfrahubBranchEdge(InfrahubObjectType):
|
|
114
|
+
|
|
115
|
+
class InfrahubBranchEdge(ObjectType):
|
|
130
116
|
node = Field(InfrahubBranch, required=True)
|
|
117
|
+
node_metadata = Field(InfrahubStandardNodeMetaData, required=True)
|
|
131
118
|
|
|
132
119
|
|
|
133
|
-
class InfrahubBranchType(
|
|
120
|
+
class InfrahubBranchType(ObjectType):
|
|
134
121
|
count = Field(Int, description="Total number of items")
|
|
135
122
|
edges = Field(NonNull(List(of_type=NonNull(InfrahubBranchEdge))))
|
|
136
123
|
default_branch = Field(
|
infrahub/graphql/types/enums.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from graphene import Enum
|
|
2
2
|
|
|
3
|
+
from infrahub.constants.enums import OrderDirection
|
|
3
4
|
from infrahub.core import constants
|
|
4
5
|
from infrahub.core.branch.enums import BranchStatus
|
|
5
6
|
from infrahub.permissions import constants as permission_constants
|
|
@@ -13,3 +14,5 @@ Severity = Enum.from_enum(constants.Severity)
|
|
|
13
14
|
BranchRelativePermissionDecision = Enum.from_enum(permission_constants.BranchRelativePermissionDecision)
|
|
14
15
|
|
|
15
16
|
InfrahubBranchStatus = Enum.from_enum(BranchStatus)
|
|
17
|
+
|
|
18
|
+
InfrahubOrderDirection = Enum.from_enum(OrderDirection)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from graphene import Boolean, DateTime, Field, InputObjectType, ObjectType
|
|
4
|
+
|
|
5
|
+
from infrahub.graphql.types.enums import InfrahubOrderDirection
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class InfrahubNodeMetadataOrder(InputObjectType):
|
|
9
|
+
created_at = Field(InfrahubOrderDirection, required=False, description="Order by creation timestamp")
|
|
10
|
+
updated_at = Field(InfrahubOrderDirection, required=False, description="Order by updated timestamp")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class OrderInput(InputObjectType):
|
|
14
|
+
disable = Boolean(required=False)
|
|
15
|
+
node_metadata = Field(InfrahubNodeMetadataOrder, required=False, description="Order settings for branch metadata")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class InfrahubStandardNodeMetaData(ObjectType):
|
|
19
|
+
"""Base metadata type for standard nodes.
|
|
20
|
+
|
|
21
|
+
Note: created_by and updated_by fields are added dynamically by
|
|
22
|
+
GraphQLSchemaManager._patch_static_types() to use the GenericAccount interface.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
created_at = DateTime(required=False, description="Date/Time the object has been created")
|
|
26
|
+
updated_at = DateTime(
|
|
27
|
+
required=False, description="Date/Time when the object was last modified by a user or a system task"
|
|
28
|
+
)
|
infrahub/graphql/types/node.py
CHANGED
|
@@ -5,7 +5,7 @@ from typing import Any
|
|
|
5
5
|
from graphene import ObjectType
|
|
6
6
|
from graphene.types.objecttype import ObjectTypeOptions
|
|
7
7
|
|
|
8
|
-
from infrahub.core.schema import
|
|
8
|
+
from infrahub.core.schema import MainSchemaTypes
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
class InfrahubObjectOptions(ObjectTypeOptions):
|
|
@@ -21,7 +21,27 @@ class InfrahubObject(ObjectType):
|
|
|
21
21
|
_meta: InfrahubObjectOptions | None = None,
|
|
22
22
|
**options: Any,
|
|
23
23
|
) -> None:
|
|
24
|
-
if not isinstance(schema,
|
|
24
|
+
if not isinstance(schema, MainSchemaTypes):
|
|
25
|
+
raise ValueError(f"You need to pass a valid NodeSchema in '{cls.__name__}.Meta', received '{schema}'")
|
|
26
|
+
|
|
27
|
+
if not _meta:
|
|
28
|
+
_meta = InfrahubObjectOptions(cls)
|
|
29
|
+
|
|
30
|
+
_meta.schema = schema
|
|
31
|
+
|
|
32
|
+
super().__init_subclass_with_meta__(_meta=_meta, interfaces=interfaces, **options)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class InfrahubObjectWithoutMeta(ObjectType):
|
|
36
|
+
@classmethod
|
|
37
|
+
def __init_subclass_with_meta__(
|
|
38
|
+
cls,
|
|
39
|
+
schema: MainSchemaTypes | None = None,
|
|
40
|
+
interfaces: tuple = (),
|
|
41
|
+
_meta: InfrahubObjectOptions | None = None,
|
|
42
|
+
**options: Any,
|
|
43
|
+
) -> None:
|
|
44
|
+
if not isinstance(schema, MainSchemaTypes):
|
|
25
45
|
raise ValueError(f"You need to pass a valid NodeSchema in '{cls.__name__}.Meta', received '{schema}'")
|
|
26
46
|
|
|
27
47
|
if not _meta:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from graphene import Field, List, NonNull, ObjectType, String
|
|
3
|
+
from graphene import DateTime, Field, List, NonNull, ObjectType, String
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
class RelationshipPeer(ObjectType):
|
|
@@ -8,7 +8,15 @@ class RelationshipPeer(ObjectType):
|
|
|
8
8
|
kind = String(required=False)
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
class
|
|
11
|
+
class InfrahubRelationshipMetaObject(ObjectType):
|
|
12
|
+
updated_by = String(required=False, description="User that last modified the relationship")
|
|
13
|
+
updated_at = DateTime(
|
|
14
|
+
required=False,
|
|
15
|
+
description="Date/Time when the relationship was last modified by a user or a system task",
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Relationship(InfrahubRelationshipMetaObject):
|
|
12
20
|
id = String(required=False)
|
|
13
21
|
identifier = String(required=False)
|
|
14
22
|
peers = List(NonNull(RelationshipPeer))
|
|
@@ -8,6 +8,7 @@ from graphene.types.objecttype import ObjectTypeOptions
|
|
|
8
8
|
from infrahub import config
|
|
9
9
|
|
|
10
10
|
if TYPE_CHECKING:
|
|
11
|
+
from infrahub.core.node.standard import StandardNodeQueryFields
|
|
11
12
|
from infrahub.graphql.initialization import GraphqlContext
|
|
12
13
|
|
|
13
14
|
|
|
@@ -26,7 +27,9 @@ class InfrahubObjectType(ObjectType):
|
|
|
26
27
|
super().__init_subclass_with_meta__(_meta=_meta, interfaces=interfaces, **options)
|
|
27
28
|
|
|
28
29
|
@classmethod
|
|
29
|
-
async def get_list(
|
|
30
|
+
async def get_list(
|
|
31
|
+
cls, fields: StandardNodeQueryFields, graphql_context: GraphqlContext, **kwargs
|
|
32
|
+
) -> list[dict[str, Any]]:
|
|
30
33
|
async with graphql_context.db.session(database=config.SETTINGS.database.database_name) as db:
|
|
31
34
|
filters = {key: value for key, value in kwargs.items() if "__" in key and value}
|
|
32
35
|
|
|
@@ -36,7 +39,6 @@ class InfrahubObjectType(ObjectType):
|
|
|
36
39
|
at=graphql_context.at,
|
|
37
40
|
branch=graphql_context.branch,
|
|
38
41
|
account=graphql_context.account_session,
|
|
39
|
-
include_source=True,
|
|
40
42
|
db=db,
|
|
41
43
|
)
|
|
42
44
|
else:
|
|
@@ -44,7 +46,6 @@ class InfrahubObjectType(ObjectType):
|
|
|
44
46
|
at=graphql_context.at,
|
|
45
47
|
branch=graphql_context.branch,
|
|
46
48
|
account=graphql_context.account_session,
|
|
47
|
-
include_source=True,
|
|
48
49
|
db=db,
|
|
49
50
|
)
|
|
50
51
|
|
infrahub/hfid/tasks.py
CHANGED
|
@@ -20,9 +20,11 @@ UPDATE_HFID = """
|
|
|
20
20
|
mutation UpdateHFID(
|
|
21
21
|
$id: String!,
|
|
22
22
|
$kind: String!,
|
|
23
|
-
$value: [String!]
|
|
23
|
+
$value: [String!]!,
|
|
24
|
+
$context_account_id: String!
|
|
24
25
|
) {
|
|
25
26
|
InfrahubUpdateHFID(
|
|
27
|
+
context: {account: {id: $context_account_id}},
|
|
26
28
|
data: {id: $id, value: $value, kind: $kind}
|
|
27
29
|
) {
|
|
28
30
|
ok
|
|
@@ -40,6 +42,7 @@ async def hfid_update_value(
|
|
|
40
42
|
obj: HFIDGraphQLResponse,
|
|
41
43
|
node_kind: str,
|
|
42
44
|
hfid_definition: list[str],
|
|
45
|
+
context: InfrahubContext,
|
|
43
46
|
) -> None:
|
|
44
47
|
log = get_run_logger()
|
|
45
48
|
client = get_client()
|
|
@@ -58,7 +61,12 @@ async def hfid_update_value(
|
|
|
58
61
|
try:
|
|
59
62
|
await client.execute_graphql(
|
|
60
63
|
query=UPDATE_HFID,
|
|
61
|
-
variables={
|
|
64
|
+
variables={
|
|
65
|
+
"id": obj.node_id,
|
|
66
|
+
"kind": node_kind,
|
|
67
|
+
"value": rendered_hfid,
|
|
68
|
+
"context_account_id": context.account.account_id,
|
|
69
|
+
},
|
|
62
70
|
branch_name=branch_name,
|
|
63
71
|
)
|
|
64
72
|
log.info(f"Updating {node_kind}.human_friendly_id='{rendered_hfid}' ({obj.node_id})")
|
|
@@ -77,7 +85,7 @@ async def process_hfid(
|
|
|
77
85
|
node_kind: str,
|
|
78
86
|
object_id: str,
|
|
79
87
|
target_kind: str,
|
|
80
|
-
context: InfrahubContext,
|
|
88
|
+
context: InfrahubContext,
|
|
81
89
|
) -> None:
|
|
82
90
|
log = get_run_logger()
|
|
83
91
|
client = get_client()
|
|
@@ -115,6 +123,7 @@ async def process_hfid(
|
|
|
115
123
|
obj=node,
|
|
116
124
|
node_kind=node_schema.kind,
|
|
117
125
|
hfid_definition=hfid_definition.hfid,
|
|
126
|
+
context=context,
|
|
118
127
|
)
|
|
119
128
|
|
|
120
129
|
_ = [response async for _, response in batch.execute()]
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from prefect import task
|
|
4
|
+
from prefect.cache_policies import NONE
|
|
5
|
+
|
|
6
|
+
from infrahub.core.constants import RelationshipKind
|
|
7
|
+
from infrahub.core.registry import registry
|
|
8
|
+
from infrahub.database import InfrahubDatabase # noqa: TC001 needed for prefect flow
|
|
9
|
+
from infrahub.workflows.catalogue import PROFILE_REFRESH_PROCESS
|
|
10
|
+
|
|
11
|
+
from .models import ProfileRefreshTriggerDefinition
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@task(name="gather-trigger-profile-refresh", cache_policy=NONE)
|
|
15
|
+
async def gather_trigger_profile_refresh(
|
|
16
|
+
db: InfrahubDatabase | None = None, # noqa: ARG001 Needed to have a common function signature for gathering functions
|
|
17
|
+
) -> list[ProfileRefreshTriggerDefinition]:
|
|
18
|
+
"""Gather profile refresh triggers for all profile schemas.
|
|
19
|
+
|
|
20
|
+
This function creates trigger definitions for each profile schema that will
|
|
21
|
+
listen for `NodeUpdatedEvent` on profiles. When a profile's attributes or
|
|
22
|
+
relationships change, the trigger will fire and execute the profile refresh
|
|
23
|
+
workflow to re-apply profiles to all related nodes.
|
|
24
|
+
"""
|
|
25
|
+
branches_with_diff_from_main = registry.get_altered_schema_branches()
|
|
26
|
+
branches_to_process: list[tuple[str, list[str]]] = [(branch, []) for branch in branches_with_diff_from_main]
|
|
27
|
+
branches_to_process.append((registry.default_branch, branches_with_diff_from_main))
|
|
28
|
+
|
|
29
|
+
triggers: list[ProfileRefreshTriggerDefinition] = []
|
|
30
|
+
|
|
31
|
+
for branch_scope, branches_out_of_scope in branches_to_process:
|
|
32
|
+
schema_branch = registry.schema.get_schema_branch(name=branch_scope)
|
|
33
|
+
|
|
34
|
+
for profile_name in schema_branch.profile_names:
|
|
35
|
+
profile_schema = schema_branch.get_profile(name=profile_name, duplicate=False)
|
|
36
|
+
|
|
37
|
+
trigger_attr = [attr.name for attr in profile_schema.attributes if attr.name != "profile_name"]
|
|
38
|
+
trigger_rels = [
|
|
39
|
+
rel.name
|
|
40
|
+
for rel in profile_schema.relationships
|
|
41
|
+
if rel.kind in {RelationshipKind.GENERIC, RelationshipKind.ATTRIBUTE} and rel.name != "related_nodes"
|
|
42
|
+
]
|
|
43
|
+
trigger_fields = sorted(trigger_attr + trigger_rels)
|
|
44
|
+
|
|
45
|
+
if trigger_fields:
|
|
46
|
+
triggers.append(
|
|
47
|
+
ProfileRefreshTriggerDefinition.from_profile_schema(
|
|
48
|
+
branch=branch_scope,
|
|
49
|
+
profile_kind=profile_schema.kind,
|
|
50
|
+
trigger_fields=trigger_fields,
|
|
51
|
+
workflow=PROFILE_REFRESH_PROCESS,
|
|
52
|
+
branches_out_of_scope=branches_out_of_scope,
|
|
53
|
+
)
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
return triggers
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import TYPE_CHECKING, Any
|
|
5
|
+
|
|
6
|
+
from infrahub.core.query.node import NodeGetByHFIDQuery
|
|
7
|
+
|
|
8
|
+
from .queries.get_profile_data import GetProfileDataQuery, RelationshipFilter
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from collections.abc import Sequence
|
|
12
|
+
|
|
13
|
+
from infrahub.core.branch import Branch
|
|
14
|
+
from infrahub.core.schema import NodeSchema
|
|
15
|
+
from infrahub.database import InfrahubDatabase
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class ProfileIdentifiers:
|
|
20
|
+
ids: list[str]
|
|
21
|
+
hfids: list[list[str]]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _extract_profile_identifiers_from_input(profiles_data: Sequence[Any] | None) -> ProfileIdentifiers:
|
|
25
|
+
"""Extract profile IDs and HFIDs from input data."""
|
|
26
|
+
if not profiles_data:
|
|
27
|
+
return ProfileIdentifiers(ids=[], hfids=[])
|
|
28
|
+
|
|
29
|
+
ids: list[str] = []
|
|
30
|
+
hfids: list[list[str]] = []
|
|
31
|
+
for item in profiles_data:
|
|
32
|
+
if isinstance(item, str):
|
|
33
|
+
# IDs
|
|
34
|
+
ids.append(item)
|
|
35
|
+
elif isinstance(item, dict):
|
|
36
|
+
# Dicts with `id` or `hfid` keys
|
|
37
|
+
if profile_id := item.get("id"):
|
|
38
|
+
ids.append(profile_id)
|
|
39
|
+
elif profile_hfid := item.get("hfid"):
|
|
40
|
+
hfids.append(profile_hfid)
|
|
41
|
+
elif hasattr(item, "id"):
|
|
42
|
+
# Avoid circular import by not importing Node directly
|
|
43
|
+
ids.append(item.id)
|
|
44
|
+
|
|
45
|
+
return ProfileIdentifiers(ids=ids, hfids=hfids)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
async def _resolve_hfids_to_ids(
|
|
49
|
+
db: InfrahubDatabase, branch: Branch, profile_kind: str, hfids: list[list[str]]
|
|
50
|
+
) -> list[str]:
|
|
51
|
+
query = await NodeGetByHFIDQuery.init(db=db, branch=branch, node_kind=profile_kind, hfids=hfids)
|
|
52
|
+
await query.execute(db=db)
|
|
53
|
+
return query.get_node_uuids()
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class ProfilesMandatoryFieldGetter:
|
|
57
|
+
def __init__(self, db: InfrahubDatabase, branch: Branch) -> None:
|
|
58
|
+
self.db = db
|
|
59
|
+
self.branch = branch
|
|
60
|
+
|
|
61
|
+
async def get_mandatory_fields_from_profiles(
|
|
62
|
+
self,
|
|
63
|
+
schema: NodeSchema,
|
|
64
|
+
profiles_data: Sequence[Any] | None,
|
|
65
|
+
mandatory_attr_names: list[str],
|
|
66
|
+
mandatory_rel_names: list[str],
|
|
67
|
+
) -> tuple[set[str], set[str]]:
|
|
68
|
+
"""Get mandatory attributes and relationships that are provided by profiles."""
|
|
69
|
+
identifiers = _extract_profile_identifiers_from_input(profiles_data=profiles_data)
|
|
70
|
+
|
|
71
|
+
profile_ids = list(identifiers.ids)
|
|
72
|
+
if identifiers.hfids:
|
|
73
|
+
resolved_ids = await _resolve_hfids_to_ids(
|
|
74
|
+
db=self.db, branch=self.branch, profile_kind=f"Profile{schema.kind}", hfids=identifiers.hfids
|
|
75
|
+
)
|
|
76
|
+
profile_ids.extend(resolved_ids)
|
|
77
|
+
|
|
78
|
+
if not profile_ids:
|
|
79
|
+
return set(), set()
|
|
80
|
+
|
|
81
|
+
rel_filters: list[RelationshipFilter] = []
|
|
82
|
+
rel_name_to_filter: dict[str, RelationshipFilter] = {}
|
|
83
|
+
for rel_name in mandatory_rel_names:
|
|
84
|
+
rel_schema = schema.get_relationship(name=rel_name)
|
|
85
|
+
if not rel_schema.support_profiles:
|
|
86
|
+
continue
|
|
87
|
+
|
|
88
|
+
rel_filter = RelationshipFilter(
|
|
89
|
+
relationship_identifier=f"profile_{rel_schema.get_identifier()}", direction=rel_schema.direction
|
|
90
|
+
)
|
|
91
|
+
rel_filters.append(rel_filter)
|
|
92
|
+
rel_name_to_filter[rel_name] = rel_filter
|
|
93
|
+
|
|
94
|
+
query = await GetProfileDataQuery.init(
|
|
95
|
+
db=self.db,
|
|
96
|
+
branch=self.branch,
|
|
97
|
+
profile_ids=profile_ids,
|
|
98
|
+
attr_names=mandatory_attr_names,
|
|
99
|
+
relationship_filters=rel_filters,
|
|
100
|
+
)
|
|
101
|
+
await query.execute(db=self.db)
|
|
102
|
+
profile_data_list = query.get_profile_data()
|
|
103
|
+
|
|
104
|
+
provided_attrs: set[str] = set()
|
|
105
|
+
provided_rels: set[str] = set()
|
|
106
|
+
|
|
107
|
+
for profile_data in profile_data_list:
|
|
108
|
+
for attr_name in mandatory_attr_names:
|
|
109
|
+
if profile_data.attribute_values.get(attr_name) is not None:
|
|
110
|
+
provided_attrs.add(attr_name)
|
|
111
|
+
|
|
112
|
+
for rel_name, rel_filter in rel_name_to_filter.items():
|
|
113
|
+
if profile_data.relationship_peers.get(rel_filter):
|
|
114
|
+
provided_rels.add(rel_name)
|
|
115
|
+
|
|
116
|
+
return provided_attrs, provided_rels
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Self
|
|
4
|
+
|
|
5
|
+
from infrahub.core.registry import registry
|
|
6
|
+
from infrahub.events import NodeUpdatedEvent
|
|
7
|
+
from infrahub.trigger.constants import NAME_SEPARATOR
|
|
8
|
+
from infrahub.trigger.models import EventTrigger, ExecuteWorkflow, TriggerBranchDefinition, TriggerType
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from infrahub.workflows.models import WorkflowDefinition
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ProfileRefreshTriggerDefinition(TriggerBranchDefinition):
|
|
15
|
+
"""Trigger definition for profile refresh when profile attributes/relationships change."""
|
|
16
|
+
|
|
17
|
+
type: TriggerType = TriggerType.PROFILE
|
|
18
|
+
profile_kind: str
|
|
19
|
+
|
|
20
|
+
@classmethod
|
|
21
|
+
def from_profile_schema(
|
|
22
|
+
cls,
|
|
23
|
+
branch: str,
|
|
24
|
+
profile_kind: str,
|
|
25
|
+
trigger_fields: list[str],
|
|
26
|
+
workflow: WorkflowDefinition,
|
|
27
|
+
branches_out_of_scope: list[str] | None = None,
|
|
28
|
+
) -> Self:
|
|
29
|
+
"""Create a trigger definition for profile refresh when profile attributes/relationships change."""
|
|
30
|
+
event_trigger = EventTrigger()
|
|
31
|
+
event_trigger.events.add(NodeUpdatedEvent.event_name)
|
|
32
|
+
event_trigger.match = {"infrahub.node.kind": profile_kind}
|
|
33
|
+
|
|
34
|
+
if branches_out_of_scope:
|
|
35
|
+
event_trigger.match["infrahub.branch.name"] = [f"!{b}" for b in branches_out_of_scope]
|
|
36
|
+
elif branch != registry.default_branch:
|
|
37
|
+
event_trigger.match["infrahub.branch.name"] = branch
|
|
38
|
+
|
|
39
|
+
event_trigger.match_related = {
|
|
40
|
+
"prefect.resource.role": ["infrahub.node.attribute_update", "infrahub.node.relationship_update"],
|
|
41
|
+
"infrahub.field.name": trigger_fields,
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
workflow_action = ExecuteWorkflow(
|
|
45
|
+
workflow=workflow,
|
|
46
|
+
parameters={
|
|
47
|
+
"branch_name": "{{ event.resource['infrahub.branch.name'] }}",
|
|
48
|
+
"profile_kind": profile_kind,
|
|
49
|
+
"profile_id": "{{ event.resource['infrahub.node.id'] }}",
|
|
50
|
+
"context": {
|
|
51
|
+
"__prefect_kind": "json",
|
|
52
|
+
"value": {
|
|
53
|
+
"__prefect_kind": "jinja",
|
|
54
|
+
"template": "{{ event.payload['context'] | tojson }}",
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
return cls(
|
|
61
|
+
name=f"{profile_kind}{NAME_SEPARATOR}refresh",
|
|
62
|
+
branch=branch,
|
|
63
|
+
profile_kind=profile_kind,
|
|
64
|
+
trigger=event_trigger,
|
|
65
|
+
actions=[workflow_action],
|
|
66
|
+
)
|