infrahub-server 1.5.0b0__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 +8 -0
- infrahub/api/diff/diff.py +1 -1
- infrahub/cli/db.py +24 -0
- infrahub/cli/db_commands/clean_duplicate_schema_fields.py +212 -0
- infrahub/core/attribute.py +3 -3
- infrahub/core/branch/tasks.py +2 -1
- infrahub/core/changelog/models.py +4 -12
- 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/graph/__init__.py +1 -1
- infrahub/core/ipam/utilization.py +1 -1
- infrahub/core/manager.py +6 -3
- infrahub/core/migrations/graph/__init__.py +4 -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 +142 -40
- infrahub/core/node/constraints/attribute_uniqueness.py +3 -1
- infrahub/core/node/node_property_attribute.py +230 -0
- infrahub/core/node/standard.py +1 -1
- infrahub/core/protocols.py +7 -1
- infrahub/core/query/node.py +14 -1
- infrahub/core/registry.py +2 -2
- infrahub/core/relationship/constraints/count.py +1 -1
- infrahub/core/relationship/model.py +1 -1
- 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 +14 -1
- infrahub/core/schema/generated/base_node_schema.py +6 -1
- infrahub/core/schema/node_schema.py +5 -2
- infrahub/core/schema/schema_branch.py +134 -0
- 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 +7 -0
- infrahub/generators/tasks.py +31 -15
- infrahub/git/integrator.py +22 -14
- infrahub/graphql/analyzer.py +1 -1
- 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/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/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 +15 -4
- infrahub/middleware.py +26 -1
- infrahub/proposed_change/tasks.py +10 -1
- infrahub/server.py +16 -3
- infrahub/services/__init__.py +8 -5
- infrahub/trigger/catalogue.py +4 -0
- infrahub/trigger/models.py +2 -0
- infrahub/trigger/tasks.py +3 -0
- infrahub/workflows/catalogue.py +72 -0
- infrahub/workflows/initialization.py +16 -0
- infrahub_sdk/checks.py +1 -1
- 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 +6 -2
- 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 +38 -0
- infrahub_sdk/schema/repository.py +8 -0
- infrahub_sdk/spec/object.py +84 -10
- 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.0b1.dist-info}/METADATA +5 -4
- {infrahub_server-1.5.0b0.dist-info → infrahub_server-1.5.0b1.dist-info}/RECORD +104 -79
- infrahub_testcontainers/container.py +1 -1
- infrahub_testcontainers/docker-compose-cluster.test.yml +1 -1
- infrahub_testcontainers/docker-compose.test.yml +1 -1
- {infrahub_server-1.5.0b0.dist-info → infrahub_server-1.5.0b1.dist-info}/LICENSE.txt +0 -0
- {infrahub_server-1.5.0b0.dist-info → infrahub_server-1.5.0b1.dist-info}/WHEEL +0 -0
- {infrahub_server-1.5.0b0.dist-info → infrahub_server-1.5.0b1.dist-info}/entry_points.txt +0 -0
infrahub/actions/tasks.py
CHANGED
|
@@ -61,6 +61,12 @@ def get_generator_run_query(definition_id: str, target_ids: list[str]) -> Query:
|
|
|
61
61
|
"parameters": {
|
|
62
62
|
"value": None,
|
|
63
63
|
},
|
|
64
|
+
"execute_in_proposed_change": {
|
|
65
|
+
"value": None,
|
|
66
|
+
},
|
|
67
|
+
"execute_after_merge": {
|
|
68
|
+
"value": None,
|
|
69
|
+
},
|
|
64
70
|
"targets": {
|
|
65
71
|
"node": {
|
|
66
72
|
"id": None,
|
|
@@ -287,6 +293,8 @@ async def _run_generators(
|
|
|
287
293
|
convert_query_response=data["convert_query_response"]["value"],
|
|
288
294
|
group_id=data["targets"]["node"]["id"],
|
|
289
295
|
parameters=data["parameters"]["value"],
|
|
296
|
+
execute_in_proposed_change=data["execute_in_proposed_change"]["value"],
|
|
297
|
+
execute_after_merge=data["execute_after_merge"]["value"],
|
|
290
298
|
),
|
|
291
299
|
commit=data["repository"]["node"]["commit"]["value"],
|
|
292
300
|
repository_id=data["repository"]["node"]["id"],
|
infrahub/api/diff/diff.py
CHANGED
|
@@ -52,7 +52,7 @@ async def get_diff_files(
|
|
|
52
52
|
for branch_name, items in diff_files.items():
|
|
53
53
|
for item in items:
|
|
54
54
|
repository_id = item.repository.get_id()
|
|
55
|
-
display_label = await item.repository.
|
|
55
|
+
display_label = await item.repository.get_display_label(db=db)
|
|
56
56
|
if repository_id not in response[branch_name]:
|
|
57
57
|
response[branch_name][repository_id] = BranchDiffRepository(
|
|
58
58
|
id=repository_id,
|
infrahub/cli/db.py
CHANGED
|
@@ -54,6 +54,7 @@ from infrahub.log import get_logger
|
|
|
54
54
|
|
|
55
55
|
from .constants import ERROR_BADGE, FAILED_BADGE, SUCCESS_BADGE
|
|
56
56
|
from .db_commands.check_inheritance import check_inheritance
|
|
57
|
+
from .db_commands.clean_duplicate_schema_fields import clean_duplicate_schema_fields
|
|
57
58
|
from .patch import patch_app
|
|
58
59
|
|
|
59
60
|
|
|
@@ -200,6 +201,29 @@ async def check_inheritance_cmd(
|
|
|
200
201
|
await dbdriver.close()
|
|
201
202
|
|
|
202
203
|
|
|
204
|
+
@app.command(name="check-duplicate-schema-fields")
|
|
205
|
+
async def check_duplicate_schema_fields_cmd(
|
|
206
|
+
ctx: typer.Context,
|
|
207
|
+
fix: bool = typer.Option(False, help="Fix the duplicate schema fields on the default branch."),
|
|
208
|
+
config_file: str = typer.Argument("infrahub.toml", envvar="INFRAHUB_CONFIG"),
|
|
209
|
+
) -> None:
|
|
210
|
+
"""Check for any duplicate schema attributes or relationships on the default branch"""
|
|
211
|
+
logging.getLogger("infrahub").setLevel(logging.WARNING)
|
|
212
|
+
logging.getLogger("neo4j").setLevel(logging.ERROR)
|
|
213
|
+
logging.getLogger("prefect").setLevel(logging.ERROR)
|
|
214
|
+
|
|
215
|
+
config.load_and_exit(config_file_name=config_file)
|
|
216
|
+
|
|
217
|
+
context: CliContext = ctx.obj
|
|
218
|
+
dbdriver = await context.init_db(retry=1)
|
|
219
|
+
|
|
220
|
+
success = await clean_duplicate_schema_fields(db=dbdriver, fix=fix)
|
|
221
|
+
if not success:
|
|
222
|
+
raise typer.Exit(code=1)
|
|
223
|
+
|
|
224
|
+
await dbdriver.close()
|
|
225
|
+
|
|
226
|
+
|
|
203
227
|
@app.command(name="update-core-schema")
|
|
204
228
|
async def update_core_schema_cmd(
|
|
205
229
|
ctx: typer.Context,
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from enum import Enum
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from rich import print as rprint
|
|
6
|
+
from rich.console import Console
|
|
7
|
+
from rich.table import Table
|
|
8
|
+
|
|
9
|
+
from infrahub.cli.constants import FAILED_BADGE, SUCCESS_BADGE
|
|
10
|
+
from infrahub.core.query import Query, QueryType
|
|
11
|
+
from infrahub.database import InfrahubDatabase
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class SchemaFieldType(str, Enum):
|
|
15
|
+
ATTRIBUTE = "attribute"
|
|
16
|
+
RELATIONSHIP = "relationship"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class SchemaFieldDetails:
|
|
21
|
+
schema_kind: str
|
|
22
|
+
schema_uuid: str
|
|
23
|
+
field_type: SchemaFieldType
|
|
24
|
+
field_name: str
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class DuplicateSchemaFields(Query):
|
|
28
|
+
async def query_init(self, db: InfrahubDatabase, **kwargs: dict[str, Any]) -> None: # noqa: ARG002
|
|
29
|
+
query = """
|
|
30
|
+
MATCH (root:Root)
|
|
31
|
+
LIMIT 1
|
|
32
|
+
WITH root.default_branch AS default_branch
|
|
33
|
+
MATCH (field:SchemaAttribute|SchemaRelationship)
|
|
34
|
+
CALL (default_branch, field) {
|
|
35
|
+
MATCH (field)-[is_part_of:IS_PART_OF]->(:Root)
|
|
36
|
+
WHERE is_part_of.branch = default_branch
|
|
37
|
+
ORDER BY is_part_of.from DESC
|
|
38
|
+
RETURN is_part_of
|
|
39
|
+
LIMIT 1
|
|
40
|
+
}
|
|
41
|
+
WITH default_branch, field, CASE
|
|
42
|
+
WHEN is_part_of.status = "active" AND is_part_of.to IS NULL THEN is_part_of.from
|
|
43
|
+
ELSE NULL
|
|
44
|
+
END AS active_from
|
|
45
|
+
WHERE active_from IS NOT NULL
|
|
46
|
+
WITH default_branch, field, active_from, "SchemaAttribute" IN labels(field) AS is_attribute
|
|
47
|
+
CALL (field, default_branch) {
|
|
48
|
+
MATCH (field)-[r1:HAS_ATTRIBUTE]->(:Attribute {name: "name"})-[r2:HAS_VALUE]->(name_value:AttributeValue)
|
|
49
|
+
WHERE r1.branch = default_branch AND r2.branch = default_branch
|
|
50
|
+
AND r1.status = "active" AND r2.status = "active"
|
|
51
|
+
AND r1.to IS NULL AND r2.to IS NULL
|
|
52
|
+
ORDER BY r1.from DESC, r1.status ASC, r2.from DESC, r2.status ASC
|
|
53
|
+
LIMIT 1
|
|
54
|
+
RETURN name_value.value AS field_name
|
|
55
|
+
}
|
|
56
|
+
CALL (field, default_branch) {
|
|
57
|
+
MATCH (field)-[r1:IS_RELATED]-(rel:Relationship)-[r2:IS_RELATED]-(peer:SchemaNode|SchemaGeneric)
|
|
58
|
+
WHERE rel.name IN ["schema__node__relationships", "schema__node__attributes"]
|
|
59
|
+
AND r1.branch = default_branch AND r2.branch = default_branch
|
|
60
|
+
AND r1.status = "active" AND r2.status = "active"
|
|
61
|
+
AND r1.to IS NULL AND r2.to IS NULL
|
|
62
|
+
ORDER BY r1.from DESC, r1.status ASC, r2.from DESC, r2.status ASC
|
|
63
|
+
LIMIT 1
|
|
64
|
+
RETURN peer AS schema_vertex
|
|
65
|
+
}
|
|
66
|
+
WITH default_branch, field, field_name, is_attribute, active_from, schema_vertex
|
|
67
|
+
ORDER BY active_from DESC
|
|
68
|
+
WITH default_branch, field_name, is_attribute, schema_vertex, collect(field) AS fields_reverse_chron
|
|
69
|
+
WHERE size(fields_reverse_chron) > 1
|
|
70
|
+
"""
|
|
71
|
+
self.add_to_query(query)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class GetDuplicateSchemaFields(DuplicateSchemaFields):
|
|
75
|
+
"""
|
|
76
|
+
Get the kind, field type, and field name for any duplicated attributes or relationships on a given schema
|
|
77
|
+
on the default branch
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
name = "get_duplicate_schema_fields"
|
|
81
|
+
type = QueryType.READ
|
|
82
|
+
insert_return = False
|
|
83
|
+
|
|
84
|
+
async def query_init(self, db: InfrahubDatabase, **kwargs: dict[str, Any]) -> None:
|
|
85
|
+
await super().query_init(db=db, **kwargs)
|
|
86
|
+
query = """
|
|
87
|
+
CALL (schema_vertex, default_branch) {
|
|
88
|
+
MATCH (schema_vertex)-[r1:HAS_ATTRIBUTE]->(:Attribute {name: "namespace"})-[r2:HAS_VALUE]->(name_value:AttributeValue)
|
|
89
|
+
WHERE r1.branch = default_branch AND r2.branch = default_branch
|
|
90
|
+
ORDER BY r1.from DESC, r1.status ASC, r2.from DESC, r2.status ASC
|
|
91
|
+
LIMIT 1
|
|
92
|
+
RETURN name_value.value AS schema_namespace
|
|
93
|
+
}
|
|
94
|
+
CALL (schema_vertex, default_branch) {
|
|
95
|
+
MATCH (schema_vertex)-[r1:HAS_ATTRIBUTE]->(:Attribute {name: "name"})-[r2:HAS_VALUE]->(name_value:AttributeValue)
|
|
96
|
+
WHERE r1.branch = default_branch AND r2.branch = default_branch
|
|
97
|
+
ORDER BY r1.from DESC, r1.status ASC, r2.from DESC, r2.status ASC
|
|
98
|
+
LIMIT 1
|
|
99
|
+
RETURN name_value.value AS schema_name
|
|
100
|
+
}
|
|
101
|
+
RETURN schema_namespace + schema_name AS schema_kind, schema_vertex.uuid AS schema_uuid, field_name, is_attribute
|
|
102
|
+
ORDER BY schema_kind ASC, is_attribute DESC, field_name ASC
|
|
103
|
+
"""
|
|
104
|
+
self.return_labels = ["schema_kind", "schema_uuid", "field_name", "is_attribute"]
|
|
105
|
+
self.add_to_query(query)
|
|
106
|
+
|
|
107
|
+
def get_schema_field_details(self) -> list[SchemaFieldDetails]:
|
|
108
|
+
schema_field_details: list[SchemaFieldDetails] = []
|
|
109
|
+
for result in self.results:
|
|
110
|
+
schema_kind = result.get_as_type(label="schema_kind", return_type=str)
|
|
111
|
+
schema_uuid = result.get_as_type(label="schema_uuid", return_type=str)
|
|
112
|
+
field_name = result.get_as_type(label="field_name", return_type=str)
|
|
113
|
+
is_attribute = result.get_as_type(label="is_attribute", return_type=bool)
|
|
114
|
+
schema_field_details.append(
|
|
115
|
+
SchemaFieldDetails(
|
|
116
|
+
schema_kind=schema_kind,
|
|
117
|
+
schema_uuid=schema_uuid,
|
|
118
|
+
field_name=field_name,
|
|
119
|
+
field_type=SchemaFieldType.ATTRIBUTE if is_attribute else SchemaFieldType.RELATIONSHIP,
|
|
120
|
+
)
|
|
121
|
+
)
|
|
122
|
+
return schema_field_details
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class FixDuplicateSchemaFields(DuplicateSchemaFields):
|
|
126
|
+
"""
|
|
127
|
+
Fix the duplicate schema fields by hard deleting the earlier duplicate(s)
|
|
128
|
+
"""
|
|
129
|
+
|
|
130
|
+
name = "fix_duplicate_schema_fields"
|
|
131
|
+
type = QueryType.WRITE
|
|
132
|
+
insert_return = False
|
|
133
|
+
|
|
134
|
+
async def query_init(self, db: InfrahubDatabase, **kwargs: dict[str, Any]) -> None:
|
|
135
|
+
await super().query_init(db=db, **kwargs)
|
|
136
|
+
query = """
|
|
137
|
+
WITH default_branch, tail(fields_reverse_chron) AS fields_to_delete
|
|
138
|
+
UNWIND fields_to_delete AS field_to_delete
|
|
139
|
+
CALL (field_to_delete, default_branch) {
|
|
140
|
+
MATCH (field_to_delete)-[r:IS_PART_OF {branch: default_branch}]-()
|
|
141
|
+
DELETE r
|
|
142
|
+
WITH field_to_delete
|
|
143
|
+
MATCH (field_to_delete)-[:IS_RELATED {branch: default_branch}]-(rel:Relationship)
|
|
144
|
+
WITH DISTINCT field_to_delete, rel
|
|
145
|
+
MATCH (rel)-[r {branch: default_branch}]-()
|
|
146
|
+
DELETE r
|
|
147
|
+
WITH field_to_delete, rel
|
|
148
|
+
OPTIONAL MATCH (rel)
|
|
149
|
+
WHERE NOT exists((rel)--())
|
|
150
|
+
DELETE rel
|
|
151
|
+
WITH DISTINCT field_to_delete
|
|
152
|
+
MATCH (field_to_delete)-[:HAS_ATTRIBUTE {branch: default_branch}]->(attr:Attribute)
|
|
153
|
+
MATCH (attr)-[r {branch: default_branch}]-()
|
|
154
|
+
DELETE r
|
|
155
|
+
WITH field_to_delete, attr
|
|
156
|
+
OPTIONAL MATCH (attr)
|
|
157
|
+
WHERE NOT exists((attr)--())
|
|
158
|
+
DELETE attr
|
|
159
|
+
WITH DISTINCT field_to_delete
|
|
160
|
+
OPTIONAL MATCH (field_to_delete)
|
|
161
|
+
WHERE NOT exists((field_to_delete)--())
|
|
162
|
+
DELETE field_to_delete
|
|
163
|
+
}
|
|
164
|
+
"""
|
|
165
|
+
self.add_to_query(query)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def display_duplicate_schema_fields(duplicate_schema_fields: list[SchemaFieldDetails]) -> None:
|
|
169
|
+
console = Console()
|
|
170
|
+
|
|
171
|
+
table = Table(title="Duplicate Schema Fields on Default Branch")
|
|
172
|
+
|
|
173
|
+
table.add_column("Schema Kind")
|
|
174
|
+
table.add_column("Schema UUID")
|
|
175
|
+
table.add_column("Field Name")
|
|
176
|
+
table.add_column("Field Type")
|
|
177
|
+
|
|
178
|
+
for duplicate_schema_field in duplicate_schema_fields:
|
|
179
|
+
table.add_row(
|
|
180
|
+
duplicate_schema_field.schema_kind,
|
|
181
|
+
duplicate_schema_field.schema_uuid,
|
|
182
|
+
duplicate_schema_field.field_name,
|
|
183
|
+
duplicate_schema_field.field_type.value,
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
console.print(table)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
async def clean_duplicate_schema_fields(db: InfrahubDatabase, fix: bool = False) -> bool:
|
|
190
|
+
"""
|
|
191
|
+
Identify any attributes or relationships that are duplicated in a schema on the default branch
|
|
192
|
+
If fix is True, runs cypher queries to hard delete the earlier duplicate
|
|
193
|
+
"""
|
|
194
|
+
|
|
195
|
+
duplicate_schema_fields_query = await GetDuplicateSchemaFields.init(db=db)
|
|
196
|
+
await duplicate_schema_fields_query.execute(db=db)
|
|
197
|
+
duplicate_schema_fields = duplicate_schema_fields_query.get_schema_field_details()
|
|
198
|
+
|
|
199
|
+
if not duplicate_schema_fields:
|
|
200
|
+
rprint(f"{SUCCESS_BADGE} No duplicate schema fields found")
|
|
201
|
+
return True
|
|
202
|
+
|
|
203
|
+
display_duplicate_schema_fields(duplicate_schema_fields)
|
|
204
|
+
|
|
205
|
+
if not fix:
|
|
206
|
+
rprint(f"{FAILED_BADGE} Use the --fix flag to fix the duplicate schema fields")
|
|
207
|
+
return False
|
|
208
|
+
|
|
209
|
+
fix_duplicate_schema_fields_query = await FixDuplicateSchemaFields.init(db=db)
|
|
210
|
+
await fix_duplicate_schema_fields_query.execute(db=db)
|
|
211
|
+
rprint(f"{SUCCESS_BADGE} Duplicate schema fields deleted from the default branch")
|
|
212
|
+
return True
|
infrahub/core/attribute.py
CHANGED
|
@@ -36,7 +36,7 @@ from .schema.attribute_parameters import NumberAttributeParameters
|
|
|
36
36
|
if TYPE_CHECKING:
|
|
37
37
|
from infrahub.core.branch import Branch
|
|
38
38
|
from infrahub.core.node import Node
|
|
39
|
-
from infrahub.core.schema import AttributeSchema
|
|
39
|
+
from infrahub.core.schema import AttributeSchema, MainSchemaTypes
|
|
40
40
|
from infrahub.database import InfrahubDatabase
|
|
41
41
|
|
|
42
42
|
|
|
@@ -630,7 +630,7 @@ class BaseAttribute(FlagPropertyMixin, NodePropertyMixin):
|
|
|
630
630
|
return AttributeDBNodeType.DEFAULT
|
|
631
631
|
return AttributeDBNodeType.INDEXED
|
|
632
632
|
|
|
633
|
-
def get_create_data(self) -> AttributeCreateData:
|
|
633
|
+
def get_create_data(self, node_schema: MainSchemaTypes) -> AttributeCreateData:
|
|
634
634
|
branch = self.branch
|
|
635
635
|
hierarchy_level = branch.hierarchy_level
|
|
636
636
|
if self.schema.branch == BranchSupportType.AGNOSTIC:
|
|
@@ -645,7 +645,7 @@ class BaseAttribute(FlagPropertyMixin, NodePropertyMixin):
|
|
|
645
645
|
branch=branch.name,
|
|
646
646
|
status="active",
|
|
647
647
|
branch_level=hierarchy_level,
|
|
648
|
-
branch_support=self.schema.branch.value,
|
|
648
|
+
branch_support=self.schema.branch.value if self.schema.branch is not None else node_schema.branch,
|
|
649
649
|
content=self.to_db(),
|
|
650
650
|
is_default=self.is_default,
|
|
651
651
|
is_protected=self.is_protected,
|
infrahub/core/branch/tasks.py
CHANGED
|
@@ -33,6 +33,7 @@ from infrahub.events.branch_action import BranchCreatedEvent, BranchDeletedEvent
|
|
|
33
33
|
from infrahub.events.models import EventMeta, InfrahubEvent
|
|
34
34
|
from infrahub.events.node_action import get_node_event
|
|
35
35
|
from infrahub.exceptions import BranchNotFoundError, ValidationError
|
|
36
|
+
from infrahub.generators.constants import GeneratorDefinitionRunSource
|
|
36
37
|
from infrahub.graphql.mutations.models import BranchCreateModel # noqa: TC001
|
|
37
38
|
from infrahub.workers.dependencies import get_component, get_database, get_event_service, get_workflow
|
|
38
39
|
from infrahub.workflows.catalogue import (
|
|
@@ -437,7 +438,7 @@ async def post_process_branch_merge(source_branch: str, target_branch: str, cont
|
|
|
437
438
|
await get_workflow().submit_workflow(
|
|
438
439
|
workflow=TRIGGER_GENERATOR_DEFINITION_RUN,
|
|
439
440
|
context=context,
|
|
440
|
-
parameters={"branch": target_branch},
|
|
441
|
+
parameters={"branch": target_branch, "source": GeneratorDefinitionRunSource.MERGE},
|
|
441
442
|
)
|
|
442
443
|
|
|
443
444
|
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,
|
|
@@ -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 = 42
|
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
|
|
|
@@ -42,6 +42,8 @@ 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
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
|
|
45
47
|
|
|
46
48
|
if TYPE_CHECKING:
|
|
47
49
|
from infrahub.core.root import Root
|
|
@@ -89,6 +91,8 @@ MIGRATIONS: list[type[GraphMigration | InternalSchemaMigration | ArbitraryMigrat
|
|
|
89
91
|
Migration038,
|
|
90
92
|
Migration039,
|
|
91
93
|
Migration040,
|
|
94
|
+
Migration041,
|
|
95
|
+
Migration042,
|
|
92
96
|
]
|
|
93
97
|
|
|
94
98
|
|
|
@@ -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)
|