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
infrahub/core/node/create.py
CHANGED
|
@@ -2,18 +2,23 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from typing import TYPE_CHECKING, Any, Mapping
|
|
4
4
|
|
|
5
|
+
from infrahub import lock
|
|
5
6
|
from infrahub.core import registry
|
|
6
7
|
from infrahub.core.constants import RelationshipCardinality, RelationshipKind
|
|
7
8
|
from infrahub.core.constraint.node.runner import NodeConstraintRunner
|
|
8
|
-
from infrahub.core.manager import NodeManager
|
|
9
9
|
from infrahub.core.node import Node
|
|
10
|
+
from infrahub.core.node.lock_utils import get_lock_names_on_object_mutation
|
|
10
11
|
from infrahub.core.protocols import CoreObjectTemplate
|
|
12
|
+
from infrahub.core.schema import GenericSchema
|
|
11
13
|
from infrahub.dependencies.registry import get_component_registry
|
|
14
|
+
from infrahub.lock import InfrahubMultiLock
|
|
15
|
+
from infrahub.profiles.node_applier import NodeProfilesApplier
|
|
12
16
|
|
|
13
17
|
if TYPE_CHECKING:
|
|
14
18
|
from infrahub.core.branch import Branch
|
|
15
19
|
from infrahub.core.relationship.model import RelationshipManager
|
|
16
20
|
from infrahub.core.schema import MainSchemaTypes, NonGenericSchemaTypes, RelationshipSchema
|
|
21
|
+
from infrahub.core.timestamp import Timestamp
|
|
17
22
|
from infrahub.database import InfrahubDatabase
|
|
18
23
|
|
|
19
24
|
|
|
@@ -87,6 +92,7 @@ async def handle_template_relationships(
|
|
|
87
92
|
template: CoreObjectTemplate,
|
|
88
93
|
fields: list,
|
|
89
94
|
constraint_runner: NodeConstraintRunner | None = None,
|
|
95
|
+
at: Timestamp | None = None,
|
|
90
96
|
) -> None:
|
|
91
97
|
if constraint_runner is None:
|
|
92
98
|
component_registry = get_component_registry()
|
|
@@ -114,7 +120,7 @@ async def handle_template_relationships(
|
|
|
114
120
|
current_template=template,
|
|
115
121
|
)
|
|
116
122
|
|
|
117
|
-
obj_peer = await Node.init(schema=obj_peer_schema, db=db, branch=branch)
|
|
123
|
+
obj_peer = await Node.init(schema=obj_peer_schema, db=db, branch=branch, at=at)
|
|
118
124
|
await obj_peer.new(db=db, **obj_peer_data)
|
|
119
125
|
await constraint_runner.check(node=obj_peer, field_filters=list(obj_peer_data))
|
|
120
126
|
await obj_peer.save(db=db)
|
|
@@ -126,6 +132,7 @@ async def handle_template_relationships(
|
|
|
126
132
|
obj=obj_peer,
|
|
127
133
|
template=template_relationship_peer,
|
|
128
134
|
fields=fields,
|
|
135
|
+
at=at,
|
|
129
136
|
)
|
|
130
137
|
|
|
131
138
|
|
|
@@ -136,43 +143,20 @@ async def get_profile_ids(db: InfrahubDatabase, obj: Node) -> set[str]:
|
|
|
136
143
|
return {pr.peer_id for pr in profile_rels}
|
|
137
144
|
|
|
138
145
|
|
|
139
|
-
async def refresh_for_profile_update(
|
|
140
|
-
db: InfrahubDatabase,
|
|
141
|
-
branch: Branch,
|
|
142
|
-
obj: Node,
|
|
143
|
-
schema: NonGenericSchemaTypes,
|
|
144
|
-
previous_profile_ids: set[str] | None = None,
|
|
145
|
-
) -> Node:
|
|
146
|
-
if not hasattr(obj, "profiles"):
|
|
147
|
-
return obj
|
|
148
|
-
current_profile_ids = await get_profile_ids(db=db, obj=obj)
|
|
149
|
-
if previous_profile_ids is None or previous_profile_ids != current_profile_ids:
|
|
150
|
-
refreshed_node = await NodeManager.get_one_by_id_or_default_filter(
|
|
151
|
-
db=db,
|
|
152
|
-
kind=schema.kind,
|
|
153
|
-
id=obj.get_id(),
|
|
154
|
-
branch=branch,
|
|
155
|
-
include_owner=True,
|
|
156
|
-
include_source=True,
|
|
157
|
-
)
|
|
158
|
-
refreshed_node._node_changelog = obj.node_changelog
|
|
159
|
-
return refreshed_node
|
|
160
|
-
return obj
|
|
161
|
-
|
|
162
|
-
|
|
163
146
|
async def _do_create_node(
|
|
164
147
|
node_class: type[Node],
|
|
148
|
+
node_constraint_runner: NodeConstraintRunner,
|
|
165
149
|
db: InfrahubDatabase,
|
|
166
|
-
data: dict,
|
|
167
150
|
schema: NonGenericSchemaTypes,
|
|
168
|
-
fields_to_validate: list,
|
|
169
151
|
branch: Branch,
|
|
170
|
-
|
|
152
|
+
fields_to_validate: list[str],
|
|
153
|
+
data: dict[str, Any],
|
|
154
|
+
at: Timestamp | None = None,
|
|
171
155
|
) -> Node:
|
|
172
156
|
obj = await node_class.init(db=db, schema=schema, branch=branch)
|
|
173
157
|
await obj.new(db=db, **data)
|
|
174
158
|
await node_constraint_runner.check(node=obj, field_filters=fields_to_validate)
|
|
175
|
-
await obj.save(db=db)
|
|
159
|
+
await obj.save(db=db, at=at)
|
|
176
160
|
|
|
177
161
|
object_template = await obj.get_object_template(db=db)
|
|
178
162
|
if object_template:
|
|
@@ -182,50 +166,70 @@ async def _do_create_node(
|
|
|
182
166
|
template=object_template,
|
|
183
167
|
obj=obj,
|
|
184
168
|
fields=fields_to_validate,
|
|
169
|
+
at=at,
|
|
185
170
|
)
|
|
186
171
|
return obj
|
|
187
172
|
|
|
188
173
|
|
|
189
174
|
async def create_node(
|
|
190
|
-
data: dict,
|
|
175
|
+
data: dict[str, Any],
|
|
191
176
|
db: InfrahubDatabase,
|
|
192
177
|
branch: Branch,
|
|
193
|
-
schema:
|
|
178
|
+
schema: MainSchemaTypes,
|
|
179
|
+
at: Timestamp | None = None,
|
|
194
180
|
) -> Node:
|
|
195
181
|
"""Create a node in the database if constraint checks succeed."""
|
|
196
182
|
|
|
183
|
+
if isinstance(schema, GenericSchema):
|
|
184
|
+
raise ValueError(f"Node of generic schema `{schema.name=}` can not be instantiated.")
|
|
185
|
+
|
|
197
186
|
component_registry = get_component_registry()
|
|
198
|
-
node_constraint_runner = await component_registry.get_component(
|
|
199
|
-
NodeConstraintRunner, db=db.start_session() if not db.is_transaction else db, branch=branch
|
|
200
|
-
)
|
|
201
187
|
node_class = Node
|
|
202
188
|
if schema.kind in registry.node:
|
|
203
189
|
node_class = registry.node[schema.kind]
|
|
204
190
|
|
|
205
191
|
fields_to_validate = list(data)
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
async with db.start_transaction() as dbt:
|
|
192
|
+
|
|
193
|
+
preview_obj = await node_class.init(db=db, schema=schema, branch=branch)
|
|
194
|
+
await preview_obj.new(db=db, process_pools=False, **data)
|
|
195
|
+
schema_branch = db.schema.get_schema_branch(name=branch.name)
|
|
196
|
+
lock_names = get_lock_names_on_object_mutation(node=preview_obj, schema_branch=schema_branch)
|
|
197
|
+
|
|
198
|
+
obj: Node
|
|
199
|
+
async with InfrahubMultiLock(lock_registry=lock.registry, locks=lock_names, metrics=False):
|
|
200
|
+
if db.is_transaction:
|
|
201
|
+
node_constraint_runner = await component_registry.get_component(NodeConstraintRunner, db=db, branch=branch)
|
|
202
|
+
|
|
218
203
|
obj = await _do_create_node(
|
|
219
204
|
node_class=node_class,
|
|
220
205
|
node_constraint_runner=node_constraint_runner,
|
|
221
|
-
db=
|
|
206
|
+
db=db,
|
|
222
207
|
schema=schema,
|
|
223
208
|
branch=branch,
|
|
224
209
|
fields_to_validate=fields_to_validate,
|
|
225
210
|
data=data,
|
|
211
|
+
at=at,
|
|
226
212
|
)
|
|
213
|
+
else:
|
|
214
|
+
async with db.start_transaction() as dbt:
|
|
215
|
+
node_constraint_runner = await component_registry.get_component(
|
|
216
|
+
NodeConstraintRunner, db=dbt, branch=branch
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
obj = await _do_create_node(
|
|
220
|
+
node_class=node_class,
|
|
221
|
+
node_constraint_runner=node_constraint_runner,
|
|
222
|
+
db=dbt,
|
|
223
|
+
schema=schema,
|
|
224
|
+
branch=branch,
|
|
225
|
+
fields_to_validate=fields_to_validate,
|
|
226
|
+
data=data,
|
|
227
|
+
at=at,
|
|
228
|
+
)
|
|
227
229
|
|
|
228
230
|
if await get_profile_ids(db=db, obj=obj):
|
|
229
|
-
|
|
231
|
+
node_profiles_applier = NodeProfilesApplier(db=db, branch=branch)
|
|
232
|
+
await node_profiles_applier.apply_profiles(node=obj)
|
|
233
|
+
await obj.save(db=db)
|
|
230
234
|
|
|
231
235
|
return obj
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import hashlib
|
|
2
|
+
from typing import TYPE_CHECKING
|
|
3
|
+
|
|
4
|
+
from infrahub.core.node import Node
|
|
5
|
+
from infrahub.core.schema import GenericSchema
|
|
6
|
+
from infrahub.core.schema.schema_branch import SchemaBranch
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from infrahub.core.relationship import RelationshipManager
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
RESOURCE_POOL_LOCK_NAMESPACE = "resource_pool"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _get_kinds_to_lock_on_object_mutation(kind: str, schema_branch: SchemaBranch) -> list[str]:
|
|
16
|
+
"""
|
|
17
|
+
Return kinds for which we want to lock during creating / updating an object of a given schema node.
|
|
18
|
+
Lock should be performed on schema kind and its generics having a uniqueness_constraint defined.
|
|
19
|
+
If a generic uniqueness constraint is the same as the node schema one,
|
|
20
|
+
it means node schema overrided this constraint, in which case we only need to lock on the generic.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
node_schema = schema_branch.get(name=kind, duplicate=False)
|
|
24
|
+
|
|
25
|
+
schema_uc = None
|
|
26
|
+
kinds = []
|
|
27
|
+
if node_schema.uniqueness_constraints:
|
|
28
|
+
kinds.append(node_schema.kind)
|
|
29
|
+
schema_uc = node_schema.uniqueness_constraints
|
|
30
|
+
|
|
31
|
+
if isinstance(node_schema, GenericSchema):
|
|
32
|
+
return kinds
|
|
33
|
+
|
|
34
|
+
generics_kinds = node_schema.inherit_from
|
|
35
|
+
|
|
36
|
+
node_schema_kind_removed = False
|
|
37
|
+
for generic_kind in generics_kinds:
|
|
38
|
+
generic_uc = schema_branch.get(name=generic_kind, duplicate=False).uniqueness_constraints
|
|
39
|
+
if generic_uc:
|
|
40
|
+
kinds.append(generic_kind)
|
|
41
|
+
if not node_schema_kind_removed and generic_uc == schema_uc:
|
|
42
|
+
# Check whether we should remove original schema kind as it simply overrides uniqueness_constraint
|
|
43
|
+
# of a generic
|
|
44
|
+
kinds.pop(0)
|
|
45
|
+
node_schema_kind_removed = True
|
|
46
|
+
return kinds
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _hash(value: str) -> str:
|
|
50
|
+
# Do not use builtin `hash` for lock names as due to randomization results would differ between
|
|
51
|
+
# different processes.
|
|
52
|
+
return hashlib.sha256(value.encode()).hexdigest()
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def get_lock_names_on_object_mutation(node: Node, schema_branch: SchemaBranch) -> list[str]:
|
|
56
|
+
"""
|
|
57
|
+
Return lock names for object on which we want to avoid concurrent mutation (create/update).
|
|
58
|
+
Lock names include kind, some generic kinds, resource pool ids, and values of attributes of corresponding uniqueness constraints.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
lock_names: set[str] = set()
|
|
62
|
+
|
|
63
|
+
# Check if node is using resource manager allocation via attributes
|
|
64
|
+
for attr_name in node.get_schema().attribute_names:
|
|
65
|
+
attribute = getattr(node, attr_name, None)
|
|
66
|
+
if attribute is not None and getattr(attribute, "from_pool", None) and "id" in attribute.from_pool:
|
|
67
|
+
lock_names.add(f"{RESOURCE_POOL_LOCK_NAMESPACE}.{attribute.from_pool['id']}")
|
|
68
|
+
|
|
69
|
+
# Check if relationships allocate resources
|
|
70
|
+
for rel_name in node._relationships:
|
|
71
|
+
rel_manager: RelationshipManager = getattr(node, rel_name)
|
|
72
|
+
for rel in rel_manager._relationships:
|
|
73
|
+
if rel.from_pool and "id" in rel.from_pool:
|
|
74
|
+
lock_names.add(f"{RESOURCE_POOL_LOCK_NAMESPACE}.{rel.from_pool['id']}")
|
|
75
|
+
|
|
76
|
+
lock_kinds = _get_kinds_to_lock_on_object_mutation(node.get_kind(), schema_branch)
|
|
77
|
+
for kind in lock_kinds:
|
|
78
|
+
schema = schema_branch.get(name=kind, duplicate=False)
|
|
79
|
+
ucs = schema.uniqueness_constraints
|
|
80
|
+
if ucs is None:
|
|
81
|
+
continue
|
|
82
|
+
|
|
83
|
+
ucs_lock_names: list[str] = []
|
|
84
|
+
uc_attributes_names = set()
|
|
85
|
+
|
|
86
|
+
for uc in ucs:
|
|
87
|
+
uc_attributes_values = []
|
|
88
|
+
# Keep only attributes constraints
|
|
89
|
+
for field_path in uc:
|
|
90
|
+
# Some attributes may exist in different uniqueness constraints, we de-duplicate them
|
|
91
|
+
if field_path in uc_attributes_names:
|
|
92
|
+
continue
|
|
93
|
+
|
|
94
|
+
# Exclude relationships uniqueness constraints
|
|
95
|
+
schema_path = schema.parse_schema_path(path=field_path, schema=schema_branch)
|
|
96
|
+
if schema_path.related_schema is not None or schema_path.attribute_schema is None:
|
|
97
|
+
continue
|
|
98
|
+
|
|
99
|
+
uc_attributes_names.add(field_path)
|
|
100
|
+
attr = getattr(node, schema_path.attribute_schema.name, None)
|
|
101
|
+
if attr is None or attr.value is None:
|
|
102
|
+
# `attr.value` being None corresponds to optional unique attribute.
|
|
103
|
+
# `attr` being None is not supposed to happen.
|
|
104
|
+
value_hashed = _hash("")
|
|
105
|
+
else:
|
|
106
|
+
value_hashed = _hash(str(attr.value))
|
|
107
|
+
|
|
108
|
+
uc_attributes_values.append(value_hashed)
|
|
109
|
+
|
|
110
|
+
if uc_attributes_values:
|
|
111
|
+
uc_lock_name = ".".join(uc_attributes_values)
|
|
112
|
+
ucs_lock_names.append(uc_lock_name)
|
|
113
|
+
|
|
114
|
+
if not ucs_lock_names:
|
|
115
|
+
continue
|
|
116
|
+
|
|
117
|
+
partial_lock_name = kind + "." + ".".join(ucs_lock_names)
|
|
118
|
+
lock_names.add(build_object_lock_name(partial_lock_name))
|
|
119
|
+
|
|
120
|
+
return sorted(lock_names)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def build_object_lock_name(name: str) -> str:
|
|
124
|
+
return f"global.object.{name}"
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from abc import abstractmethod
|
|
4
|
+
from enum import Enum
|
|
5
|
+
from typing import TYPE_CHECKING, Any, Generic, TypeVar
|
|
6
|
+
|
|
7
|
+
from infrahub_sdk.template import Jinja2Template
|
|
8
|
+
|
|
9
|
+
from infrahub.core.query.node import AttributeFromDB
|
|
10
|
+
from infrahub.core.schema import NodeSchema, ProfileSchema, TemplateSchema
|
|
11
|
+
|
|
12
|
+
from ..attribute import BaseAttribute, ListAttributeOptional, StringOptional
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from infrahub.core.node import Node
|
|
16
|
+
from infrahub.core.schema import NodeSchema, ProfileSchema, TemplateSchema
|
|
17
|
+
from infrahub.core.schema.attribute_schema import AttributeSchema
|
|
18
|
+
from infrahub.core.timestamp import Timestamp
|
|
19
|
+
from infrahub.database import InfrahubDatabase
|
|
20
|
+
|
|
21
|
+
T = TypeVar("T")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class NodePropertyAttribute(Generic[T]):
|
|
25
|
+
"""A node property attribute is a construct that seats between a property and an attribute.
|
|
26
|
+
|
|
27
|
+
View it as a property, set at the node level but stored in the database as an attribute. It usually is something computed from other components of
|
|
28
|
+
a node, such as its attributes and its relationships.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __init__(
|
|
32
|
+
self,
|
|
33
|
+
node_schema: NodeSchema | ProfileSchema | TemplateSchema,
|
|
34
|
+
template: T | None,
|
|
35
|
+
value: AttributeFromDB | T | None = None,
|
|
36
|
+
) -> None:
|
|
37
|
+
self.node_schema = node_schema
|
|
38
|
+
|
|
39
|
+
self.node_attributes: list[str] = []
|
|
40
|
+
self.node_relationships: list[str] = []
|
|
41
|
+
|
|
42
|
+
self.template = template
|
|
43
|
+
self._value = value
|
|
44
|
+
self._manually_assigned = False
|
|
45
|
+
|
|
46
|
+
self.schema: AttributeSchema
|
|
47
|
+
|
|
48
|
+
self.analyze_variables()
|
|
49
|
+
|
|
50
|
+
def needs_update(self, fields: list[str] | None) -> bool:
|
|
51
|
+
"""Tell if this node property attribute must be recomputed given a list of updated fields of a node."""
|
|
52
|
+
if self._manually_assigned or not fields:
|
|
53
|
+
return True
|
|
54
|
+
for field in fields:
|
|
55
|
+
if field in self.node_attributes or field in self.node_relationships:
|
|
56
|
+
return True
|
|
57
|
+
|
|
58
|
+
return False
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def attribute_value(self) -> AttributeFromDB | dict[str, T | None]:
|
|
62
|
+
if isinstance(self._value, AttributeFromDB):
|
|
63
|
+
return self._value
|
|
64
|
+
return {"value": self._value}
|
|
65
|
+
|
|
66
|
+
def set_value(self, value: T | None, manually_assigned: bool = False) -> None:
|
|
67
|
+
"""Force the value of the node property attribute to the given one."""
|
|
68
|
+
if isinstance(self._value, AttributeFromDB):
|
|
69
|
+
self._value.value = value
|
|
70
|
+
else:
|
|
71
|
+
self._value = value
|
|
72
|
+
|
|
73
|
+
if manually_assigned:
|
|
74
|
+
self._manually_assigned = True
|
|
75
|
+
|
|
76
|
+
def get_value(self, node: Node, at: Timestamp) -> T | None:
|
|
77
|
+
if isinstance(self._value, AttributeFromDB):
|
|
78
|
+
attr = self.get_node_attribute(node=node, at=at)
|
|
79
|
+
return attr.value # type: ignore
|
|
80
|
+
|
|
81
|
+
return self._value
|
|
82
|
+
|
|
83
|
+
@abstractmethod
|
|
84
|
+
def analyze_variables(self) -> None: ...
|
|
85
|
+
|
|
86
|
+
@abstractmethod
|
|
87
|
+
async def compute(self, db: InfrahubDatabase, node: Node) -> None: ...
|
|
88
|
+
|
|
89
|
+
@abstractmethod
|
|
90
|
+
def get_node_attribute(self, node: Node, at: Timestamp) -> BaseAttribute: ...
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class DisplayLabel(NodePropertyAttribute[str]):
|
|
94
|
+
def __init__(
|
|
95
|
+
self,
|
|
96
|
+
node_schema: NodeSchema | ProfileSchema | TemplateSchema,
|
|
97
|
+
template: str | None,
|
|
98
|
+
value: AttributeFromDB | str | None = None,
|
|
99
|
+
) -> None:
|
|
100
|
+
super().__init__(node_schema=node_schema, template=template, value=value)
|
|
101
|
+
|
|
102
|
+
self.schema = node_schema.get_attribute(name="display_label")
|
|
103
|
+
|
|
104
|
+
@property
|
|
105
|
+
def is_jinja2_template(self) -> bool:
|
|
106
|
+
if self.template is None:
|
|
107
|
+
return False
|
|
108
|
+
|
|
109
|
+
return any(c in self.template for c in "{}")
|
|
110
|
+
|
|
111
|
+
def _analyze_plain_value(self) -> None:
|
|
112
|
+
if self.template is None or "__" not in self.template:
|
|
113
|
+
return
|
|
114
|
+
|
|
115
|
+
items = self.template.split("__", maxsplit=1)
|
|
116
|
+
if items[0] not in self.node_schema.attribute_names:
|
|
117
|
+
raise ValueError(f"{items[0]} is not an attribute of {self.node_schema.kind}")
|
|
118
|
+
|
|
119
|
+
self.node_attributes.append(items[0])
|
|
120
|
+
|
|
121
|
+
def _analyze_jinja2_value(self) -> None:
|
|
122
|
+
if self.template is None or not self.is_jinja2_template:
|
|
123
|
+
return
|
|
124
|
+
|
|
125
|
+
tpl = Jinja2Template(template=self.template)
|
|
126
|
+
for variable in tpl.get_variables():
|
|
127
|
+
items = variable.split("__", maxsplit=1)
|
|
128
|
+
if items[0] in self.node_schema.attribute_names:
|
|
129
|
+
self.node_attributes.append(items[0])
|
|
130
|
+
elif items[0] in self.node_schema.relationship_names:
|
|
131
|
+
self.node_relationships.append(items[0])
|
|
132
|
+
else:
|
|
133
|
+
raise ValueError(f"{items[0]} is neither an attribute or a relationship of {self.node_schema.kind}")
|
|
134
|
+
|
|
135
|
+
def analyze_variables(self) -> None:
|
|
136
|
+
"""Look at variables used in the display label and record attributes and relationships required to compute it."""
|
|
137
|
+
if not self.is_jinja2_template:
|
|
138
|
+
self._analyze_plain_value()
|
|
139
|
+
else:
|
|
140
|
+
self._analyze_jinja2_value()
|
|
141
|
+
|
|
142
|
+
async def compute(self, db: InfrahubDatabase, node: Node) -> None:
|
|
143
|
+
"""Update the display label value by recomputing it from the template."""
|
|
144
|
+
if self.template is None or self._manually_assigned:
|
|
145
|
+
return
|
|
146
|
+
|
|
147
|
+
if node.get_schema() != self.node_schema:
|
|
148
|
+
raise ValueError(
|
|
149
|
+
f"display_label for schema {self.node_schema.kind} cannot be rendered for node {node.get_schema().kind} {node.id}"
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
if not self.is_jinja2_template:
|
|
153
|
+
path_value = await node.get_path_value(db=db, path=self.template)
|
|
154
|
+
# Use .value for enum to keep compat with old style display label
|
|
155
|
+
self.set_value(value=str(path_value if not isinstance(path_value, Enum) else path_value.value))
|
|
156
|
+
return
|
|
157
|
+
|
|
158
|
+
jinja2_template = Jinja2Template(template=self.template)
|
|
159
|
+
|
|
160
|
+
variables: dict[str, Any] = {}
|
|
161
|
+
for variable in jinja2_template.get_variables():
|
|
162
|
+
variables[variable] = await node.get_path_value(db=db, path=variable)
|
|
163
|
+
|
|
164
|
+
self.set_value(value=await jinja2_template.render(variables=variables))
|
|
165
|
+
|
|
166
|
+
def get_node_attribute(self, node: Node, at: Timestamp) -> StringOptional:
|
|
167
|
+
"""Return a node attribute that can be stored in the database for this display label and node."""
|
|
168
|
+
return StringOptional(
|
|
169
|
+
name="display_label",
|
|
170
|
+
schema=self.schema,
|
|
171
|
+
branch=node.get_branch(),
|
|
172
|
+
at=at,
|
|
173
|
+
node=node,
|
|
174
|
+
data=self.attribute_value,
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
class HumanFriendlyIdentifier(NodePropertyAttribute[list[str]]):
|
|
179
|
+
def __init__(
|
|
180
|
+
self,
|
|
181
|
+
node_schema: NodeSchema | ProfileSchema | TemplateSchema,
|
|
182
|
+
template: list[str] | None,
|
|
183
|
+
value: AttributeFromDB | list[str] | None = None,
|
|
184
|
+
) -> None:
|
|
185
|
+
super().__init__(node_schema=node_schema, template=template, value=value)
|
|
186
|
+
|
|
187
|
+
self.schema = node_schema.get_attribute(name="human_friendly_id")
|
|
188
|
+
|
|
189
|
+
def _analyze_single_variable(self, value: str) -> None:
|
|
190
|
+
items = value.split("__", maxsplit=1)
|
|
191
|
+
if items[0] in self.node_schema.attribute_names:
|
|
192
|
+
self.node_attributes.append(items[0])
|
|
193
|
+
elif items[0] in self.node_schema.relationship_names:
|
|
194
|
+
self.node_relationships.append(items[0])
|
|
195
|
+
else:
|
|
196
|
+
raise ValueError(f"{items[0]} is neither an attribute or a relationship of {self.node_schema.kind}")
|
|
197
|
+
|
|
198
|
+
def analyze_variables(self) -> None:
|
|
199
|
+
"""Look at variables used in the HFID and record attributes and relationships required to compute it."""
|
|
200
|
+
for item in self.template or []:
|
|
201
|
+
self._analyze_single_variable(value=item)
|
|
202
|
+
|
|
203
|
+
async def compute(self, db: InfrahubDatabase, node: Node) -> None:
|
|
204
|
+
"""Update the HFID value by recomputing it from the template."""
|
|
205
|
+
if self.template is None or self._manually_assigned:
|
|
206
|
+
return
|
|
207
|
+
|
|
208
|
+
if node.get_schema() != self.node_schema:
|
|
209
|
+
raise ValueError(
|
|
210
|
+
f"human_friendly_id for schema {self.node_schema.kind} cannot be computed for node {node.get_schema().kind} {node.id}"
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
value: list[str] = []
|
|
214
|
+
for path in self.template:
|
|
215
|
+
path_value = await node.get_path_value(db=db, path=path)
|
|
216
|
+
# Use .value for enum to be consistent with display label
|
|
217
|
+
value.append(path_value if not isinstance(path_value, Enum) else path_value.value)
|
|
218
|
+
|
|
219
|
+
self.set_value(value=value)
|
|
220
|
+
|
|
221
|
+
def get_node_attribute(self, node: Node, at: Timestamp) -> ListAttributeOptional:
|
|
222
|
+
"""Return a node attribute that can be stored in the database for this HFID and node."""
|
|
223
|
+
return ListAttributeOptional(
|
|
224
|
+
name="human_friendly_id",
|
|
225
|
+
schema=self.schema,
|
|
226
|
+
branch=node.get_branch(),
|
|
227
|
+
at=at,
|
|
228
|
+
node=node,
|
|
229
|
+
data=self.attribute_value,
|
|
230
|
+
)
|
|
@@ -15,6 +15,7 @@ from infrahub.exceptions import PoolExhaustedError, ValidationError
|
|
|
15
15
|
from infrahub.pools.address import get_available
|
|
16
16
|
|
|
17
17
|
from .. import Node
|
|
18
|
+
from ..lock_utils import RESOURCE_POOL_LOCK_NAMESPACE
|
|
18
19
|
|
|
19
20
|
if TYPE_CHECKING:
|
|
20
21
|
from infrahub.core.branch import Branch
|
|
@@ -34,7 +35,7 @@ class CoreIPAddressPool(Node):
|
|
|
34
35
|
prefixlen: int | None = None,
|
|
35
36
|
at: Timestamp | None = None,
|
|
36
37
|
) -> Node:
|
|
37
|
-
async with lock.registry.get(name=self.get_id(), namespace=
|
|
38
|
+
async with lock.registry.get(name=self.get_id(), namespace=RESOURCE_POOL_LOCK_NAMESPACE):
|
|
38
39
|
# Check if there is already a resource allocated with this identifier
|
|
39
40
|
# if not, pull all existing prefixes and allocated the next available
|
|
40
41
|
|
|
@@ -17,6 +17,7 @@ from infrahub.exceptions import ValidationError
|
|
|
17
17
|
from infrahub.pools.prefix import get_next_available_prefix
|
|
18
18
|
|
|
19
19
|
from .. import Node
|
|
20
|
+
from ..lock_utils import RESOURCE_POOL_LOCK_NAMESPACE
|
|
20
21
|
|
|
21
22
|
if TYPE_CHECKING:
|
|
22
23
|
from infrahub.core.branch import Branch
|
|
@@ -37,7 +38,7 @@ class CoreIPPrefixPool(Node):
|
|
|
37
38
|
prefix_type: str | None = None,
|
|
38
39
|
at: Timestamp | None = None,
|
|
39
40
|
) -> Node:
|
|
40
|
-
async with lock.registry.get(name=self.get_id(), namespace=
|
|
41
|
+
async with lock.registry.get(name=self.get_id(), namespace=RESOURCE_POOL_LOCK_NAMESPACE):
|
|
41
42
|
# Check if there is already a resource allocated with this identifier
|
|
42
43
|
# if not, pull all existing prefixes and allocated the next available
|
|
43
44
|
if identifier:
|
|
@@ -9,6 +9,7 @@ from infrahub.core.schema.attribute_parameters import NumberAttributeParameters
|
|
|
9
9
|
from infrahub.exceptions import PoolExhaustedError
|
|
10
10
|
|
|
11
11
|
from .. import Node
|
|
12
|
+
from ..lock_utils import RESOURCE_POOL_LOCK_NAMESPACE
|
|
12
13
|
|
|
13
14
|
if TYPE_CHECKING:
|
|
14
15
|
from infrahub.core.branch import Branch
|
|
@@ -63,7 +64,7 @@ class CoreNumberPool(Node):
|
|
|
63
64
|
identifier: str | None = None,
|
|
64
65
|
at: Timestamp | None = None,
|
|
65
66
|
) -> int:
|
|
66
|
-
async with lock.registry.get(name=self.get_id(), namespace=
|
|
67
|
+
async with lock.registry.get(name=self.get_id(), namespace=RESOURCE_POOL_LOCK_NAMESPACE):
|
|
67
68
|
# NOTE: ideally we should use the HFID as the identifier (if available)
|
|
68
69
|
# one of the challenge with using the HFID is that it might change over time
|
|
69
70
|
# so we need to ensure that the identifier is stable, or we need to handle the case where the identifier changes
|
infrahub/core/node/standard.py
CHANGED
infrahub/core/property.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from enum import Enum
|
|
3
4
|
from typing import TYPE_CHECKING
|
|
4
5
|
from uuid import UUID
|
|
5
6
|
|
|
@@ -26,6 +27,10 @@ class NodePropertyData(BaseModel):
|
|
|
26
27
|
peer_id: str
|
|
27
28
|
|
|
28
29
|
|
|
30
|
+
class ClearValue(Enum):
|
|
31
|
+
CLEAR = "clear"
|
|
32
|
+
|
|
33
|
+
|
|
29
34
|
class FlagPropertyMixin:
|
|
30
35
|
_flag_properties: list[str] = [v.value for v in FlagProperty]
|
|
31
36
|
|
|
@@ -51,6 +56,7 @@ class NodePropertyMixin:
|
|
|
51
56
|
for node in self._node_properties:
|
|
52
57
|
setattr(self, f"_{node}", None)
|
|
53
58
|
setattr(self, f"{node}_id", None)
|
|
59
|
+
setattr(self, f"_clear_{node}", False)
|
|
54
60
|
|
|
55
61
|
if not kwargs:
|
|
56
62
|
return
|
|
@@ -79,12 +85,14 @@ class NodePropertyMixin:
|
|
|
79
85
|
|
|
80
86
|
def clear_owner(self) -> None:
|
|
81
87
|
self._set_node_property(name="owner", value=None)
|
|
88
|
+
self._clear_owner = True
|
|
82
89
|
|
|
83
90
|
async def get_source(self, db: InfrahubDatabase) -> Node | None:
|
|
84
91
|
return await self._get_node_property(name="source", db=db)
|
|
85
92
|
|
|
86
93
|
def clear_source(self) -> None:
|
|
87
94
|
self._set_node_property(name="source", value=None)
|
|
95
|
+
self._clear_source = True
|
|
88
96
|
|
|
89
97
|
def set_source(self, value: str | Node | UUID) -> None:
|
|
90
98
|
self._set_node_property(name="source", value=value)
|
|
@@ -95,6 +103,9 @@ class NodePropertyMixin:
|
|
|
95
103
|
def set_owner(self, value: str | Node | UUID) -> None:
|
|
96
104
|
self._set_node_property(name="owner", value=value)
|
|
97
105
|
|
|
106
|
+
def is_clear(self, name: str) -> bool:
|
|
107
|
+
return getattr(self, f"_clear_{name}", False)
|
|
108
|
+
|
|
98
109
|
def _get_node_property_from_cache(self, name: str) -> Node:
|
|
99
110
|
"""Return the node attribute if it's already present locally,
|
|
100
111
|
Otherwise raise an exception
|
infrahub/core/protocols.py
CHANGED
|
@@ -4,7 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
from typing import TYPE_CHECKING
|
|
6
6
|
|
|
7
|
-
from .protocols_base import CoreNode
|
|
7
|
+
from infrahub.core.protocols_base import CoreNode
|
|
8
8
|
|
|
9
9
|
if TYPE_CHECKING:
|
|
10
10
|
from enum import Enum
|
|
@@ -125,6 +125,7 @@ class CoreGenericRepository(CoreNode):
|
|
|
125
125
|
queries: RelationshipManager
|
|
126
126
|
checks: RelationshipManager
|
|
127
127
|
generators: RelationshipManager
|
|
128
|
+
groups_objects: RelationshipManager
|
|
128
129
|
|
|
129
130
|
|
|
130
131
|
class CoreGroup(CoreNode):
|
|
@@ -349,6 +350,10 @@ class CoreGeneratorAction(CoreAction):
|
|
|
349
350
|
generator: RelationshipManager
|
|
350
351
|
|
|
351
352
|
|
|
353
|
+
class CoreGeneratorAwareGroup(CoreGroup):
|
|
354
|
+
pass
|
|
355
|
+
|
|
356
|
+
|
|
352
357
|
class CoreGeneratorCheck(CoreCheck):
|
|
353
358
|
instance: String
|
|
354
359
|
|
|
@@ -360,6 +365,8 @@ class CoreGeneratorDefinition(CoreTaskTarget):
|
|
|
360
365
|
file_path: String
|
|
361
366
|
class_name: String
|
|
362
367
|
convert_query_response: BooleanOptional
|
|
368
|
+
execute_in_proposed_change: BooleanOptional
|
|
369
|
+
execute_after_merge: BooleanOptional
|
|
363
370
|
query: RelationshipManager
|
|
364
371
|
repository: RelationshipManager
|
|
365
372
|
targets: RelationshipManager
|