infrahub-server 1.1.6__py3-none-any.whl → 1.1.8__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/core/attribute.py +4 -1
- infrahub/core/branch/tasks.py +7 -4
- infrahub/core/diff/combiner.py +11 -7
- infrahub/core/diff/coordinator.py +49 -70
- infrahub/core/diff/data_check_synchronizer.py +86 -7
- infrahub/core/diff/enricher/aggregated.py +3 -3
- infrahub/core/diff/enricher/cardinality_one.py +6 -6
- infrahub/core/diff/enricher/hierarchy.py +17 -4
- infrahub/core/diff/enricher/labels.py +18 -3
- infrahub/core/diff/enricher/path_identifier.py +7 -8
- infrahub/core/diff/merger/merger.py +5 -3
- infrahub/core/diff/model/path.py +66 -25
- infrahub/core/diff/parent_node_adder.py +78 -0
- infrahub/core/diff/payload_builder.py +13 -2
- infrahub/core/diff/query/all_conflicts.py +5 -2
- infrahub/core/diff/query/diff_get.py +2 -1
- infrahub/core/diff/query/field_specifiers.py +2 -0
- infrahub/core/diff/query/field_summary.py +2 -1
- infrahub/core/diff/query/filters.py +12 -1
- infrahub/core/diff/query/has_conflicts_query.py +5 -2
- infrahub/core/diff/query/{drop_tracking_id.py → merge_tracking_id.py} +3 -3
- infrahub/core/diff/query/roots_metadata.py +8 -1
- infrahub/core/diff/query/save.py +230 -139
- infrahub/core/diff/query/summary_counts_enricher.py +267 -0
- infrahub/core/diff/query/time_range_query.py +2 -1
- infrahub/core/diff/query_parser.py +49 -24
- infrahub/core/diff/repository/deserializer.py +31 -27
- infrahub/core/diff/repository/repository.py +215 -41
- infrahub/core/diff/tasks.py +4 -4
- infrahub/core/graph/__init__.py +1 -1
- infrahub/core/graph/index.py +3 -0
- infrahub/core/migrations/graph/__init__.py +4 -0
- infrahub/core/migrations/graph/m019_restore_rels_to_time.py +256 -0
- infrahub/core/migrations/graph/m020_duplicate_edges.py +160 -0
- infrahub/core/migrations/query/node_duplicate.py +38 -18
- infrahub/core/migrations/schema/node_remove.py +26 -12
- infrahub/core/migrations/shared.py +10 -8
- infrahub/core/node/__init__.py +19 -9
- infrahub/core/node/constraints/grouped_uniqueness.py +25 -5
- infrahub/core/node/ipam.py +6 -1
- infrahub/core/node/permissions.py +4 -0
- infrahub/core/query/attribute.py +2 -0
- infrahub/core/query/diff.py +41 -3
- infrahub/core/query/node.py +74 -21
- infrahub/core/query/relationship.py +107 -17
- infrahub/core/query/resource_manager.py +5 -1
- infrahub/core/relationship/model.py +8 -12
- infrahub/core/schema/definitions/core.py +1 -0
- infrahub/core/utils.py +1 -0
- infrahub/core/validators/uniqueness/query.py +20 -17
- infrahub/database/__init__.py +14 -0
- infrahub/dependencies/builder/constraint/grouped/node_runner.py +0 -2
- infrahub/dependencies/builder/diff/coordinator.py +0 -2
- infrahub/dependencies/builder/diff/deserializer.py +3 -1
- infrahub/dependencies/builder/diff/enricher/hierarchy.py +3 -1
- infrahub/dependencies/builder/diff/parent_node_adder.py +8 -0
- infrahub/graphql/mutations/computed_attribute.py +3 -1
- infrahub/graphql/mutations/diff.py +41 -10
- infrahub/graphql/mutations/main.py +11 -6
- infrahub/graphql/mutations/relationship.py +29 -1
- infrahub/graphql/mutations/resource_manager.py +3 -3
- infrahub/graphql/mutations/tasks.py +6 -3
- infrahub/graphql/queries/resource_manager.py +7 -3
- infrahub/permissions/__init__.py +2 -1
- infrahub/permissions/types.py +26 -0
- infrahub_sdk/client.py +10 -2
- infrahub_sdk/config.py +3 -0
- infrahub_sdk/ctl/check.py +3 -3
- infrahub_sdk/ctl/cli_commands.py +16 -11
- infrahub_sdk/ctl/exceptions.py +0 -6
- infrahub_sdk/ctl/exporter.py +1 -1
- infrahub_sdk/ctl/generator.py +5 -5
- infrahub_sdk/ctl/importer.py +3 -2
- infrahub_sdk/ctl/menu.py +1 -1
- infrahub_sdk/ctl/object.py +1 -1
- infrahub_sdk/ctl/repository.py +23 -15
- infrahub_sdk/ctl/schema.py +2 -2
- infrahub_sdk/ctl/utils.py +4 -3
- infrahub_sdk/ctl/validate.py +2 -1
- infrahub_sdk/exceptions.py +12 -0
- infrahub_sdk/generator.py +3 -0
- infrahub_sdk/node.py +7 -4
- infrahub_sdk/testing/schemas/animal.py +9 -0
- infrahub_sdk/utils.py +11 -1
- infrahub_sdk/yaml.py +2 -3
- {infrahub_server-1.1.6.dist-info → infrahub_server-1.1.8.dist-info}/METADATA +41 -7
- {infrahub_server-1.1.6.dist-info → infrahub_server-1.1.8.dist-info}/RECORD +94 -91
- infrahub_testcontainers/container.py +12 -3
- infrahub_testcontainers/docker-compose.test.yml +22 -3
- infrahub_testcontainers/haproxy.cfg +43 -0
- infrahub_testcontainers/helpers.py +85 -1
- infrahub/core/diff/enricher/summary_counts.py +0 -105
- infrahub/dependencies/builder/diff/enricher/summary_counts.py +0 -8
- infrahub_sdk/ctl/_file.py +0 -13
- {infrahub_server-1.1.6.dist-info → infrahub_server-1.1.8.dist-info}/LICENSE.txt +0 -0
- {infrahub_server-1.1.6.dist-info → infrahub_server-1.1.8.dist-info}/WHEEL +0 -0
- {infrahub_server-1.1.6.dist-info → infrahub_server-1.1.8.dist-info}/entry_points.txt +0 -0
|
@@ -884,6 +884,8 @@ class RelationshipManager:
|
|
|
884
884
|
"""If the attribute is branch aware, return the Branch object associated with this attribute
|
|
885
885
|
If the attribute is branch agnostic return the Global Branch
|
|
886
886
|
|
|
887
|
+
Note that if this relationship is Aware and source node is Agnostic, it will return -global- branch.
|
|
888
|
+
|
|
887
889
|
Returns:
|
|
888
890
|
Branch:
|
|
889
891
|
"""
|
|
@@ -959,7 +961,7 @@ class RelationshipManager:
|
|
|
959
961
|
self.has_fetched_relationships = True
|
|
960
962
|
|
|
961
963
|
for peer_id in details.peer_ids_present_local_only:
|
|
962
|
-
await self.
|
|
964
|
+
await self.remove_locally(peer_id=peer_id, db=db)
|
|
963
965
|
|
|
964
966
|
async def get(self, db: InfrahubDatabase) -> Relationship | list[Relationship] | None:
|
|
965
967
|
rels = await self.get_relationships(db=db)
|
|
@@ -1077,22 +1079,17 @@ class RelationshipManager:
|
|
|
1077
1079
|
for rel in self._relationships:
|
|
1078
1080
|
await rel.resolve(db=db)
|
|
1079
1081
|
|
|
1080
|
-
async def
|
|
1082
|
+
async def remove_locally(
|
|
1081
1083
|
self,
|
|
1082
1084
|
peer_id: Union[str, UUID],
|
|
1083
1085
|
db: InfrahubDatabase,
|
|
1084
|
-
update_db: bool = False,
|
|
1085
1086
|
) -> bool:
|
|
1086
|
-
"""Remove a peer id from the local relationships list
|
|
1087
|
-
need to investigate if and when we should update the relationship in the database."""
|
|
1087
|
+
"""Remove a peer id from the local relationships list"""
|
|
1088
1088
|
|
|
1089
1089
|
for idx, rel in enumerate(await self.get_relationships(db=db)):
|
|
1090
1090
|
if str(rel.peer_id) != str(peer_id):
|
|
1091
1091
|
continue
|
|
1092
1092
|
|
|
1093
|
-
if update_db:
|
|
1094
|
-
await rel.delete(db=db)
|
|
1095
|
-
|
|
1096
1093
|
self._relationships.pop(idx)
|
|
1097
1094
|
return True
|
|
1098
1095
|
|
|
@@ -1109,14 +1106,13 @@ class RelationshipManager:
|
|
|
1109
1106
|
|
|
1110
1107
|
# - Update the existing relationship if we are on the same branch
|
|
1111
1108
|
rel_ids_per_branch = peer_data.rel_ids_per_branch()
|
|
1109
|
+
|
|
1110
|
+
# In which cases do we end up here and do not want to set `to` time?
|
|
1112
1111
|
if branch.name in rel_ids_per_branch:
|
|
1113
1112
|
await update_relationships_to([str(ri) for ri in rel_ids_per_branch[branch.name]], to=remove_at, db=db)
|
|
1114
1113
|
|
|
1115
1114
|
# - Create a new rel of type DELETED if the existing relationship is on a different branch
|
|
1116
|
-
|
|
1117
|
-
if peer_data.rels:
|
|
1118
|
-
rel_branches = {r.branch for r in peer_data.rels}
|
|
1119
|
-
if rel_branches == {peer_data.branch}:
|
|
1115
|
+
if peer_data.rels and {r.branch for r in peer_data.rels} == {peer_data.branch}:
|
|
1120
1116
|
return
|
|
1121
1117
|
|
|
1122
1118
|
query = await RelationshipDataDeleteQuery.init(
|
infrahub/core/utils.py
CHANGED
|
@@ -30,7 +30,7 @@ class NodeUniqueAttributeConstraintQuery(Query):
|
|
|
30
30
|
def get_context(self) -> dict[str, str]:
|
|
31
31
|
return {"kind": self.query_request.kind}
|
|
32
32
|
|
|
33
|
-
async def query_init(self, db: InfrahubDatabase, **kwargs: Any) -> None:
|
|
33
|
+
async def query_init(self, db: InfrahubDatabase, **kwargs: Any) -> None: # pylint: disable=too-many-branches
|
|
34
34
|
branch_filter, branch_params = self.branch.get_query_filter_path(at=self.at.to_string(), is_isolated=False)
|
|
35
35
|
self.params.update(branch_params)
|
|
36
36
|
from_times = db.render_list_comprehension(items="relationships(potential_path)", item_name="from")
|
|
@@ -56,6 +56,7 @@ class NodeUniqueAttributeConstraintQuery(Query):
|
|
|
56
56
|
relationship_names = set()
|
|
57
57
|
relationship_attr_paths = []
|
|
58
58
|
relationship_only_attr_paths = []
|
|
59
|
+
relationship_only_attr_values = []
|
|
59
60
|
relationship_attr_paths_with_value = []
|
|
60
61
|
for rel_path in self.query_request.relationship_attribute_paths:
|
|
61
62
|
relationship_names.add(rel_path.identifier)
|
|
@@ -67,6 +68,8 @@ class NodeUniqueAttributeConstraintQuery(Query):
|
|
|
67
68
|
relationship_attr_paths.append((rel_path.identifier, rel_path.attribute_name))
|
|
68
69
|
else:
|
|
69
70
|
relationship_only_attr_paths.append(rel_path.identifier)
|
|
71
|
+
if rel_path.value:
|
|
72
|
+
relationship_only_attr_values.append(rel_path.value)
|
|
70
73
|
|
|
71
74
|
if (
|
|
72
75
|
not attr_paths
|
|
@@ -89,34 +92,37 @@ class NodeUniqueAttributeConstraintQuery(Query):
|
|
|
89
92
|
"relationship_attr_paths": relationship_attr_paths,
|
|
90
93
|
"relationship_attr_paths_with_value": relationship_attr_paths_with_value,
|
|
91
94
|
"relationship_only_attr_paths": relationship_only_attr_paths,
|
|
95
|
+
"relationship_only_attr_values": relationship_only_attr_values,
|
|
92
96
|
"min_count_required": self.min_count_required,
|
|
93
97
|
}
|
|
94
98
|
)
|
|
95
99
|
|
|
96
100
|
attr_paths_subquery = """
|
|
97
|
-
|
|
98
|
-
MATCH attr_path = (start_node)-[:HAS_ATTRIBUTE]->(attr:Attribute)-[r:HAS_VALUE]->(attr_value:AttributeValue)
|
|
101
|
+
MATCH attr_path = (start_node:%(node_kind)s)-[:HAS_ATTRIBUTE]->(attr:Attribute)-[r:HAS_VALUE]->(attr_value:AttributeValue)
|
|
99
102
|
WHERE attr.name in $attribute_names
|
|
100
103
|
AND ([attr.name, type(r)] in $attr_paths
|
|
101
104
|
OR [attr.name, type(r), attr_value.value] in $attr_paths_with_value)
|
|
102
|
-
RETURN attr_path as potential_path, NULL as rel_identifier, attr.name as potential_attr, attr_value.value as potential_attr_value
|
|
103
|
-
"""
|
|
105
|
+
RETURN start_node, attr_path as potential_path, NULL as rel_identifier, attr.name as potential_attr, attr_value.value as potential_attr_value
|
|
106
|
+
""" % {"node_kind": self.query_request.kind}
|
|
104
107
|
|
|
105
108
|
relationship_attr_paths_with_value_subquery = """
|
|
106
|
-
|
|
107
|
-
MATCH rel_path = (start_node)-[:IS_RELATED]-(relationship_node:Relationship)-[:IS_RELATED]-(related_n:Node)-[:HAS_ATTRIBUTE]->(rel_attr:Attribute)-[:HAS_VALUE]->(rel_attr_value:AttributeValue)
|
|
109
|
+
MATCH rel_path = (start_node:%(node_kind)s)-[:IS_RELATED]-(relationship_node:Relationship)-[:IS_RELATED]-(related_n:Node)-[:HAS_ATTRIBUTE]->(rel_attr:Attribute)-[:HAS_VALUE]->(rel_attr_value:AttributeValue)
|
|
108
110
|
WHERE relationship_node.name in $relationship_names
|
|
109
111
|
AND ([relationship_node.name, rel_attr.name] in $relationship_attr_paths
|
|
110
112
|
OR [relationship_node.name, rel_attr.name, rel_attr_value.value] in $relationship_attr_paths_with_value)
|
|
111
|
-
RETURN rel_path as potential_path, relationship_node.name as rel_identifier, rel_attr.name as potential_attr, rel_attr_value.value as potential_attr_value
|
|
112
|
-
"""
|
|
113
|
+
RETURN start_node, rel_path as potential_path, relationship_node.name as rel_identifier, rel_attr.name as potential_attr, rel_attr_value.value as potential_attr_value
|
|
114
|
+
""" % {"node_kind": self.query_request.kind}
|
|
113
115
|
|
|
114
116
|
relationship_only_attr_paths_subquery = """
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
117
|
+
MATCH rel_path = (start_node:%(node_kind)s)-[:IS_RELATED]-(relationship_node:Relationship)-[:IS_RELATED]-(related_n:Node)
|
|
118
|
+
WHERE %(rel_node_filter)s relationship_node.name in $relationship_only_attr_paths
|
|
119
|
+
RETURN start_node, rel_path as potential_path, relationship_node.name as rel_identifier, "id" as potential_attr, related_n.uuid as potential_attr_value
|
|
120
|
+
""" % {
|
|
121
|
+
"node_kind": self.query_request.kind,
|
|
122
|
+
"rel_node_filter": "related_n.uuid IN $relationship_only_attr_values AND "
|
|
123
|
+
if relationship_only_attr_values
|
|
124
|
+
else "",
|
|
125
|
+
}
|
|
120
126
|
|
|
121
127
|
select_subqueries = []
|
|
122
128
|
if attr_paths or attr_paths_with_value:
|
|
@@ -130,8 +136,6 @@ class NodeUniqueAttributeConstraintQuery(Query):
|
|
|
130
136
|
|
|
131
137
|
# ruff: noqa: E501
|
|
132
138
|
query = """
|
|
133
|
-
// group by node
|
|
134
|
-
MATCH (start_node:%(node_kind)s)
|
|
135
139
|
// get attributes for node and its relationships
|
|
136
140
|
CALL {
|
|
137
141
|
%(select_subqueries_str)s
|
|
@@ -201,7 +205,6 @@ class NodeUniqueAttributeConstraintQuery(Query):
|
|
|
201
205
|
attr_value,
|
|
202
206
|
relationship_identifier
|
|
203
207
|
""" % {
|
|
204
|
-
"node_kind": self.query_request.kind,
|
|
205
208
|
"select_subqueries_str": select_subqueries_str,
|
|
206
209
|
"branch_filter": branch_filter,
|
|
207
210
|
"from_times": from_times,
|
infrahub/database/__init__.py
CHANGED
|
@@ -173,6 +173,19 @@ class InfrahubDatabase:
|
|
|
173
173
|
elif self.db_type == DatabaseType.MEMGRAPH:
|
|
174
174
|
self.manager = DatabaseManagerMemgraph(db=self)
|
|
175
175
|
|
|
176
|
+
def __del__(self) -> None:
|
|
177
|
+
if not self._session or not self._is_session_local or self._session.closed():
|
|
178
|
+
return
|
|
179
|
+
|
|
180
|
+
try:
|
|
181
|
+
loop = asyncio.get_running_loop()
|
|
182
|
+
except RuntimeError:
|
|
183
|
+
loop = None
|
|
184
|
+
if loop and loop.is_running():
|
|
185
|
+
loop.create_task(self._session.close())
|
|
186
|
+
else:
|
|
187
|
+
asyncio.run(self._session.close())
|
|
188
|
+
|
|
176
189
|
@property
|
|
177
190
|
def is_session(self) -> bool:
|
|
178
191
|
if self._mode == InfrahubDatabaseMode.SESSION:
|
|
@@ -501,6 +514,7 @@ def retry_db_transaction(
|
|
|
501
514
|
if exc.code != "Neo.ClientError.Statement.EntityNotFound":
|
|
502
515
|
raise exc
|
|
503
516
|
retry_time: float = random.randrange(100, 500) / 1000
|
|
517
|
+
log.exception("Retry handler caught database error")
|
|
504
518
|
log.info(
|
|
505
519
|
f"Retrying database transaction, attempt {attempt}/{config.SETTINGS.database.retry_limit}",
|
|
506
520
|
retry_time=retry_time,
|
|
@@ -2,7 +2,6 @@ from infrahub.core.constraint.node.runner import NodeConstraintRunner
|
|
|
2
2
|
from infrahub.dependencies.interface import DependencyBuilder, DependencyBuilderContext
|
|
3
3
|
|
|
4
4
|
from ..node.grouped_uniqueness import NodeGroupedUniquenessConstraintDependency
|
|
5
|
-
from ..node.uniqueness import NodeAttributeUniquenessConstraintDependency
|
|
6
5
|
from ..relationship_manager.count import RelationshipCountConstraintDependency
|
|
7
6
|
from ..relationship_manager.peer_kind import RelationshipPeerKindConstraintDependency
|
|
8
7
|
from ..relationship_manager.profiles_kind import RelationshipProfilesKindConstraintDependency
|
|
@@ -15,7 +14,6 @@ class NodeConstraintRunnerDependency(DependencyBuilder[NodeConstraintRunner]):
|
|
|
15
14
|
db=context.db,
|
|
16
15
|
branch=context.branch,
|
|
17
16
|
node_constraints=[
|
|
18
|
-
NodeAttributeUniquenessConstraintDependency.build(context=context),
|
|
19
17
|
NodeGroupedUniquenessConstraintDependency.build(context=context),
|
|
20
18
|
],
|
|
21
19
|
relationship_manager_constraints=[
|
|
@@ -8,7 +8,6 @@ from .conflicts_enricher import DiffConflictsEnricherDependency
|
|
|
8
8
|
from .data_check_synchronizer import DiffDataCheckSynchronizerDependency
|
|
9
9
|
from .enricher.aggregated import DiffAggregatedEnricherDependency
|
|
10
10
|
from .enricher.labels import DiffLabelsEnricherDependency
|
|
11
|
-
from .enricher.summary_counts import DiffSummaryCountsEnricherDependency
|
|
12
11
|
from .repository import DiffRepositoryDependency
|
|
13
12
|
|
|
14
13
|
|
|
@@ -22,7 +21,6 @@ class DiffCoordinatorDependency(DependencyBuilder[DiffCoordinator]):
|
|
|
22
21
|
diff_enricher=DiffAggregatedEnricherDependency.build(context=context),
|
|
23
22
|
conflicts_enricher=DiffConflictsEnricherDependency.build(context=context),
|
|
24
23
|
labels_enricher=DiffLabelsEnricherDependency.build(context=context),
|
|
25
|
-
summary_counts_enricher=DiffSummaryCountsEnricherDependency.build(context=context),
|
|
26
24
|
data_check_synchronizer=DiffDataCheckSynchronizerDependency.build(context=context),
|
|
27
25
|
conflict_transferer=DiffConflictTransfererDependency.build(context=context),
|
|
28
26
|
)
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
from infrahub.core.diff.repository.deserializer import EnrichedDiffDeserializer
|
|
2
2
|
from infrahub.dependencies.interface import DependencyBuilder, DependencyBuilderContext
|
|
3
3
|
|
|
4
|
+
from .parent_node_adder import DiffParentNodeAdderDependency
|
|
5
|
+
|
|
4
6
|
|
|
5
7
|
class DiffDeserializerDependency(DependencyBuilder[EnrichedDiffDeserializer]):
|
|
6
8
|
@classmethod
|
|
7
9
|
def build(cls, context: DependencyBuilderContext) -> EnrichedDiffDeserializer:
|
|
8
|
-
return EnrichedDiffDeserializer()
|
|
10
|
+
return EnrichedDiffDeserializer(parent_adder=DiffParentNodeAdderDependency.build(context=context))
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
from infrahub.core.diff.enricher.hierarchy import DiffHierarchyEnricher
|
|
2
2
|
from infrahub.dependencies.interface import DependencyBuilder, DependencyBuilderContext
|
|
3
3
|
|
|
4
|
+
from ..parent_node_adder import DiffParentNodeAdderDependency
|
|
5
|
+
|
|
4
6
|
|
|
5
7
|
class DiffHierarchyEnricherDependency(DependencyBuilder[DiffHierarchyEnricher]):
|
|
6
8
|
@classmethod
|
|
7
9
|
def build(cls, context: DependencyBuilderContext) -> DiffHierarchyEnricher:
|
|
8
|
-
return DiffHierarchyEnricher(db=context.db)
|
|
10
|
+
return DiffHierarchyEnricher(db=context.db, parent_adder=DiffParentNodeAdderDependency.build(context=context))
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
from infrahub.core.diff.parent_node_adder import DiffParentNodeAdder
|
|
2
|
+
from infrahub.dependencies.interface import DependencyBuilder, DependencyBuilderContext
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class DiffParentNodeAdderDependency(DependencyBuilder[DiffParentNodeAdder]):
|
|
6
|
+
@classmethod
|
|
7
|
+
def build(cls, context: DependencyBuilderContext) -> DiffParentNodeAdder:
|
|
8
|
+
return DiffParentNodeAdder()
|
|
@@ -87,7 +87,9 @@ class UpdateComputedAttribute(Mutation):
|
|
|
87
87
|
log_data = get_log_data()
|
|
88
88
|
request_id = log_data.get("request_id", "")
|
|
89
89
|
|
|
90
|
-
graphql_payload = await target_node.to_graphql(
|
|
90
|
+
graphql_payload = await target_node.to_graphql(
|
|
91
|
+
db=context.db, filter_sensitive=True, include_properties=False
|
|
92
|
+
)
|
|
91
93
|
|
|
92
94
|
event = NodeMutatedEvent(
|
|
93
95
|
branch=context.branch.name,
|
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
from typing import TYPE_CHECKING
|
|
2
2
|
|
|
3
|
-
from graphene import Boolean, DateTime, InputObjectType, Mutation, String
|
|
3
|
+
from graphene import Boolean, DateTime, Field, InputObjectType, Mutation, String
|
|
4
4
|
from graphql import GraphQLResolveInfo
|
|
5
5
|
|
|
6
6
|
from infrahub.core import registry
|
|
7
7
|
from infrahub.core.diff.coordinator import DiffCoordinator
|
|
8
|
+
from infrahub.core.diff.model.path import NameTrackingId
|
|
8
9
|
from infrahub.core.diff.models import RequestDiffUpdate
|
|
9
|
-
from infrahub.
|
|
10
|
+
from infrahub.core.diff.repository.repository import DiffRepository
|
|
11
|
+
from infrahub.core.timestamp import Timestamp
|
|
10
12
|
from infrahub.dependencies.registry import get_component_registry
|
|
13
|
+
from infrahub.exceptions import ValidationError
|
|
11
14
|
from infrahub.workflows.catalogue import DIFF_UPDATE
|
|
12
15
|
|
|
16
|
+
from ..types.task import TaskInfo
|
|
17
|
+
|
|
13
18
|
if TYPE_CHECKING:
|
|
14
19
|
from ..initialization import GraphqlContext
|
|
15
20
|
|
|
@@ -19,32 +24,57 @@ class DiffUpdateInput(InputObjectType):
|
|
|
19
24
|
name = String(required=False)
|
|
20
25
|
from_time = DateTime(required=False)
|
|
21
26
|
to_time = DateTime(required=False)
|
|
22
|
-
wait_for_completion = Boolean(required=False)
|
|
27
|
+
wait_for_completion = Boolean(required=False, deprecation_reason="Please use `wait_until_completion` instead")
|
|
23
28
|
|
|
24
29
|
|
|
25
30
|
class DiffUpdateMutation(Mutation):
|
|
26
31
|
class Arguments:
|
|
27
32
|
data = DiffUpdateInput(required=True)
|
|
33
|
+
wait_until_completion = Boolean(required=False)
|
|
28
34
|
|
|
29
35
|
ok = Boolean()
|
|
36
|
+
task = Field(TaskInfo, required=False)
|
|
30
37
|
|
|
31
38
|
@classmethod
|
|
32
|
-
@retry_db_transaction(name="diff_update")
|
|
33
39
|
async def mutate(
|
|
34
40
|
cls,
|
|
35
41
|
root: dict, # pylint: disable=unused-argument
|
|
36
42
|
info: GraphQLResolveInfo,
|
|
37
43
|
data: DiffUpdateInput,
|
|
38
|
-
|
|
44
|
+
wait_until_completion: bool = False,
|
|
45
|
+
) -> dict[str, bool | dict[str, str]]:
|
|
39
46
|
context: GraphqlContext = info.context
|
|
40
47
|
|
|
48
|
+
if data.wait_for_completion is True:
|
|
49
|
+
wait_until_completion = True
|
|
50
|
+
|
|
41
51
|
from_timestamp_str = DateTime.serialize(data.from_time) if data.from_time else None
|
|
42
52
|
to_timestamp_str = DateTime.serialize(data.to_time) if data.to_time else None
|
|
43
|
-
if data.
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
53
|
+
if (data.from_time or data.to_time) and not data.name:
|
|
54
|
+
raise ValidationError("diff with specified time range requires a name")
|
|
55
|
+
|
|
56
|
+
component_registry = get_component_registry()
|
|
57
|
+
base_branch = await registry.get_branch(db=context.db, branch=registry.default_branch)
|
|
58
|
+
diff_branch = await registry.get_branch(db=context.db, branch=data.branch)
|
|
59
|
+
diff_repository = await component_registry.get_component(DiffRepository, db=context.db, branch=diff_branch)
|
|
60
|
+
|
|
61
|
+
tracking_id = NameTrackingId(name=data.name)
|
|
62
|
+
existing_diffs_metadatas = await diff_repository.get_roots_metadata(
|
|
63
|
+
diff_branch_names=[diff_branch.name], base_branch_names=[base_branch.name], tracking_id=tracking_id
|
|
64
|
+
)
|
|
65
|
+
if existing_diffs_metadatas:
|
|
66
|
+
metadata = existing_diffs_metadatas[0]
|
|
67
|
+
from_time = Timestamp(from_timestamp_str) if from_timestamp_str else None
|
|
68
|
+
to_time = Timestamp(to_timestamp_str) if to_timestamp_str else None
|
|
69
|
+
branched_from_timestamp = Timestamp(diff_branch.get_branched_from())
|
|
70
|
+
if from_time and from_time > metadata.from_time:
|
|
71
|
+
raise ValidationError(f"from_time must be null or less than or equal to {metadata.from_time}")
|
|
72
|
+
if from_time and from_time < branched_from_timestamp:
|
|
73
|
+
raise ValidationError(f"from_time must be null or greater than or equal to {branched_from_timestamp}")
|
|
74
|
+
if to_time and to_time < metadata.to_time:
|
|
75
|
+
raise ValidationError(f"to_time must be null or greater than or equal to {metadata.to_time}")
|
|
47
76
|
|
|
77
|
+
if wait_until_completion is True:
|
|
48
78
|
diff_coordinator = await component_registry.get_component(
|
|
49
79
|
DiffCoordinator, db=context.db, branch=diff_branch
|
|
50
80
|
)
|
|
@@ -65,6 +95,7 @@ class DiffUpdateMutation(Mutation):
|
|
|
65
95
|
to_time=to_timestamp_str,
|
|
66
96
|
)
|
|
67
97
|
if context.service:
|
|
68
|
-
await context.service.workflow.submit_workflow(workflow=DIFF_UPDATE, parameters={"model": model})
|
|
98
|
+
workflow = await context.service.workflow.submit_workflow(workflow=DIFF_UPDATE, parameters={"model": model})
|
|
99
|
+
return {"ok": True, "task": {"id": str(workflow.id)}}
|
|
69
100
|
|
|
70
101
|
return {"ok": True}
|
|
@@ -97,7 +97,7 @@ class InfrahubMutationMixin:
|
|
|
97
97
|
log_data = get_log_data()
|
|
98
98
|
request_id = log_data.get("request_id", "")
|
|
99
99
|
|
|
100
|
-
graphql_payload = await obj.to_graphql(db=context.db, filter_sensitive=True)
|
|
100
|
+
graphql_payload = await obj.to_graphql(db=context.db, filter_sensitive=True, include_properties=False)
|
|
101
101
|
event = NodeMutatedEvent(
|
|
102
102
|
branch=context.branch.name,
|
|
103
103
|
kind=obj._schema.kind,
|
|
@@ -175,20 +175,25 @@ class InfrahubMutationMixin:
|
|
|
175
175
|
branch: Branch,
|
|
176
176
|
) -> Node:
|
|
177
177
|
component_registry = get_component_registry()
|
|
178
|
-
node_constraint_runner = await component_registry.get_component(
|
|
178
|
+
node_constraint_runner = await component_registry.get_component(
|
|
179
|
+
NodeConstraintRunner, db=db.start_session(), branch=branch
|
|
180
|
+
)
|
|
179
181
|
node_class = Node
|
|
180
182
|
if cls._meta.schema.kind in registry.node:
|
|
181
183
|
node_class = registry.node[cls._meta.schema.kind]
|
|
182
184
|
|
|
185
|
+
fields_to_validate = list(data)
|
|
183
186
|
try:
|
|
184
|
-
obj = await node_class.init(db=db, schema=cls._meta.schema, branch=branch)
|
|
185
|
-
await obj.new(db=db, **data)
|
|
186
|
-
fields_to_validate = list(data)
|
|
187
|
-
await node_constraint_runner.check(node=obj, field_filters=fields_to_validate)
|
|
188
187
|
if db.is_transaction:
|
|
188
|
+
obj = await node_class.init(db=db, schema=cls._meta.schema, branch=branch)
|
|
189
|
+
await obj.new(db=db, **data)
|
|
190
|
+
await node_constraint_runner.check(node=obj, field_filters=fields_to_validate)
|
|
189
191
|
await obj.save(db=db)
|
|
190
192
|
else:
|
|
191
193
|
async with db.start_transaction() as dbt:
|
|
194
|
+
obj = await node_class.init(db=dbt, schema=cls._meta.schema, branch=branch)
|
|
195
|
+
await obj.new(db=dbt, **data)
|
|
196
|
+
await node_constraint_runner.check(node=obj, field_filters=fields_to_validate)
|
|
192
197
|
await obj.save(db=dbt)
|
|
193
198
|
|
|
194
199
|
except ValidationError as exc:
|
|
@@ -5,7 +5,8 @@ from typing import TYPE_CHECKING
|
|
|
5
5
|
from graphene import Boolean, InputField, InputObjectType, List, Mutation, String
|
|
6
6
|
from infrahub_sdk.utils import compare_lists
|
|
7
7
|
|
|
8
|
-
from infrahub.core.
|
|
8
|
+
from infrahub.core.account import GlobalPermission, ObjectPermission
|
|
9
|
+
from infrahub.core.constants import InfrahubKind, PermissionAction, PermissionDecision, RelationshipCardinality
|
|
9
10
|
from infrahub.core.manager import NodeManager
|
|
10
11
|
from infrahub.core.query.relationship import (
|
|
11
12
|
RelationshipGetPeerQuery,
|
|
@@ -14,6 +15,7 @@ from infrahub.core.query.relationship import (
|
|
|
14
15
|
from infrahub.core.relationship import Relationship
|
|
15
16
|
from infrahub.database import retry_db_transaction
|
|
16
17
|
from infrahub.exceptions import NodeNotFoundError, ValidationError
|
|
18
|
+
from infrahub.permissions import get_global_permission_for_kind
|
|
17
19
|
|
|
18
20
|
from ..types import RelatedNodeInput
|
|
19
21
|
|
|
@@ -76,6 +78,32 @@ class RelationshipMixin:
|
|
|
76
78
|
db=context.db, ids=node_ids, fields={"display_label": None}, branch=context.branch
|
|
77
79
|
)
|
|
78
80
|
|
|
81
|
+
if context.account_session:
|
|
82
|
+
impacted_schemas = {node.get_schema() for node in [source] + list(nodes.values())}
|
|
83
|
+
required_permissions: list[GlobalPermission | ObjectPermission] = []
|
|
84
|
+
decision = (
|
|
85
|
+
PermissionDecision.ALLOW_DEFAULT.value
|
|
86
|
+
if context.branch.is_default
|
|
87
|
+
else PermissionDecision.ALLOW_OTHER.value
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
for impacted_schema in impacted_schemas:
|
|
91
|
+
global_action = get_global_permission_for_kind(schema=impacted_schema)
|
|
92
|
+
|
|
93
|
+
if global_action:
|
|
94
|
+
required_permissions.append(GlobalPermission(action=global_action, decision=decision))
|
|
95
|
+
else:
|
|
96
|
+
required_permissions.append(
|
|
97
|
+
ObjectPermission(
|
|
98
|
+
namespace=impacted_schema.namespace,
|
|
99
|
+
name=impacted_schema.name,
|
|
100
|
+
action=PermissionAction.UPDATE.value,
|
|
101
|
+
decision=decision,
|
|
102
|
+
)
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
context.active_permissions.raise_for_permissions(permissions=required_permissions)
|
|
106
|
+
|
|
79
107
|
_, _, in_list2 = compare_lists(list1=list(nodes.keys()), list2=node_ids)
|
|
80
108
|
if in_list2:
|
|
81
109
|
for node_id in in_list2:
|
|
@@ -2,7 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from typing import TYPE_CHECKING, Any
|
|
4
4
|
|
|
5
|
-
from graphene import Boolean, Field, InputField, InputObjectType, Int, Mutation, String
|
|
5
|
+
from graphene import Boolean, Field, InputField, InputObjectType, Int, List, Mutation, String
|
|
6
6
|
from graphene.types.generic import GenericScalar
|
|
7
7
|
from typing_extensions import Self
|
|
8
8
|
|
|
@@ -30,7 +30,7 @@ if TYPE_CHECKING:
|
|
|
30
30
|
|
|
31
31
|
class IPPrefixPoolGetResourceInput(InputObjectType):
|
|
32
32
|
id = InputField(String(required=False), description="ID of the pool to allocate from")
|
|
33
|
-
hfid = InputField(String
|
|
33
|
+
hfid = InputField(List(of_type=String, required=False), description="HFID of the pool to allocate from")
|
|
34
34
|
identifier = InputField(String(required=False), description="Identifier for the allocated resource")
|
|
35
35
|
prefix_length = InputField(Int(required=False), description="Size of the prefix to allocate")
|
|
36
36
|
member_type = InputField(String(required=False), description="Type of members for the newly created prefix")
|
|
@@ -40,7 +40,7 @@ class IPPrefixPoolGetResourceInput(InputObjectType):
|
|
|
40
40
|
|
|
41
41
|
class IPAddressPoolGetResourceInput(InputObjectType):
|
|
42
42
|
id = InputField(String(required=False), description="ID of the pool to allocate from")
|
|
43
|
-
hfid = InputField(String
|
|
43
|
+
hfid = InputField(List(of_type=String, required=False), description="HFID of the pool to allocate from")
|
|
44
44
|
identifier = InputField(String(required=False), description="Identifier for the allocated resource")
|
|
45
45
|
prefix_length = InputField(
|
|
46
46
|
Int(required=False), description="Size of the prefix mask to allocate on the new IP address"
|
|
@@ -31,14 +31,17 @@ async def merge_branch_mutation(branch: str) -> None:
|
|
|
31
31
|
diff_coordinator = await component_registry.get_component(DiffCoordinator, db=db, branch=obj)
|
|
32
32
|
diff_repository = await component_registry.get_component(DiffRepository, db=db, branch=obj)
|
|
33
33
|
diff_merger = await component_registry.get_component(DiffMerger, db=db, branch=obj)
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
enriched_diff_metadata = await diff_coordinator.update_branch_diff(base_branch=base_branch, diff_branch=obj)
|
|
35
|
+
async for _ in diff_repository.get_all_conflicts_for_diff(
|
|
36
|
+
diff_branch_name=enriched_diff_metadata.diff_branch_name, diff_id=enriched_diff_metadata.uuid
|
|
37
|
+
):
|
|
38
|
+
# if there are any conflicts, raise the error
|
|
36
39
|
raise ValidationError(
|
|
37
40
|
f"Branch {obj.name} contains conflicts with the default branch."
|
|
38
41
|
" Please create a Proposed Change to resolve the conflicts or manually update them before merging."
|
|
39
42
|
)
|
|
40
43
|
node_diff_field_summaries = await diff_repository.get_node_field_summaries(
|
|
41
|
-
diff_branch_name=
|
|
44
|
+
diff_branch_name=enriched_diff_metadata.diff_branch_name, diff_id=enriched_diff_metadata.uuid
|
|
42
45
|
)
|
|
43
46
|
|
|
44
47
|
merger = BranchMerger(
|
|
@@ -21,8 +21,10 @@ from infrahub.pools.number import NumberUtilizationGetter
|
|
|
21
21
|
if TYPE_CHECKING:
|
|
22
22
|
from graphql import GraphQLResolveInfo
|
|
23
23
|
|
|
24
|
+
from infrahub.core.branch import Branch
|
|
24
25
|
from infrahub.core.node import Node
|
|
25
26
|
from infrahub.core.protocols import CoreNode
|
|
27
|
+
from infrahub.core.timestamp import Timestamp
|
|
26
28
|
from infrahub.database import InfrahubDatabase
|
|
27
29
|
from infrahub.graphql.initialization import GraphqlContext
|
|
28
30
|
|
|
@@ -184,7 +186,7 @@ class PoolUtilization(ObjectType):
|
|
|
184
186
|
pool: CoreNode | None = await NodeManager.get_one(id=pool_id, db=db, branch=context.branch)
|
|
185
187
|
pool = _validate_pool_type(pool_id=pool_id, pool=pool)
|
|
186
188
|
if pool.get_kind() == "CoreNumberPool":
|
|
187
|
-
return await resolve_number_pool_utilization(db=db,
|
|
189
|
+
return await resolve_number_pool_utilization(db=db, at=context.at, pool=pool, branch=context.branch)
|
|
188
190
|
|
|
189
191
|
resources_map: dict[str, Node] = {}
|
|
190
192
|
|
|
@@ -290,8 +292,10 @@ async def resolve_number_pool_allocation(
|
|
|
290
292
|
return response
|
|
291
293
|
|
|
292
294
|
|
|
293
|
-
async def resolve_number_pool_utilization(
|
|
294
|
-
|
|
295
|
+
async def resolve_number_pool_utilization(
|
|
296
|
+
db: InfrahubDatabase, pool: CoreNode, at: Timestamp | str | None, branch: Branch
|
|
297
|
+
) -> dict:
|
|
298
|
+
number_pool = NumberUtilizationGetter(db=db, pool=pool, at=at, branch=branch)
|
|
295
299
|
await number_pool.load_data()
|
|
296
300
|
|
|
297
301
|
return {
|
infrahub/permissions/__init__.py
CHANGED
|
@@ -2,12 +2,13 @@ from infrahub.permissions.backend import PermissionBackend
|
|
|
2
2
|
from infrahub.permissions.local_backend import LocalPermissionBackend
|
|
3
3
|
from infrahub.permissions.manager import PermissionManager
|
|
4
4
|
from infrahub.permissions.report import report_schema_permissions
|
|
5
|
-
from infrahub.permissions.types import AssignedPermissions
|
|
5
|
+
from infrahub.permissions.types import AssignedPermissions, get_global_permission_for_kind
|
|
6
6
|
|
|
7
7
|
__all__ = [
|
|
8
8
|
"AssignedPermissions",
|
|
9
9
|
"LocalPermissionBackend",
|
|
10
10
|
"PermissionBackend",
|
|
11
11
|
"PermissionManager",
|
|
12
|
+
"get_global_permission_for_kind",
|
|
12
13
|
"report_schema_permissions",
|
|
13
14
|
]
|
infrahub/permissions/types.py
CHANGED
|
@@ -2,8 +2,12 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from typing import TYPE_CHECKING, TypedDict
|
|
4
4
|
|
|
5
|
+
from infrahub.core.constants import GlobalPermissions, InfrahubKind
|
|
6
|
+
from infrahub.core.schema import NodeSchema
|
|
7
|
+
|
|
5
8
|
if TYPE_CHECKING:
|
|
6
9
|
from infrahub.core.account import GlobalPermission, ObjectPermission
|
|
10
|
+
from infrahub.core.schema import MainSchemaTypes
|
|
7
11
|
from infrahub.permissions.constants import BranchRelativePermissionDecision
|
|
8
12
|
|
|
9
13
|
|
|
@@ -18,3 +22,25 @@ class KindPermissions(TypedDict):
|
|
|
18
22
|
delete: BranchRelativePermissionDecision
|
|
19
23
|
update: BranchRelativePermissionDecision
|
|
20
24
|
view: BranchRelativePermissionDecision
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def get_global_permission_for_kind(schema: MainSchemaTypes) -> GlobalPermissions | None:
|
|
28
|
+
kind_permission_map = {
|
|
29
|
+
InfrahubKind.GENERICACCOUNT: GlobalPermissions.MANAGE_ACCOUNTS,
|
|
30
|
+
InfrahubKind.ACCOUNTGROUP: GlobalPermissions.MANAGE_ACCOUNTS,
|
|
31
|
+
InfrahubKind.ACCOUNTROLE: GlobalPermissions.MANAGE_ACCOUNTS,
|
|
32
|
+
InfrahubKind.BASEPERMISSION: GlobalPermissions.MANAGE_PERMISSIONS,
|
|
33
|
+
InfrahubKind.GENERICREPOSITORY: GlobalPermissions.MANAGE_REPOSITORIES,
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if schema.kind in kind_permission_map:
|
|
37
|
+
return kind_permission_map[schema.kind]
|
|
38
|
+
|
|
39
|
+
if isinstance(schema, NodeSchema):
|
|
40
|
+
for base in schema.inherit_from:
|
|
41
|
+
try:
|
|
42
|
+
return kind_permission_map[base]
|
|
43
|
+
except KeyError:
|
|
44
|
+
continue
|
|
45
|
+
|
|
46
|
+
return None
|