infrahub-server 1.2.10__py3-none-any.whl → 1.3.0a0__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/constants.py +86 -0
- infrahub/actions/gather.py +114 -0
- infrahub/actions/models.py +241 -0
- infrahub/actions/parsers.py +104 -0
- infrahub/actions/schema.py +382 -0
- infrahub/actions/tasks.py +126 -0
- infrahub/actions/triggers.py +21 -0
- infrahub/cli/db.py +1 -2
- infrahub/config.py +9 -0
- infrahub/core/account.py +24 -47
- infrahub/core/attribute.py +10 -12
- infrahub/core/constants/infrahubkind.py +8 -0
- infrahub/core/constraint/node/runner.py +1 -1
- infrahub/core/convert_object_type/__init__.py +0 -0
- infrahub/core/convert_object_type/conversion.py +122 -0
- infrahub/core/convert_object_type/schema_mapping.py +56 -0
- infrahub/core/diff/query/all_conflicts.py +1 -5
- infrahub/core/diff/query/artifact.py +10 -20
- infrahub/core/diff/query/diff_get.py +3 -6
- infrahub/core/diff/query/field_summary.py +2 -4
- infrahub/core/diff/query/merge.py +70 -123
- infrahub/core/diff/query/save.py +20 -32
- infrahub/core/diff/query/summary_counts_enricher.py +34 -54
- infrahub/core/diff/query_parser.py +5 -1
- infrahub/core/diff/tasks.py +3 -3
- infrahub/core/manager.py +14 -11
- infrahub/core/migrations/graph/m003_relationship_parent_optional.py +1 -2
- infrahub/core/migrations/graph/m013_convert_git_password_credential.py +2 -4
- infrahub/core/migrations/graph/m019_restore_rels_to_time.py +11 -22
- infrahub/core/migrations/graph/m020_duplicate_edges.py +3 -6
- infrahub/core/migrations/graph/m021_missing_hierarchy_merge.py +1 -2
- infrahub/core/migrations/graph/m024_missing_hierarchy_backfill.py +1 -2
- infrahub/core/migrations/query/attribute_add.py +1 -2
- infrahub/core/migrations/query/attribute_rename.py +3 -6
- infrahub/core/migrations/query/delete_element_in_schema.py +3 -6
- infrahub/core/migrations/query/node_duplicate.py +3 -6
- infrahub/core/migrations/query/relationship_duplicate.py +3 -6
- infrahub/core/migrations/schema/node_attribute_remove.py +3 -6
- infrahub/core/migrations/schema/node_remove.py +3 -6
- infrahub/core/models.py +29 -2
- infrahub/core/node/__init__.py +18 -4
- infrahub/core/node/create.py +211 -0
- infrahub/core/protocols.py +51 -0
- infrahub/core/protocols_base.py +3 -0
- infrahub/core/query/__init__.py +2 -2
- infrahub/core/query/diff.py +26 -32
- infrahub/core/query/ipam.py +10 -20
- infrahub/core/query/node.py +28 -46
- infrahub/core/query/relationship.py +51 -28
- infrahub/core/query/resource_manager.py +1 -2
- infrahub/core/query/subquery.py +2 -4
- infrahub/core/relationship/model.py +3 -0
- infrahub/core/schema/__init__.py +2 -1
- infrahub/core/schema/attribute_parameters.py +36 -0
- infrahub/core/schema/attribute_schema.py +83 -8
- infrahub/core/schema/basenode_schema.py +25 -1
- infrahub/core/schema/definitions/core/__init__.py +21 -0
- infrahub/core/schema/definitions/internal.py +13 -3
- infrahub/core/schema/generated/attribute_schema.py +9 -3
- infrahub/core/schema/schema_branch.py +12 -7
- infrahub/core/validators/__init__.py +5 -1
- infrahub/core/validators/attribute/choices.py +1 -2
- infrahub/core/validators/attribute/enum.py +1 -2
- infrahub/core/validators/attribute/kind.py +1 -2
- infrahub/core/validators/attribute/length.py +13 -6
- infrahub/core/validators/attribute/optional.py +1 -2
- infrahub/core/validators/attribute/regex.py +5 -5
- infrahub/core/validators/attribute/unique.py +1 -3
- infrahub/core/validators/determiner.py +18 -2
- infrahub/core/validators/enum.py +7 -0
- infrahub/core/validators/node/hierarchy.py +3 -6
- infrahub/core/validators/query.py +1 -3
- infrahub/core/validators/relationship/count.py +6 -12
- infrahub/core/validators/relationship/optional.py +2 -4
- infrahub/core/validators/relationship/peer.py +3 -8
- infrahub/core/validators/tasks.py +1 -1
- infrahub/core/validators/uniqueness/query.py +5 -9
- infrahub/database/__init__.py +1 -3
- infrahub/events/group_action.py +1 -0
- infrahub/graphql/analyzer.py +139 -18
- infrahub/graphql/app.py +1 -1
- infrahub/graphql/loaders/node.py +1 -1
- infrahub/graphql/loaders/peers.py +1 -1
- infrahub/graphql/manager.py +4 -0
- infrahub/graphql/mutations/action.py +164 -0
- infrahub/graphql/mutations/convert_object_type.py +62 -0
- infrahub/graphql/mutations/main.py +24 -175
- infrahub/graphql/mutations/proposed_change.py +21 -18
- infrahub/graphql/queries/convert_object_type_mapping.py +36 -0
- infrahub/graphql/queries/relationship.py +1 -1
- infrahub/graphql/resolvers/many_relationship.py +4 -4
- infrahub/graphql/resolvers/resolver.py +4 -4
- infrahub/graphql/resolvers/single_relationship.py +2 -2
- infrahub/graphql/schema.py +6 -0
- infrahub/graphql/subscription/graphql_query.py +2 -2
- infrahub/graphql/types/branch.py +1 -1
- infrahub/menu/menu.py +31 -0
- infrahub/message_bus/messages/__init__.py +0 -10
- infrahub/message_bus/operations/__init__.py +0 -8
- infrahub/message_bus/operations/refresh/registry.py +1 -1
- infrahub/patch/queries/consolidate_duplicated_nodes.py +3 -6
- infrahub/patch/queries/delete_duplicated_edges.py +5 -10
- infrahub/prefect_server/models.py +1 -19
- infrahub/proposed_change/models.py +68 -3
- infrahub/proposed_change/tasks.py +907 -30
- infrahub/task_manager/models.py +10 -6
- infrahub/telemetry/database.py +1 -1
- infrahub/telemetry/tasks.py +1 -1
- infrahub/trigger/catalogue.py +2 -0
- infrahub/trigger/models.py +18 -2
- infrahub/trigger/tasks.py +3 -1
- infrahub/workflows/catalogue.py +76 -0
- {infrahub_server-1.2.10.dist-info → infrahub_server-1.3.0a0.dist-info}/METADATA +2 -2
- {infrahub_server-1.2.10.dist-info → infrahub_server-1.3.0a0.dist-info}/RECORD +121 -118
- infrahub_testcontainers/container.py +0 -1
- infrahub_testcontainers/docker-compose.test.yml +1 -1
- infrahub_testcontainers/helpers.py +8 -2
- infrahub/message_bus/messages/check_generator_run.py +0 -26
- infrahub/message_bus/messages/finalize_validator_execution.py +0 -15
- infrahub/message_bus/messages/proposed_change/base_with_diff.py +0 -16
- infrahub/message_bus/messages/proposed_change/request_proposedchange_refreshartifacts.py +0 -11
- infrahub/message_bus/messages/request_generatordefinition_check.py +0 -20
- infrahub/message_bus/messages/request_proposedchange_pipeline.py +0 -23
- infrahub/message_bus/operations/check/__init__.py +0 -3
- infrahub/message_bus/operations/check/generator.py +0 -156
- infrahub/message_bus/operations/finalize/__init__.py +0 -3
- infrahub/message_bus/operations/finalize/validator.py +0 -133
- infrahub/message_bus/operations/requests/__init__.py +0 -9
- infrahub/message_bus/operations/requests/generator_definition.py +0 -140
- infrahub/message_bus/operations/requests/proposed_change.py +0 -629
- /infrahub/{message_bus/messages/proposed_change → actions}/__init__.py +0 -0
- {infrahub_server-1.2.10.dist-info → infrahub_server-1.3.0a0.dist-info}/LICENSE.txt +0 -0
- {infrahub_server-1.2.10.dist-info → infrahub_server-1.3.0a0.dist-info}/WHEEL +0 -0
- {infrahub_server-1.2.10.dist-info → infrahub_server-1.3.0a0.dist-info}/entry_points.txt +0 -0
|
@@ -1,14 +1,20 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING, Any
|
|
2
|
+
|
|
1
3
|
from infrahub.core.constants import RelationshipKind, SchemaPathType
|
|
2
4
|
from infrahub.core.constants.schema import UpdateSupport
|
|
3
5
|
from infrahub.core.diff.model.path import NodeDiffFieldSummary
|
|
4
6
|
from infrahub.core.models import SchemaUpdateConstraintInfo
|
|
5
7
|
from infrahub.core.path import SchemaPath
|
|
6
8
|
from infrahub.core.schema import AttributeSchema, MainSchemaTypes
|
|
9
|
+
from infrahub.core.schema.attribute_parameters import AttributeParameters
|
|
7
10
|
from infrahub.core.schema.relationship_schema import RelationshipSchema
|
|
8
11
|
from infrahub.core.schema.schema_branch import SchemaBranch
|
|
9
12
|
from infrahub.core.validators import CONSTRAINT_VALIDATOR_MAP
|
|
10
13
|
from infrahub.log import get_logger
|
|
11
14
|
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from pydantic.fields import FieldInfo
|
|
17
|
+
|
|
12
18
|
LOG = get_logger(__name__)
|
|
13
19
|
|
|
14
20
|
|
|
@@ -133,7 +139,17 @@ class ConstraintValidatorDeterminer:
|
|
|
133
139
|
self, schema: MainSchemaTypes, field: AttributeSchema | RelationshipSchema
|
|
134
140
|
) -> list[SchemaUpdateConstraintInfo]:
|
|
135
141
|
constraints: list[SchemaUpdateConstraintInfo] = []
|
|
136
|
-
|
|
142
|
+
prop_details_list: list[tuple[str, FieldInfo, Any]] = []
|
|
143
|
+
for p_name, p_info in field.model_fields.items():
|
|
144
|
+
p_value = getattr(field, p_name)
|
|
145
|
+
if isinstance(p_value, AttributeParameters):
|
|
146
|
+
for parameter_name, parameter_field_info in p_value.model_fields.items():
|
|
147
|
+
parameter_value = getattr(p_value, parameter_name)
|
|
148
|
+
prop_details_list.append((f"{p_name}.{parameter_name}", parameter_field_info, parameter_value))
|
|
149
|
+
else:
|
|
150
|
+
prop_details_list.append((p_name, p_info, p_value))
|
|
151
|
+
|
|
152
|
+
for prop_name, prop_field_info, prop_value in prop_details_list:
|
|
137
153
|
if not prop_field_info.json_schema_extra or not isinstance(prop_field_info.json_schema_extra, dict):
|
|
138
154
|
continue
|
|
139
155
|
|
|
@@ -141,7 +157,7 @@ class ConstraintValidatorDeterminer:
|
|
|
141
157
|
if prop_field_update != UpdateSupport.VALIDATE_CONSTRAINT.value:
|
|
142
158
|
continue
|
|
143
159
|
|
|
144
|
-
if
|
|
160
|
+
if prop_value is None:
|
|
145
161
|
continue
|
|
146
162
|
|
|
147
163
|
path_type = SchemaPathType.ATTRIBUTE
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class ConstraintIdentifier(str, Enum):
|
|
5
|
+
ATTRIBUTE_PARAMETERS_REGEX_UPDATE = "attribute.parameters.regex.update"
|
|
6
|
+
ATTRIBUTE_PARAMETERS_MIN_LENGTH_UPDATE = "attribute.parameters.min_length.update"
|
|
7
|
+
ATTRIBUTE_PARAMETERS_MAX_LENGTH_UPDATE = "attribute.parameters.max_length.update"
|
|
@@ -59,8 +59,7 @@ class NodeHierarchyUpdateValidatorQuery(SchemaValidatorQuery):
|
|
|
59
59
|
# ruff: noqa: E501
|
|
60
60
|
query = """
|
|
61
61
|
MATCH (n:%(node_kind)s)
|
|
62
|
-
CALL {
|
|
63
|
-
WITH n
|
|
62
|
+
CALL (n) {
|
|
64
63
|
MATCH path = (root:Root)<-[rroot:IS_PART_OF]-(n)
|
|
65
64
|
WHERE all(r in relationships(path) WHERE %(branch_filter)s)
|
|
66
65
|
RETURN path as full_path, n as active_node
|
|
@@ -70,8 +69,7 @@ class NodeHierarchyUpdateValidatorQuery(SchemaValidatorQuery):
|
|
|
70
69
|
WITH full_path, active_node
|
|
71
70
|
WITH full_path, active_node
|
|
72
71
|
WHERE all(r in relationships(full_path) WHERE r.status = "active")
|
|
73
|
-
CALL {
|
|
74
|
-
WITH active_node
|
|
72
|
+
CALL (active_node) {
|
|
75
73
|
MATCH path = (active_node)%(to_children)s-[hrel1:IS_RELATED]-%(to_parent)s(:Relationship {name: "parent__child"})%(to_children)s-[hrel2:IS_RELATED]-%(to_parent)s(peer:Node)
|
|
76
74
|
WHERE all(
|
|
77
75
|
r in relationships(path)
|
|
@@ -91,8 +89,7 @@ class NodeHierarchyUpdateValidatorQuery(SchemaValidatorQuery):
|
|
|
91
89
|
collect([branch_level_sum, from_times, active_relationship_count, hierarchy_path, deepest_branch_name]) as enriched_paths,
|
|
92
90
|
start_node,
|
|
93
91
|
peer_node
|
|
94
|
-
CALL {
|
|
95
|
-
WITH enriched_paths, peer_node
|
|
92
|
+
CALL (enriched_paths, peer_node) {
|
|
96
93
|
UNWIND enriched_paths as path_to_check
|
|
97
94
|
RETURN path_to_check[3] as current_path, path_to_check[4] as branch_name, peer_node as current_peer
|
|
98
95
|
ORDER BY
|
|
@@ -20,8 +20,7 @@ class NodeNotPresentValidatorQuery(SchemaValidatorQuery):
|
|
|
20
20
|
|
|
21
21
|
query = """
|
|
22
22
|
MATCH (n:%(node_kind)s)
|
|
23
|
-
CALL {
|
|
24
|
-
WITH n
|
|
23
|
+
CALL (n) {
|
|
25
24
|
MATCH path = (root:Root)<-[rr:IS_PART_OF]-(n)
|
|
26
25
|
WHERE all(
|
|
27
26
|
r in relationships(path)
|
|
@@ -32,7 +31,6 @@ class NodeNotPresentValidatorQuery(SchemaValidatorQuery):
|
|
|
32
31
|
LIMIT 1
|
|
33
32
|
}
|
|
34
33
|
WITH full_path, node, root_relationship
|
|
35
|
-
WITH full_path, node, root_relationship
|
|
36
34
|
WHERE all(r in relationships(full_path) WHERE r.status = "active")
|
|
37
35
|
""" % {"branch_filter": branch_filter, "node_kind": self.node_schema.kind}
|
|
38
36
|
|
|
@@ -50,8 +50,7 @@ class RelationshipCountUpdateValidatorQuery(RelationshipSchemaValidatorQuery):
|
|
|
50
50
|
query = """
|
|
51
51
|
// get the nodes on these branches nodes
|
|
52
52
|
MATCH (n:%(node_kind)s)
|
|
53
|
-
CALL {
|
|
54
|
-
WITH n
|
|
53
|
+
CALL (n) {
|
|
55
54
|
MATCH path = (root:Root)<-[rroot:IS_PART_OF]-(n)
|
|
56
55
|
WHERE all(r in relationships(path) WHERE %(branch_filter)s)
|
|
57
56
|
RETURN path as full_path, n as active_node
|
|
@@ -60,11 +59,9 @@ class RelationshipCountUpdateValidatorQuery(RelationshipSchemaValidatorQuery):
|
|
|
60
59
|
}
|
|
61
60
|
// filter to only the active nodes
|
|
62
61
|
WITH full_path, active_node
|
|
63
|
-
WITH full_path, active_node
|
|
64
62
|
WHERE all(r in relationships(full_path) WHERE r.status = "active")
|
|
65
63
|
// get the relationships using the given identifier for each node
|
|
66
|
-
CALL {
|
|
67
|
-
WITH active_node
|
|
64
|
+
CALL (active_node) {
|
|
68
65
|
MATCH path = (active_node)-[rrel1:IS_RELATED]-(rel:Relationship { name: $relationship_id })-[rrel2:IS_RELATED]-(peer:Node)
|
|
69
66
|
WHERE ($relationship_direction <> "outbound" OR (startNode(rrel1) = active_node AND startNode(rrel2) = rel))
|
|
70
67
|
AND ($relationship_direction <> "inbound" OR (startNode(rrel1) = rel AND startNode(rrel2) = peer))
|
|
@@ -87,8 +84,7 @@ class RelationshipCountUpdateValidatorQuery(RelationshipSchemaValidatorQuery):
|
|
|
87
84
|
start_node,
|
|
88
85
|
peer_node
|
|
89
86
|
// make sure to only use the latest version of this particular path
|
|
90
|
-
CALL {
|
|
91
|
-
WITH enriched_paths, peer_node
|
|
87
|
+
CALL (enriched_paths, peer_node) {
|
|
92
88
|
UNWIND enriched_paths as path_to_check
|
|
93
89
|
RETURN path_to_check[3] as current_path, path_to_check[4] as branch_name, peer_node as current_peer
|
|
94
90
|
ORDER BY
|
|
@@ -100,8 +96,7 @@ class RelationshipCountUpdateValidatorQuery(RelationshipSchemaValidatorQuery):
|
|
|
100
96
|
}
|
|
101
97
|
// filter to only the current active paths
|
|
102
98
|
WITH collect([current_peer, current_path]) as peers_and_paths, start_node, branch_name
|
|
103
|
-
CALL {
|
|
104
|
-
WITH peers_and_paths
|
|
99
|
+
CALL (peers_and_paths) {
|
|
105
100
|
UNWIND peers_and_paths AS peer_and_path
|
|
106
101
|
WITH peer_and_path
|
|
107
102
|
WHERE all(r in relationships(peer_and_path[1]) WHERE r.status = "active")
|
|
@@ -109,9 +104,8 @@ class RelationshipCountUpdateValidatorQuery(RelationshipSchemaValidatorQuery):
|
|
|
109
104
|
}
|
|
110
105
|
// sum all the relationships across branches and identify the violators
|
|
111
106
|
WITH collect([branch_name, num_relationships_on_branch]) as branches_and_counts, start_node
|
|
112
|
-
CALL {
|
|
113
|
-
WITH
|
|
114
|
-
WITH start_node, branches_and_counts, reduce(rel_total = 0, bnc in branches_and_counts | rel_total + bnc[1]) AS total_relationships_count
|
|
107
|
+
CALL (start_node, branches_and_counts) {
|
|
108
|
+
WITH reduce(rel_total = 0, bnc in branches_and_counts | rel_total + bnc[1]) AS total_relationships_count
|
|
115
109
|
WHERE
|
|
116
110
|
(toInteger($min_count) IS NOT NULL AND total_relationships_count < toInteger($min_count))
|
|
117
111
|
OR (toInteger($max_count) IS NOT NULL AND total_relationships_count > toInteger($max_count))
|
|
@@ -30,8 +30,7 @@ class RelationshipOptionalUpdateValidatorQuery(RelationshipSchemaValidatorQuery)
|
|
|
30
30
|
// Query all Active Nodes of type
|
|
31
31
|
// and store their UUID in uuids_active_node
|
|
32
32
|
MATCH (n:%(node_kind)s)
|
|
33
|
-
CALL {
|
|
34
|
-
WITH n
|
|
33
|
+
CALL (n) {
|
|
35
34
|
MATCH (root:Root)<-[r:IS_PART_OF]-(n)
|
|
36
35
|
WHERE %(branch_filter)s
|
|
37
36
|
RETURN n as n1, r as r1
|
|
@@ -44,8 +43,7 @@ class RelationshipOptionalUpdateValidatorQuery(RelationshipSchemaValidatorQuery)
|
|
|
44
43
|
// identifier all nodes with at least one active member for this relationship
|
|
45
44
|
// and store their UUID in uuids_with_rel
|
|
46
45
|
MATCH (n:%(node_kind)s)
|
|
47
|
-
CALL {
|
|
48
|
-
WITH n, uuids_active_node
|
|
46
|
+
CALL (n, uuids_active_node) {
|
|
49
47
|
MATCH path = (n)-[r:IS_RELATED]-(:Relationship { name: $relationship_id })
|
|
50
48
|
WHERE %(branch_filter)s
|
|
51
49
|
RETURN n as n1, r as r1
|
|
@@ -36,8 +36,7 @@ class RelationshipPeerUpdateValidatorQuery(RelationshipSchemaValidatorQuery):
|
|
|
36
36
|
# ruff: noqa: E501
|
|
37
37
|
query = """
|
|
38
38
|
MATCH (n:%(node_kind)s)
|
|
39
|
-
CALL {
|
|
40
|
-
WITH n
|
|
39
|
+
CALL (n) {
|
|
41
40
|
MATCH path = (root:Root)<-[rroot:IS_PART_OF]-(n)
|
|
42
41
|
WHERE all(r in relationships(path) WHERE %(branch_filter)s)
|
|
43
42
|
RETURN path as full_path, n as active_node
|
|
@@ -45,10 +44,8 @@ class RelationshipPeerUpdateValidatorQuery(RelationshipSchemaValidatorQuery):
|
|
|
45
44
|
LIMIT 1
|
|
46
45
|
}
|
|
47
46
|
WITH full_path, active_node
|
|
48
|
-
WITH full_path, active_node
|
|
49
47
|
WHERE all(r in relationships(full_path) WHERE r.status = "active")
|
|
50
|
-
CALL {
|
|
51
|
-
WITH active_node
|
|
48
|
+
CALL (active_node) {
|
|
52
49
|
MATCH path = (active_node)-[rrel1:IS_RELATED]-(rel:Relationship { name: $relationship_id })-[rrel2:IS_RELATED]-(peer:Node)
|
|
53
50
|
WHERE all(
|
|
54
51
|
r in relationships(path)
|
|
@@ -68,8 +65,7 @@ class RelationshipPeerUpdateValidatorQuery(RelationshipSchemaValidatorQuery):
|
|
|
68
65
|
collect([branch_level_sum, from_times, active_relationship_count, relationship_path, deepest_branch_name]) as enriched_paths,
|
|
69
66
|
start_node,
|
|
70
67
|
peer_node
|
|
71
|
-
CALL {
|
|
72
|
-
WITH enriched_paths, peer_node
|
|
68
|
+
CALL (enriched_paths, peer_node) {
|
|
73
69
|
UNWIND enriched_paths as path_to_check
|
|
74
70
|
RETURN path_to_check[3] as current_path, path_to_check[4] as branch_name, peer_node as current_peer
|
|
75
71
|
ORDER BY
|
|
@@ -80,7 +76,6 @@ class RelationshipPeerUpdateValidatorQuery(RelationshipSchemaValidatorQuery):
|
|
|
80
76
|
LIMIT 1
|
|
81
77
|
}
|
|
82
78
|
WITH start_node, current_peer, branch_name, current_path
|
|
83
|
-
WITH start_node, current_peer, branch_name, current_path
|
|
84
79
|
WHERE all(r in relationships(current_path) WHERE r.status = "active")
|
|
85
80
|
AND NOT any(label IN LABELS(current_peer) WHERE label IN $allowed_peer_kinds)
|
|
86
81
|
""" % {"branch_filter": branch_filter, "node_kind": self.node_schema.kind}
|
|
@@ -71,7 +71,7 @@ async def schema_path_validate(
|
|
|
71
71
|
schema_branch: SchemaBranch,
|
|
72
72
|
service: InfrahubServices,
|
|
73
73
|
) -> SchemaValidatorPathResponseData:
|
|
74
|
-
async with service.database.start_session() as db:
|
|
74
|
+
async with service.database.start_session(read_only=True) as db:
|
|
75
75
|
constraint_request = SchemaConstraintValidatorRequest(
|
|
76
76
|
branch=branch,
|
|
77
77
|
constraint_name=constraint_name,
|
|
@@ -158,12 +158,11 @@ class NodeUniqueAttributeConstraintQuery(Query):
|
|
|
158
158
|
# ruff: noqa: E501
|
|
159
159
|
query = """
|
|
160
160
|
// get attributes for node and its relationships
|
|
161
|
-
CALL {
|
|
161
|
+
CALL () {
|
|
162
162
|
%(select_subqueries_str)s
|
|
163
163
|
}
|
|
164
|
-
CALL {
|
|
164
|
+
CALL (potential_path) {
|
|
165
165
|
WITH potential_path
|
|
166
|
-
WITH potential_path // workaround for neo4j not allowing WHERE in a WITH of a subquery
|
|
167
166
|
// only the branches and times we care about
|
|
168
167
|
WHERE all(
|
|
169
168
|
r IN relationships(potential_path) WHERE (
|
|
@@ -183,8 +182,7 @@ class NodeUniqueAttributeConstraintQuery(Query):
|
|
|
183
182
|
start_node,
|
|
184
183
|
rel_identifier,
|
|
185
184
|
potential_attr
|
|
186
|
-
CALL {
|
|
187
|
-
WITH enriched_paths
|
|
185
|
+
CALL (enriched_paths) {
|
|
188
186
|
UNWIND enriched_paths as path_to_check
|
|
189
187
|
RETURN path_to_check[0] as current_path, path_to_check[4] as latest_value
|
|
190
188
|
ORDER BY
|
|
@@ -194,16 +192,14 @@ class NodeUniqueAttributeConstraintQuery(Query):
|
|
|
194
192
|
path_to_check[3] DESC
|
|
195
193
|
LIMIT 1
|
|
196
194
|
}
|
|
197
|
-
CALL {
|
|
195
|
+
CALL (current_path) {
|
|
198
196
|
// only active paths
|
|
199
197
|
WITH current_path
|
|
200
|
-
WITH current_path // workaround for neo4j not allowing WHERE in a WITH of a subquery
|
|
201
198
|
WHERE all(r IN relationships(current_path) WHERE r.status = "active")
|
|
202
199
|
RETURN current_path as active_path
|
|
203
200
|
}
|
|
204
|
-
CALL {
|
|
201
|
+
CALL (active_path) {
|
|
205
202
|
// get deepest branch name
|
|
206
|
-
WITH active_path
|
|
207
203
|
UNWIND %(branch_name_and_level)s as branch_name_and_level
|
|
208
204
|
RETURN branch_name_and_level[0] as branch_name
|
|
209
205
|
ORDER BY branch_name_and_level[1] DESC
|
infrahub/database/__init__.py
CHANGED
|
@@ -476,8 +476,6 @@ async def validate_database(
|
|
|
476
476
|
|
|
477
477
|
|
|
478
478
|
async def get_db(retry: int = 0) -> AsyncDriver:
|
|
479
|
-
URI = f"{config.SETTINGS.database.protocol}://{config.SETTINGS.database.address}:{config.SETTINGS.database.port}"
|
|
480
|
-
|
|
481
479
|
trusted_certificates = TrustSystemCAs()
|
|
482
480
|
if config.SETTINGS.database.tls_insecure:
|
|
483
481
|
trusted_certificates = TrustAll()
|
|
@@ -485,7 +483,7 @@ async def get_db(retry: int = 0) -> AsyncDriver:
|
|
|
485
483
|
trusted_certificates = TrustCustomCAs(config.SETTINGS.database.tls_ca_file)
|
|
486
484
|
|
|
487
485
|
driver = AsyncGraphDatabase.driver(
|
|
488
|
-
|
|
486
|
+
config.SETTINGS.database.database_uri,
|
|
489
487
|
auth=(config.SETTINGS.database.username, config.SETTINGS.database.password),
|
|
490
488
|
encrypted=config.SETTINGS.database.tls_enabled,
|
|
491
489
|
trusted_certificates=trusted_certificates,
|
infrahub/events/group_action.py
CHANGED
infrahub/graphql/analyzer.py
CHANGED
|
@@ -13,11 +13,27 @@ from graphql import (
|
|
|
13
13
|
FragmentSpreadNode,
|
|
14
14
|
GraphQLSchema,
|
|
15
15
|
InlineFragmentNode,
|
|
16
|
+
ListTypeNode,
|
|
16
17
|
NamedTypeNode,
|
|
17
18
|
NonNullTypeNode,
|
|
18
19
|
OperationDefinitionNode,
|
|
19
20
|
OperationType,
|
|
20
21
|
SelectionSetNode,
|
|
22
|
+
TypeNode,
|
|
23
|
+
)
|
|
24
|
+
from graphql.language.ast import (
|
|
25
|
+
BooleanValueNode,
|
|
26
|
+
ConstListValueNode,
|
|
27
|
+
ConstObjectValueNode,
|
|
28
|
+
EnumValueNode,
|
|
29
|
+
FloatValueNode,
|
|
30
|
+
IntValueNode,
|
|
31
|
+
ListValueNode,
|
|
32
|
+
NullValueNode,
|
|
33
|
+
ObjectValueNode,
|
|
34
|
+
StringValueNode,
|
|
35
|
+
ValueNode,
|
|
36
|
+
VariableNode,
|
|
21
37
|
)
|
|
22
38
|
from infrahub_sdk.analyzer import GraphQLQueryAnalyzer
|
|
23
39
|
from infrahub_sdk.utils import extract_fields
|
|
@@ -91,9 +107,24 @@ class GraphQLSelectionSet:
|
|
|
91
107
|
@dataclass
|
|
92
108
|
class GraphQLArgument:
|
|
93
109
|
name: str
|
|
94
|
-
value:
|
|
110
|
+
value: Any
|
|
95
111
|
kind: str
|
|
96
112
|
|
|
113
|
+
@property
|
|
114
|
+
def is_variable(self) -> bool:
|
|
115
|
+
return self.kind == "variable"
|
|
116
|
+
|
|
117
|
+
@property
|
|
118
|
+
def as_variable_name(self) -> str:
|
|
119
|
+
"""Return the name without a $ prefix"""
|
|
120
|
+
return str(self.value).removeprefix("$")
|
|
121
|
+
|
|
122
|
+
@property
|
|
123
|
+
def fields(self) -> list[str]:
|
|
124
|
+
if self.kind != "object_value" or not isinstance(self.value, dict):
|
|
125
|
+
return []
|
|
126
|
+
return sorted(self.value.keys())
|
|
127
|
+
|
|
97
128
|
|
|
98
129
|
@dataclass
|
|
99
130
|
class ObjectAccess:
|
|
@@ -106,6 +137,9 @@ class GraphQLVariable:
|
|
|
106
137
|
name: str
|
|
107
138
|
type: str
|
|
108
139
|
required: bool
|
|
140
|
+
is_list: bool = False
|
|
141
|
+
inner_required: bool = False
|
|
142
|
+
default: Any | None = None
|
|
109
143
|
|
|
110
144
|
|
|
111
145
|
@dataclass
|
|
@@ -266,6 +300,28 @@ class GraphQLQueryReport:
|
|
|
266
300
|
|
|
267
301
|
return fields
|
|
268
302
|
|
|
303
|
+
@cached_property
|
|
304
|
+
def variables(self) -> list[GraphQLVariable]:
|
|
305
|
+
"""Return input variables defined on the query document
|
|
306
|
+
|
|
307
|
+
All subqueries will use the same document level queries,
|
|
308
|
+
so only the first entry is required
|
|
309
|
+
"""
|
|
310
|
+
if self.queries:
|
|
311
|
+
return self.queries[0].variables
|
|
312
|
+
return []
|
|
313
|
+
|
|
314
|
+
def required_argument(self, argument: GraphQLArgument) -> bool:
|
|
315
|
+
if not argument.is_variable:
|
|
316
|
+
# If the argument isn't a variable it would have been
|
|
317
|
+
# statically defined in the input and as such required
|
|
318
|
+
return True
|
|
319
|
+
for variable in self.variables:
|
|
320
|
+
if variable.name == argument.as_variable_name and variable.required:
|
|
321
|
+
return True
|
|
322
|
+
|
|
323
|
+
return False
|
|
324
|
+
|
|
269
325
|
@cached_property
|
|
270
326
|
def top_level_kinds(self) -> list[str]:
|
|
271
327
|
return [query.infrahub_model.kind for query in self.queries if query.infrahub_model]
|
|
@@ -298,6 +354,22 @@ class GraphQLQueryReport:
|
|
|
298
354
|
|
|
299
355
|
return access
|
|
300
356
|
|
|
357
|
+
@property
|
|
358
|
+
def only_has_unique_targets(self) -> bool:
|
|
359
|
+
"""Indicate if the query document is defined so that it will return a single root level object"""
|
|
360
|
+
for query in self.queries:
|
|
361
|
+
targets_single_query = False
|
|
362
|
+
if query.infrahub_model and query.infrahub_model.uniqueness_constraints:
|
|
363
|
+
for argument in query.arguments:
|
|
364
|
+
if [[argument.name]] == query.infrahub_model.uniqueness_constraints:
|
|
365
|
+
if self.required_argument(argument=argument):
|
|
366
|
+
targets_single_query = True
|
|
367
|
+
|
|
368
|
+
if not targets_single_query:
|
|
369
|
+
return False
|
|
370
|
+
|
|
371
|
+
return True
|
|
372
|
+
|
|
301
373
|
|
|
302
374
|
class InfrahubGraphQLQueryAnalyzer(GraphQLQueryAnalyzer):
|
|
303
375
|
def __init__(
|
|
@@ -603,31 +675,80 @@ class InfrahubGraphQLQueryAnalyzer(GraphQLQueryAnalyzer):
|
|
|
603
675
|
],
|
|
604
676
|
)
|
|
605
677
|
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
for variable in operation.variable_definitions:
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
678
|
+
def _get_variables(self, operation: OperationDefinitionNode) -> list[GraphQLVariable]:
|
|
679
|
+
variables: list[GraphQLVariable] = []
|
|
680
|
+
|
|
681
|
+
for variable in operation.variable_definitions or []:
|
|
682
|
+
type_node: TypeNode = variable.type
|
|
683
|
+
required = False
|
|
684
|
+
is_list = False
|
|
685
|
+
inner_required = False
|
|
686
|
+
|
|
687
|
+
if isinstance(type_node, NonNullTypeNode):
|
|
688
|
+
required = True
|
|
689
|
+
type_node = type_node.type
|
|
690
|
+
|
|
691
|
+
if isinstance(type_node, ListTypeNode):
|
|
692
|
+
is_list = True
|
|
693
|
+
inner_type = type_node.type
|
|
694
|
+
|
|
695
|
+
if isinstance(inner_type, NonNullTypeNode):
|
|
696
|
+
inner_required = True
|
|
697
|
+
inner_type = inner_type.type
|
|
698
|
+
|
|
699
|
+
if isinstance(inner_type, NamedTypeNode):
|
|
700
|
+
type_name = inner_type.name.value
|
|
701
|
+
else:
|
|
702
|
+
raise TypeError(f"Unsupported inner type node: {inner_type}")
|
|
703
|
+
elif isinstance(type_node, NamedTypeNode):
|
|
704
|
+
type_name = type_node.name.value
|
|
705
|
+
else:
|
|
706
|
+
raise TypeError(f"Unsupported type node: {type_node}")
|
|
707
|
+
|
|
708
|
+
variables.append(
|
|
709
|
+
GraphQLVariable(
|
|
710
|
+
name=variable.variable.name.value,
|
|
711
|
+
type=type_name,
|
|
712
|
+
required=required,
|
|
713
|
+
is_list=is_list,
|
|
714
|
+
inner_required=inner_required,
|
|
715
|
+
default=self._parse_value(variable.default_value) if variable.default_value else None,
|
|
613
716
|
)
|
|
614
|
-
|
|
615
|
-
if isinstance(variable.type.type, NamedTypeNode):
|
|
616
|
-
variables.append(
|
|
617
|
-
GraphQLVariable(
|
|
618
|
-
name=variable.variable.name.value, type=variable.type.type.name.value, required=True
|
|
619
|
-
)
|
|
620
|
-
)
|
|
717
|
+
)
|
|
621
718
|
|
|
622
719
|
return variables
|
|
623
720
|
|
|
624
|
-
|
|
625
|
-
def _parse_arguments(field_node: FieldNode) -> list[GraphQLArgument]:
|
|
721
|
+
def _parse_arguments(self, field_node: FieldNode) -> list[GraphQLArgument]:
|
|
626
722
|
return [
|
|
627
723
|
GraphQLArgument(
|
|
628
724
|
name=argument.name.value,
|
|
629
|
-
value=
|
|
725
|
+
value=self._parse_value(argument.value),
|
|
630
726
|
kind=argument.value.kind,
|
|
631
727
|
)
|
|
632
728
|
for argument in field_node.arguments
|
|
633
729
|
]
|
|
730
|
+
|
|
731
|
+
def _parse_value(self, node: ValueNode) -> Any:
|
|
732
|
+
match node:
|
|
733
|
+
case VariableNode():
|
|
734
|
+
value: Any = f"${node.name.value}"
|
|
735
|
+
case IntValueNode():
|
|
736
|
+
value = int(node.value)
|
|
737
|
+
case FloatValueNode():
|
|
738
|
+
value = float(node.value)
|
|
739
|
+
case StringValueNode():
|
|
740
|
+
value = node.value
|
|
741
|
+
case BooleanValueNode():
|
|
742
|
+
value = node.value
|
|
743
|
+
case NullValueNode():
|
|
744
|
+
value = None
|
|
745
|
+
case EnumValueNode():
|
|
746
|
+
value = node.value
|
|
747
|
+
case ListValueNode() | ConstListValueNode():
|
|
748
|
+
value = [self._parse_value(item) for item in node.values]
|
|
749
|
+
case ObjectValueNode() | ConstObjectValueNode():
|
|
750
|
+
value = {field.name.value: self._parse_value(field.value) for field in node.fields}
|
|
751
|
+
case _:
|
|
752
|
+
raise TypeError(f"Unsupported value node: {node}")
|
|
753
|
+
|
|
754
|
+
return value
|
infrahub/graphql/app.py
CHANGED
|
@@ -155,7 +155,7 @@ class InfrahubGraphQLApp:
|
|
|
155
155
|
|
|
156
156
|
db = websocket.app.state.db
|
|
157
157
|
|
|
158
|
-
async with db.start_session() as db:
|
|
158
|
+
async with db.start_session(read_only=True) as db:
|
|
159
159
|
branch_name = websocket.path_params.get("branch_name", registry.default_branch)
|
|
160
160
|
branch = await registry.get_branch(db=db, branch=branch_name)
|
|
161
161
|
|
infrahub/graphql/loaders/node.py
CHANGED
|
@@ -53,7 +53,7 @@ class NodeDataLoader(DataLoader[str, Node | None]):
|
|
|
53
53
|
self.db = db
|
|
54
54
|
|
|
55
55
|
async def batch_load_fn(self, keys: list[Any]) -> list[Node | None]:
|
|
56
|
-
async with self.db.start_session() as db:
|
|
56
|
+
async with self.db.start_session(read_only=True) as db:
|
|
57
57
|
nodes_by_id = await NodeManager.get_many(
|
|
58
58
|
db=db,
|
|
59
59
|
ids=keys,
|
|
@@ -51,7 +51,7 @@ class PeerRelationshipsDataLoader(DataLoader[str, list[Relationship]]):
|
|
|
51
51
|
self.db = db
|
|
52
52
|
|
|
53
53
|
async def batch_load_fn(self, keys: list[Any]) -> list[list[Relationship]]: # pylint: disable=method-hidden
|
|
54
|
-
async with self.db.start_session() as db:
|
|
54
|
+
async with self.db.start_session(read_only=True) as db:
|
|
55
55
|
peer_rels = await NodeManager.query_peers(
|
|
56
56
|
db=db,
|
|
57
57
|
ids=keys,
|
infrahub/graphql/manager.py
CHANGED
|
@@ -25,6 +25,7 @@ from infrahub.types import ATTRIBUTE_TYPES, InfrahubDataType, get_attribute_type
|
|
|
25
25
|
from .directives import DIRECTIVES
|
|
26
26
|
from .enums import generate_graphql_enum, get_enum_attribute_type_name
|
|
27
27
|
from .metrics import SCHEMA_GENERATE_GRAPHQL_METRICS
|
|
28
|
+
from .mutations.action import InfrahubTriggerRuleMatchMutation, InfrahubTriggerRuleMutation
|
|
28
29
|
from .mutations.artifact_definition import InfrahubArtifactDefinitionMutation
|
|
29
30
|
from .mutations.ipam import (
|
|
30
31
|
InfrahubIPAddressMutation,
|
|
@@ -524,6 +525,9 @@ class GraphQLSchemaManager:
|
|
|
524
525
|
InfrahubKind.MENUITEM: InfrahubCoreMenuMutation,
|
|
525
526
|
InfrahubKind.STANDARDWEBHOOK: InfrahubWebhookMutation,
|
|
526
527
|
InfrahubKind.CUSTOMWEBHOOK: InfrahubWebhookMutation,
|
|
528
|
+
InfrahubKind.NODETRIGGERRULE: InfrahubTriggerRuleMutation,
|
|
529
|
+
InfrahubKind.NODETRIGGERATTRIBUTEMATCH: InfrahubTriggerRuleMatchMutation,
|
|
530
|
+
InfrahubKind.NODETRIGGERRELATIONSHIPMATCH: InfrahubTriggerRuleMatchMutation,
|
|
527
531
|
}
|
|
528
532
|
|
|
529
533
|
if isinstance(node_schema, NodeSchema) and node_schema.is_ip_prefix():
|