infrahub-server 1.5.0b0__py3-none-any.whl → 1.5.0b2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- infrahub/actions/tasks.py +8 -0
- infrahub/api/diff/diff.py +1 -1
- infrahub/api/internal.py +2 -0
- infrahub/api/oauth2.py +13 -19
- infrahub/api/oidc.py +15 -21
- infrahub/api/schema.py +24 -3
- infrahub/artifacts/models.py +2 -1
- infrahub/auth.py +137 -3
- infrahub/cli/__init__.py +2 -0
- infrahub/cli/db.py +103 -98
- infrahub/cli/db_commands/clean_duplicate_schema_fields.py +212 -0
- infrahub/cli/dev.py +118 -0
- infrahub/cli/tasks.py +46 -0
- infrahub/cli/upgrade.py +30 -3
- infrahub/computed_attribute/tasks.py +20 -8
- infrahub/core/attribute.py +13 -5
- infrahub/core/branch/enums.py +1 -1
- infrahub/core/branch/models.py +7 -3
- infrahub/core/branch/tasks.py +70 -8
- infrahub/core/changelog/models.py +4 -12
- infrahub/core/constants/__init__.py +3 -0
- infrahub/core/constants/infrahubkind.py +1 -0
- 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 +5 -2
- infrahub/core/ipam/utilization.py +1 -1
- infrahub/core/manager.py +6 -3
- infrahub/core/migrations/__init__.py +3 -0
- infrahub/core/migrations/exceptions.py +4 -0
- infrahub/core/migrations/graph/__init__.py +12 -11
- infrahub/core/migrations/graph/load_schema_branch.py +21 -0
- infrahub/core/migrations/graph/m013_convert_git_password_credential.py +1 -1
- infrahub/core/migrations/graph/m040_duplicated_attributes.py +81 -0
- infrahub/core/migrations/graph/m041_profile_attrs_in_db.py +145 -0
- infrahub/core/migrations/graph/m042_create_hfid_display_label_in_db.py +164 -0
- infrahub/core/migrations/graph/m043_backfill_hfid_display_label_in_db.py +866 -0
- infrahub/core/migrations/query/__init__.py +7 -8
- infrahub/core/migrations/query/attribute_add.py +8 -6
- infrahub/core/migrations/query/attribute_remove.py +134 -0
- infrahub/core/migrations/runner.py +54 -0
- infrahub/core/migrations/schema/attribute_kind_update.py +9 -3
- infrahub/core/migrations/schema/attribute_supports_profile.py +90 -0
- infrahub/core/migrations/schema/node_attribute_add.py +35 -4
- 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 +52 -19
- infrahub/core/node/__init__.py +158 -51
- infrahub/core/node/constraints/attribute_uniqueness.py +3 -1
- infrahub/core/node/create.py +46 -63
- infrahub/core/node/lock_utils.py +70 -44
- 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/protocols.py +7 -1
- infrahub/core/query/attribute.py +55 -0
- infrahub/core/query/ipam.py +1 -0
- infrahub/core/query/node.py +23 -4
- infrahub/core/query/relationship.py +1 -0
- infrahub/core/registry.py +2 -2
- infrahub/core/relationship/constraints/count.py +1 -1
- infrahub/core/relationship/model.py +1 -1
- infrahub/core/schema/__init__.py +56 -0
- infrahub/core/schema/attribute_schema.py +4 -0
- infrahub/core/schema/basenode_schema.py +42 -2
- infrahub/core/schema/definitions/core/__init__.py +2 -0
- infrahub/core/schema/definitions/core/generator.py +2 -0
- infrahub/core/schema/definitions/core/group.py +16 -2
- infrahub/core/schema/definitions/internal.py +16 -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 +22 -1
- infrahub/core/schema/node_schema.py +5 -2
- infrahub/core/schema/schema_branch.py +300 -8
- infrahub/core/schema/schema_branch_display.py +123 -0
- infrahub/core/schema/schema_branch_hfid.py +114 -0
- infrahub/core/validators/aggregated_checker.py +1 -1
- infrahub/core/validators/determiner.py +12 -1
- infrahub/core/validators/relationship/peer.py +1 -1
- infrahub/core/validators/tasks.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 +7 -0
- infrahub/generators/tasks.py +34 -22
- infrahub/git/base.py +4 -1
- infrahub/git/integrator.py +23 -15
- infrahub/git/models.py +2 -1
- infrahub/git/repository.py +22 -5
- infrahub/git/tasks.py +66 -10
- infrahub/git/utils.py +123 -1
- infrahub/graphql/analyzer.py +1 -1
- infrahub/graphql/api/endpoints.py +14 -4
- infrahub/graphql/manager.py +4 -9
- infrahub/graphql/mutations/convert_object_type.py +11 -1
- infrahub/graphql/mutations/display_label.py +118 -0
- infrahub/graphql/mutations/generator.py +25 -7
- infrahub/graphql/mutations/hfid.py +125 -0
- infrahub/graphql/mutations/ipam.py +54 -35
- infrahub/graphql/mutations/main.py +27 -28
- infrahub/graphql/mutations/relationship.py +2 -2
- infrahub/graphql/mutations/resource_manager.py +2 -2
- infrahub/graphql/mutations/schema.py +5 -5
- infrahub/graphql/queries/resource_manager.py +1 -1
- infrahub/graphql/resolvers/resolver.py +2 -0
- infrahub/graphql/schema.py +4 -0
- 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 +67 -16
- infrahub/message_bus/types.py +2 -1
- infrahub/middleware.py +26 -1
- infrahub/permissions/constants.py +2 -0
- infrahub/proposed_change/tasks.py +35 -17
- infrahub/server.py +21 -4
- infrahub/services/__init__.py +8 -5
- infrahub/services/adapters/http/__init__.py +5 -0
- infrahub/services/adapters/workflow/worker.py +14 -3
- infrahub/task_manager/event.py +5 -0
- infrahub/task_manager/models.py +7 -0
- infrahub/task_manager/task.py +73 -0
- infrahub/trigger/catalogue.py +4 -0
- infrahub/trigger/models.py +2 -0
- infrahub/trigger/setup.py +13 -4
- infrahub/trigger/tasks.py +6 -0
- infrahub/workers/dependencies.py +10 -1
- infrahub/workers/infrahub_async.py +10 -2
- infrahub/workflows/catalogue.py +80 -0
- infrahub/workflows/initialization.py +21 -0
- infrahub/workflows/utils.py +2 -1
- infrahub_sdk/checks.py +1 -1
- infrahub_sdk/client.py +13 -10
- infrahub_sdk/config.py +29 -2
- infrahub_sdk/ctl/cli_commands.py +2 -0
- infrahub_sdk/ctl/generator.py +4 -0
- infrahub_sdk/ctl/graphql.py +184 -0
- infrahub_sdk/ctl/schema.py +28 -9
- 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} +81 -73
- infrahub_sdk/graphql/utils.py +40 -0
- infrahub_sdk/protocols.py +14 -0
- infrahub_sdk/schema/__init__.py +70 -4
- infrahub_sdk/schema/repository.py +8 -0
- infrahub_sdk/spec/models.py +7 -0
- infrahub_sdk/spec/object.py +53 -44
- 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 +1 -1
- infrahub_sdk/transforms.py +1 -1
- {infrahub_server-1.5.0b0.dist-info → infrahub_server-1.5.0b2.dist-info}/METADATA +7 -4
- {infrahub_server-1.5.0b0.dist-info → infrahub_server-1.5.0b2.dist-info}/RECORD +182 -143
- infrahub_testcontainers/container.py +115 -3
- infrahub_testcontainers/docker-compose-cluster.test.yml +6 -1
- infrahub_testcontainers/docker-compose.test.yml +6 -1
- infrahub/core/migrations/graph/m040_profile_attrs_in_db.py +0 -166
- {infrahub_server-1.5.0b0.dist-info → infrahub_server-1.5.0b2.dist-info}/LICENSE.txt +0 -0
- {infrahub_server-1.5.0b0.dist-info → infrahub_server-1.5.0b2.dist-info}/WHEEL +0 -0
- {infrahub_server-1.5.0b0.dist-info → infrahub_server-1.5.0b2.dist-info}/entry_points.txt +0 -0
infrahub/cli/tasks.py
CHANGED
|
@@ -3,9 +3,11 @@ import logging
|
|
|
3
3
|
import typer
|
|
4
4
|
from infrahub_sdk.async_typer import AsyncTyper
|
|
5
5
|
from prefect.client.orchestration import get_client
|
|
6
|
+
from prefect.client.schemas.objects import StateType
|
|
6
7
|
|
|
7
8
|
from infrahub import config
|
|
8
9
|
from infrahub.services.adapters.workflow.worker import WorkflowWorkerExecution
|
|
10
|
+
from infrahub.task_manager.task import PrefectTask
|
|
9
11
|
from infrahub.tasks.dummy import DUMMY_FLOW, DummyInput
|
|
10
12
|
from infrahub.workflows.initialization import setup_task_manager
|
|
11
13
|
from infrahub.workflows.models import WorkerPoolDefinition
|
|
@@ -50,3 +52,47 @@ async def execute(
|
|
|
50
52
|
workflow=DUMMY_FLOW, parameters={"data": DummyInput(firstname="John", lastname="Doe")}
|
|
51
53
|
) # type: ignore[var-annotated]
|
|
52
54
|
print(result)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
flush_app = AsyncTyper()
|
|
58
|
+
|
|
59
|
+
app.add_typer(flush_app, name="flush")
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@flush_app.command()
|
|
63
|
+
async def flow_runs(
|
|
64
|
+
ctx: typer.Context, # noqa: ARG001
|
|
65
|
+
config_file: str = typer.Argument("infrahub.toml", envvar="INFRAHUB_CONFIG"),
|
|
66
|
+
days_to_keep: int = 30,
|
|
67
|
+
batch_size: int = 100,
|
|
68
|
+
) -> None:
|
|
69
|
+
"""Flush old task runs"""
|
|
70
|
+
logging.getLogger("infrahub").setLevel(logging.WARNING)
|
|
71
|
+
logging.getLogger("neo4j").setLevel(logging.ERROR)
|
|
72
|
+
logging.getLogger("prefect").setLevel(logging.ERROR)
|
|
73
|
+
|
|
74
|
+
config.load_and_exit(config_file_name=config_file)
|
|
75
|
+
|
|
76
|
+
await PrefectTask.delete_flow_runs(
|
|
77
|
+
days_to_keep=days_to_keep,
|
|
78
|
+
batch_size=batch_size,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@flush_app.command()
|
|
83
|
+
async def stale_runs(
|
|
84
|
+
ctx: typer.Context, # noqa: ARG001
|
|
85
|
+
config_file: str = typer.Argument("infrahub.toml", envvar="INFRAHUB_CONFIG"),
|
|
86
|
+
days_to_keep: int = 2,
|
|
87
|
+
batch_size: int = 100,
|
|
88
|
+
) -> None:
|
|
89
|
+
"""Flush stale task runs"""
|
|
90
|
+
logging.getLogger("infrahub").setLevel(logging.WARNING)
|
|
91
|
+
logging.getLogger("neo4j").setLevel(logging.ERROR)
|
|
92
|
+
logging.getLogger("prefect").setLevel(logging.ERROR)
|
|
93
|
+
|
|
94
|
+
config.load_and_exit(config_file_name=config_file)
|
|
95
|
+
|
|
96
|
+
await PrefectTask.delete_flow_runs(
|
|
97
|
+
states=[StateType.RUNNING], delete=False, days_to_keep=days_to_keep, batch_size=batch_size
|
|
98
|
+
)
|
infrahub/cli/upgrade.py
CHANGED
|
@@ -11,10 +11,16 @@ from prefect.client.orchestration import get_client
|
|
|
11
11
|
from rich import print as rprint
|
|
12
12
|
|
|
13
13
|
from infrahub import config
|
|
14
|
-
from infrahub.core.initialization import
|
|
14
|
+
from infrahub.core.initialization import (
|
|
15
|
+
create_anonymous_role,
|
|
16
|
+
create_default_account_groups,
|
|
17
|
+
get_root_node,
|
|
18
|
+
initialize_registry,
|
|
19
|
+
)
|
|
15
20
|
from infrahub.core.manager import NodeManager
|
|
16
21
|
from infrahub.core.protocols import CoreAccount, CoreObjectPermission
|
|
17
22
|
from infrahub.dependencies.registry import build_component_registry
|
|
23
|
+
from infrahub.lock import initialize_lock
|
|
18
24
|
from infrahub.menu.menu import default_menu
|
|
19
25
|
from infrahub.menu.models import MenuDict
|
|
20
26
|
from infrahub.menu.repository import MenuRepository
|
|
@@ -26,7 +32,13 @@ from infrahub.workflows.initialization import (
|
|
|
26
32
|
setup_worker_pools,
|
|
27
33
|
)
|
|
28
34
|
|
|
29
|
-
from .db import
|
|
35
|
+
from .db import (
|
|
36
|
+
detect_migration_to_run,
|
|
37
|
+
initialize_internal_schema,
|
|
38
|
+
migrate_database,
|
|
39
|
+
trigger_rebase_branches,
|
|
40
|
+
update_core_schema,
|
|
41
|
+
)
|
|
30
42
|
|
|
31
43
|
if TYPE_CHECKING:
|
|
32
44
|
from infrahub.cli.context import CliContext
|
|
@@ -40,6 +52,7 @@ async def upgrade_cmd(
|
|
|
40
52
|
ctx: typer.Context,
|
|
41
53
|
config_file: str = typer.Argument("infrahub.toml", envvar="INFRAHUB_CONFIG"),
|
|
42
54
|
check: bool = typer.Option(False, help="Check the state of the system without upgrading."),
|
|
55
|
+
rebase_branches: bool = typer.Option(False, help="Rebase and apply migrations to branches if required."),
|
|
43
56
|
) -> None:
|
|
44
57
|
"""Upgrade Infrahub to the latest version."""
|
|
45
58
|
|
|
@@ -54,9 +67,12 @@ async def upgrade_cmd(
|
|
|
54
67
|
dbdriver = await context.init_db(retry=1)
|
|
55
68
|
|
|
56
69
|
await initialize_registry(db=dbdriver)
|
|
70
|
+
initialize_lock()
|
|
57
71
|
|
|
58
72
|
build_component_registry()
|
|
59
73
|
|
|
74
|
+
root_node = await get_root_node(db=dbdriver)
|
|
75
|
+
|
|
60
76
|
# NOTE add step to validate if the database and the task manager are reachable
|
|
61
77
|
|
|
62
78
|
# -------------------------------------------
|
|
@@ -67,7 +83,12 @@ async def upgrade_cmd(
|
|
|
67
83
|
# Upgrade Infrahub Database and Schema
|
|
68
84
|
# -------------------------------------------
|
|
69
85
|
|
|
70
|
-
|
|
86
|
+
migrations = await detect_migration_to_run(current_graph_version=root_node.graph_version)
|
|
87
|
+
if check:
|
|
88
|
+
await dbdriver.close()
|
|
89
|
+
return
|
|
90
|
+
|
|
91
|
+
if not await migrate_database(db=dbdriver, initialize=False, migrations=migrations):
|
|
71
92
|
# A migration failed, stop the upgrade process
|
|
72
93
|
rprint("Upgrade cancelled due to migration failure.")
|
|
73
94
|
await dbdriver.close()
|
|
@@ -91,6 +112,12 @@ async def upgrade_cmd(
|
|
|
91
112
|
await setup_deployments(client=client)
|
|
92
113
|
await trigger_configure_all()
|
|
93
114
|
|
|
115
|
+
# -------------------------------------------
|
|
116
|
+
# Perform branch rebase and apply migrations to them
|
|
117
|
+
# -------------------------------------------
|
|
118
|
+
if rebase_branches:
|
|
119
|
+
await trigger_rebase_branches(db=dbdriver)
|
|
120
|
+
|
|
94
121
|
await dbdriver.close()
|
|
95
122
|
|
|
96
123
|
|
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from typing import TYPE_CHECKING
|
|
4
4
|
|
|
5
|
+
from infrahub_sdk.exceptions import URLNotFoundError
|
|
5
6
|
from infrahub_sdk.protocols import CoreTransformPython
|
|
6
7
|
from infrahub_sdk.template import Jinja2Template
|
|
7
8
|
from prefect import flow
|
|
@@ -104,7 +105,7 @@ async def process_transform(
|
|
|
104
105
|
) # type: ignore[misc]
|
|
105
106
|
|
|
106
107
|
data = await client.query_gql_query(
|
|
107
|
-
name=transform.query.
|
|
108
|
+
name=transform.query.id,
|
|
108
109
|
branch_name=branch_name,
|
|
109
110
|
variables={"id": object_id},
|
|
110
111
|
update_group=True,
|
|
@@ -177,12 +178,17 @@ async def computed_attribute_jinja2_update_value(
|
|
|
177
178
|
log.debug(f"Ignoring to update {obj} with existing value on {attribute_name}={value}")
|
|
178
179
|
return
|
|
179
180
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
181
|
+
try:
|
|
182
|
+
await client.execute_graphql(
|
|
183
|
+
query=UPDATE_ATTRIBUTE,
|
|
184
|
+
variables={"id": obj.node_id, "kind": node_kind, "attribute": attribute_name, "value": value},
|
|
185
|
+
branch_name=branch_name,
|
|
186
|
+
)
|
|
187
|
+
log.info(f"Updating computed attribute {node_kind}.{attribute_name}='{value}' ({obj.node_id})")
|
|
188
|
+
except URLNotFoundError:
|
|
189
|
+
log.warning(
|
|
190
|
+
f"Update of computed attribute {node_kind}.{attribute_name} failed for branch {branch_name} (not found)"
|
|
191
|
+
)
|
|
186
192
|
|
|
187
193
|
|
|
188
194
|
@flow(
|
|
@@ -229,7 +235,13 @@ async def process_jinja2(
|
|
|
229
235
|
|
|
230
236
|
for id_filter in computed_macro.node_filters:
|
|
231
237
|
query = attribute_graphql.render_graphql_query(query_filter=id_filter, filter_id=object_id)
|
|
232
|
-
|
|
238
|
+
try:
|
|
239
|
+
response = await client.execute_graphql(query=query, branch_name=branch_name)
|
|
240
|
+
except URLNotFoundError:
|
|
241
|
+
log.warning(
|
|
242
|
+
f"Process computed attributes for {computed_attribute_kind}.{computed_attribute_name} failed for branch {branch_name} (not found)"
|
|
243
|
+
)
|
|
244
|
+
return
|
|
233
245
|
output = attribute_graphql.parse_response(response=response)
|
|
234
246
|
found.extend(output)
|
|
235
247
|
|
infrahub/core/attribute.py
CHANGED
|
@@ -18,6 +18,7 @@ from infrahub.core.changelog.models import AttributeChangelog
|
|
|
18
18
|
from infrahub.core.constants import NULL_VALUE, AttributeDBNodeType, BranchSupportType, RelationshipStatus
|
|
19
19
|
from infrahub.core.property import FlagPropertyMixin, NodePropertyData, NodePropertyMixin
|
|
20
20
|
from infrahub.core.query.attribute import (
|
|
21
|
+
AttributeClearNodePropertyQuery,
|
|
21
22
|
AttributeGetQuery,
|
|
22
23
|
AttributeUpdateFlagQuery,
|
|
23
24
|
AttributeUpdateNodePropertyQuery,
|
|
@@ -36,7 +37,7 @@ from .schema.attribute_parameters import NumberAttributeParameters
|
|
|
36
37
|
if TYPE_CHECKING:
|
|
37
38
|
from infrahub.core.branch import Branch
|
|
38
39
|
from infrahub.core.node import Node
|
|
39
|
-
from infrahub.core.schema import AttributeSchema
|
|
40
|
+
from infrahub.core.schema import AttributeSchema, MainSchemaTypes
|
|
40
41
|
from infrahub.database import InfrahubDatabase
|
|
41
42
|
|
|
42
43
|
|
|
@@ -491,6 +492,12 @@ class BaseAttribute(FlagPropertyMixin, NodePropertyMixin):
|
|
|
491
492
|
)
|
|
492
493
|
await query.execute(db=db)
|
|
493
494
|
|
|
495
|
+
if needs_clear:
|
|
496
|
+
query = await AttributeClearNodePropertyQuery.init(
|
|
497
|
+
db=db, attr=self, at=update_at, prop_name=prop_name, prop_id=database_prop_id
|
|
498
|
+
)
|
|
499
|
+
await query.execute(db=db)
|
|
500
|
+
|
|
494
501
|
# set the to time on the previously active edge
|
|
495
502
|
rel = current_attr_result.get(f"rel_{prop_name}")
|
|
496
503
|
if rel and rel.get("branch") == branch.name:
|
|
@@ -581,7 +588,7 @@ class BaseAttribute(FlagPropertyMixin, NodePropertyMixin):
|
|
|
581
588
|
|
|
582
589
|
return value
|
|
583
590
|
|
|
584
|
-
async def from_graphql(self, data: dict, db: InfrahubDatabase) -> bool:
|
|
591
|
+
async def from_graphql(self, data: dict, db: InfrahubDatabase, process_pools: bool = True) -> bool:
|
|
585
592
|
"""Update attr from GraphQL payload"""
|
|
586
593
|
|
|
587
594
|
changed = False
|
|
@@ -595,7 +602,8 @@ class BaseAttribute(FlagPropertyMixin, NodePropertyMixin):
|
|
|
595
602
|
changed = True
|
|
596
603
|
elif "from_pool" in data:
|
|
597
604
|
self.from_pool = data["from_pool"]
|
|
598
|
-
|
|
605
|
+
if process_pools:
|
|
606
|
+
await self.node.handle_pool(db=db, attribute=self, errors=[])
|
|
599
607
|
changed = True
|
|
600
608
|
|
|
601
609
|
if changed and self.is_from_profile:
|
|
@@ -630,7 +638,7 @@ class BaseAttribute(FlagPropertyMixin, NodePropertyMixin):
|
|
|
630
638
|
return AttributeDBNodeType.DEFAULT
|
|
631
639
|
return AttributeDBNodeType.INDEXED
|
|
632
640
|
|
|
633
|
-
def get_create_data(self) -> AttributeCreateData:
|
|
641
|
+
def get_create_data(self, node_schema: MainSchemaTypes) -> AttributeCreateData:
|
|
634
642
|
branch = self.branch
|
|
635
643
|
hierarchy_level = branch.hierarchy_level
|
|
636
644
|
if self.schema.branch == BranchSupportType.AGNOSTIC:
|
|
@@ -645,7 +653,7 @@ class BaseAttribute(FlagPropertyMixin, NodePropertyMixin):
|
|
|
645
653
|
branch=branch.name,
|
|
646
654
|
status="active",
|
|
647
655
|
branch_level=hierarchy_level,
|
|
648
|
-
branch_support=self.schema.branch.value,
|
|
656
|
+
branch_support=self.schema.branch.value if self.schema.branch is not None else node_schema.branch,
|
|
649
657
|
content=self.to_db(),
|
|
650
658
|
is_default=self.is_default,
|
|
651
659
|
is_protected=self.is_protected,
|
infrahub/core/branch/enums.py
CHANGED
infrahub/core/branch/models.py
CHANGED
|
@@ -6,9 +6,8 @@ from typing import TYPE_CHECKING, Any, Optional, Self, Union
|
|
|
6
6
|
from pydantic import Field, field_validator
|
|
7
7
|
|
|
8
8
|
from infrahub.core.branch.enums import BranchStatus
|
|
9
|
-
from infrahub.core.constants import
|
|
10
|
-
|
|
11
|
-
)
|
|
9
|
+
from infrahub.core.constants import GLOBAL_BRANCH_NAME
|
|
10
|
+
from infrahub.core.graph import GRAPH_VERSION
|
|
12
11
|
from infrahub.core.models import SchemaBranchHash # noqa: TC001
|
|
13
12
|
from infrahub.core.node.standard import StandardNode
|
|
14
13
|
from infrahub.core.query import QueryType
|
|
@@ -46,6 +45,7 @@ class Branch(StandardNode):
|
|
|
46
45
|
is_isolated: bool = True
|
|
47
46
|
schema_changed_at: Optional[str] = None
|
|
48
47
|
schema_hash: Optional[SchemaBranchHash] = None
|
|
48
|
+
graph_version: int | None = None
|
|
49
49
|
|
|
50
50
|
_exclude_attrs: list[str] = ["id", "uuid", "owner"]
|
|
51
51
|
|
|
@@ -261,6 +261,10 @@ class Branch(StandardNode):
|
|
|
261
261
|
|
|
262
262
|
return start, end
|
|
263
263
|
|
|
264
|
+
async def create(self, db: InfrahubDatabase) -> bool:
|
|
265
|
+
self.graph_version = GRAPH_VERSION
|
|
266
|
+
return await super().create(db=db)
|
|
267
|
+
|
|
264
268
|
async def delete(self, db: InfrahubDatabase) -> None:
|
|
265
269
|
if self.is_default:
|
|
266
270
|
raise ValidationError(f"Unable to delete {self.name} it is the default branch.")
|
infrahub/core/branch/tasks.py
CHANGED
|
@@ -12,6 +12,7 @@ from infrahub import lock
|
|
|
12
12
|
from infrahub.context import InfrahubContext # noqa: TC001 needed for prefect flow
|
|
13
13
|
from infrahub.core import registry
|
|
14
14
|
from infrahub.core.branch import Branch
|
|
15
|
+
from infrahub.core.branch.enums import BranchStatus
|
|
15
16
|
from infrahub.core.changelog.diff import DiffChangelogCollector, MigrationTracker
|
|
16
17
|
from infrahub.core.constants import MutationAction
|
|
17
18
|
from infrahub.core.diff.coordinator import DiffCoordinator
|
|
@@ -21,7 +22,10 @@ from infrahub.core.diff.merger.merger import DiffMerger
|
|
|
21
22
|
from infrahub.core.diff.model.path import BranchTrackingId, EnrichedDiffRoot, EnrichedDiffRootMetadata
|
|
22
23
|
from infrahub.core.diff.models import RequestDiffUpdate
|
|
23
24
|
from infrahub.core.diff.repository.repository import DiffRepository
|
|
25
|
+
from infrahub.core.graph import GRAPH_VERSION
|
|
24
26
|
from infrahub.core.merge import BranchMerger
|
|
27
|
+
from infrahub.core.migrations.exceptions import MigrationFailureError
|
|
28
|
+
from infrahub.core.migrations.runner import MigrationRunner
|
|
25
29
|
from infrahub.core.migrations.schema.models import SchemaApplyMigrationData
|
|
26
30
|
from infrahub.core.migrations.schema.tasks import schema_apply_migrations
|
|
27
31
|
from infrahub.core.timestamp import Timestamp
|
|
@@ -29,10 +33,17 @@ from infrahub.core.validators.determiner import ConstraintValidatorDeterminer
|
|
|
29
33
|
from infrahub.core.validators.models.validate_migration import SchemaValidateMigrationData
|
|
30
34
|
from infrahub.core.validators.tasks import schema_validate_migrations
|
|
31
35
|
from infrahub.dependencies.registry import get_component_registry
|
|
32
|
-
from infrahub.events.branch_action import
|
|
36
|
+
from infrahub.events.branch_action import (
|
|
37
|
+
BranchCreatedEvent,
|
|
38
|
+
BranchDeletedEvent,
|
|
39
|
+
BranchMergedEvent,
|
|
40
|
+
BranchMigratedEvent,
|
|
41
|
+
BranchRebasedEvent,
|
|
42
|
+
)
|
|
33
43
|
from infrahub.events.models import EventMeta, InfrahubEvent
|
|
34
44
|
from infrahub.events.node_action import get_node_event
|
|
35
45
|
from infrahub.exceptions import BranchNotFoundError, ValidationError
|
|
46
|
+
from infrahub.generators.constants import GeneratorDefinitionRunSource
|
|
36
47
|
from infrahub.graphql.mutations.models import BranchCreateModel # noqa: TC001
|
|
37
48
|
from infrahub.workers.dependencies import get_component, get_database, get_event_service, get_workflow
|
|
38
49
|
from infrahub.workflows.catalogue import (
|
|
@@ -48,8 +59,57 @@ from infrahub.workflows.catalogue import (
|
|
|
48
59
|
from infrahub.workflows.utils import add_tags
|
|
49
60
|
|
|
50
61
|
|
|
62
|
+
@flow(name="branch-migrate", flow_run_name="Apply migrations to branch {branch}")
|
|
63
|
+
async def migrate_branch(branch: str, context: InfrahubContext, send_events: bool = True) -> None:
|
|
64
|
+
await add_tags(branches=[branch])
|
|
65
|
+
|
|
66
|
+
database = await get_database()
|
|
67
|
+
async with database.start_session() as db:
|
|
68
|
+
log = get_run_logger()
|
|
69
|
+
|
|
70
|
+
obj = await Branch.get_by_name(db=db, name=branch)
|
|
71
|
+
|
|
72
|
+
if obj.graph_version == GRAPH_VERSION:
|
|
73
|
+
log.info(f"Branch '{obj.name}' has graph version {obj.graph_version}, no migrations to apply")
|
|
74
|
+
return
|
|
75
|
+
|
|
76
|
+
migration_runner = MigrationRunner(branch=obj)
|
|
77
|
+
if not migration_runner.has_migrations():
|
|
78
|
+
log.info(f"No migrations detected for branch '{obj.name}'")
|
|
79
|
+
obj.graph_version = GRAPH_VERSION
|
|
80
|
+
await obj.save(db=db)
|
|
81
|
+
return
|
|
82
|
+
|
|
83
|
+
# Branch status will remain as so if the migration process fails
|
|
84
|
+
# This will help user to know that a branch is in an invalid state to be used properly and that actions need to be taken
|
|
85
|
+
if obj.status != BranchStatus.NEED_UPGRADE_REBASE:
|
|
86
|
+
obj.status = BranchStatus.NEED_UPGRADE_REBASE
|
|
87
|
+
await obj.save(db=db)
|
|
88
|
+
|
|
89
|
+
try:
|
|
90
|
+
log.info(f"Running migrations for branch '{obj.name}'")
|
|
91
|
+
await migration_runner.run(db=db)
|
|
92
|
+
except MigrationFailureError as exc:
|
|
93
|
+
log.error(f"Failed to run migrations for branch '{obj.name}': {exc.errors}")
|
|
94
|
+
raise
|
|
95
|
+
|
|
96
|
+
if obj.status == BranchStatus.NEED_UPGRADE_REBASE:
|
|
97
|
+
obj.status = BranchStatus.OPEN
|
|
98
|
+
obj.graph_version = GRAPH_VERSION
|
|
99
|
+
await obj.save(db=db)
|
|
100
|
+
|
|
101
|
+
if send_events:
|
|
102
|
+
event_service = await get_event_service()
|
|
103
|
+
await event_service.send(
|
|
104
|
+
BranchMigratedEvent(
|
|
105
|
+
branch_name=obj.name, branch_id=str(obj.uuid), meta=EventMeta(branch=obj, context=context)
|
|
106
|
+
)
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
|
|
51
110
|
@flow(name="branch-rebase", flow_run_name="Rebase branch {branch}")
|
|
52
|
-
async def rebase_branch(branch: str, context: InfrahubContext) -> None: # noqa: PLR0915
|
|
111
|
+
async def rebase_branch(branch: str, context: InfrahubContext, send_events: bool = True) -> None: # noqa: PLR0915
|
|
112
|
+
workflow = get_workflow()
|
|
53
113
|
database = await get_database()
|
|
54
114
|
async with database.start_session() as db:
|
|
55
115
|
log = get_run_logger()
|
|
@@ -68,7 +128,7 @@ async def rebase_branch(branch: str, context: InfrahubContext) -> None: # noqa:
|
|
|
68
128
|
diff_repository=diff_repository,
|
|
69
129
|
source_branch=obj,
|
|
70
130
|
diff_locker=DiffLocker(),
|
|
71
|
-
workflow=
|
|
131
|
+
workflow=workflow,
|
|
72
132
|
)
|
|
73
133
|
|
|
74
134
|
enriched_diff_metadata = await diff_coordinator.update_branch_diff(base_branch=base_branch, diff_branch=obj)
|
|
@@ -155,15 +215,17 @@ async def rebase_branch(branch: str, context: InfrahubContext) -> None: # noqa:
|
|
|
155
215
|
target_branch_name=registry.default_branch,
|
|
156
216
|
)
|
|
157
217
|
if ipam_node_details:
|
|
158
|
-
await
|
|
218
|
+
await workflow.submit_workflow(
|
|
159
219
|
workflow=IPAM_RECONCILIATION,
|
|
160
220
|
context=context,
|
|
161
221
|
parameters={"branch": obj.name, "ipam_node_details": ipam_node_details},
|
|
162
222
|
)
|
|
163
223
|
|
|
164
|
-
await
|
|
165
|
-
|
|
166
|
-
|
|
224
|
+
await migrate_branch(branch=branch, context=context, send_events=send_events)
|
|
225
|
+
await workflow.submit_workflow(workflow=DIFF_REFRESH_ALL, context=context, parameters={"branch_name": obj.name})
|
|
226
|
+
|
|
227
|
+
if not send_events:
|
|
228
|
+
return
|
|
167
229
|
|
|
168
230
|
# -------------------------------------------------------------
|
|
169
231
|
# Generate an event to indicate that a branch has been rebased
|
|
@@ -437,7 +499,7 @@ async def post_process_branch_merge(source_branch: str, target_branch: str, cont
|
|
|
437
499
|
await get_workflow().submit_workflow(
|
|
438
500
|
workflow=TRIGGER_GENERATOR_DEFINITION_RUN,
|
|
439
501
|
context=context,
|
|
440
|
-
parameters={"branch": target_branch},
|
|
502
|
+
parameters={"branch": target_branch, "source": GeneratorDefinitionRunSource.MERGE},
|
|
441
503
|
)
|
|
442
504
|
|
|
443
505
|
for diff_root in branch_diff_roots:
|
|
@@ -560,7 +560,7 @@ class RelationshipChangelogGetter:
|
|
|
560
560
|
|
|
561
561
|
for peer in relationship.peers:
|
|
562
562
|
if peer.peer_status == DiffAction.ADDED:
|
|
563
|
-
peer_schema = schema_branch.get(name=peer.peer_kind)
|
|
563
|
+
peer_schema = schema_branch.get(name=peer.peer_kind, duplicate=False)
|
|
564
564
|
secondaries.extend(
|
|
565
565
|
self._process_added_peers(
|
|
566
566
|
peer_id=peer.peer_id,
|
|
@@ -572,7 +572,7 @@ class RelationshipChangelogGetter:
|
|
|
572
572
|
)
|
|
573
573
|
|
|
574
574
|
elif peer.peer_status == DiffAction.REMOVED:
|
|
575
|
-
peer_schema = schema_branch.get(name=peer.peer_kind)
|
|
575
|
+
peer_schema = schema_branch.get(name=peer.peer_kind, duplicate=False)
|
|
576
576
|
secondaries.extend(
|
|
577
577
|
self._process_removed_peers(
|
|
578
578
|
peer_id=peer.peer_id,
|
|
@@ -596,11 +596,7 @@ class RelationshipChangelogGetter:
|
|
|
596
596
|
secondaries: list[NodeChangelog] = []
|
|
597
597
|
peer_relation = peer_schema.get_relationship_by_identifier(id=str(rel_schema.identifier), raise_on_error=False)
|
|
598
598
|
if peer_relation:
|
|
599
|
-
node_changelog = NodeChangelog(
|
|
600
|
-
node_id=peer_id,
|
|
601
|
-
node_kind=peer_kind,
|
|
602
|
-
display_label="n/a",
|
|
603
|
-
)
|
|
599
|
+
node_changelog = NodeChangelog(node_id=peer_id, node_kind=peer_kind, display_label="n/a")
|
|
604
600
|
if peer_relation.cardinality == RelationshipCardinality.ONE:
|
|
605
601
|
node_changelog.relationships[peer_relation.name] = RelationshipCardinalityOneChangelog(
|
|
606
602
|
name=peer_relation.name,
|
|
@@ -634,11 +630,7 @@ class RelationshipChangelogGetter:
|
|
|
634
630
|
secondaries: list[NodeChangelog] = []
|
|
635
631
|
peer_relation = peer_schema.get_relationship_by_identifier(id=str(rel_schema.identifier), raise_on_error=False)
|
|
636
632
|
if peer_relation:
|
|
637
|
-
node_changelog = NodeChangelog(
|
|
638
|
-
node_id=peer_id,
|
|
639
|
-
node_kind=peer_kind,
|
|
640
|
-
display_label="n/a",
|
|
641
|
-
)
|
|
633
|
+
node_changelog = NodeChangelog(node_id=peer_id, node_kind=peer_kind, display_label="n/a")
|
|
642
634
|
if peer_relation.cardinality == RelationshipCardinality.ONE:
|
|
643
635
|
node_changelog.relationships[peer_relation.name] = RelationshipCardinalityOneChangelog(
|
|
644
636
|
name=peer_relation.name,
|
|
@@ -37,6 +37,7 @@ RESERVED_ATTR_REL_NAMES = [
|
|
|
37
37
|
"rels",
|
|
38
38
|
"save",
|
|
39
39
|
"hfid",
|
|
40
|
+
"process_pools",
|
|
40
41
|
]
|
|
41
42
|
|
|
42
43
|
RESERVED_ATTR_GEN_NAMES = ["type"]
|
|
@@ -50,6 +51,7 @@ class EventType(InfrahubStringEnum):
|
|
|
50
51
|
BRANCH_CREATED = f"{EVENT_NAMESPACE}.branch.created"
|
|
51
52
|
BRANCH_DELETED = f"{EVENT_NAMESPACE}.branch.deleted"
|
|
52
53
|
BRANCH_MERGED = f"{EVENT_NAMESPACE}.branch.merged"
|
|
54
|
+
BRANCH_MIGRATED = f"{EVENT_NAMESPACE}.branch.migrated"
|
|
53
55
|
BRANCH_REBASED = f"{EVENT_NAMESPACE}.branch.rebased"
|
|
54
56
|
|
|
55
57
|
SCHEMA_UPDATED = f"{EVENT_NAMESPACE}.schema.updated"
|
|
@@ -99,6 +101,7 @@ class GlobalPermissions(InfrahubStringEnum):
|
|
|
99
101
|
MANAGE_PERMISSIONS = "manage_permissions"
|
|
100
102
|
MANAGE_REPOSITORIES = "manage_repositories"
|
|
101
103
|
OVERRIDE_CONTEXT = "override_context"
|
|
104
|
+
UPDATE_OBJECT_HFID_DISPLAY_LABEL = "update_object_hfid_display_label"
|
|
102
105
|
|
|
103
106
|
|
|
104
107
|
class PermissionAction(InfrahubStringEnum):
|
|
@@ -28,6 +28,7 @@ GENERATORDEFINITION = "CoreGeneratorDefinition"
|
|
|
28
28
|
GENERATORINSTANCE = "CoreGeneratorInstance"
|
|
29
29
|
GENERATORVALIDATOR = "CoreGeneratorValidator"
|
|
30
30
|
GENERATORGROUP = "CoreGeneratorGroup"
|
|
31
|
+
GENERATORAWAREGROUP = "CoreGeneratorAwareGroup"
|
|
31
32
|
GENERICGROUP = "CoreGroup"
|
|
32
33
|
GLOBALPERMISSION = "CoreGlobalPermission"
|
|
33
34
|
GRAPHQLQUERY = "CoreGraphQLQuery"
|
infrahub/core/diff/model/path.py
CHANGED
|
@@ -335,6 +335,10 @@ class EnrichedDiffNode(BaseSummary):
|
|
|
335
335
|
def kind(self) -> str:
|
|
336
336
|
return self.identifier.kind
|
|
337
337
|
|
|
338
|
+
@property
|
|
339
|
+
def is_schema_node(self) -> bool:
|
|
340
|
+
return self.identifier.kind.startswith("Schema")
|
|
341
|
+
|
|
338
342
|
@property
|
|
339
343
|
def num_properties(self) -> int:
|
|
340
344
|
return sum(a.num_properties for a in self.attributes) + sum(r.num_properties for r in self.relationships)
|
|
@@ -36,7 +36,7 @@ async def get_display_labels_per_kind(
|
|
|
36
36
|
break
|
|
37
37
|
node_map = await NodeManager.get_many(ids=limited_ids, fields=fields, db=db, branch=branch)
|
|
38
38
|
for node_id, node in node_map.items():
|
|
39
|
-
display_label_map[node_id] = await node.
|
|
39
|
+
display_label_map[node_id] = await node.get_display_label(db=db)
|
|
40
40
|
offset += limit
|
|
41
41
|
return display_label_map
|
|
42
42
|
|
infrahub/core/graph/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
GRAPH_VERSION =
|
|
1
|
+
GRAPH_VERSION = 43
|
infrahub/core/initialization.py
CHANGED
|
@@ -50,7 +50,7 @@ async def get_root_node(db: InfrahubDatabase, initialize: bool = False) -> Root:
|
|
|
50
50
|
roots = await Root.get_list(db=db)
|
|
51
51
|
if len(roots) == 0 and not initialize:
|
|
52
52
|
raise DatabaseError(
|
|
53
|
-
"The Database hasn't been initialized for Infrahub, please
|
|
53
|
+
"The Database hasn't been initialized for Infrahub, please 'infrahub server start' to initialize the database."
|
|
54
54
|
)
|
|
55
55
|
|
|
56
56
|
if len(roots) == 0:
|
|
@@ -137,7 +137,8 @@ async def add_indexes(db: InfrahubDatabase) -> None:
|
|
|
137
137
|
await index_manager.add()
|
|
138
138
|
|
|
139
139
|
|
|
140
|
-
async def initialization(db: InfrahubDatabase, add_database_indexes: bool = False) ->
|
|
140
|
+
async def initialization(db: InfrahubDatabase, add_database_indexes: bool = False) -> bool:
|
|
141
|
+
"""Run initialization and setup, returns a boolean to indicate if it's the initial setup."""
|
|
141
142
|
if config.SETTINGS.database.db_type == config.DatabaseType.MEMGRAPH:
|
|
142
143
|
session = await db.session()
|
|
143
144
|
await session.run(query="SET DATABASE SETTING 'log.level' TO 'INFO'")
|
|
@@ -148,6 +149,7 @@ async def initialization(db: InfrahubDatabase, add_database_indexes: bool = Fals
|
|
|
148
149
|
# Initialize the database and Load the Root node
|
|
149
150
|
# ---------------------------------------------------
|
|
150
151
|
async with lock.registry.initialization():
|
|
152
|
+
first_time_initialization = len(await Root.get_list(db=db)) == 0
|
|
151
153
|
log.debug("Checking Root Node")
|
|
152
154
|
await initialize_registry(db=db, initialize=True)
|
|
153
155
|
|
|
@@ -210,6 +212,7 @@ async def initialization(db: InfrahubDatabase, add_database_indexes: bool = Fals
|
|
|
210
212
|
ip_namespace = await get_default_ipnamespace(db=db)
|
|
211
213
|
if ip_namespace:
|
|
212
214
|
registry.default_ipnamespace = ip_namespace.id
|
|
215
|
+
return first_time_initialization
|
|
213
216
|
|
|
214
217
|
|
|
215
218
|
async def create_root_node(db: InfrahubDatabase) -> Root:
|
infrahub/core/manager.py
CHANGED
|
@@ -60,12 +60,15 @@ def identify_node_class(node: NodeToProcess) -> type[Node]:
|
|
|
60
60
|
|
|
61
61
|
|
|
62
62
|
def get_schema(
|
|
63
|
-
db: InfrahubDatabase,
|
|
63
|
+
db: InfrahubDatabase,
|
|
64
|
+
branch: Branch,
|
|
65
|
+
node_schema: type[SchemaProtocol] | MainSchemaTypes | str,
|
|
66
|
+
duplicate: bool = False,
|
|
64
67
|
) -> MainSchemaTypes:
|
|
65
68
|
if isinstance(node_schema, str):
|
|
66
|
-
return db.schema.get(name=node_schema, branch=branch.name)
|
|
69
|
+
return db.schema.get(name=node_schema, branch=branch.name, duplicate=duplicate)
|
|
67
70
|
if hasattr(node_schema, "_is_runtime_protocol") and node_schema._is_runtime_protocol:
|
|
68
|
-
return db.schema.get(name=node_schema.__name__, branch=branch.name)
|
|
71
|
+
return db.schema.get(name=node_schema.__name__, branch=branch.name, duplicate=duplicate)
|
|
69
72
|
if not isinstance(node_schema, (MainSchemaTypes)):
|
|
70
73
|
raise ValueError(f"Invalid schema provided {node_schema}")
|
|
71
74
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from .schema.attribute_kind_update import AttributeKindUpdateMigration
|
|
2
2
|
from .schema.attribute_name_update import AttributeNameUpdateMigration
|
|
3
|
+
from .schema.attribute_supports_profile import AttributeSupportsProfileUpdateMigration
|
|
3
4
|
from .schema.node_attribute_add import NodeAttributeAddMigration
|
|
4
5
|
from .schema.node_attribute_remove import NodeAttributeRemoveMigration
|
|
5
6
|
from .schema.node_kind_update import NodeKindUpdateMigration
|
|
@@ -19,6 +20,8 @@ MIGRATION_MAP: dict[str, type[SchemaMigration] | None] = {
|
|
|
19
20
|
"attribute.name.update": AttributeNameUpdateMigration,
|
|
20
21
|
"attribute.branch.update": None,
|
|
21
22
|
"attribute.kind.update": AttributeKindUpdateMigration,
|
|
23
|
+
"attribute.optional.update": AttributeSupportsProfileUpdateMigration,
|
|
24
|
+
"attribute.read_only.update": AttributeSupportsProfileUpdateMigration,
|
|
22
25
|
"relationship.branch.update": None,
|
|
23
26
|
"relationship.direction.update": None,
|
|
24
27
|
"relationship.identifier.update": PlaceholderDummyMigration,
|