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.
Files changed (134) hide show
  1. infrahub/actions/constants.py +86 -0
  2. infrahub/actions/gather.py +114 -0
  3. infrahub/actions/models.py +241 -0
  4. infrahub/actions/parsers.py +104 -0
  5. infrahub/actions/schema.py +382 -0
  6. infrahub/actions/tasks.py +126 -0
  7. infrahub/actions/triggers.py +21 -0
  8. infrahub/cli/db.py +1 -2
  9. infrahub/config.py +9 -0
  10. infrahub/core/account.py +24 -47
  11. infrahub/core/attribute.py +10 -12
  12. infrahub/core/constants/infrahubkind.py +8 -0
  13. infrahub/core/constraint/node/runner.py +1 -1
  14. infrahub/core/convert_object_type/__init__.py +0 -0
  15. infrahub/core/convert_object_type/conversion.py +122 -0
  16. infrahub/core/convert_object_type/schema_mapping.py +56 -0
  17. infrahub/core/diff/query/all_conflicts.py +1 -5
  18. infrahub/core/diff/query/artifact.py +10 -20
  19. infrahub/core/diff/query/diff_get.py +3 -6
  20. infrahub/core/diff/query/field_summary.py +2 -4
  21. infrahub/core/diff/query/merge.py +70 -123
  22. infrahub/core/diff/query/save.py +20 -32
  23. infrahub/core/diff/query/summary_counts_enricher.py +34 -54
  24. infrahub/core/diff/query_parser.py +5 -1
  25. infrahub/core/diff/tasks.py +3 -3
  26. infrahub/core/manager.py +14 -11
  27. infrahub/core/migrations/graph/m003_relationship_parent_optional.py +1 -2
  28. infrahub/core/migrations/graph/m013_convert_git_password_credential.py +2 -4
  29. infrahub/core/migrations/graph/m019_restore_rels_to_time.py +11 -22
  30. infrahub/core/migrations/graph/m020_duplicate_edges.py +3 -6
  31. infrahub/core/migrations/graph/m021_missing_hierarchy_merge.py +1 -2
  32. infrahub/core/migrations/graph/m024_missing_hierarchy_backfill.py +1 -2
  33. infrahub/core/migrations/query/attribute_add.py +1 -2
  34. infrahub/core/migrations/query/attribute_rename.py +3 -6
  35. infrahub/core/migrations/query/delete_element_in_schema.py +3 -6
  36. infrahub/core/migrations/query/node_duplicate.py +3 -6
  37. infrahub/core/migrations/query/relationship_duplicate.py +3 -6
  38. infrahub/core/migrations/schema/node_attribute_remove.py +3 -6
  39. infrahub/core/migrations/schema/node_remove.py +3 -6
  40. infrahub/core/models.py +29 -2
  41. infrahub/core/node/__init__.py +18 -4
  42. infrahub/core/node/create.py +211 -0
  43. infrahub/core/protocols.py +51 -0
  44. infrahub/core/protocols_base.py +3 -0
  45. infrahub/core/query/__init__.py +2 -2
  46. infrahub/core/query/diff.py +26 -32
  47. infrahub/core/query/ipam.py +10 -20
  48. infrahub/core/query/node.py +28 -46
  49. infrahub/core/query/relationship.py +51 -28
  50. infrahub/core/query/resource_manager.py +1 -2
  51. infrahub/core/query/subquery.py +2 -4
  52. infrahub/core/relationship/model.py +3 -0
  53. infrahub/core/schema/__init__.py +2 -1
  54. infrahub/core/schema/attribute_parameters.py +36 -0
  55. infrahub/core/schema/attribute_schema.py +83 -8
  56. infrahub/core/schema/basenode_schema.py +25 -1
  57. infrahub/core/schema/definitions/core/__init__.py +21 -0
  58. infrahub/core/schema/definitions/internal.py +13 -3
  59. infrahub/core/schema/generated/attribute_schema.py +9 -3
  60. infrahub/core/schema/schema_branch.py +12 -7
  61. infrahub/core/validators/__init__.py +5 -1
  62. infrahub/core/validators/attribute/choices.py +1 -2
  63. infrahub/core/validators/attribute/enum.py +1 -2
  64. infrahub/core/validators/attribute/kind.py +1 -2
  65. infrahub/core/validators/attribute/length.py +13 -6
  66. infrahub/core/validators/attribute/optional.py +1 -2
  67. infrahub/core/validators/attribute/regex.py +5 -5
  68. infrahub/core/validators/attribute/unique.py +1 -3
  69. infrahub/core/validators/determiner.py +18 -2
  70. infrahub/core/validators/enum.py +7 -0
  71. infrahub/core/validators/node/hierarchy.py +3 -6
  72. infrahub/core/validators/query.py +1 -3
  73. infrahub/core/validators/relationship/count.py +6 -12
  74. infrahub/core/validators/relationship/optional.py +2 -4
  75. infrahub/core/validators/relationship/peer.py +3 -8
  76. infrahub/core/validators/tasks.py +1 -1
  77. infrahub/core/validators/uniqueness/query.py +5 -9
  78. infrahub/database/__init__.py +1 -3
  79. infrahub/events/group_action.py +1 -0
  80. infrahub/graphql/analyzer.py +139 -18
  81. infrahub/graphql/app.py +1 -1
  82. infrahub/graphql/loaders/node.py +1 -1
  83. infrahub/graphql/loaders/peers.py +1 -1
  84. infrahub/graphql/manager.py +4 -0
  85. infrahub/graphql/mutations/action.py +164 -0
  86. infrahub/graphql/mutations/convert_object_type.py +62 -0
  87. infrahub/graphql/mutations/main.py +24 -175
  88. infrahub/graphql/mutations/proposed_change.py +21 -18
  89. infrahub/graphql/queries/convert_object_type_mapping.py +36 -0
  90. infrahub/graphql/queries/relationship.py +1 -1
  91. infrahub/graphql/resolvers/many_relationship.py +4 -4
  92. infrahub/graphql/resolvers/resolver.py +4 -4
  93. infrahub/graphql/resolvers/single_relationship.py +2 -2
  94. infrahub/graphql/schema.py +6 -0
  95. infrahub/graphql/subscription/graphql_query.py +2 -2
  96. infrahub/graphql/types/branch.py +1 -1
  97. infrahub/menu/menu.py +31 -0
  98. infrahub/message_bus/messages/__init__.py +0 -10
  99. infrahub/message_bus/operations/__init__.py +0 -8
  100. infrahub/message_bus/operations/refresh/registry.py +1 -1
  101. infrahub/patch/queries/consolidate_duplicated_nodes.py +3 -6
  102. infrahub/patch/queries/delete_duplicated_edges.py +5 -10
  103. infrahub/prefect_server/models.py +1 -19
  104. infrahub/proposed_change/models.py +68 -3
  105. infrahub/proposed_change/tasks.py +907 -30
  106. infrahub/task_manager/models.py +10 -6
  107. infrahub/telemetry/database.py +1 -1
  108. infrahub/telemetry/tasks.py +1 -1
  109. infrahub/trigger/catalogue.py +2 -0
  110. infrahub/trigger/models.py +18 -2
  111. infrahub/trigger/tasks.py +3 -1
  112. infrahub/workflows/catalogue.py +76 -0
  113. {infrahub_server-1.2.10.dist-info → infrahub_server-1.3.0a0.dist-info}/METADATA +2 -2
  114. {infrahub_server-1.2.10.dist-info → infrahub_server-1.3.0a0.dist-info}/RECORD +121 -118
  115. infrahub_testcontainers/container.py +0 -1
  116. infrahub_testcontainers/docker-compose.test.yml +1 -1
  117. infrahub_testcontainers/helpers.py +8 -2
  118. infrahub/message_bus/messages/check_generator_run.py +0 -26
  119. infrahub/message_bus/messages/finalize_validator_execution.py +0 -15
  120. infrahub/message_bus/messages/proposed_change/base_with_diff.py +0 -16
  121. infrahub/message_bus/messages/proposed_change/request_proposedchange_refreshartifacts.py +0 -11
  122. infrahub/message_bus/messages/request_generatordefinition_check.py +0 -20
  123. infrahub/message_bus/messages/request_proposedchange_pipeline.py +0 -23
  124. infrahub/message_bus/operations/check/__init__.py +0 -3
  125. infrahub/message_bus/operations/check/generator.py +0 -156
  126. infrahub/message_bus/operations/finalize/__init__.py +0 -3
  127. infrahub/message_bus/operations/finalize/validator.py +0 -133
  128. infrahub/message_bus/operations/requests/__init__.py +0 -9
  129. infrahub/message_bus/operations/requests/generator_definition.py +0 -140
  130. infrahub/message_bus/operations/requests/proposed_change.py +0 -629
  131. /infrahub/{message_bus/messages/proposed_change → actions}/__init__.py +0 -0
  132. {infrahub_server-1.2.10.dist-info → infrahub_server-1.3.0a0.dist-info}/LICENSE.txt +0 -0
  133. {infrahub_server-1.2.10.dist-info → infrahub_server-1.3.0a0.dist-info}/WHEEL +0 -0
  134. {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
- for prop_name, prop_field_info in field.model_fields.items():
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 getattr(field, prop_name) is None:
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 start_node, branches_and_counts
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
@@ -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
- URI,
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,
@@ -89,6 +89,7 @@ class GroupMutatedEvent(InfrahubEvent):
89
89
  "infrahub.node.id": self.node_id,
90
90
  "infrahub.node.action": self.action.value,
91
91
  "infrahub.node.root_id": self.node_id,
92
+ "infrahub.branch.name": self.meta.context.branch.name,
92
93
  }
93
94
 
94
95
 
@@ -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: str
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
- @staticmethod
607
- def _get_variables(operation: OperationDefinitionNode) -> list[GraphQLVariable]:
608
- variables = []
609
- for variable in operation.variable_definitions:
610
- if isinstance(variable.type, NamedTypeNode):
611
- variables.append(
612
- GraphQLVariable(name=variable.variable.name.value, type=variable.type.name.value, required=False)
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
- elif isinstance(variable.type, NonNullTypeNode):
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
- @staticmethod
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=getattr(argument.value, "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
 
@@ -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,
@@ -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():