infrahub-server 1.4.13__py3-none-any.whl → 1.5.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- infrahub/actions/tasks.py +208 -16
- infrahub/api/artifact.py +3 -0
- infrahub/api/diff/diff.py +1 -1
- infrahub/api/internal.py +2 -0
- infrahub/api/query.py +2 -0
- infrahub/api/schema.py +27 -3
- infrahub/auth.py +5 -5
- infrahub/cli/__init__.py +2 -0
- infrahub/cli/db.py +160 -157
- infrahub/cli/dev.py +118 -0
- infrahub/cli/upgrade.py +56 -9
- infrahub/computed_attribute/tasks.py +19 -7
- infrahub/config.py +7 -2
- infrahub/core/attribute.py +35 -24
- infrahub/core/branch/enums.py +1 -1
- infrahub/core/branch/models.py +9 -5
- infrahub/core/branch/needs_rebase_status.py +11 -0
- infrahub/core/branch/tasks.py +72 -10
- infrahub/core/changelog/models.py +2 -10
- infrahub/core/constants/__init__.py +4 -0
- infrahub/core/constants/infrahubkind.py +1 -0
- infrahub/core/convert_object_type/object_conversion.py +201 -0
- infrahub/core/convert_object_type/repository_conversion.py +89 -0
- infrahub/core/convert_object_type/schema_mapping.py +27 -3
- infrahub/core/diff/model/path.py +4 -0
- infrahub/core/diff/payload_builder.py +1 -1
- infrahub/core/diff/query/artifact.py +1 -0
- infrahub/core/diff/query/field_summary.py +1 -0
- infrahub/core/graph/__init__.py +1 -1
- infrahub/core/initialization.py +7 -4
- infrahub/core/manager.py +3 -81
- infrahub/core/migrations/__init__.py +3 -0
- infrahub/core/migrations/exceptions.py +4 -0
- infrahub/core/migrations/graph/__init__.py +11 -10
- 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/m037_index_attr_vals.py +11 -30
- infrahub/core/migrations/graph/m039_ipam_reconcile.py +9 -7
- infrahub/core/migrations/graph/m042_profile_attrs_in_db.py +147 -0
- infrahub/core/migrations/graph/m043_create_hfid_display_label_in_db.py +164 -0
- infrahub/core/migrations/graph/m044_backfill_hfid_display_label_in_db.py +864 -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 +26 -5
- 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 +66 -19
- infrahub/core/models.py +2 -2
- infrahub/core/node/__init__.py +207 -54
- infrahub/core/node/create.py +53 -49
- infrahub/core/node/lock_utils.py +124 -0
- infrahub/core/node/node_property_attribute.py +230 -0
- infrahub/core/node/resource_manager/ip_address_pool.py +2 -1
- infrahub/core/node/resource_manager/ip_prefix_pool.py +2 -1
- infrahub/core/node/resource_manager/number_pool.py +2 -1
- infrahub/core/node/standard.py +1 -1
- infrahub/core/property.py +11 -0
- infrahub/core/protocols.py +8 -1
- infrahub/core/query/attribute.py +82 -15
- infrahub/core/query/ipam.py +16 -4
- infrahub/core/query/node.py +66 -188
- infrahub/core/query/relationship.py +44 -26
- infrahub/core/query/subquery.py +0 -8
- infrahub/core/relationship/model.py +69 -24
- infrahub/core/schema/__init__.py +56 -0
- infrahub/core/schema/attribute_schema.py +4 -2
- infrahub/core/schema/basenode_schema.py +42 -2
- infrahub/core/schema/definitions/core/__init__.py +2 -0
- infrahub/core/schema/definitions/core/check.py +1 -1
- infrahub/core/schema/definitions/core/generator.py +2 -0
- infrahub/core/schema/definitions/core/group.py +16 -2
- infrahub/core/schema/definitions/core/repository.py +7 -0
- infrahub/core/schema/definitions/core/transform.py +1 -1
- infrahub/core/schema/definitions/internal.py +12 -3
- infrahub/core/schema/generated/attribute_schema.py +2 -2
- infrahub/core/schema/generated/base_node_schema.py +6 -1
- infrahub/core/schema/manager.py +3 -0
- infrahub/core/schema/node_schema.py +1 -0
- infrahub/core/schema/relationship_schema.py +0 -1
- infrahub/core/schema/schema_branch.py +295 -10
- infrahub/core/schema/schema_branch_display.py +135 -0
- infrahub/core/schema/schema_branch_hfid.py +120 -0
- infrahub/core/validators/aggregated_checker.py +1 -1
- infrahub/database/graph.py +21 -0
- infrahub/display_labels/__init__.py +0 -0
- infrahub/display_labels/gather.py +48 -0
- infrahub/display_labels/models.py +240 -0
- infrahub/display_labels/tasks.py +192 -0
- infrahub/display_labels/triggers.py +22 -0
- infrahub/events/branch_action.py +27 -1
- infrahub/events/group_action.py +1 -1
- infrahub/events/node_action.py +1 -1
- infrahub/generators/constants.py +7 -0
- infrahub/generators/models.py +38 -12
- infrahub/generators/tasks.py +34 -16
- infrahub/git/base.py +38 -1
- infrahub/git/integrator.py +22 -14
- infrahub/graphql/api/dependencies.py +2 -4
- infrahub/graphql/api/endpoints.py +16 -6
- infrahub/graphql/app.py +2 -4
- infrahub/graphql/initialization.py +2 -3
- infrahub/graphql/manager.py +213 -137
- infrahub/graphql/middleware.py +12 -0
- infrahub/graphql/mutations/branch.py +16 -0
- infrahub/graphql/mutations/computed_attribute.py +110 -3
- infrahub/graphql/mutations/convert_object_type.py +44 -13
- infrahub/graphql/mutations/display_label.py +118 -0
- infrahub/graphql/mutations/generator.py +25 -7
- infrahub/graphql/mutations/hfid.py +125 -0
- infrahub/graphql/mutations/ipam.py +73 -41
- infrahub/graphql/mutations/main.py +61 -178
- infrahub/graphql/mutations/profile.py +195 -0
- infrahub/graphql/mutations/proposed_change.py +8 -1
- infrahub/graphql/mutations/relationship.py +2 -2
- infrahub/graphql/mutations/repository.py +22 -83
- infrahub/graphql/mutations/resource_manager.py +2 -2
- infrahub/graphql/mutations/webhook.py +1 -1
- infrahub/graphql/queries/resource_manager.py +1 -1
- infrahub/graphql/registry.py +173 -0
- infrahub/graphql/resolvers/resolver.py +2 -0
- infrahub/graphql/schema.py +8 -1
- infrahub/graphql/schema_sort.py +170 -0
- infrahub/graphql/types/branch.py +4 -1
- infrahub/graphql/types/enums.py +3 -0
- infrahub/groups/tasks.py +1 -1
- infrahub/hfid/__init__.py +0 -0
- infrahub/hfid/gather.py +48 -0
- infrahub/hfid/models.py +240 -0
- infrahub/hfid/tasks.py +191 -0
- infrahub/hfid/triggers.py +22 -0
- infrahub/lock.py +119 -42
- infrahub/locks/__init__.py +0 -0
- infrahub/locks/tasks.py +37 -0
- infrahub/patch/plan_writer.py +2 -2
- infrahub/permissions/constants.py +2 -0
- infrahub/profiles/__init__.py +0 -0
- infrahub/profiles/node_applier.py +101 -0
- infrahub/profiles/queries/__init__.py +0 -0
- infrahub/profiles/queries/get_profile_data.py +98 -0
- infrahub/profiles/tasks.py +63 -0
- infrahub/proposed_change/tasks.py +24 -5
- infrahub/repositories/__init__.py +0 -0
- infrahub/repositories/create_repository.py +113 -0
- infrahub/server.py +9 -1
- infrahub/services/__init__.py +8 -5
- infrahub/services/adapters/workflow/worker.py +5 -2
- infrahub/task_manager/event.py +5 -0
- infrahub/task_manager/models.py +7 -0
- infrahub/tasks/registry.py +6 -4
- infrahub/trigger/catalogue.py +4 -0
- infrahub/trigger/models.py +2 -0
- infrahub/trigger/setup.py +13 -4
- infrahub/trigger/tasks.py +6 -0
- infrahub/webhook/models.py +1 -1
- infrahub/workers/dependencies.py +3 -1
- infrahub/workers/infrahub_async.py +5 -1
- infrahub/workflows/catalogue.py +118 -3
- infrahub/workflows/initialization.py +21 -0
- infrahub/workflows/models.py +17 -2
- infrahub_sdk/branch.py +17 -8
- infrahub_sdk/checks.py +1 -1
- infrahub_sdk/client.py +376 -95
- infrahub_sdk/config.py +29 -2
- infrahub_sdk/convert_object_type.py +61 -0
- infrahub_sdk/ctl/branch.py +3 -0
- infrahub_sdk/ctl/check.py +2 -3
- infrahub_sdk/ctl/cli_commands.py +20 -12
- infrahub_sdk/ctl/config.py +8 -2
- infrahub_sdk/ctl/generator.py +6 -3
- infrahub_sdk/ctl/graphql.py +184 -0
- infrahub_sdk/ctl/repository.py +39 -1
- infrahub_sdk/ctl/schema.py +40 -10
- infrahub_sdk/ctl/task.py +110 -0
- infrahub_sdk/ctl/utils.py +4 -0
- infrahub_sdk/ctl/validate.py +5 -3
- infrahub_sdk/diff.py +4 -5
- infrahub_sdk/exceptions.py +2 -0
- infrahub_sdk/generator.py +7 -1
- infrahub_sdk/graphql/__init__.py +12 -0
- infrahub_sdk/graphql/constants.py +1 -0
- infrahub_sdk/graphql/plugin.py +85 -0
- infrahub_sdk/graphql/query.py +77 -0
- infrahub_sdk/{graphql.py → graphql/renderers.py} +88 -75
- infrahub_sdk/graphql/utils.py +40 -0
- infrahub_sdk/node/attribute.py +2 -0
- infrahub_sdk/node/node.py +28 -20
- infrahub_sdk/node/relationship.py +1 -3
- infrahub_sdk/playback.py +1 -2
- infrahub_sdk/protocols.py +54 -6
- infrahub_sdk/pytest_plugin/plugin.py +7 -4
- infrahub_sdk/pytest_plugin/utils.py +40 -0
- infrahub_sdk/repository.py +1 -2
- infrahub_sdk/schema/__init__.py +70 -4
- infrahub_sdk/schema/main.py +1 -0
- infrahub_sdk/schema/repository.py +8 -0
- infrahub_sdk/spec/models.py +7 -0
- infrahub_sdk/spec/object.py +54 -6
- infrahub_sdk/spec/processors/__init__.py +0 -0
- infrahub_sdk/spec/processors/data_processor.py +10 -0
- infrahub_sdk/spec/processors/factory.py +34 -0
- infrahub_sdk/spec/processors/range_expand_processor.py +56 -0
- infrahub_sdk/spec/range_expansion.py +118 -0
- infrahub_sdk/task/models.py +6 -4
- infrahub_sdk/timestamp.py +18 -6
- infrahub_sdk/transforms.py +1 -1
- {infrahub_server-1.4.13.dist-info → infrahub_server-1.5.0.dist-info}/METADATA +9 -10
- {infrahub_server-1.4.13.dist-info → infrahub_server-1.5.0.dist-info}/RECORD +221 -165
- infrahub_testcontainers/container.py +114 -2
- infrahub_testcontainers/docker-compose-cluster.test.yml +5 -0
- infrahub_testcontainers/docker-compose.test.yml +5 -0
- infrahub_testcontainers/models.py +2 -2
- infrahub_testcontainers/performance_test.py +4 -4
- infrahub/core/convert_object_type/conversion.py +0 -134
- {infrahub_server-1.4.13.dist-info → infrahub_server-1.5.0.dist-info}/LICENSE.txt +0 -0
- {infrahub_server-1.4.13.dist-info → infrahub_server-1.5.0.dist-info}/WHEEL +0 -0
- {infrahub_server-1.4.13.dist-info → infrahub_server-1.5.0.dist-info}/entry_points.txt +0 -0
|
@@ -16,10 +16,12 @@ from infrahub.core.node import Node
|
|
|
16
16
|
from infrahub.core.schema import NodeSchema
|
|
17
17
|
from infrahub.database import InfrahubDatabase, retry_db_transaction
|
|
18
18
|
from infrahub.exceptions import NodeNotFoundError, ValidationError
|
|
19
|
-
from infrahub.lock import InfrahubMultiLock
|
|
19
|
+
from infrahub.lock import InfrahubMultiLock
|
|
20
20
|
from infrahub.log import get_logger
|
|
21
21
|
|
|
22
|
-
from .
|
|
22
|
+
from ...core.node.create import create_node
|
|
23
|
+
from ...core.node.lock_utils import build_object_lock_name, get_lock_names_on_object_mutation
|
|
24
|
+
from .main import DeleteResult, InfrahubMutationMixin, InfrahubMutationOptions, build_graphql_response
|
|
23
25
|
from .node_getter.by_default_filter import MutationNodeGetterByDefaultFilter
|
|
24
26
|
|
|
25
27
|
if TYPE_CHECKING:
|
|
@@ -106,11 +108,11 @@ class InfrahubIPAddressMutation(InfrahubMutationMixin, Mutation):
|
|
|
106
108
|
super().__init_subclass_with_meta__(_meta=_meta, **options)
|
|
107
109
|
|
|
108
110
|
@staticmethod
|
|
109
|
-
def
|
|
111
|
+
def _get_lock_names(namespace_id: str, branch: Branch) -> list[str]:
|
|
110
112
|
if not branch.is_default:
|
|
111
113
|
# Do not lock on other branches as reconciliation will be performed at least when merging in main branch.
|
|
112
|
-
return
|
|
113
|
-
return build_object_lock_name(InfrahubKind.IPADDRESS + "_" + namespace_id)
|
|
114
|
+
return []
|
|
115
|
+
return [build_object_lock_name(InfrahubKind.IPADDRESS + "_" + namespace_id)]
|
|
114
116
|
|
|
115
117
|
@classmethod
|
|
116
118
|
async def _mutate_create_object_and_reconcile(
|
|
@@ -121,7 +123,13 @@ class InfrahubIPAddressMutation(InfrahubMutationMixin, Mutation):
|
|
|
121
123
|
ip_address: IPv4Interface | ipaddress.IPv6Interface,
|
|
122
124
|
namespace_id: str,
|
|
123
125
|
) -> Node:
|
|
124
|
-
address = await
|
|
126
|
+
address = await create_node(
|
|
127
|
+
data=dict(data),
|
|
128
|
+
db=db,
|
|
129
|
+
branch=branch,
|
|
130
|
+
schema=cls._meta.active_schema,
|
|
131
|
+
)
|
|
132
|
+
|
|
125
133
|
reconciler = IpamReconciler(db=db, branch=branch)
|
|
126
134
|
reconciled_address = await reconciler.reconcile(
|
|
127
135
|
ip_value=ip_address, namespace=namespace_id, node_uuid=address.get_id()
|
|
@@ -142,19 +150,15 @@ class InfrahubIPAddressMutation(InfrahubMutationMixin, Mutation):
|
|
|
142
150
|
ip_address = ipaddress.ip_interface(data["address"]["value"])
|
|
143
151
|
namespace_id = await validate_namespace(db=db, branch=branch, data=data)
|
|
144
152
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
reconciled_address = await cls._mutate_create_object_and_reconcile(
|
|
149
|
-
data=data, branch=branch, db=dbt, ip_address=ip_address, namespace_id=namespace_id
|
|
150
|
-
)
|
|
151
|
-
else:
|
|
153
|
+
lock_names = cls._get_lock_names(namespace_id, branch)
|
|
154
|
+
async with InfrahubMultiLock(lock_registry=lock.registry, locks=lock_names):
|
|
155
|
+
async with db.start_transaction() as dbt:
|
|
152
156
|
reconciled_address = await cls._mutate_create_object_and_reconcile(
|
|
153
157
|
data=data, branch=branch, db=dbt, ip_address=ip_address, namespace_id=namespace_id
|
|
154
158
|
)
|
|
155
|
-
|
|
159
|
+
graphql_response = await build_graphql_response(info=info, db=dbt, obj=reconciled_address)
|
|
156
160
|
|
|
157
|
-
return reconciled_address,
|
|
161
|
+
return reconciled_address, cls(**graphql_response)
|
|
158
162
|
|
|
159
163
|
@classmethod
|
|
160
164
|
async def _mutate_update_object_and_reconcile(
|
|
@@ -198,18 +202,28 @@ class InfrahubIPAddressMutation(InfrahubMutationMixin, Mutation):
|
|
|
198
202
|
namespace = await address.ip_namespace.get_peer(db)
|
|
199
203
|
namespace_id = await validate_namespace(db=db, branch=branch, data=data, existing_namespace_id=namespace.id)
|
|
200
204
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
205
|
+
# Prepare a clone to compute locks without triggering pool allocations
|
|
206
|
+
preview_obj = await NodeManager.get_one_by_id_or_default_filter(
|
|
207
|
+
db=db,
|
|
208
|
+
kind=address.get_kind(),
|
|
209
|
+
id=address.get_id(),
|
|
210
|
+
branch=branch,
|
|
211
|
+
)
|
|
212
|
+
await preview_obj.from_graphql(db=db, data=data, process_pools=False)
|
|
213
|
+
|
|
214
|
+
schema_branch = db.schema.get_schema_branch(name=branch.name)
|
|
215
|
+
lock_names = get_lock_names_on_object_mutation(node=preview_obj, schema_branch=schema_branch)
|
|
216
|
+
|
|
217
|
+
namespace_lock_names = cls._get_lock_names(namespace_id, branch)
|
|
218
|
+
async with InfrahubMultiLock(lock_registry=lock.registry, locks=namespace_lock_names):
|
|
219
|
+
# FIXME: do not lock when data does not contain uniqueness constraint fields or resource pool allocations
|
|
220
|
+
async with InfrahubMultiLock(lock_registry=lock.registry, locks=lock_names, metrics=False):
|
|
221
|
+
async with db.start_transaction() as dbt:
|
|
204
222
|
reconciled_address = await cls._mutate_update_object_and_reconcile(
|
|
205
223
|
info=info, data=data, branch=branch, address=address, namespace_id=namespace_id, db=dbt
|
|
206
224
|
)
|
|
207
|
-
else:
|
|
208
|
-
reconciled_address = await cls._mutate_update_object_and_reconcile(
|
|
209
|
-
info=info, data=data, branch=branch, address=address, namespace_id=namespace_id, db=dbt
|
|
210
|
-
)
|
|
211
225
|
|
|
212
|
-
|
|
226
|
+
result = await cls.mutate_update_to_graphql(db=dbt, info=info, obj=reconciled_address)
|
|
213
227
|
|
|
214
228
|
return address, result
|
|
215
229
|
|
|
@@ -261,11 +275,11 @@ class InfrahubIPPrefixMutation(InfrahubMutationMixin, Mutation):
|
|
|
261
275
|
super().__init_subclass_with_meta__(_meta=_meta, **options)
|
|
262
276
|
|
|
263
277
|
@staticmethod
|
|
264
|
-
def
|
|
278
|
+
def _get_lock_names(namespace_id: str) -> list[str]:
|
|
265
279
|
# IPPrefix has some cardinality-one relationships involved (parent/child/ip_address),
|
|
266
280
|
# so we need to lock on any branch to avoid creating multiple peers for these relationships
|
|
267
281
|
# during concurrent ipam reconciliations.
|
|
268
|
-
return build_object_lock_name(InfrahubKind.IPPREFIX + "_" + namespace_id)
|
|
282
|
+
return [build_object_lock_name(InfrahubKind.IPPREFIX + "_" + namespace_id)]
|
|
269
283
|
|
|
270
284
|
@classmethod
|
|
271
285
|
async def _mutate_create_object_and_reconcile(
|
|
@@ -275,7 +289,12 @@ class InfrahubIPPrefixMutation(InfrahubMutationMixin, Mutation):
|
|
|
275
289
|
db: InfrahubDatabase,
|
|
276
290
|
namespace_id: str,
|
|
277
291
|
) -> Node:
|
|
278
|
-
prefix = await
|
|
292
|
+
prefix = await create_node(
|
|
293
|
+
data=dict(data),
|
|
294
|
+
db=db,
|
|
295
|
+
branch=branch,
|
|
296
|
+
schema=cls._meta.active_schema,
|
|
297
|
+
)
|
|
279
298
|
return await cls._reconcile_prefix(
|
|
280
299
|
branch=branch, db=db, prefix=prefix, namespace_id=namespace_id, is_delete=False
|
|
281
300
|
)
|
|
@@ -293,16 +312,16 @@ class InfrahubIPPrefixMutation(InfrahubMutationMixin, Mutation):
|
|
|
293
312
|
db = database or graphql_context.db
|
|
294
313
|
namespace_id = await validate_namespace(db=db, branch=branch, data=data)
|
|
295
314
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
async with
|
|
315
|
+
lock_names = cls._get_lock_names(namespace_id)
|
|
316
|
+
async with InfrahubMultiLock(lock_registry=lock.registry, locks=lock_names):
|
|
317
|
+
async with db.start_transaction() as dbt:
|
|
299
318
|
reconciled_prefix = await cls._mutate_create_object_and_reconcile(
|
|
300
319
|
data=data, branch=branch, db=dbt, namespace_id=namespace_id
|
|
301
320
|
)
|
|
302
321
|
|
|
303
|
-
|
|
322
|
+
graphql_response = await build_graphql_response(info=info, db=dbt, obj=reconciled_prefix)
|
|
304
323
|
|
|
305
|
-
return reconciled_prefix,
|
|
324
|
+
return reconciled_prefix, cls(**graphql_response)
|
|
306
325
|
|
|
307
326
|
@classmethod
|
|
308
327
|
async def _mutate_update_object_and_reconcile(
|
|
@@ -343,13 +362,26 @@ class InfrahubIPPrefixMutation(InfrahubMutationMixin, Mutation):
|
|
|
343
362
|
namespace = await prefix.ip_namespace.get_peer(db)
|
|
344
363
|
namespace_id = await validate_namespace(db=db, branch=branch, data=data, existing_namespace_id=namespace.id)
|
|
345
364
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
365
|
+
# Prepare a clone to compute locks without triggering pool allocations
|
|
366
|
+
preview_obj = await NodeManager.get_one_by_id_or_default_filter(
|
|
367
|
+
db=db,
|
|
368
|
+
kind=prefix.get_kind(),
|
|
369
|
+
id=prefix.get_id(),
|
|
370
|
+
branch=branch,
|
|
371
|
+
)
|
|
372
|
+
await preview_obj.from_graphql(db=db, data=data, process_pools=False)
|
|
373
|
+
|
|
374
|
+
schema_branch = db.schema.get_schema_branch(name=branch.name)
|
|
375
|
+
lock_names = get_lock_names_on_object_mutation(node=preview_obj, schema_branch=schema_branch)
|
|
376
|
+
|
|
377
|
+
namespace_lock_names = cls._get_lock_names(namespace_id)
|
|
378
|
+
async with InfrahubMultiLock(lock_registry=lock.registry, locks=namespace_lock_names):
|
|
379
|
+
async with InfrahubMultiLock(lock_registry=lock.registry, locks=lock_names, metrics=False):
|
|
380
|
+
async with db.start_transaction() as dbt:
|
|
381
|
+
reconciled_prefix = await cls._mutate_update_object_and_reconcile(
|
|
382
|
+
info=info, data=data, prefix=prefix, db=dbt, namespace_id=namespace_id, branch=branch
|
|
383
|
+
)
|
|
384
|
+
result = await cls.mutate_update_to_graphql(db=dbt, info=info, obj=reconciled_prefix)
|
|
353
385
|
|
|
354
386
|
return prefix, result
|
|
355
387
|
|
|
@@ -408,9 +440,9 @@ class InfrahubIPPrefixMutation(InfrahubMutationMixin, Mutation):
|
|
|
408
440
|
namespace_rels = await prefix.ip_namespace.get_relationships(db=db)
|
|
409
441
|
namespace_id = namespace_rels[0].peer_id
|
|
410
442
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
async with
|
|
443
|
+
lock_names = cls._get_lock_names(namespace_id)
|
|
444
|
+
async with InfrahubMultiLock(lock_registry=lock.registry, locks=lock_names):
|
|
445
|
+
async with graphql_context.db.start_transaction() as dbt:
|
|
414
446
|
reconciled_prefix = await cls._reconcile_prefix(
|
|
415
447
|
branch=branch, db=dbt, prefix=prefix, namespace_id=namespace_id, is_delete=True
|
|
416
448
|
)
|
|
@@ -1,22 +1,18 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import hashlib
|
|
4
3
|
from dataclasses import dataclass, field
|
|
5
4
|
from typing import TYPE_CHECKING, Any
|
|
6
5
|
|
|
7
6
|
from graphene import InputObjectType, Mutation
|
|
8
7
|
from graphene.types.mutation import MutationOptions
|
|
8
|
+
from infrahub_sdk.utils import extract_fields_first_node
|
|
9
9
|
from typing_extensions import Self
|
|
10
10
|
|
|
11
11
|
from infrahub import config, lock
|
|
12
|
-
from infrahub.core.constants import
|
|
12
|
+
from infrahub.core.constants import MutationAction
|
|
13
13
|
from infrahub.core.constraint.node.runner import NodeConstraintRunner
|
|
14
14
|
from infrahub.core.manager import NodeManager
|
|
15
|
-
from infrahub.core.node.create import
|
|
16
|
-
create_node,
|
|
17
|
-
get_profile_ids,
|
|
18
|
-
refresh_for_profile_update,
|
|
19
|
-
)
|
|
15
|
+
from infrahub.core.node.create import create_node, get_profile_ids
|
|
20
16
|
from infrahub.core.schema import MainSchemaTypes, NodeSchema
|
|
21
17
|
from infrahub.core.schema.generic_schema import GenericSchema
|
|
22
18
|
from infrahub.core.schema.profile_schema import ProfileSchema
|
|
@@ -28,9 +24,11 @@ from infrahub.events.generator import generate_node_mutation_events
|
|
|
28
24
|
from infrahub.exceptions import HFIDViolatedError, InitializationError, NodeNotFoundError
|
|
29
25
|
from infrahub.graphql.context import apply_external_context
|
|
30
26
|
from infrahub.graphql.field_extractor import extract_graphql_fields
|
|
31
|
-
from infrahub.lock import InfrahubMultiLock
|
|
27
|
+
from infrahub.lock import InfrahubMultiLock
|
|
32
28
|
from infrahub.log import get_log_data, get_logger
|
|
29
|
+
from infrahub.profiles.node_applier import NodeProfilesApplier
|
|
33
30
|
|
|
31
|
+
from ...core.node.lock_utils import get_lock_names_on_object_mutation
|
|
34
32
|
from .node_getter.by_default_filter import MutationNodeGetterByDefaultFilter
|
|
35
33
|
|
|
36
34
|
if TYPE_CHECKING:
|
|
@@ -38,7 +36,6 @@ if TYPE_CHECKING:
|
|
|
38
36
|
|
|
39
37
|
from infrahub.core.branch import Branch
|
|
40
38
|
from infrahub.core.node import Node
|
|
41
|
-
from infrahub.core.schema.schema_branch import SchemaBranch
|
|
42
39
|
from infrahub.database import InfrahubDatabase
|
|
43
40
|
from infrahub.graphql.types.context import ContextInput
|
|
44
41
|
|
|
@@ -47,8 +44,6 @@ if TYPE_CHECKING:
|
|
|
47
44
|
|
|
48
45
|
log = get_logger()
|
|
49
46
|
|
|
50
|
-
KINDS_CONCURRENT_MUTATIONS_NOT_ALLOWED = [InfrahubKind.GENERICGROUP]
|
|
51
|
-
|
|
52
47
|
|
|
53
48
|
@dataclass
|
|
54
49
|
class DeleteResult:
|
|
@@ -146,23 +141,6 @@ class InfrahubMutationMixin:
|
|
|
146
141
|
|
|
147
142
|
return mutation
|
|
148
143
|
|
|
149
|
-
@classmethod
|
|
150
|
-
async def _call_mutate_create_object(
|
|
151
|
-
cls, data: InputObjectType, db: InfrahubDatabase, branch: Branch, override_data: dict[str, Any] | None = None
|
|
152
|
-
) -> Node:
|
|
153
|
-
"""
|
|
154
|
-
Wrapper around mutate_create_object to potentially activate locking.
|
|
155
|
-
"""
|
|
156
|
-
schema_branch = db.schema.get_schema_branch(name=branch.name)
|
|
157
|
-
lock_names = _get_kind_lock_names_on_object_mutation(
|
|
158
|
-
kind=cls._meta.active_schema.kind, branch=branch, schema_branch=schema_branch, data=data
|
|
159
|
-
)
|
|
160
|
-
if lock_names:
|
|
161
|
-
async with InfrahubMultiLock(lock_registry=lock.registry, locks=lock_names):
|
|
162
|
-
return await cls.mutate_create_object(data=data, db=db, branch=branch, override_data=override_data)
|
|
163
|
-
|
|
164
|
-
return await cls.mutate_create_object(data=data, db=db, branch=branch, override_data=override_data)
|
|
165
|
-
|
|
166
144
|
@classmethod
|
|
167
145
|
async def mutate_create(
|
|
168
146
|
cls,
|
|
@@ -172,40 +150,21 @@ class InfrahubMutationMixin:
|
|
|
172
150
|
database: InfrahubDatabase | None = None,
|
|
173
151
|
override_data: dict[str, Any] | None = None,
|
|
174
152
|
) -> tuple[Node, Self]:
|
|
175
|
-
|
|
176
|
-
db = database or graphql_context.db
|
|
177
|
-
obj = await cls._call_mutate_create_object(data=data, db=db, branch=branch, override_data=override_data)
|
|
178
|
-
result = await cls.mutate_create_to_graphql(info=info, db=db, obj=obj)
|
|
179
|
-
return obj, result
|
|
180
|
-
|
|
181
|
-
@classmethod
|
|
182
|
-
@retry_db_transaction(name="object_create")
|
|
183
|
-
async def mutate_create_object(
|
|
184
|
-
cls,
|
|
185
|
-
data: InputObjectType,
|
|
186
|
-
db: InfrahubDatabase,
|
|
187
|
-
branch: Branch,
|
|
188
|
-
override_data: dict[str, Any] | None = None,
|
|
189
|
-
) -> Node:
|
|
153
|
+
db = database or info.context.db
|
|
190
154
|
schema = cls._meta.active_schema
|
|
191
|
-
|
|
192
|
-
raise ValueError(f"Node of generic schema `{schema.name=}` can not be instantiated.")
|
|
155
|
+
|
|
193
156
|
create_data = dict(data)
|
|
194
157
|
create_data.update(override_data or {})
|
|
195
|
-
|
|
158
|
+
|
|
159
|
+
obj = await create_node(
|
|
196
160
|
data=create_data,
|
|
197
161
|
db=db,
|
|
198
162
|
branch=branch,
|
|
199
163
|
schema=schema,
|
|
200
164
|
)
|
|
201
165
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
fields = extract_graphql_fields(info=info)
|
|
205
|
-
result: dict[str, Any] = {"ok": True}
|
|
206
|
-
if "object" in fields:
|
|
207
|
-
result["object"] = await obj.to_graphql(db=db, fields=fields.get("object", {}))
|
|
208
|
-
return cls(**result)
|
|
166
|
+
graphql_response = await build_graphql_response(info=info, db=db, obj=obj)
|
|
167
|
+
return obj, cls(**graphql_response)
|
|
209
168
|
|
|
210
169
|
@classmethod
|
|
211
170
|
async def _call_mutate_update(
|
|
@@ -221,41 +180,40 @@ class InfrahubMutationMixin:
|
|
|
221
180
|
Wrapper around mutate_update to potentially activate locking and call it within a database transaction.
|
|
222
181
|
"""
|
|
223
182
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
183
|
+
# Prepare a clone to compute locks without triggering pool allocations
|
|
184
|
+
preview_obj = await NodeManager.get_one_by_id_or_default_filter(
|
|
185
|
+
db=db,
|
|
186
|
+
kind=obj.get_kind(),
|
|
187
|
+
id=obj.get_id(),
|
|
188
|
+
branch=branch,
|
|
227
189
|
)
|
|
190
|
+
await preview_obj.from_graphql(db=db, data=data, process_pools=False)
|
|
228
191
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
else:
|
|
192
|
+
schema_branch = db.schema.get_schema_branch(name=branch.name)
|
|
193
|
+
lock_names = get_lock_names_on_object_mutation(node=preview_obj, schema_branch=schema_branch)
|
|
194
|
+
|
|
195
|
+
# FIXME: do not lock when data does not contain uniqueness constraint fields or resource pool allocations
|
|
196
|
+
async with InfrahubMultiLock(lock_registry=lock.registry, locks=lock_names, metrics=False):
|
|
197
|
+
if db.is_transaction:
|
|
236
198
|
obj = await cls.mutate_update_object(
|
|
237
199
|
db=db, info=info, data=data, branch=branch, obj=obj, skip_uniqueness_check=skip_uniqueness_check
|
|
238
200
|
)
|
|
239
|
-
result = await cls.mutate_update_to_graphql(db=db, info=info, obj=obj)
|
|
240
|
-
return obj, result
|
|
241
201
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
db=dbt,
|
|
247
|
-
info=info,
|
|
248
|
-
data=data,
|
|
249
|
-
branch=branch,
|
|
250
|
-
obj=obj,
|
|
251
|
-
skip_uniqueness_check=skip_uniqueness_check,
|
|
252
|
-
)
|
|
253
|
-
else:
|
|
202
|
+
result = await cls.mutate_update_to_graphql(db=db, info=info, obj=obj)
|
|
203
|
+
return obj, result
|
|
204
|
+
|
|
205
|
+
async with db.start_transaction() as dbt:
|
|
254
206
|
obj = await cls.mutate_update_object(
|
|
255
|
-
db=dbt,
|
|
207
|
+
db=dbt,
|
|
208
|
+
info=info,
|
|
209
|
+
data=data,
|
|
210
|
+
branch=branch,
|
|
211
|
+
obj=obj,
|
|
212
|
+
skip_uniqueness_check=skip_uniqueness_check,
|
|
256
213
|
)
|
|
257
|
-
|
|
258
|
-
|
|
214
|
+
|
|
215
|
+
result = await cls.mutate_update_to_graphql(db=dbt, info=info, obj=obj)
|
|
216
|
+
return obj, result
|
|
259
217
|
|
|
260
218
|
@classmethod
|
|
261
219
|
@retry_db_transaction(name="object_update")
|
|
@@ -290,7 +248,6 @@ class InfrahubMutationMixin:
|
|
|
290
248
|
component_registry = get_component_registry()
|
|
291
249
|
node_constraint_runner = await component_registry.get_component(NodeConstraintRunner, db=db, branch=branch)
|
|
292
250
|
|
|
293
|
-
before_mutate_profile_ids = await get_profile_ids(db=db, obj=obj)
|
|
294
251
|
await obj.from_graphql(db=db, data=data)
|
|
295
252
|
fields_to_validate = list(data)
|
|
296
253
|
await node_constraint_runner.check(
|
|
@@ -302,15 +259,13 @@ class InfrahubMutationMixin:
|
|
|
302
259
|
if field_to_remove in fields:
|
|
303
260
|
fields.remove(field_to_remove)
|
|
304
261
|
|
|
262
|
+
after_mutate_profile_ids = await get_profile_ids(db=db, obj=obj)
|
|
263
|
+
if after_mutate_profile_ids or (not after_mutate_profile_ids and obj.uses_profiles()):
|
|
264
|
+
node_profiles_applier = NodeProfilesApplier(db=db, branch=branch)
|
|
265
|
+
updated_field_names = await node_profiles_applier.apply_profiles(node=obj)
|
|
266
|
+
fields += updated_field_names
|
|
305
267
|
await obj.save(db=db, fields=fields)
|
|
306
268
|
|
|
307
|
-
obj = await refresh_for_profile_update(
|
|
308
|
-
db=db,
|
|
309
|
-
branch=branch,
|
|
310
|
-
obj=obj,
|
|
311
|
-
previous_profile_ids=before_mutate_profile_ids,
|
|
312
|
-
schema=cls._meta.active_schema,
|
|
313
|
-
)
|
|
314
269
|
return obj
|
|
315
270
|
|
|
316
271
|
@classmethod
|
|
@@ -422,6 +377,15 @@ class InfrahubMutationMixin:
|
|
|
422
377
|
)
|
|
423
378
|
return updated_obj, mutation, False
|
|
424
379
|
|
|
380
|
+
@classmethod
|
|
381
|
+
async def _delete_obj(cls, graphql_context: GraphqlContext, branch: Branch, obj: Node) -> list[Node]:
|
|
382
|
+
db = graphql_context.db
|
|
383
|
+
async with db.start_transaction() as dbt:
|
|
384
|
+
deleted = await NodeManager.delete(db=dbt, branch=branch, nodes=[obj])
|
|
385
|
+
deleted_str = ", ".join([f"{d.get_kind()}({d.get_id()})" for d in deleted])
|
|
386
|
+
log.info(f"nodes deleted: {deleted_str}")
|
|
387
|
+
return deleted
|
|
388
|
+
|
|
425
389
|
@classmethod
|
|
426
390
|
@retry_db_transaction(name="object_delete")
|
|
427
391
|
async def mutate_delete(
|
|
@@ -440,11 +404,7 @@ class InfrahubMutationMixin:
|
|
|
440
404
|
branch=branch,
|
|
441
405
|
)
|
|
442
406
|
|
|
443
|
-
|
|
444
|
-
deleted = await NodeManager.delete(db=db, branch=branch, nodes=[obj])
|
|
445
|
-
|
|
446
|
-
deleted_str = ", ".join([f"{d.get_kind()}({d.get_id()})" for d in deleted])
|
|
447
|
-
log.info(f"nodes deleted: {deleted_str}")
|
|
407
|
+
deleted = await cls._delete_obj(graphql_context=graphql_context, branch=branch, obj=obj)
|
|
448
408
|
|
|
449
409
|
ok = True
|
|
450
410
|
|
|
@@ -471,90 +431,13 @@ class InfrahubMutation(InfrahubMutationMixin, Mutation):
|
|
|
471
431
|
super().__init_subclass_with_meta__(_meta=_meta, **options)
|
|
472
432
|
|
|
473
433
|
|
|
474
|
-
def _get_kinds_to_lock_on_object_mutation(kind: str, schema_branch: SchemaBranch) -> list[str]:
|
|
475
|
-
"""
|
|
476
|
-
Return kinds for which we want to lock during creating / updating an object of a given schema node.
|
|
477
|
-
Lock should be performed on schema kind and its generics having a uniqueness_constraint defined.
|
|
478
|
-
If a generic uniqueness constraint is the same as the node schema one,
|
|
479
|
-
it means node schema overrided this constraint, in which case we only need to lock on the generic.
|
|
480
|
-
"""
|
|
481
|
-
|
|
482
|
-
node_schema = schema_branch.get(name=kind, duplicate=False)
|
|
483
|
-
|
|
484
|
-
schema_uc = None
|
|
485
|
-
kinds = []
|
|
486
|
-
if node_schema.uniqueness_constraints:
|
|
487
|
-
kinds.append(node_schema.kind)
|
|
488
|
-
schema_uc = node_schema.uniqueness_constraints
|
|
489
|
-
|
|
490
|
-
if node_schema.is_generic_schema:
|
|
491
|
-
return kinds
|
|
492
|
-
|
|
493
|
-
generics_kinds = node_schema.inherit_from
|
|
494
|
-
|
|
495
|
-
node_schema_kind_removed = False
|
|
496
|
-
for generic_kind in generics_kinds:
|
|
497
|
-
generic_uc = schema_branch.get(name=generic_kind, duplicate=False).uniqueness_constraints
|
|
498
|
-
if generic_uc:
|
|
499
|
-
kinds.append(generic_kind)
|
|
500
|
-
if not node_schema_kind_removed and generic_uc == schema_uc:
|
|
501
|
-
# Check whether we should remove original schema kind as it simply overrides uniqueness_constraint
|
|
502
|
-
# of a generic
|
|
503
|
-
kinds.pop(0)
|
|
504
|
-
node_schema_kind_removed = True
|
|
505
|
-
return kinds
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
def _should_kind_be_locked_on_any_branch(kind: str, schema_branch: SchemaBranch) -> bool:
|
|
509
|
-
"""
|
|
510
|
-
Check whether kind or any kind generic is in KINDS_TO_LOCK_ON_ANY_BRANCH.
|
|
511
|
-
"""
|
|
512
|
-
|
|
513
|
-
if kind in KINDS_CONCURRENT_MUTATIONS_NOT_ALLOWED:
|
|
514
|
-
return True
|
|
515
|
-
|
|
516
|
-
node_schema = schema_branch.get(name=kind, duplicate=False)
|
|
517
|
-
if node_schema.is_generic_schema:
|
|
518
|
-
return False
|
|
519
|
-
|
|
520
|
-
for generic_kind in node_schema.inherit_from:
|
|
521
|
-
if generic_kind in KINDS_CONCURRENT_MUTATIONS_NOT_ALLOWED:
|
|
522
|
-
return True
|
|
523
|
-
return False
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
def _hash(value: str) -> str:
|
|
527
|
-
# Do not use builtin `hash` for lock names as due to randomization results would differ between
|
|
528
|
-
# different processes.
|
|
529
|
-
return hashlib.sha256(value.encode()).hexdigest()
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
def _get_kind_lock_names_on_object_mutation(
|
|
533
|
-
kind: str, branch: Branch, schema_branch: SchemaBranch, data: InputObjectType
|
|
534
|
-
) -> list[str]:
|
|
535
|
-
"""
|
|
536
|
-
Return objects kind for which we want to avoid concurrent mutation (create/update). Except for some specific kinds,
|
|
537
|
-
concurrent mutations are only allowed on non-main branch as objects validations will be performed at least when merging in main branch.
|
|
538
|
-
"""
|
|
539
|
-
|
|
540
|
-
if not branch.is_default and not _should_kind_be_locked_on_any_branch(kind=kind, schema_branch=schema_branch):
|
|
541
|
-
return []
|
|
542
|
-
|
|
543
|
-
if kind == InfrahubKind.GRAPHQLQUERYGROUP:
|
|
544
|
-
# Lock on name as well to improve performances
|
|
545
|
-
try:
|
|
546
|
-
name = data.name.value
|
|
547
|
-
return [build_object_lock_name(kind + "." + _hash(name))]
|
|
548
|
-
except AttributeError:
|
|
549
|
-
# We might reach here if we are updating a CoreGraphQLQueryGroup without updating the name,
|
|
550
|
-
# in which case we would not need to lock. This is not supposed to happen as current `update`
|
|
551
|
-
# logic first fetches the node with its name.
|
|
552
|
-
return []
|
|
553
|
-
|
|
554
|
-
lock_kinds = _get_kinds_to_lock_on_object_mutation(kind, schema_branch)
|
|
555
|
-
lock_names = [build_object_lock_name(kind) for kind in lock_kinds]
|
|
556
|
-
return lock_names
|
|
557
|
-
|
|
558
|
-
|
|
559
434
|
def _get_data_fields(data: InputObjectType) -> list[str]:
|
|
560
435
|
return [field for field in data.keys() if field not in ["id", "hfid"]]
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
async def build_graphql_response(info: GraphQLResolveInfo, db: InfrahubDatabase, obj: Node) -> dict:
|
|
439
|
+
fields = await extract_fields_first_node(info)
|
|
440
|
+
result: dict[str, Any] = {"ok": True}
|
|
441
|
+
if "object" in fields:
|
|
442
|
+
result["object"] = await obj.to_graphql(db=db, fields=fields.get("object", {}))
|
|
443
|
+
return result
|