infrahub-server 1.4.10__py3-none-any.whl → 1.5.0b1__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/query.py +2 -0
- infrahub/api/schema.py +3 -0
- infrahub/auth.py +5 -5
- infrahub/cli/db.py +26 -2
- infrahub/cli/db_commands/clean_duplicate_schema_fields.py +212 -0
- infrahub/config.py +7 -2
- infrahub/core/attribute.py +25 -22
- infrahub/core/branch/models.py +2 -2
- infrahub/core/branch/needs_rebase_status.py +11 -0
- infrahub/core/branch/tasks.py +4 -3
- infrahub/core/changelog/models.py +4 -12
- infrahub/core/constants/__init__.py +1 -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 -1
- infrahub/core/graph/__init__.py +1 -1
- infrahub/core/initialization.py +2 -2
- infrahub/core/ipam/utilization.py +1 -1
- infrahub/core/manager.py +9 -84
- infrahub/core/migrations/graph/__init__.py +6 -0
- infrahub/core/migrations/graph/m040_profile_attrs_in_db.py +166 -0
- infrahub/core/migrations/graph/m041_create_hfid_display_label_in_db.py +97 -0
- infrahub/core/migrations/graph/m042_backfill_hfid_display_label_in_db.py +86 -0
- infrahub/core/migrations/schema/node_attribute_add.py +5 -2
- infrahub/core/migrations/shared.py +5 -6
- infrahub/core/node/__init__.py +165 -42
- infrahub/core/node/constraints/attribute_uniqueness.py +3 -1
- infrahub/core/node/create.py +67 -35
- infrahub/core/node/lock_utils.py +98 -0
- infrahub/core/node/node_property_attribute.py +230 -0
- infrahub/core/node/standard.py +1 -1
- infrahub/core/property.py +11 -0
- infrahub/core/protocols.py +8 -1
- infrahub/core/query/attribute.py +27 -15
- infrahub/core/query/node.py +61 -185
- infrahub/core/query/relationship.py +43 -26
- infrahub/core/query/subquery.py +0 -8
- infrahub/core/registry.py +2 -2
- infrahub/core/relationship/constraints/count.py +1 -1
- infrahub/core/relationship/model.py +60 -20
- infrahub/core/schema/attribute_schema.py +0 -2
- 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/core/repository.py +7 -0
- infrahub/core/schema/definitions/internal.py +14 -1
- infrahub/core/schema/generated/base_node_schema.py +6 -1
- infrahub/core/schema/node_schema.py +5 -2
- infrahub/core/schema/relationship_schema.py +0 -1
- infrahub/core/schema/schema_branch.py +137 -2
- 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/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 +186 -0
- infrahub/display_labels/triggers.py +22 -0
- 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/analyzer.py +1 -1
- infrahub/graphql/api/dependencies.py +2 -4
- infrahub/graphql/api/endpoints.py +2 -2
- infrahub/graphql/app.py +2 -4
- infrahub/graphql/initialization.py +2 -3
- infrahub/graphql/manager.py +212 -137
- infrahub/graphql/middleware.py +12 -0
- infrahub/graphql/mutations/branch.py +11 -0
- infrahub/graphql/mutations/computed_attribute.py +110 -3
- infrahub/graphql/mutations/convert_object_type.py +34 -13
- infrahub/graphql/mutations/display_label.py +111 -0
- infrahub/graphql/mutations/generator.py +25 -7
- infrahub/graphql/mutations/hfid.py +118 -0
- infrahub/graphql/mutations/ipam.py +21 -8
- infrahub/graphql/mutations/main.py +37 -153
- infrahub/graphql/mutations/profile.py +195 -0
- infrahub/graphql/mutations/proposed_change.py +2 -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/schema.py +5 -5
- 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/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 +185 -0
- infrahub/hfid/triggers.py +22 -0
- infrahub/lock.py +67 -30
- infrahub/locks/__init__.py +0 -0
- infrahub/locks/tasks.py +37 -0
- infrahub/middleware.py +26 -1
- infrahub/patch/plan_writer.py +2 -2
- 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 +99 -0
- infrahub/profiles/tasks.py +63 -0
- infrahub/proposed_change/tasks.py +10 -1
- infrahub/repositories/__init__.py +0 -0
- infrahub/repositories/create_repository.py +113 -0
- infrahub/server.py +16 -3
- infrahub/services/__init__.py +8 -5
- infrahub/tasks/registry.py +6 -4
- infrahub/trigger/catalogue.py +4 -0
- infrahub/trigger/models.py +2 -0
- infrahub/trigger/tasks.py +3 -0
- infrahub/webhook/models.py +1 -1
- infrahub/workflows/catalogue.py +110 -3
- infrahub/workflows/initialization.py +16 -0
- infrahub/workflows/models.py +17 -2
- infrahub_sdk/branch.py +5 -8
- infrahub_sdk/checks.py +1 -1
- infrahub_sdk/client.py +364 -84
- infrahub_sdk/convert_object_type.py +61 -0
- infrahub_sdk/ctl/check.py +2 -3
- infrahub_sdk/ctl/cli_commands.py +18 -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 +18 -3
- 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/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 +38 -0
- infrahub_sdk/schema/main.py +1 -0
- infrahub_sdk/schema/repository.py +8 -0
- infrahub_sdk/spec/object.py +120 -7
- infrahub_sdk/spec/range_expansion.py +118 -0
- infrahub_sdk/timestamp.py +18 -6
- infrahub_sdk/transforms.py +1 -1
- {infrahub_server-1.4.10.dist-info → infrahub_server-1.5.0b1.dist-info}/METADATA +9 -11
- {infrahub_server-1.4.10.dist-info → infrahub_server-1.5.0b1.dist-info}/RECORD +177 -134
- infrahub_testcontainers/container.py +1 -1
- infrahub_testcontainers/docker-compose-cluster.test.yml +1 -1
- infrahub_testcontainers/docker-compose.test.yml +1 -1
- 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.10.dist-info → infrahub_server-1.5.0b1.dist-info}/LICENSE.txt +0 -0
- {infrahub_server-1.4.10.dist-info → infrahub_server-1.5.0b1.dist-info}/WHEEL +0 -0
- {infrahub_server-1.4.10.dist-info → infrahub_server-1.5.0b1.dist-info}/entry_points.txt +0 -0
infrahub/core/manager.py
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from copy import copy
|
|
4
|
-
from functools import reduce
|
|
5
4
|
from typing import TYPE_CHECKING, Any, Iterable, Literal, TypeVar, overload
|
|
6
5
|
|
|
7
6
|
from infrahub_sdk.utils import deep_merge_dict, is_valid_uuid
|
|
@@ -11,9 +10,7 @@ from infrahub.core.node import Node
|
|
|
11
10
|
from infrahub.core.node.delete_validator import NodeDeleteValidator
|
|
12
11
|
from infrahub.core.query.node import (
|
|
13
12
|
AttributeFromDB,
|
|
14
|
-
AttributeNodePropertyFromDB,
|
|
15
13
|
GroupedPeerNodes,
|
|
16
|
-
NodeAttributesFromDB,
|
|
17
14
|
NodeGetHierarchyQuery,
|
|
18
15
|
NodeGetListQuery,
|
|
19
16
|
NodeListGetAttributeQuery,
|
|
@@ -63,72 +60,21 @@ def identify_node_class(node: NodeToProcess) -> type[Node]:
|
|
|
63
60
|
|
|
64
61
|
|
|
65
62
|
def get_schema(
|
|
66
|
-
db: InfrahubDatabase,
|
|
63
|
+
db: InfrahubDatabase,
|
|
64
|
+
branch: Branch,
|
|
65
|
+
node_schema: type[SchemaProtocol] | MainSchemaTypes | str,
|
|
66
|
+
duplicate: bool = False,
|
|
67
67
|
) -> MainSchemaTypes:
|
|
68
68
|
if isinstance(node_schema, str):
|
|
69
|
-
return db.schema.get(name=node_schema, branch=branch.name)
|
|
69
|
+
return db.schema.get(name=node_schema, branch=branch.name, duplicate=duplicate)
|
|
70
70
|
if hasattr(node_schema, "_is_runtime_protocol") and node_schema._is_runtime_protocol:
|
|
71
|
-
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)
|
|
72
72
|
if not isinstance(node_schema, (MainSchemaTypes)):
|
|
73
73
|
raise ValueError(f"Invalid schema provided {node_schema}")
|
|
74
74
|
|
|
75
75
|
return node_schema
|
|
76
76
|
|
|
77
77
|
|
|
78
|
-
class ProfileAttributeIndex:
|
|
79
|
-
def __init__(
|
|
80
|
-
self,
|
|
81
|
-
profile_attributes_id_map: dict[str, NodeAttributesFromDB],
|
|
82
|
-
profile_ids_by_node_id: dict[str, list[str]],
|
|
83
|
-
) -> None:
|
|
84
|
-
self._profile_attributes_id_map = profile_attributes_id_map
|
|
85
|
-
self._profile_ids_by_node_id = profile_ids_by_node_id
|
|
86
|
-
|
|
87
|
-
def apply_profiles(self, node_data_dict: dict[str, Any]) -> dict[str, Any]:
|
|
88
|
-
updated_data: dict[str, Any] = {**node_data_dict}
|
|
89
|
-
node_id = node_data_dict.get("id")
|
|
90
|
-
profile_ids = self._profile_ids_by_node_id.get(node_id, [])
|
|
91
|
-
if not profile_ids:
|
|
92
|
-
return updated_data
|
|
93
|
-
profiles = [
|
|
94
|
-
self._profile_attributes_id_map[p_id] for p_id in profile_ids if p_id in self._profile_attributes_id_map
|
|
95
|
-
]
|
|
96
|
-
|
|
97
|
-
def get_profile_priority(nafd: NodeAttributesFromDB) -> tuple[int | float, str]:
|
|
98
|
-
try:
|
|
99
|
-
return (int(nafd.attrs.get("profile_priority").value), nafd.node.get("uuid"))
|
|
100
|
-
except (TypeError, AttributeError):
|
|
101
|
-
return (float("inf"), "")
|
|
102
|
-
|
|
103
|
-
profiles.sort(key=get_profile_priority)
|
|
104
|
-
|
|
105
|
-
for attr_name, attr_data in updated_data.items():
|
|
106
|
-
if not isinstance(attr_data, AttributeFromDB):
|
|
107
|
-
continue
|
|
108
|
-
if not attr_data.is_default:
|
|
109
|
-
continue
|
|
110
|
-
profile_value, profile_uuid = None, None
|
|
111
|
-
index = 0
|
|
112
|
-
|
|
113
|
-
while profile_value is None and index <= (len(profiles) - 1):
|
|
114
|
-
try:
|
|
115
|
-
profile_value = profiles[index].attrs[attr_name].value
|
|
116
|
-
if profile_value != "NULL":
|
|
117
|
-
profile_uuid = profiles[index].node["uuid"]
|
|
118
|
-
break
|
|
119
|
-
profile_value = None
|
|
120
|
-
except (IndexError, KeyError, AttributeError):
|
|
121
|
-
...
|
|
122
|
-
index += 1
|
|
123
|
-
|
|
124
|
-
if profile_value is not None:
|
|
125
|
-
attr_data.value = profile_value
|
|
126
|
-
attr_data.is_from_profile = True
|
|
127
|
-
attr_data.is_default = False
|
|
128
|
-
attr_data.node_properties["source"] = AttributeNodePropertyFromDB(uuid=profile_uuid, labels=[])
|
|
129
|
-
return updated_data
|
|
130
|
-
|
|
131
|
-
|
|
132
78
|
class NodeManager:
|
|
133
79
|
@overload
|
|
134
80
|
@classmethod
|
|
@@ -1129,21 +1075,11 @@ class NodeManager:
|
|
|
1129
1075
|
)
|
|
1130
1076
|
await query.execute(db=db)
|
|
1131
1077
|
nodes_info_by_id: dict[str, NodeToProcess] = {node.node_uuid: node async for node in query.get_nodes(db=db)}
|
|
1132
|
-
profile_ids_by_node_id = query.get_profile_ids_by_node_id()
|
|
1133
|
-
all_profile_ids = reduce(
|
|
1134
|
-
lambda all_ids, these_ids: all_ids | set(these_ids), profile_ids_by_node_id.values(), set()
|
|
1135
|
-
)
|
|
1136
|
-
|
|
1137
|
-
if fields and all_profile_ids:
|
|
1138
|
-
if "profile_priority" not in fields:
|
|
1139
|
-
fields["profile_priority"] = {}
|
|
1140
|
-
if "value" not in fields["profile_priority"]:
|
|
1141
|
-
fields["profile_priority"]["value"] = None
|
|
1142
1078
|
|
|
1143
1079
|
# Query list of all Attributes
|
|
1144
1080
|
query = await NodeListGetAttributeQuery.init(
|
|
1145
1081
|
db=db,
|
|
1146
|
-
ids=list(nodes_info_by_id.keys())
|
|
1082
|
+
ids=list(nodes_info_by_id.keys()),
|
|
1147
1083
|
fields=fields,
|
|
1148
1084
|
branch=branch,
|
|
1149
1085
|
include_source=include_source,
|
|
@@ -1153,17 +1089,7 @@ class NodeManager:
|
|
|
1153
1089
|
branch_agnostic=branch_agnostic,
|
|
1154
1090
|
)
|
|
1155
1091
|
await query.execute(db=db)
|
|
1156
|
-
|
|
1157
|
-
profile_attributes: dict[str, dict[str, AttributeFromDB]] = {}
|
|
1158
|
-
node_attributes: dict[str, dict[str, AttributeFromDB]] = {}
|
|
1159
|
-
for node_id, attribute_dict in all_node_attributes.items():
|
|
1160
|
-
if node_id in all_profile_ids:
|
|
1161
|
-
profile_attributes[node_id] = attribute_dict
|
|
1162
|
-
else:
|
|
1163
|
-
node_attributes[node_id] = attribute_dict
|
|
1164
|
-
profile_index = ProfileAttributeIndex(
|
|
1165
|
-
profile_attributes_id_map=profile_attributes, profile_ids_by_node_id=profile_ids_by_node_id
|
|
1166
|
-
)
|
|
1092
|
+
node_attributes = query.get_attributes_group_by_node()
|
|
1167
1093
|
|
|
1168
1094
|
nodes: dict[str, Node] = {}
|
|
1169
1095
|
|
|
@@ -1192,11 +1118,10 @@ class NodeManager:
|
|
|
1192
1118
|
for attr_name, attr in node_attributes[node_id].attrs.items():
|
|
1193
1119
|
new_node_data[attr_name] = attr
|
|
1194
1120
|
|
|
1195
|
-
new_node_data_with_profile_overrides = profile_index.apply_profiles(new_node_data)
|
|
1196
1121
|
node_class = identify_node_class(node=node)
|
|
1197
1122
|
node_branch = await registry.get_branch(db=db, branch=node.branch)
|
|
1198
1123
|
item = await node_class.init(schema=node.schema, branch=node_branch, at=at, db=db)
|
|
1199
|
-
await item.load(**
|
|
1124
|
+
await item.load(**new_node_data, db=db)
|
|
1200
1125
|
|
|
1201
1126
|
nodes[node_id] = item
|
|
1202
1127
|
|
|
@@ -41,6 +41,9 @@ from .m036_drop_attr_value_index import Migration036
|
|
|
41
41
|
from .m037_index_attr_vals import Migration037
|
|
42
42
|
from .m038_redo_0000_prefix_fix import Migration038
|
|
43
43
|
from .m039_ipam_reconcile import Migration039
|
|
44
|
+
from .m040_profile_attrs_in_db import Migration040
|
|
45
|
+
from .m041_create_hfid_display_label_in_db import Migration041
|
|
46
|
+
from .m042_backfill_hfid_display_label_in_db import Migration042
|
|
44
47
|
|
|
45
48
|
if TYPE_CHECKING:
|
|
46
49
|
from infrahub.core.root import Root
|
|
@@ -87,6 +90,9 @@ MIGRATIONS: list[type[GraphMigration | InternalSchemaMigration | ArbitraryMigrat
|
|
|
87
90
|
Migration037,
|
|
88
91
|
Migration038,
|
|
89
92
|
Migration039,
|
|
93
|
+
Migration040,
|
|
94
|
+
Migration041,
|
|
95
|
+
Migration042,
|
|
90
96
|
]
|
|
91
97
|
|
|
92
98
|
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections import defaultdict
|
|
4
|
+
from typing import TYPE_CHECKING, Any
|
|
5
|
+
|
|
6
|
+
from rich.console import Console
|
|
7
|
+
from rich.progress import Progress
|
|
8
|
+
|
|
9
|
+
from infrahub.core.branch.models import Branch
|
|
10
|
+
from infrahub.core.initialization import initialization
|
|
11
|
+
from infrahub.core.manager import NodeManager
|
|
12
|
+
from infrahub.core.migrations.shared import MigrationResult
|
|
13
|
+
from infrahub.core.query import Query, QueryType
|
|
14
|
+
from infrahub.core.timestamp import Timestamp
|
|
15
|
+
from infrahub.lock import initialize_lock
|
|
16
|
+
from infrahub.log import get_logger
|
|
17
|
+
from infrahub.profiles.node_applier import NodeProfilesApplier
|
|
18
|
+
|
|
19
|
+
from ..shared import ArbitraryMigration
|
|
20
|
+
|
|
21
|
+
if TYPE_CHECKING:
|
|
22
|
+
from infrahub.core.node import Node
|
|
23
|
+
from infrahub.database import InfrahubDatabase
|
|
24
|
+
|
|
25
|
+
log = get_logger()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class GetProfilesByBranchQuery(Query):
|
|
29
|
+
"""
|
|
30
|
+
Get CoreProfile UUIDs by which branches they have attribute updates on
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
name = "get_profiles_by_branch"
|
|
34
|
+
type = QueryType.READ
|
|
35
|
+
insert_return = False
|
|
36
|
+
|
|
37
|
+
async def query_init(self, db: InfrahubDatabase, **kwargs: dict[str, Any]) -> None: # noqa: ARG002
|
|
38
|
+
query = """
|
|
39
|
+
MATCH (profile:CoreProfile)-[:HAS_ATTRIBUTE]->(attr:Attribute)-[e:HAS_VALUE]->(:AttributeValue)
|
|
40
|
+
WITH DISTINCT profile.uuid AS profile_uuid, e.branch AS branch
|
|
41
|
+
RETURN profile_uuid, collect(branch) AS branches
|
|
42
|
+
"""
|
|
43
|
+
self.add_to_query(query)
|
|
44
|
+
self.return_labels = ["profile_uuid", "branches"]
|
|
45
|
+
|
|
46
|
+
def get_profile_ids_by_branch(self) -> dict[str, set[str]]:
|
|
47
|
+
"""Get dictionary of branch names to set of updated profile UUIDs"""
|
|
48
|
+
profiles_by_branch = defaultdict(set)
|
|
49
|
+
for result in self.get_results():
|
|
50
|
+
profile_uuid = result.get_as_type("profile_uuid", str)
|
|
51
|
+
branches = result.get_as_type("branches", list[str])
|
|
52
|
+
for branch in branches:
|
|
53
|
+
profiles_by_branch[branch].add(profile_uuid)
|
|
54
|
+
return profiles_by_branch
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class GetNodesWithProfileUpdatesByBranchQuery(Query):
|
|
58
|
+
"""
|
|
59
|
+
Get Node UUIDs by which branches they have updated profiles on
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
name = "get_nodes_with_profile_updates_by_branch"
|
|
63
|
+
type = QueryType.READ
|
|
64
|
+
insert_return = False
|
|
65
|
+
|
|
66
|
+
async def query_init(self, db: InfrahubDatabase, **kwargs: dict[str, Any]) -> None: # noqa: ARG002
|
|
67
|
+
query = """
|
|
68
|
+
MATCH (node:Node)-[e1:IS_RELATED]->(:Relationship {name: "node__profile"})
|
|
69
|
+
WHERE NOT node:CoreProfile
|
|
70
|
+
WITH DISTINCT node.uuid AS node_uuid, e1.branch AS branch
|
|
71
|
+
RETURN node_uuid, collect(branch) AS branches
|
|
72
|
+
"""
|
|
73
|
+
self.add_to_query(query)
|
|
74
|
+
self.return_labels = ["node_uuid", "branches"]
|
|
75
|
+
|
|
76
|
+
def get_node_ids_by_branch(self) -> dict[str, set[str]]:
|
|
77
|
+
"""Get dictionary of branch names to set of updated node UUIDs"""
|
|
78
|
+
nodes_by_branch = defaultdict(set)
|
|
79
|
+
for result in self.get_results():
|
|
80
|
+
node_uuid = result.get_as_type("node_uuid", str)
|
|
81
|
+
branches = result.get_as_type("branches", list[str])
|
|
82
|
+
for branch in branches:
|
|
83
|
+
nodes_by_branch[branch].add(node_uuid)
|
|
84
|
+
return nodes_by_branch
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class Migration040(ArbitraryMigration):
|
|
88
|
+
"""
|
|
89
|
+
Save profile attribute values on each node using the profile in the database
|
|
90
|
+
For any profile that has updates on a given branch (including default branch)
|
|
91
|
+
- run NodeProfilesApplier.apply_profiles on each node related to the profile on that branch
|
|
92
|
+
For any node that has an updated relationship to a profile on a given branch
|
|
93
|
+
- run NodeProfilesApplier.apply_profiles on the node on that branch
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
name: str = "040_profile_attrs_in_db"
|
|
97
|
+
minimum_version: int = 39
|
|
98
|
+
|
|
99
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
100
|
+
super().__init__(*args, **kwargs)
|
|
101
|
+
self._appliers_by_branch: dict[str, NodeProfilesApplier] = {}
|
|
102
|
+
|
|
103
|
+
async def _get_profile_applier(self, db: InfrahubDatabase, branch_name: str) -> NodeProfilesApplier:
|
|
104
|
+
if branch_name not in self._appliers_by_branch:
|
|
105
|
+
branch = await Branch.get_by_name(db=db, name=branch_name)
|
|
106
|
+
self._appliers_by_branch[branch_name] = NodeProfilesApplier(db=db, branch=branch)
|
|
107
|
+
return self._appliers_by_branch[branch_name]
|
|
108
|
+
|
|
109
|
+
async def validate_migration(self, db: InfrahubDatabase) -> MigrationResult: # noqa: ARG002
|
|
110
|
+
return MigrationResult()
|
|
111
|
+
|
|
112
|
+
async def execute(self, db: InfrahubDatabase) -> MigrationResult:
|
|
113
|
+
console = Console()
|
|
114
|
+
result = MigrationResult()
|
|
115
|
+
# load schemas from database into registry
|
|
116
|
+
initialize_lock()
|
|
117
|
+
await initialization(db=db)
|
|
118
|
+
|
|
119
|
+
console.print("Gathering profiles for each branch...", end="")
|
|
120
|
+
get_profiles_by_branch_query = await GetProfilesByBranchQuery.init(db=db)
|
|
121
|
+
await get_profiles_by_branch_query.execute(db=db)
|
|
122
|
+
profiles_ids_by_branch = get_profiles_by_branch_query.get_profile_ids_by_branch()
|
|
123
|
+
|
|
124
|
+
profiles_by_branch: dict[str, list[Node]] = {}
|
|
125
|
+
for branch_name, profile_ids in profiles_ids_by_branch.items():
|
|
126
|
+
profiles_map = await NodeManager.get_many(db=db, branch=branch_name, ids=list(profile_ids))
|
|
127
|
+
profiles_by_branch[branch_name] = list(profiles_map.values())
|
|
128
|
+
console.print("done")
|
|
129
|
+
|
|
130
|
+
node_ids_to_update_by_branch: dict[str, set[str]] = defaultdict(set)
|
|
131
|
+
total_size = sum(len(profiles) for profiles in profiles_by_branch.values())
|
|
132
|
+
with Progress() as progress:
|
|
133
|
+
gather_nodes_task = progress.add_task(
|
|
134
|
+
"Gathering affected objects for each profile on each branch...", total=total_size
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
for branch_name, profiles in profiles_by_branch.items():
|
|
138
|
+
for profile in profiles:
|
|
139
|
+
node_relationship_manager = profile.get_relationship("related_nodes")
|
|
140
|
+
node_peers = await node_relationship_manager.get_db_peers(db=db)
|
|
141
|
+
node_ids_to_update_by_branch[branch_name].update({str(peer.peer_id) for peer in node_peers})
|
|
142
|
+
progress.update(gather_nodes_task, advance=1)
|
|
143
|
+
|
|
144
|
+
console.print("Identifying nodes with profile updates by branch...", end="")
|
|
145
|
+
get_nodes_with_profile_updates_by_branch_query = await GetNodesWithProfileUpdatesByBranchQuery.init(db=db)
|
|
146
|
+
await get_nodes_with_profile_updates_by_branch_query.execute(db=db)
|
|
147
|
+
nodes_ids_by_branch = get_nodes_with_profile_updates_by_branch_query.get_node_ids_by_branch()
|
|
148
|
+
for branch_name, node_ids in nodes_ids_by_branch.items():
|
|
149
|
+
node_ids_to_update_by_branch[branch_name].update(node_ids)
|
|
150
|
+
console.print("done")
|
|
151
|
+
|
|
152
|
+
right_now = Timestamp()
|
|
153
|
+
total_size = sum(len(node_ids) for node_ids in node_ids_to_update_by_branch.values())
|
|
154
|
+
with Progress() as progress:
|
|
155
|
+
apply_task = progress.add_task("Applying profiles to nodes...", total=total_size)
|
|
156
|
+
for branch_name, node_ids in node_ids_to_update_by_branch.items():
|
|
157
|
+
applier = await self._get_profile_applier(db=db, branch_name=branch_name)
|
|
158
|
+
for node_id in node_ids:
|
|
159
|
+
node = await NodeManager.get_one(db=db, branch=branch_name, id=node_id, at=right_now)
|
|
160
|
+
if node:
|
|
161
|
+
updated_field_names = await applier.apply_profiles(node=node)
|
|
162
|
+
if updated_field_names:
|
|
163
|
+
await node.save(db=db, fields=updated_field_names, at=right_now)
|
|
164
|
+
progress.update(apply_task, advance=1)
|
|
165
|
+
|
|
166
|
+
return result
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Any
|
|
4
|
+
|
|
5
|
+
from rich.progress import Progress
|
|
6
|
+
from typing_extensions import Self
|
|
7
|
+
|
|
8
|
+
from infrahub.core import registry
|
|
9
|
+
from infrahub.core.constants import SchemaPathType
|
|
10
|
+
from infrahub.core.initialization import initialization
|
|
11
|
+
from infrahub.core.migrations.schema.node_attribute_add import NodeAttributeAddMigration
|
|
12
|
+
from infrahub.core.migrations.shared import InternalSchemaMigration, MigrationResult
|
|
13
|
+
from infrahub.core.path import SchemaPath
|
|
14
|
+
from infrahub.lock import initialize_lock
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from infrahub.database import InfrahubDatabase
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class Migration041(InternalSchemaMigration):
|
|
21
|
+
name: str = "041_create_hfid_display_label_in_db"
|
|
22
|
+
minimum_version: int = 40
|
|
23
|
+
|
|
24
|
+
@classmethod
|
|
25
|
+
def init(cls, **kwargs: Any) -> Self:
|
|
26
|
+
internal_schema = cls.get_internal_schema()
|
|
27
|
+
schema_node = internal_schema.get_node(name="SchemaNode")
|
|
28
|
+
schema_generic = internal_schema.get_node(name="SchemaGeneric")
|
|
29
|
+
|
|
30
|
+
cls.migrations = [
|
|
31
|
+
# HFID is not needed, it was introduced at graph v8
|
|
32
|
+
NodeAttributeAddMigration(
|
|
33
|
+
new_node_schema=schema_node,
|
|
34
|
+
previous_node_schema=schema_node,
|
|
35
|
+
schema_path=SchemaPath(
|
|
36
|
+
schema_kind="SchemaNode", path_type=SchemaPathType.ATTRIBUTE, field_name="display_label"
|
|
37
|
+
),
|
|
38
|
+
),
|
|
39
|
+
NodeAttributeAddMigration(
|
|
40
|
+
new_node_schema=schema_generic,
|
|
41
|
+
previous_node_schema=schema_generic,
|
|
42
|
+
schema_path=SchemaPath(
|
|
43
|
+
schema_kind="SchemaGeneric", path_type=SchemaPathType.ATTRIBUTE, field_name="display_label"
|
|
44
|
+
),
|
|
45
|
+
),
|
|
46
|
+
]
|
|
47
|
+
return cls(migrations=cls.migrations, **kwargs) # type: ignore[arg-type]
|
|
48
|
+
|
|
49
|
+
async def execute(self, db: InfrahubDatabase) -> MigrationResult:
|
|
50
|
+
result = MigrationResult()
|
|
51
|
+
|
|
52
|
+
# load schemas from database into registry
|
|
53
|
+
initialize_lock()
|
|
54
|
+
await initialization(db=db)
|
|
55
|
+
|
|
56
|
+
default_branch = registry.get_branch_from_registry()
|
|
57
|
+
schema_branch = await registry.schema.load_schema_from_db(db=db, branch=default_branch)
|
|
58
|
+
|
|
59
|
+
migrations = list(self.migrations)
|
|
60
|
+
|
|
61
|
+
for node_schema_kind in schema_branch.node_names:
|
|
62
|
+
schema = schema_branch.get(name=node_schema_kind, duplicate=False)
|
|
63
|
+
migrations.extend(
|
|
64
|
+
[
|
|
65
|
+
NodeAttributeAddMigration(
|
|
66
|
+
new_node_schema=schema,
|
|
67
|
+
previous_node_schema=schema,
|
|
68
|
+
schema_path=SchemaPath(
|
|
69
|
+
schema_kind=schema.kind, path_type=SchemaPathType.ATTRIBUTE, field_name="human_friendly_id"
|
|
70
|
+
),
|
|
71
|
+
),
|
|
72
|
+
NodeAttributeAddMigration(
|
|
73
|
+
new_node_schema=schema,
|
|
74
|
+
previous_node_schema=schema,
|
|
75
|
+
schema_path=SchemaPath(
|
|
76
|
+
schema_kind=schema.kind, path_type=SchemaPathType.ATTRIBUTE, field_name="display_label"
|
|
77
|
+
),
|
|
78
|
+
),
|
|
79
|
+
]
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
with Progress() as progress:
|
|
83
|
+
update_task = progress.add_task("Adding HFID and display label to nodes", total=len(migrations))
|
|
84
|
+
|
|
85
|
+
for migration in migrations:
|
|
86
|
+
try:
|
|
87
|
+
execution_result = await migration.execute(db=db, branch=default_branch)
|
|
88
|
+
result.errors.extend(execution_result.errors)
|
|
89
|
+
progress.update(update_task, advance=1)
|
|
90
|
+
except Exception as exc:
|
|
91
|
+
result.errors.append(str(exc))
|
|
92
|
+
return result
|
|
93
|
+
|
|
94
|
+
return result
|
|
95
|
+
|
|
96
|
+
async def validate_migration(self, db: InfrahubDatabase) -> MigrationResult: # noqa: ARG002
|
|
97
|
+
return MigrationResult()
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Any, Sequence
|
|
4
|
+
|
|
5
|
+
from rich.progress import Progress, TaskID
|
|
6
|
+
|
|
7
|
+
from infrahub.core import registry
|
|
8
|
+
from infrahub.core.initialization import initialization
|
|
9
|
+
from infrahub.core.manager import NodeManager
|
|
10
|
+
from infrahub.core.migrations.shared import MigrationResult
|
|
11
|
+
from infrahub.lock import initialize_lock
|
|
12
|
+
|
|
13
|
+
from ..shared import ArbitraryMigration
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from infrahub.core.node import Node
|
|
17
|
+
from infrahub.core.schema import MainSchemaTypes
|
|
18
|
+
from infrahub.database import InfrahubDatabase
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Migration042(ArbitraryMigration):
|
|
22
|
+
"""
|
|
23
|
+
Backfill `human_friendly_id` and `display_label` attributes for nodes with schemas that define them.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
name: str = "042_backfill_hfid_display_label_in_db"
|
|
27
|
+
minimum_version: int = 41
|
|
28
|
+
|
|
29
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
30
|
+
super().__init__(*args, **kwargs)
|
|
31
|
+
|
|
32
|
+
async def validate_migration(self, db: InfrahubDatabase) -> MigrationResult: # noqa: ARG002
|
|
33
|
+
return MigrationResult()
|
|
34
|
+
|
|
35
|
+
async def _update_batch(
|
|
36
|
+
self,
|
|
37
|
+
db: InfrahubDatabase,
|
|
38
|
+
node_schema: MainSchemaTypes,
|
|
39
|
+
nodes: Sequence[Node],
|
|
40
|
+
progress: Progress,
|
|
41
|
+
update_task: TaskID,
|
|
42
|
+
) -> None:
|
|
43
|
+
for node in nodes:
|
|
44
|
+
fields = []
|
|
45
|
+
if node_schema.human_friendly_id:
|
|
46
|
+
await node.add_human_friendly_id(db=db)
|
|
47
|
+
fields.append("human_friendly_id")
|
|
48
|
+
if node_schema.display_label:
|
|
49
|
+
await node.add_display_label(db=db)
|
|
50
|
+
fields.append("display_label")
|
|
51
|
+
|
|
52
|
+
if fields:
|
|
53
|
+
await node.save(db=db, fields=fields)
|
|
54
|
+
|
|
55
|
+
progress.update(task_id=update_task, advance=1)
|
|
56
|
+
|
|
57
|
+
async def execute(self, db: InfrahubDatabase) -> MigrationResult:
|
|
58
|
+
result = MigrationResult()
|
|
59
|
+
# load schemas from database into registry
|
|
60
|
+
initialize_lock()
|
|
61
|
+
await initialization(db=db)
|
|
62
|
+
|
|
63
|
+
schemas_to_update: dict[MainSchemaTypes, int] = {}
|
|
64
|
+
for node_schema in registry.get_full_schema(duplicate=False).values():
|
|
65
|
+
if node_schema.is_generic_schema or (not node_schema.human_friendly_id and not node_schema.display_label):
|
|
66
|
+
continue
|
|
67
|
+
|
|
68
|
+
node_count = await NodeManager.count(db=db, schema=node_schema)
|
|
69
|
+
if node_count:
|
|
70
|
+
schemas_to_update[node_schema] = node_count
|
|
71
|
+
|
|
72
|
+
with Progress() as progress:
|
|
73
|
+
batch_size = 1000
|
|
74
|
+
update_task = progress.add_task(
|
|
75
|
+
"Backfill HFID and display_label for nodes in database", total=sum(schemas_to_update.values())
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
for schema, count in schemas_to_update.items():
|
|
79
|
+
for offset in range(0, count, batch_size):
|
|
80
|
+
limit = min(batch_size, count - offset)
|
|
81
|
+
nodes: list[Node] = await NodeManager.query(db=db, schema=schema, offset=offset, limit=limit)
|
|
82
|
+
await self._update_batch(
|
|
83
|
+
db=db, node_schema=schema, nodes=nodes, progress=progress, update_task=update_task
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
return result
|
|
@@ -52,8 +52,11 @@ class NodeAttributeAddMigration(AttributeSchemaMigration):
|
|
|
52
52
|
if self.new_attribute_schema.kind != "NumberPool":
|
|
53
53
|
return result
|
|
54
54
|
|
|
55
|
-
number_pool: CoreNumberPool = await Node.fetch_or_create_number_pool(
|
|
56
|
-
db=db,
|
|
55
|
+
number_pool: CoreNumberPool = await Node.fetch_or_create_number_pool(
|
|
56
|
+
db=db,
|
|
57
|
+
branch=branch,
|
|
58
|
+
schema_node=self.new_schema, # type: ignore
|
|
59
|
+
schema_attribute=self.new_attribute_schema,
|
|
57
60
|
)
|
|
58
61
|
|
|
59
62
|
await update_branch_registry(db=db, branch=branch)
|
|
@@ -10,8 +10,7 @@ from infrahub.core.path import SchemaPath # noqa: TC001
|
|
|
10
10
|
from infrahub.core.query import Query # noqa: TC001
|
|
11
11
|
from infrahub.core.schema import (
|
|
12
12
|
AttributeSchema,
|
|
13
|
-
|
|
14
|
-
NodeSchema,
|
|
13
|
+
MainSchemaTypes,
|
|
15
14
|
RelationshipSchema,
|
|
16
15
|
SchemaRoot,
|
|
17
16
|
internal_schema,
|
|
@@ -43,8 +42,8 @@ class SchemaMigration(BaseModel):
|
|
|
43
42
|
name: str = Field(..., description="Name of the migration")
|
|
44
43
|
queries: Sequence[type[MigrationQuery]] = Field(..., description="List of queries to execute for this migration")
|
|
45
44
|
|
|
46
|
-
new_node_schema:
|
|
47
|
-
previous_node_schema:
|
|
45
|
+
new_node_schema: MainSchemaTypes | None = None
|
|
46
|
+
previous_node_schema: MainSchemaTypes | None = None
|
|
48
47
|
schema_path: SchemaPath
|
|
49
48
|
|
|
50
49
|
async def execute_pre_queries(
|
|
@@ -91,13 +90,13 @@ class SchemaMigration(BaseModel):
|
|
|
91
90
|
return result
|
|
92
91
|
|
|
93
92
|
@property
|
|
94
|
-
def new_schema(self) ->
|
|
93
|
+
def new_schema(self) -> MainSchemaTypes:
|
|
95
94
|
if self.new_node_schema:
|
|
96
95
|
return self.new_node_schema
|
|
97
96
|
raise ValueError("new_node_schema hasn't been initialized")
|
|
98
97
|
|
|
99
98
|
@property
|
|
100
|
-
def previous_schema(self) ->
|
|
99
|
+
def previous_schema(self) -> MainSchemaTypes:
|
|
101
100
|
if self.previous_node_schema:
|
|
102
101
|
return self.previous_node_schema
|
|
103
102
|
raise ValueError("previous_node_schema hasn't been initialized")
|