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
|
@@ -150,7 +150,8 @@ class InfrahubMutationMixin:
|
|
|
150
150
|
database: InfrahubDatabase | None = None,
|
|
151
151
|
override_data: dict[str, Any] | None = None,
|
|
152
152
|
) -> tuple[Node, Self]:
|
|
153
|
-
|
|
153
|
+
graphql_context: GraphqlContext = info.context
|
|
154
|
+
db = database or graphql_context.db
|
|
154
155
|
schema = cls._meta.active_schema
|
|
155
156
|
|
|
156
157
|
create_data = dict(data)
|
|
@@ -161,6 +162,7 @@ class InfrahubMutationMixin:
|
|
|
161
162
|
db=db,
|
|
162
163
|
branch=branch,
|
|
163
164
|
schema=schema,
|
|
165
|
+
user_id=graphql_context.assigned_user_id,
|
|
164
166
|
)
|
|
165
167
|
|
|
166
168
|
graphql_response = await build_graphql_response(info=info, db=db, obj=obj)
|
|
@@ -239,12 +241,13 @@ class InfrahubMutationMixin:
|
|
|
239
241
|
async def mutate_update_object(
|
|
240
242
|
cls,
|
|
241
243
|
db: InfrahubDatabase,
|
|
242
|
-
info: GraphQLResolveInfo,
|
|
244
|
+
info: GraphQLResolveInfo,
|
|
243
245
|
data: InputObjectType,
|
|
244
246
|
branch: Branch,
|
|
245
247
|
obj: Node,
|
|
246
248
|
skip_uniqueness_check: bool = False,
|
|
247
249
|
) -> Node:
|
|
250
|
+
graphql_context: GraphqlContext = info.context
|
|
248
251
|
component_registry = get_component_registry()
|
|
249
252
|
node_constraint_runner = await component_registry.get_component(NodeConstraintRunner, db=db, branch=branch)
|
|
250
253
|
|
|
@@ -264,7 +267,7 @@ class InfrahubMutationMixin:
|
|
|
264
267
|
node_profiles_applier = NodeProfilesApplier(db=db, branch=branch)
|
|
265
268
|
updated_field_names = await node_profiles_applier.apply_profiles(node=obj)
|
|
266
269
|
fields += updated_field_names
|
|
267
|
-
await obj.save(db=db, fields=fields)
|
|
270
|
+
await obj.save(db=db, fields=fields, user_id=graphql_context.assigned_user_id)
|
|
268
271
|
|
|
269
272
|
return obj
|
|
270
273
|
|
|
@@ -381,7 +384,9 @@ class InfrahubMutationMixin:
|
|
|
381
384
|
async def _delete_obj(cls, graphql_context: GraphqlContext, branch: Branch, obj: Node) -> list[Node]:
|
|
382
385
|
db = graphql_context.db
|
|
383
386
|
async with db.start_transaction() as dbt:
|
|
384
|
-
deleted = await NodeManager.delete(
|
|
387
|
+
deleted = await NodeManager.delete(
|
|
388
|
+
db=dbt, branch=branch, nodes=[obj], user_id=graphql_context.assigned_user_id
|
|
389
|
+
)
|
|
385
390
|
deleted_str = ", ".join([f"{d.get_kind()}({d.get_id()})" for d in deleted])
|
|
386
391
|
log.info(f"nodes deleted: {deleted_str}")
|
|
387
392
|
return deleted
|
|
@@ -7,8 +7,11 @@ from graphql import GraphQLResolveInfo
|
|
|
7
7
|
from opentelemetry import trace
|
|
8
8
|
from typing_extensions import Self
|
|
9
9
|
|
|
10
|
+
from infrahub.core.constants import MetadataOptions
|
|
10
11
|
from infrahub.core.manager import NodeManager
|
|
12
|
+
from infrahub.core.relationship.constraints.profiles_removal import RelationshipProfileRemovalConstraint
|
|
11
13
|
from infrahub.core.schema import ProfileSchema
|
|
14
|
+
from infrahub.dependencies.registry import get_component_registry
|
|
12
15
|
from infrahub.graphql.types.context import ContextInput
|
|
13
16
|
from infrahub.log import get_logger
|
|
14
17
|
from infrahub.profiles.node_applier import NodeProfilesApplier
|
|
@@ -69,16 +72,6 @@ class InfrahubProfileMutation(InfrahubMutationMixin, Mutation):
|
|
|
69
72
|
},
|
|
70
73
|
)
|
|
71
74
|
|
|
72
|
-
@classmethod
|
|
73
|
-
def _get_profile_attr_values_map(cls, obj: Node) -> dict[str, Any]:
|
|
74
|
-
attr_values_map = {}
|
|
75
|
-
for attr_schema in obj.get_schema().attributes:
|
|
76
|
-
# profile name update can be ignored
|
|
77
|
-
if attr_schema.name == "profile_name":
|
|
78
|
-
continue
|
|
79
|
-
attr_values_map[attr_schema.name] = getattr(obj, attr_schema.name).value
|
|
80
|
-
return attr_values_map
|
|
81
|
-
|
|
82
75
|
@classmethod
|
|
83
76
|
async def _get_profile_related_node_ids(cls, db: InfrahubDatabase, obj: Node) -> set[str]:
|
|
84
77
|
related_nodes = []
|
|
@@ -126,29 +119,24 @@ class InfrahubProfileMutation(InfrahubMutationMixin, Mutation):
|
|
|
126
119
|
skip_uniqueness_check: bool = False,
|
|
127
120
|
) -> tuple[Node, Self]:
|
|
128
121
|
workflow_service = info.context.active_service.workflow
|
|
129
|
-
original_attr_values = cls._get_profile_attr_values_map(obj=obj)
|
|
130
122
|
original_related_node_ids = await cls._get_profile_related_node_ids(db=db, obj=obj)
|
|
131
123
|
|
|
132
124
|
obj, mutation = await super()._call_mutate_update(
|
|
133
125
|
info=info, data=data, branch=branch, db=db, obj=obj, skip_uniqueness_check=skip_uniqueness_check
|
|
134
126
|
)
|
|
135
127
|
|
|
136
|
-
updated_attr_values = cls._get_profile_attr_values_map(obj=obj)
|
|
137
128
|
updated_related_node_ids = await cls._get_profile_related_node_ids(db=db, obj=obj)
|
|
138
129
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
elif updated_related_node_ids != original_related_node_ids:
|
|
144
|
-
removed_node_ids = original_related_node_ids - updated_related_node_ids
|
|
145
|
-
added_node_ids = updated_related_node_ids - original_related_node_ids
|
|
130
|
+
# Handle nodes removed from related_nodes - these need explicit refresh
|
|
131
|
+
# since the async automation won't find them in the profile's related_nodes after the change.
|
|
132
|
+
# Attribute changes and added nodes are handled by the Prefect automation
|
|
133
|
+
if removed_node_ids := original_related_node_ids - updated_related_node_ids:
|
|
146
134
|
await cls._send_profile_refresh_workflows(
|
|
147
135
|
db=db,
|
|
148
136
|
workflow_service=workflow_service,
|
|
149
137
|
branch_name=branch.name,
|
|
150
138
|
obj=obj,
|
|
151
|
-
node_ids=list(removed_node_ids)
|
|
139
|
+
node_ids=list(removed_node_ids),
|
|
152
140
|
)
|
|
153
141
|
|
|
154
142
|
return obj, mutation
|
|
@@ -158,6 +146,12 @@ class InfrahubProfileMutation(InfrahubMutationMixin, Mutation):
|
|
|
158
146
|
db = graphql_context.db
|
|
159
147
|
workflow_service = graphql_context.active_service.workflow
|
|
160
148
|
related_node_ids = await cls._get_profile_related_node_ids(db=db, obj=obj)
|
|
149
|
+
|
|
150
|
+
profile_schema: ProfileSchema = obj.get_schema() # type: ignore[assignment]
|
|
151
|
+
component_registry = get_component_registry()
|
|
152
|
+
constraint = await component_registry.get_component(RelationshipProfileRemovalConstraint, db=db, branch=branch)
|
|
153
|
+
await constraint.validate_profile_deletion(profile=obj, profile_schema=profile_schema)
|
|
154
|
+
|
|
161
155
|
deleted = await super()._delete_obj(graphql_context=graphql_context, branch=branch, obj=obj)
|
|
162
156
|
await cls._send_profile_refresh_workflows(
|
|
163
157
|
db=db, workflow_service=workflow_service, branch_name=branch.name, obj=obj, node_ids=list(related_node_ids)
|
|
@@ -192,12 +186,12 @@ class InfrahubProfilesRefresh(Mutation):
|
|
|
192
186
|
db=db,
|
|
193
187
|
branch=branch,
|
|
194
188
|
id=str(data.id),
|
|
195
|
-
|
|
189
|
+
include_metadata=MetadataOptions.SOURCE,
|
|
196
190
|
raise_on_error=True,
|
|
197
191
|
)
|
|
198
192
|
node_profiles_applier = NodeProfilesApplier(db=db, branch=branch)
|
|
199
193
|
updated_fields = await node_profiles_applier.apply_profiles(node=obj)
|
|
200
194
|
if updated_fields:
|
|
201
|
-
await obj.save(db=db, fields=updated_fields)
|
|
195
|
+
await obj.save(db=db, fields=updated_fields, user_id=graphql_context.assigned_user_id)
|
|
202
196
|
|
|
203
197
|
return cls(ok=True)
|
|
@@ -13,6 +13,7 @@ from infrahub.core.constants import (
|
|
|
13
13
|
CheckType,
|
|
14
14
|
GlobalPermissions,
|
|
15
15
|
InfrahubKind,
|
|
16
|
+
MetadataOptions,
|
|
16
17
|
PermissionDecision,
|
|
17
18
|
)
|
|
18
19
|
from infrahub.core.manager import NodeManager
|
|
@@ -128,8 +129,7 @@ class InfrahubProposedChangeMutation(InfrahubMutationMixin, Mutation):
|
|
|
128
129
|
kind=cls._meta.schema.kind,
|
|
129
130
|
id=data.get("id"),
|
|
130
131
|
branch=branch,
|
|
131
|
-
|
|
132
|
-
include_source=True,
|
|
132
|
+
include_metadata=MetadataOptions.LINKED_NODES,
|
|
133
133
|
)
|
|
134
134
|
state = ProposedChangeState(obj.state.value.value)
|
|
135
135
|
state.validate_updatable()
|
|
@@ -292,7 +292,7 @@ class ProposedChangeReview(Mutation):
|
|
|
292
292
|
current_user=current_user,
|
|
293
293
|
context=graphql_context,
|
|
294
294
|
)
|
|
295
|
-
await proposed_change.save(db=db)
|
|
295
|
+
await proposed_change.save(db=db, user_id=graphql_context.active_account_session.account_id)
|
|
296
296
|
|
|
297
297
|
if event:
|
|
298
298
|
event_service = await get_event_service()
|
|
@@ -426,7 +426,7 @@ class ProposedChangeMerge(Mutation):
|
|
|
426
426
|
|
|
427
427
|
async with graphql_context.db.start_session() as db:
|
|
428
428
|
proposed_change.state.value = ProposedChangeState.MERGING.value
|
|
429
|
-
await proposed_change.save(db=db)
|
|
429
|
+
await proposed_change.save(db=db, user_id=graphql_context.assigned_user_id)
|
|
430
430
|
|
|
431
431
|
if wait_until_completion:
|
|
432
432
|
await graphql_context.service.workflow.execute_workflow(
|
|
@@ -11,6 +11,7 @@ from infrahub.core.account import GlobalPermission, ObjectPermission
|
|
|
11
11
|
from infrahub.core.changelog.models import NodeChangelog, RelationshipChangelogGetter
|
|
12
12
|
from infrahub.core.constants import (
|
|
13
13
|
InfrahubKind,
|
|
14
|
+
MetadataOptions,
|
|
14
15
|
PermissionAction,
|
|
15
16
|
PermissionDecision,
|
|
16
17
|
RelationshipCardinality,
|
|
@@ -22,7 +23,7 @@ from infrahub.core.query.relationship import (
|
|
|
22
23
|
RelationshipPeerData,
|
|
23
24
|
)
|
|
24
25
|
from infrahub.core.relationship import Relationship
|
|
25
|
-
from infrahub.database import retry_db_transaction
|
|
26
|
+
from infrahub.database import InfrahubDatabase, retry_db_transaction
|
|
26
27
|
from infrahub.events import EventMeta
|
|
27
28
|
from infrahub.events.group_action import GroupMemberAddedEvent, GroupMemberRemovedEvent
|
|
28
29
|
from infrahub.events.models import EventNode
|
|
@@ -32,12 +33,14 @@ from infrahub.graphql.context import apply_external_context
|
|
|
32
33
|
from infrahub.graphql.types.context import ContextInput
|
|
33
34
|
from infrahub.groups.ancestors import collect_ancestors
|
|
34
35
|
from infrahub.permissions import get_global_permission_for_kind
|
|
36
|
+
from infrahub.profiles.node_applier import NodeProfilesApplier
|
|
35
37
|
|
|
36
38
|
from ..types import RelatedNodeInput
|
|
37
39
|
|
|
38
40
|
if TYPE_CHECKING:
|
|
39
41
|
from graphql import GraphQLResolveInfo
|
|
40
42
|
|
|
43
|
+
from infrahub.core.branch import Branch
|
|
41
44
|
from infrahub.core.node import Node
|
|
42
45
|
from infrahub.core.relationship import RelationshipManager
|
|
43
46
|
from infrahub.core.schema.relationship_schema import RelationshipSchema
|
|
@@ -91,7 +94,7 @@ class RelationshipAdd(Mutation):
|
|
|
91
94
|
await apply_external_context(graphql_context=graphql_context, context_input=context)
|
|
92
95
|
|
|
93
96
|
rel_schema = source.get_schema().get_relationship(name=relationship_name)
|
|
94
|
-
display_label
|
|
97
|
+
display_label = await source.get_display_label(db=graphql_context.db)
|
|
95
98
|
node_changelog = NodeChangelog(
|
|
96
99
|
node_id=source.get_id(), node_kind=source.get_kind(), display_label=display_label
|
|
97
100
|
)
|
|
@@ -107,7 +110,9 @@ class RelationshipAdd(Mutation):
|
|
|
107
110
|
for node_data in data.get("nodes"):
|
|
108
111
|
# Instantiate and resolve a relationship
|
|
109
112
|
# This will take care of allocating a node from a pool if needed
|
|
110
|
-
rel = Relationship(
|
|
113
|
+
rel = Relationship(
|
|
114
|
+
schema=rel_schema, branch=graphql_context.branch, source_kind=source.get_kind(), node=source
|
|
115
|
+
)
|
|
111
116
|
await rel.new(db=db, data=node_data)
|
|
112
117
|
await rel.resolve(db=db)
|
|
113
118
|
# Save it only if it does not exist
|
|
@@ -115,7 +120,14 @@ class RelationshipAdd(Mutation):
|
|
|
115
120
|
if group_event_type != GroupUpdateType.NONE:
|
|
116
121
|
peers.append(EventNode(id=rel.get_peer_id(), kind=nodes[rel.get_peer_id()].get_kind()))
|
|
117
122
|
node_changelog.create_relationship(relationship=rel)
|
|
118
|
-
await rel.save(db=db)
|
|
123
|
+
await rel.save(db=db, user_id=graphql_context.assigned_user_id)
|
|
124
|
+
|
|
125
|
+
if relationship_name == "profiles":
|
|
126
|
+
await _apply_profiles(node=source, db=db, branch=graphql_context.branch)
|
|
127
|
+
|
|
128
|
+
if source.get_schema().is_profile_schema and relationship_name == "related_nodes":
|
|
129
|
+
for node in nodes.values():
|
|
130
|
+
await _apply_profiles(node=node, db=db, branch=graphql_context.branch)
|
|
119
131
|
|
|
120
132
|
if config.SETTINGS.broker.enable and graphql_context.background and node_changelog.has_changes:
|
|
121
133
|
if group_event_type == GroupUpdateType.MEMBERS:
|
|
@@ -230,14 +242,23 @@ class RelationshipRemove(Mutation):
|
|
|
230
242
|
for node_data in data.get("nodes"):
|
|
231
243
|
if node_data.get("id") in existing_peers.keys():
|
|
232
244
|
# TODO once https://github.com/opsmill/infrahub/issues/792 has been fixed
|
|
233
|
-
# we should use
|
|
245
|
+
# we should use RelationshipDeleteQuery to delete the relationship
|
|
234
246
|
# it would be more query efficient
|
|
235
|
-
rel = Relationship(
|
|
247
|
+
rel = Relationship(
|
|
248
|
+
schema=rel_schema, branch=graphql_context.branch, source_kind=source.get_kind(), node=source
|
|
249
|
+
)
|
|
236
250
|
rel.load(db=db, data=existing_peers[node_data.get("id")])
|
|
237
251
|
if group_event_type != GroupUpdateType.NONE:
|
|
238
252
|
peers.append(EventNode(id=rel.get_peer_id(), kind=nodes[rel.get_peer_id()].get_kind()))
|
|
239
253
|
node_changelog.delete_relationship(relationship=rel)
|
|
240
|
-
await rel.delete(db=db)
|
|
254
|
+
await rel.delete(db=db, user_id=graphql_context.assigned_user_id)
|
|
255
|
+
|
|
256
|
+
if relationship_name == "profiles":
|
|
257
|
+
await _apply_profiles(node=source, db=db, branch=graphql_context.branch)
|
|
258
|
+
|
|
259
|
+
if source.get_schema().is_profile_schema and relationship_name == "related_nodes":
|
|
260
|
+
for node in nodes.values():
|
|
261
|
+
await _apply_profiles(node=node, db=db, branch=graphql_context.branch)
|
|
241
262
|
|
|
242
263
|
if config.SETTINGS.broker.enable and graphql_context.background and node_changelog.has_changes:
|
|
243
264
|
if group_event_type == GroupUpdateType.MEMBERS:
|
|
@@ -318,8 +339,7 @@ async def _validate_node(info: GraphQLResolveInfo, data: RelationshipNodesInput)
|
|
|
318
339
|
db=graphql_context.db,
|
|
319
340
|
id=input_id,
|
|
320
341
|
branch=graphql_context.branch,
|
|
321
|
-
|
|
322
|
-
include_source=False,
|
|
342
|
+
include_metadata=MetadataOptions.NONE,
|
|
323
343
|
)
|
|
324
344
|
):
|
|
325
345
|
raise NodeNotFoundError(node_type="node", identifier=input_id, branch_name=graphql_context.branch.name)
|
|
@@ -455,7 +475,9 @@ async def _collect_current_peers(
|
|
|
455
475
|
query = await RelationshipGetPeerQuery.init(
|
|
456
476
|
db=graphql_context.db,
|
|
457
477
|
source=source_node,
|
|
458
|
-
rel=Relationship(
|
|
478
|
+
rel=Relationship(
|
|
479
|
+
schema=rel_schema, branch=graphql_context.branch, source_kind=source_node.get_kind(), node=source_node
|
|
480
|
+
),
|
|
459
481
|
)
|
|
460
482
|
await query.execute(db=graphql_context.db)
|
|
461
483
|
return {str(peer.peer_id): peer for peer in query.get_peers()}
|
|
@@ -474,3 +496,11 @@ def _get_group_event_type(
|
|
|
474
496
|
# Modifying the membership of the current node
|
|
475
497
|
group_event_type = GroupUpdateType.MEMBER_OF_GROUPS
|
|
476
498
|
return group_event_type
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
async def _apply_profiles(node: Node, db: InfrahubDatabase, branch: Branch) -> None:
|
|
502
|
+
refreshed_node = await NodeManager.get_one(db=db, id=node.get_id(), branch=branch)
|
|
503
|
+
node_profiles_applier = NodeProfilesApplier(db=db, branch=branch)
|
|
504
|
+
updated_fields = await node_profiles_applier.apply_profiles(node=refreshed_node)
|
|
505
|
+
if updated_fields:
|
|
506
|
+
await refreshed_node.save(db=db, fields=updated_fields)
|
|
@@ -7,7 +7,7 @@ import httpx
|
|
|
7
7
|
from graphene import Boolean, Field, InputObjectType, Mutation, String
|
|
8
8
|
|
|
9
9
|
from infrahub import config
|
|
10
|
-
from infrahub.core.constants import InfrahubKind
|
|
10
|
+
from infrahub.core.constants import InfrahubKind, MetadataOptions
|
|
11
11
|
from infrahub.core.manager import NodeManager
|
|
12
12
|
from infrahub.core.schema import NodeSchema
|
|
13
13
|
from infrahub.git.models import (
|
|
@@ -42,7 +42,9 @@ log = get_logger()
|
|
|
42
42
|
|
|
43
43
|
class InfrahubRepositoryMutation(InfrahubMutationMixin, Mutation):
|
|
44
44
|
@classmethod
|
|
45
|
-
def __init_subclass_with_meta__(
|
|
45
|
+
def __init_subclass_with_meta__(
|
|
46
|
+
cls, schema: NodeSchema | None = None, _meta: InfrahubMutationOptions | None = None, **options: Any
|
|
47
|
+
) -> None:
|
|
46
48
|
# Make sure schema is a valid NodeSchema Node Class
|
|
47
49
|
if not isinstance(schema, NodeSchema):
|
|
48
50
|
raise ValueError(f"You need to pass a valid NodeSchema in '{cls.__name__}.Meta', received '{schema}'")
|
|
@@ -95,21 +97,21 @@ class InfrahubRepositoryMutation(InfrahubMutationMixin, Mutation):
|
|
|
95
97
|
graphql_context: GraphqlContext = info.context
|
|
96
98
|
|
|
97
99
|
cleanup_payload(data)
|
|
98
|
-
|
|
99
|
-
|
|
100
|
+
repo_node: CoreReadOnlyRepository | CoreRepository | Node | None = node
|
|
101
|
+
if not repo_node:
|
|
102
|
+
repo_node = await NodeManager.get_one_by_id_or_default_filter(
|
|
100
103
|
db=graphql_context.db,
|
|
101
104
|
kind=cls._meta.schema.kind,
|
|
102
105
|
id=data.get("id"),
|
|
103
106
|
branch=branch,
|
|
104
|
-
|
|
105
|
-
include_source=True,
|
|
107
|
+
include_metadata=MetadataOptions.LINKED_NODES,
|
|
106
108
|
)
|
|
107
|
-
if
|
|
108
|
-
return await super().mutate_update(info, data, branch, database=graphql_context.db, node=
|
|
109
|
+
if repo_node.get_kind() != InfrahubKind.READONLYREPOSITORY:
|
|
110
|
+
return await super().mutate_update(info, data, branch, database=graphql_context.db, node=repo_node)
|
|
109
111
|
|
|
110
|
-
|
|
111
|
-
current_commit =
|
|
112
|
-
current_ref =
|
|
112
|
+
repo_node = cast("CoreReadOnlyRepository", repo_node)
|
|
113
|
+
current_commit = repo_node.commit.value
|
|
114
|
+
current_ref = repo_node.ref.value
|
|
113
115
|
new_commit = None
|
|
114
116
|
if data.commit and data.commit.value:
|
|
115
117
|
new_commit = data.commit.value
|
|
@@ -117,7 +119,7 @@ class InfrahubRepositoryMutation(InfrahubMutationMixin, Mutation):
|
|
|
117
119
|
if data.ref and data.ref.value:
|
|
118
120
|
new_ref = data.ref.value
|
|
119
121
|
|
|
120
|
-
obj, result = await super().mutate_update(info, data, branch, database=graphql_context.db, node=
|
|
122
|
+
obj, result = await super().mutate_update(info, data, branch, database=graphql_context.db, node=repo_node)
|
|
121
123
|
obj = cast("CoreReadOnlyRepository", obj)
|
|
122
124
|
|
|
123
125
|
send_update_message = (new_commit and new_commit != current_commit) or (new_ref and new_ref != current_ref)
|
|
@@ -329,11 +329,11 @@ async def update_registry(
|
|
|
329
329
|
log.info(f"Schema has diff, will need to be updated {diff.all}", branch=branch.name)
|
|
330
330
|
async with db.start_transaction() as dbt:
|
|
331
331
|
await registry.schema.update_schema_branch(
|
|
332
|
-
schema=tmp_schema, db=dbt, branch=branch.name, limit=diff.all, update_db=True
|
|
332
|
+
schema=tmp_schema, db=dbt, branch=branch.name, limit=diff.all, update_db=True, user_id=account_id
|
|
333
333
|
)
|
|
334
334
|
branch.update_schema_hash()
|
|
335
335
|
log.info("Schema has been updated", branch=branch.name, hash=branch.active_schema_hash.main)
|
|
336
|
-
await branch.save(db=dbt)
|
|
336
|
+
await branch.save(db=dbt, user_id=account_id)
|
|
337
337
|
|
|
338
338
|
await service.component.refresh_schema_hash(branches=[branch.name])
|
|
339
339
|
|
|
@@ -2,24 +2,59 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from typing import TYPE_CHECKING, Any
|
|
4
4
|
|
|
5
|
-
from graphene import ID, Field, Int, List, NonNull, String
|
|
5
|
+
from graphene import ID, Argument, Boolean, Field, Int, List, NonNull, String
|
|
6
6
|
|
|
7
|
+
from infrahub.constants.enums import OrderByField, OrderDirection
|
|
8
|
+
from infrahub.core.node.standard import StandardNodeOrdering, StandardNodeQueryFields
|
|
7
9
|
from infrahub.core.registry import registry
|
|
8
10
|
from infrahub.exceptions import ValidationError
|
|
9
11
|
from infrahub.graphql.field_extractor import extract_graphql_fields
|
|
10
12
|
from infrahub.graphql.types import BranchType, InfrahubBranch, InfrahubBranchType
|
|
13
|
+
from infrahub.graphql.types.metadata import OrderInput
|
|
11
14
|
|
|
12
15
|
if TYPE_CHECKING:
|
|
13
16
|
from graphql import GraphQLResolveInfo
|
|
14
17
|
|
|
15
18
|
|
|
19
|
+
def standard_node_ordering_from_order_input(order: OrderInput | None = None) -> StandardNodeOrdering:
|
|
20
|
+
"""Create a StandardNodeOrdering from an OrderInput.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
order: Optional ordering specification from GraphQL input.
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
StandardNodeOrdering with the specified field and direction, or defaults to ID with no direction.
|
|
27
|
+
|
|
28
|
+
Raises:
|
|
29
|
+
ValidationError: If both created_at and updated_at are specified.
|
|
30
|
+
"""
|
|
31
|
+
if order is None or not order.node_metadata:
|
|
32
|
+
return StandardNodeOrdering()
|
|
33
|
+
|
|
34
|
+
created_at = getattr(order.node_metadata, "created_at", None)
|
|
35
|
+
updated_at = getattr(order.node_metadata, "updated_at", None)
|
|
36
|
+
|
|
37
|
+
if created_at and updated_at:
|
|
38
|
+
raise ValidationError("Only one of 'created_at' or 'updated_at' can be specified for ordering.")
|
|
39
|
+
|
|
40
|
+
if created_at:
|
|
41
|
+
return StandardNodeOrdering(order_by=OrderByField.CREATED_AT, direction=OrderDirection(created_at.value))
|
|
42
|
+
|
|
43
|
+
if updated_at:
|
|
44
|
+
return StandardNodeOrdering(order_by=OrderByField.UPDATED_AT, direction=OrderDirection(updated_at.value))
|
|
45
|
+
|
|
46
|
+
return StandardNodeOrdering()
|
|
47
|
+
|
|
48
|
+
|
|
16
49
|
async def branch_resolver(
|
|
17
50
|
root: dict, # noqa: ARG001
|
|
18
51
|
info: GraphQLResolveInfo,
|
|
19
52
|
**kwargs: Any,
|
|
20
53
|
) -> list[dict[str, Any]]:
|
|
21
54
|
fields = extract_graphql_fields(info)
|
|
22
|
-
return await BranchType.get_list(
|
|
55
|
+
return await BranchType.get_list(
|
|
56
|
+
graphql_context=info.context, fields=StandardNodeQueryFields(node=fields), exclude_global=True, **kwargs
|
|
57
|
+
)
|
|
23
58
|
|
|
24
59
|
|
|
25
60
|
BranchQueryList = Field(
|
|
@@ -39,36 +74,51 @@ async def infrahub_branch_resolver(
|
|
|
39
74
|
offset: int | None = None,
|
|
40
75
|
name__value: str | None = None,
|
|
41
76
|
ids: list[str] | None = None,
|
|
77
|
+
partial_match: bool = False,
|
|
78
|
+
order: OrderInput | None = None,
|
|
42
79
|
) -> dict[str, Any]:
|
|
43
80
|
if isinstance(limit, int) and limit < 1:
|
|
44
81
|
raise ValidationError("limit must be >= 1")
|
|
45
82
|
if isinstance(offset, int) and offset < 0:
|
|
46
83
|
raise ValidationError("offset must be >= 0")
|
|
47
84
|
|
|
85
|
+
node_ordering = standard_node_ordering_from_order_input(order)
|
|
86
|
+
|
|
48
87
|
fields = extract_graphql_fields(info)
|
|
49
88
|
result: dict[str, Any] = {}
|
|
50
89
|
if "edges" in fields:
|
|
90
|
+
query_fields = StandardNodeQueryFields(
|
|
91
|
+
node=fields.get("edges", {}).get("node", {}),
|
|
92
|
+
node_metadata=fields.get("edges", {}).get("node_metadata", {}),
|
|
93
|
+
)
|
|
51
94
|
branches = await InfrahubBranch.get_list(
|
|
52
95
|
graphql_context=info.context,
|
|
53
|
-
fields=
|
|
96
|
+
fields=query_fields,
|
|
54
97
|
limit=limit,
|
|
55
98
|
offset=offset,
|
|
56
99
|
name=name__value,
|
|
57
100
|
ids=ids,
|
|
58
101
|
exclude_global=True,
|
|
102
|
+
partial_match=partial_match,
|
|
103
|
+
node_ordering=node_ordering,
|
|
59
104
|
)
|
|
60
|
-
result["edges"] =
|
|
105
|
+
result["edges"] = branches
|
|
61
106
|
if "count" in fields:
|
|
62
107
|
result["count"] = await InfrahubBranchType.get_list_count(
|
|
63
|
-
graphql_context=info.context,
|
|
108
|
+
graphql_context=info.context,
|
|
109
|
+
name=name__value,
|
|
110
|
+
ids=ids,
|
|
111
|
+
partial_match=partial_match,
|
|
112
|
+
node_ordering=node_ordering,
|
|
64
113
|
)
|
|
65
114
|
|
|
66
115
|
if "default_branch" in fields:
|
|
67
|
-
|
|
116
|
+
default_branch = await InfrahubBranch.get_by_name(
|
|
68
117
|
graphql_context=info.context,
|
|
69
118
|
fields=fields["default_branch"],
|
|
70
119
|
name=registry.default_branch,
|
|
71
120
|
)
|
|
121
|
+
result["default_branch"] = default_branch["node"]
|
|
72
122
|
|
|
73
123
|
return result
|
|
74
124
|
|
|
@@ -79,6 +129,12 @@ InfrahubBranchQueryList = Field(
|
|
|
79
129
|
limit=Int(),
|
|
80
130
|
name__value=String(),
|
|
81
131
|
ids=List(ID),
|
|
132
|
+
partial_match=Boolean(default_value=False),
|
|
133
|
+
order=Argument(
|
|
134
|
+
OrderInput,
|
|
135
|
+
required=False,
|
|
136
|
+
description="Define ordering of results for branch queries.",
|
|
137
|
+
),
|
|
82
138
|
description="Retrieve paginated information about active branches.",
|
|
83
139
|
resolver=infrahub_branch_resolver,
|
|
84
140
|
required=True,
|
|
@@ -60,10 +60,10 @@ class ConflictDetails(ObjectType):
|
|
|
60
60
|
|
|
61
61
|
|
|
62
62
|
class DiffSummaryCounts(ObjectType):
|
|
63
|
-
num_added = Int(required=
|
|
64
|
-
num_updated = Int(required=
|
|
65
|
-
num_removed = Int(required=
|
|
66
|
-
num_conflicts = Int(required=
|
|
63
|
+
num_added = Int(required=False)
|
|
64
|
+
num_updated = Int(required=False)
|
|
65
|
+
num_removed = Int(required=False)
|
|
66
|
+
num_conflicts = Int(required=False)
|
|
67
67
|
|
|
68
68
|
|
|
69
69
|
class DiffProperty(ObjectType):
|
|
@@ -146,7 +146,7 @@ class DiffTreeSummary(DiffSummaryCounts):
|
|
|
146
146
|
diff_branch = String(required=True)
|
|
147
147
|
from_time = DateTime(required=True)
|
|
148
148
|
to_time = DateTime(required=True)
|
|
149
|
-
num_unchanged = Int(required=
|
|
149
|
+
num_unchanged = Int(required=False)
|
|
150
150
|
num_untracked_base_changes = Int(required=False)
|
|
151
151
|
num_untracked_diff_changes = Int(required=False)
|
|
152
152
|
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""Resolvers for account metadata fields (created_by, updated_by)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING, Any
|
|
6
|
+
|
|
7
|
+
from infrahub.graphql.field_extractor import extract_graphql_fields
|
|
8
|
+
from infrahub.graphql.loaders.account import AccountDataLoader, AccountLoaderParams
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from graphql import GraphQLResolveInfo
|
|
12
|
+
|
|
13
|
+
from infrahub.core.branch.models import Branch
|
|
14
|
+
from infrahub.core.timestamp import Timestamp
|
|
15
|
+
from infrahub.database import InfrahubDatabase
|
|
16
|
+
from infrahub.graphql.initialization import GraphqlContext
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class AccountMetadataResolver:
|
|
20
|
+
"""Resolver class for account metadata fields (created_by, updated_by).
|
|
21
|
+
|
|
22
|
+
This class maintains DataLoader instances to enable batching and caching
|
|
23
|
+
of account lookups across multiple fields within the same request.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(self) -> None:
|
|
27
|
+
self._data_loader_instances: dict[AccountLoaderParams, AccountDataLoader] = {}
|
|
28
|
+
|
|
29
|
+
def _get_or_create_loader(
|
|
30
|
+
self,
|
|
31
|
+
db: InfrahubDatabase,
|
|
32
|
+
branch: Branch,
|
|
33
|
+
at: Timestamp | None,
|
|
34
|
+
fields: dict[str, Any],
|
|
35
|
+
) -> AccountDataLoader:
|
|
36
|
+
"""Get an existing loader or create a new one for the given parameters."""
|
|
37
|
+
params = AccountLoaderParams(branch=branch, at=at, fields=fields)
|
|
38
|
+
|
|
39
|
+
if params not in self._data_loader_instances:
|
|
40
|
+
self._data_loader_instances[params] = AccountDataLoader(db=db, params=params)
|
|
41
|
+
|
|
42
|
+
return self._data_loader_instances[params]
|
|
43
|
+
|
|
44
|
+
async def resolve(
|
|
45
|
+
self,
|
|
46
|
+
parent: dict[str, Any],
|
|
47
|
+
info: GraphQLResolveInfo,
|
|
48
|
+
) -> dict[str, Any] | None:
|
|
49
|
+
"""Resolve created_by/updated_by fields in metadata objects.
|
|
50
|
+
|
|
51
|
+
The parent dict should contain {"id": "account-uuid", "__kind__": "CoreAccount"}
|
|
52
|
+
for the field being resolved, or the field value may be None.
|
|
53
|
+
"""
|
|
54
|
+
field_name = info.field_name # "created_by" or "updated_by"
|
|
55
|
+
account_data = parent.get(field_name)
|
|
56
|
+
|
|
57
|
+
if not account_data or not account_data.get("id"):
|
|
58
|
+
return None
|
|
59
|
+
|
|
60
|
+
account_id = account_data["id"]
|
|
61
|
+
graphql_context: GraphqlContext = info.context
|
|
62
|
+
|
|
63
|
+
# Extract the fields requested for this account
|
|
64
|
+
fields = extract_graphql_fields(info=info)
|
|
65
|
+
|
|
66
|
+
# Get or create a loader for these parameters
|
|
67
|
+
loader = self._get_or_create_loader(
|
|
68
|
+
db=graphql_context.db,
|
|
69
|
+
branch=graphql_context.branch,
|
|
70
|
+
at=graphql_context.at,
|
|
71
|
+
fields=fields,
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
return await loader.load(account_id)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
async def account_metadata_resolver(
|
|
78
|
+
parent: dict[str, Any],
|
|
79
|
+
info: GraphQLResolveInfo,
|
|
80
|
+
) -> dict[str, Any] | None:
|
|
81
|
+
"""Function resolver that delegates to the AccountMetadataResolver on the context."""
|
|
82
|
+
graphql_context: GraphqlContext = info.context
|
|
83
|
+
resolver = graphql_context.account_metadata_resolver
|
|
84
|
+
return await resolver.resolve(parent=parent, info=info)
|