infrahub-server 1.2.12__py3-none-any.whl → 1.3.0__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 (205) hide show
  1. infrahub/actions/constants.py +130 -0
  2. infrahub/actions/gather.py +114 -0
  3. infrahub/actions/models.py +243 -0
  4. infrahub/actions/parsers.py +104 -0
  5. infrahub/actions/schema.py +393 -0
  6. infrahub/actions/tasks.py +119 -0
  7. infrahub/actions/triggers.py +21 -0
  8. infrahub/branch/__init__.py +0 -0
  9. infrahub/branch/tasks.py +29 -0
  10. infrahub/branch/triggers.py +22 -0
  11. infrahub/cli/db.py +3 -4
  12. infrahub/computed_attribute/gather.py +3 -1
  13. infrahub/computed_attribute/tasks.py +23 -29
  14. infrahub/core/account.py +24 -47
  15. infrahub/core/attribute.py +13 -15
  16. infrahub/core/constants/__init__.py +10 -0
  17. infrahub/core/constants/infrahubkind.py +9 -0
  18. infrahub/core/constraint/node/runner.py +3 -1
  19. infrahub/core/convert_object_type/__init__.py +0 -0
  20. infrahub/core/convert_object_type/conversion.py +124 -0
  21. infrahub/core/convert_object_type/schema_mapping.py +56 -0
  22. infrahub/core/diff/coordinator.py +8 -1
  23. infrahub/core/diff/query/all_conflicts.py +1 -5
  24. infrahub/core/diff/query/artifact.py +10 -20
  25. infrahub/core/diff/query/delete_query.py +8 -4
  26. infrahub/core/diff/query/diff_get.py +3 -6
  27. infrahub/core/diff/query/field_specifiers.py +1 -1
  28. infrahub/core/diff/query/field_summary.py +2 -4
  29. infrahub/core/diff/query/merge.py +72 -125
  30. infrahub/core/diff/query/save.py +28 -43
  31. infrahub/core/diff/query/summary_counts_enricher.py +34 -54
  32. infrahub/core/diff/query/time_range_query.py +0 -1
  33. infrahub/core/diff/repository/repository.py +4 -0
  34. infrahub/core/manager.py +14 -11
  35. infrahub/core/migrations/graph/m003_relationship_parent_optional.py +1 -2
  36. infrahub/core/migrations/graph/m012_convert_account_generic.py +1 -1
  37. infrahub/core/migrations/graph/m013_convert_git_password_credential.py +2 -6
  38. infrahub/core/migrations/graph/m015_diff_format_update.py +1 -2
  39. infrahub/core/migrations/graph/m016_diff_delete_bug_fix.py +1 -2
  40. infrahub/core/migrations/graph/m019_restore_rels_to_time.py +11 -22
  41. infrahub/core/migrations/graph/m020_duplicate_edges.py +3 -6
  42. infrahub/core/migrations/graph/m021_missing_hierarchy_merge.py +1 -2
  43. infrahub/core/migrations/graph/m023_deduplicate_cardinality_one_relationships.py +2 -2
  44. infrahub/core/migrations/graph/m024_missing_hierarchy_backfill.py +1 -2
  45. infrahub/core/migrations/graph/m028_delete_diffs.py +1 -2
  46. infrahub/core/migrations/graph/m029_duplicates_cleanup.py +30 -48
  47. infrahub/core/migrations/graph/m030_illegal_edges.py +1 -2
  48. infrahub/core/migrations/query/attribute_add.py +1 -2
  49. infrahub/core/migrations/query/attribute_rename.py +6 -11
  50. infrahub/core/migrations/query/delete_element_in_schema.py +19 -17
  51. infrahub/core/migrations/query/node_duplicate.py +19 -21
  52. infrahub/core/migrations/query/relationship_duplicate.py +19 -18
  53. infrahub/core/migrations/schema/node_attribute_remove.py +4 -8
  54. infrahub/core/migrations/schema/node_remove.py +19 -20
  55. infrahub/core/models.py +29 -2
  56. infrahub/core/node/__init__.py +131 -28
  57. infrahub/core/node/base.py +1 -1
  58. infrahub/core/node/create.py +211 -0
  59. infrahub/core/node/resource_manager/number_pool.py +31 -5
  60. infrahub/core/node/standard.py +6 -1
  61. infrahub/core/path.py +15 -1
  62. infrahub/core/protocols.py +57 -0
  63. infrahub/core/protocols_base.py +3 -0
  64. infrahub/core/query/__init__.py +2 -2
  65. infrahub/core/query/delete.py +3 -3
  66. infrahub/core/query/diff.py +19 -32
  67. infrahub/core/query/ipam.py +10 -20
  68. infrahub/core/query/node.py +29 -47
  69. infrahub/core/query/relationship.py +55 -34
  70. infrahub/core/query/resource_manager.py +1 -2
  71. infrahub/core/query/standard_node.py +19 -5
  72. infrahub/core/query/subquery.py +2 -4
  73. infrahub/core/relationship/constraints/count.py +10 -9
  74. infrahub/core/relationship/constraints/interface.py +2 -1
  75. infrahub/core/relationship/constraints/peer_kind.py +2 -1
  76. infrahub/core/relationship/constraints/peer_parent.py +56 -0
  77. infrahub/core/relationship/constraints/peer_relatives.py +72 -0
  78. infrahub/core/relationship/constraints/profiles_kind.py +1 -1
  79. infrahub/core/relationship/model.py +4 -1
  80. infrahub/core/schema/__init__.py +2 -1
  81. infrahub/core/schema/attribute_parameters.py +160 -0
  82. infrahub/core/schema/attribute_schema.py +130 -7
  83. infrahub/core/schema/basenode_schema.py +27 -3
  84. infrahub/core/schema/definitions/core/__init__.py +29 -1
  85. infrahub/core/schema/definitions/core/group.py +45 -0
  86. infrahub/core/schema/definitions/core/resource_pool.py +9 -0
  87. infrahub/core/schema/definitions/internal.py +43 -5
  88. infrahub/core/schema/generated/attribute_schema.py +16 -3
  89. infrahub/core/schema/generated/relationship_schema.py +11 -1
  90. infrahub/core/schema/manager.py +7 -2
  91. infrahub/core/schema/schema_branch.py +104 -9
  92. infrahub/core/validators/__init__.py +15 -2
  93. infrahub/core/validators/attribute/choices.py +1 -3
  94. infrahub/core/validators/attribute/enum.py +1 -3
  95. infrahub/core/validators/attribute/kind.py +1 -3
  96. infrahub/core/validators/attribute/length.py +13 -7
  97. infrahub/core/validators/attribute/min_max.py +118 -0
  98. infrahub/core/validators/attribute/number_pool.py +106 -0
  99. infrahub/core/validators/attribute/optional.py +1 -4
  100. infrahub/core/validators/attribute/regex.py +5 -6
  101. infrahub/core/validators/attribute/unique.py +1 -3
  102. infrahub/core/validators/determiner.py +18 -2
  103. infrahub/core/validators/enum.py +12 -0
  104. infrahub/core/validators/node/hierarchy.py +3 -6
  105. infrahub/core/validators/query.py +1 -3
  106. infrahub/core/validators/relationship/count.py +6 -12
  107. infrahub/core/validators/relationship/optional.py +2 -4
  108. infrahub/core/validators/relationship/peer.py +177 -12
  109. infrahub/core/validators/tasks.py +1 -1
  110. infrahub/core/validators/uniqueness/query.py +5 -9
  111. infrahub/database/__init__.py +12 -4
  112. infrahub/database/validation.py +1 -2
  113. infrahub/dependencies/builder/constraint/grouped/node_runner.py +4 -0
  114. infrahub/dependencies/builder/constraint/relationship_manager/peer_parent.py +8 -0
  115. infrahub/dependencies/builder/constraint/relationship_manager/peer_relatives.py +8 -0
  116. infrahub/dependencies/builder/constraint/schema/aggregated.py +2 -0
  117. infrahub/dependencies/builder/constraint/schema/relationship_peer.py +8 -0
  118. infrahub/dependencies/builder/diff/deserializer.py +1 -1
  119. infrahub/dependencies/registry.py +4 -0
  120. infrahub/events/group_action.py +1 -0
  121. infrahub/events/models.py +1 -1
  122. infrahub/git/base.py +5 -3
  123. infrahub/git/integrator.py +96 -5
  124. infrahub/git/tasks.py +1 -0
  125. infrahub/graphql/analyzer.py +139 -18
  126. infrahub/graphql/manager.py +4 -0
  127. infrahub/graphql/mutations/action.py +164 -0
  128. infrahub/graphql/mutations/convert_object_type.py +71 -0
  129. infrahub/graphql/mutations/main.py +24 -175
  130. infrahub/graphql/mutations/proposed_change.py +20 -17
  131. infrahub/graphql/mutations/relationship.py +32 -0
  132. infrahub/graphql/mutations/resource_manager.py +63 -7
  133. infrahub/graphql/queries/convert_object_type_mapping.py +34 -0
  134. infrahub/graphql/queries/resource_manager.py +7 -1
  135. infrahub/graphql/resolvers/many_relationship.py +1 -1
  136. infrahub/graphql/resolvers/resolver.py +2 -2
  137. infrahub/graphql/resolvers/single_relationship.py +1 -1
  138. infrahub/graphql/schema.py +6 -0
  139. infrahub/menu/menu.py +34 -2
  140. infrahub/message_bus/messages/__init__.py +0 -10
  141. infrahub/message_bus/operations/__init__.py +0 -8
  142. infrahub/message_bus/operations/refresh/registry.py +3 -6
  143. infrahub/patch/queries/delete_duplicated_edges.py +10 -15
  144. infrahub/pools/models.py +14 -0
  145. infrahub/pools/number.py +5 -3
  146. infrahub/pools/registration.py +22 -0
  147. infrahub/pools/tasks.py +126 -0
  148. infrahub/prefect_server/models.py +1 -19
  149. infrahub/proposed_change/models.py +68 -3
  150. infrahub/proposed_change/tasks.py +911 -34
  151. infrahub/schema/__init__.py +0 -0
  152. infrahub/schema/tasks.py +27 -0
  153. infrahub/schema/triggers.py +23 -0
  154. infrahub/task_manager/models.py +10 -6
  155. infrahub/trigger/catalogue.py +6 -0
  156. infrahub/trigger/models.py +23 -6
  157. infrahub/trigger/setup.py +26 -2
  158. infrahub/trigger/tasks.py +4 -2
  159. infrahub/types.py +6 -0
  160. infrahub/webhook/tasks.py +4 -8
  161. infrahub/workflows/catalogue.py +103 -1
  162. infrahub_sdk/client.py +43 -10
  163. infrahub_sdk/ctl/generator.py +4 -4
  164. infrahub_sdk/ctl/repository.py +1 -1
  165. infrahub_sdk/node/__init__.py +39 -0
  166. infrahub_sdk/node/attribute.py +122 -0
  167. infrahub_sdk/node/constants.py +21 -0
  168. infrahub_sdk/{node.py → node/node.py} +158 -803
  169. infrahub_sdk/node/parsers.py +15 -0
  170. infrahub_sdk/node/property.py +24 -0
  171. infrahub_sdk/node/related_node.py +266 -0
  172. infrahub_sdk/node/relationship.py +302 -0
  173. infrahub_sdk/protocols.py +112 -0
  174. infrahub_sdk/protocols_base.py +34 -2
  175. infrahub_sdk/pytest_plugin/items/python_transform.py +2 -1
  176. infrahub_sdk/query_groups.py +17 -5
  177. infrahub_sdk/schema/main.py +1 -0
  178. infrahub_sdk/schema/repository.py +16 -0
  179. infrahub_sdk/spec/object.py +1 -1
  180. infrahub_sdk/store.py +1 -1
  181. infrahub_sdk/testing/schemas/car_person.py +1 -0
  182. infrahub_sdk/utils.py +7 -20
  183. infrahub_sdk/yaml.py +6 -5
  184. {infrahub_server-1.2.12.dist-info → infrahub_server-1.3.0.dist-info}/METADATA +3 -3
  185. {infrahub_server-1.2.12.dist-info → infrahub_server-1.3.0.dist-info}/RECORD +192 -166
  186. infrahub_testcontainers/container.py +0 -1
  187. infrahub_testcontainers/docker-compose.test.yml +1 -1
  188. infrahub_testcontainers/helpers.py +8 -2
  189. infrahub/message_bus/messages/check_generator_run.py +0 -26
  190. infrahub/message_bus/messages/finalize_validator_execution.py +0 -15
  191. infrahub/message_bus/messages/proposed_change/base_with_diff.py +0 -16
  192. infrahub/message_bus/messages/proposed_change/request_proposedchange_refreshartifacts.py +0 -11
  193. infrahub/message_bus/messages/request_generatordefinition_check.py +0 -20
  194. infrahub/message_bus/messages/request_proposedchange_pipeline.py +0 -23
  195. infrahub/message_bus/operations/check/__init__.py +0 -3
  196. infrahub/message_bus/operations/check/generator.py +0 -156
  197. infrahub/message_bus/operations/finalize/__init__.py +0 -3
  198. infrahub/message_bus/operations/finalize/validator.py +0 -133
  199. infrahub/message_bus/operations/requests/__init__.py +0 -9
  200. infrahub/message_bus/operations/requests/generator_definition.py +0 -140
  201. infrahub/message_bus/operations/requests/proposed_change.py +0 -629
  202. /infrahub/{message_bus/messages/proposed_change → actions}/__init__.py +0 -0
  203. {infrahub_server-1.2.12.dist-info → infrahub_server-1.3.0.dist-info}/LICENSE.txt +0 -0
  204. {infrahub_server-1.2.12.dist-info → infrahub_server-1.3.0.dist-info}/WHEEL +0 -0
  205. {infrahub_server-1.2.12.dist-info → infrahub_server-1.3.0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,211 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Any, Mapping
4
+
5
+ from infrahub.core import registry
6
+ from infrahub.core.constants import RelationshipCardinality, RelationshipKind
7
+ from infrahub.core.constraint.node.runner import NodeConstraintRunner
8
+ from infrahub.core.manager import NodeManager
9
+ from infrahub.core.node import Node
10
+ from infrahub.core.protocols import CoreObjectTemplate
11
+ from infrahub.dependencies.registry import get_component_registry
12
+
13
+ if TYPE_CHECKING:
14
+ from infrahub.core.branch import Branch
15
+ from infrahub.core.relationship.model import RelationshipManager
16
+ from infrahub.core.schema import MainSchemaTypes, NonGenericSchemaTypes, RelationshipSchema
17
+ from infrahub.database import InfrahubDatabase
18
+
19
+
20
+ async def get_template_relationship_peers(
21
+ db: InfrahubDatabase, template: CoreObjectTemplate, relationship: RelationshipSchema
22
+ ) -> Mapping[str, CoreObjectTemplate]:
23
+ """For a given relationship on the template, fetch the related peers."""
24
+ template_relationship_manager: RelationshipManager = getattr(template, relationship.name)
25
+ if relationship.cardinality == RelationshipCardinality.MANY:
26
+ return await template_relationship_manager.get_peers(db=db, peer_type=CoreObjectTemplate)
27
+
28
+ peers: dict[str, CoreObjectTemplate] = {}
29
+ template_relationship_peer = await template_relationship_manager.get_peer(db=db, peer_type=CoreObjectTemplate)
30
+ if template_relationship_peer:
31
+ peers[template_relationship_peer.id] = template_relationship_peer
32
+ return peers
33
+
34
+
35
+ async def extract_peer_data(
36
+ db: InfrahubDatabase,
37
+ template_peer: CoreObjectTemplate,
38
+ obj_peer_schema: MainSchemaTypes,
39
+ parent_obj: Node,
40
+ current_template: CoreObjectTemplate,
41
+ ) -> Mapping[str, Any]:
42
+ obj_peer_data: dict[str, Any] = {}
43
+
44
+ for attr in template_peer.get_schema().attribute_names:
45
+ if attr not in obj_peer_schema.attribute_names:
46
+ continue
47
+ obj_peer_data[attr] = {"value": getattr(template_peer, attr).value, "source": template_peer.id}
48
+
49
+ for rel in template_peer.get_schema().relationship_names:
50
+ rel_manager: RelationshipManager = getattr(template_peer, rel)
51
+ if (
52
+ rel_manager.schema.kind not in [RelationshipKind.COMPONENT, RelationshipKind.PARENT]
53
+ or rel_manager.schema.name not in obj_peer_schema.relationship_names
54
+ ):
55
+ continue
56
+
57
+ if list(await rel_manager.get_peers(db=db)) == [current_template.id]:
58
+ obj_peer_data[rel] = {"id": parent_obj.id}
59
+
60
+ return obj_peer_data
61
+
62
+
63
+ async def handle_template_relationships(
64
+ db: InfrahubDatabase,
65
+ branch: Branch,
66
+ obj: Node,
67
+ template: CoreObjectTemplate,
68
+ fields: list,
69
+ constraint_runner: NodeConstraintRunner | None = None,
70
+ ) -> None:
71
+ if constraint_runner is None:
72
+ component_registry = get_component_registry()
73
+ constraint_runner = await component_registry.get_component(NodeConstraintRunner, db=db, branch=branch)
74
+
75
+ for relationship in obj.get_relationships(kind=RelationshipKind.COMPONENT, exclude=fields):
76
+ template_relationship_peers = await get_template_relationship_peers(
77
+ db=db, template=template, relationship=relationship
78
+ )
79
+ if not template_relationship_peers:
80
+ continue
81
+
82
+ for template_relationship_peer in template_relationship_peers.values():
83
+ # We retrieve peer schema for each peer in case we are processing a relationship which is based on a generic
84
+ obj_peer_schema = registry.schema.get_node_schema(
85
+ name=template_relationship_peer.get_schema().kind.removeprefix("Template"),
86
+ branch=branch,
87
+ duplicate=False,
88
+ )
89
+ obj_peer_data = await extract_peer_data(
90
+ db=db,
91
+ template_peer=template_relationship_peer,
92
+ obj_peer_schema=obj_peer_schema,
93
+ parent_obj=obj,
94
+ current_template=template,
95
+ )
96
+
97
+ obj_peer = await Node.init(schema=obj_peer_schema, db=db, branch=branch)
98
+ await obj_peer.new(db=db, **obj_peer_data)
99
+ await constraint_runner.check(node=obj_peer, field_filters=list(obj_peer_data))
100
+ await obj_peer.save(db=db)
101
+
102
+ await handle_template_relationships(
103
+ db=db,
104
+ branch=branch,
105
+ constraint_runner=constraint_runner,
106
+ obj=obj_peer,
107
+ template=template_relationship_peer,
108
+ fields=fields,
109
+ )
110
+
111
+
112
+ async def get_profile_ids(db: InfrahubDatabase, obj: Node) -> set[str]:
113
+ if not hasattr(obj, "profiles"):
114
+ return set()
115
+ profile_rels = await obj.profiles.get_relationships(db=db)
116
+ return {pr.peer_id for pr in profile_rels}
117
+
118
+
119
+ async def refresh_for_profile_update(
120
+ db: InfrahubDatabase,
121
+ branch: Branch,
122
+ obj: Node,
123
+ schema: NonGenericSchemaTypes,
124
+ previous_profile_ids: set[str] | None = None,
125
+ ) -> Node:
126
+ if not hasattr(obj, "profiles"):
127
+ return obj
128
+ current_profile_ids = await get_profile_ids(db=db, obj=obj)
129
+ if previous_profile_ids is None or previous_profile_ids != current_profile_ids:
130
+ refreshed_node = await NodeManager.get_one_by_id_or_default_filter(
131
+ db=db,
132
+ kind=schema.kind,
133
+ id=obj.get_id(),
134
+ branch=branch,
135
+ include_owner=True,
136
+ include_source=True,
137
+ )
138
+ refreshed_node._node_changelog = obj.node_changelog
139
+ return refreshed_node
140
+ return obj
141
+
142
+
143
+ async def _do_create_node(
144
+ node_class: type[Node],
145
+ db: InfrahubDatabase,
146
+ data: dict,
147
+ schema: NonGenericSchemaTypes,
148
+ fields_to_validate: list,
149
+ branch: Branch,
150
+ node_constraint_runner: NodeConstraintRunner,
151
+ ) -> Node:
152
+ obj = await node_class.init(db=db, schema=schema, branch=branch)
153
+ await obj.new(db=db, **data)
154
+ await node_constraint_runner.check(node=obj, field_filters=fields_to_validate)
155
+ await obj.save(db=db)
156
+
157
+ object_template = await obj.get_object_template(db=db)
158
+ if object_template:
159
+ await handle_template_relationships(
160
+ db=db,
161
+ branch=branch,
162
+ template=object_template,
163
+ obj=obj,
164
+ fields=fields_to_validate,
165
+ )
166
+ return obj
167
+
168
+
169
+ async def create_node(
170
+ data: dict,
171
+ db: InfrahubDatabase,
172
+ branch: Branch,
173
+ schema: NonGenericSchemaTypes,
174
+ ) -> Node:
175
+ """Create a node in the database if constraint checks succeed."""
176
+
177
+ component_registry = get_component_registry()
178
+ node_constraint_runner = await component_registry.get_component(
179
+ NodeConstraintRunner, db=db.start_session() if not db.is_transaction else db, branch=branch
180
+ )
181
+ node_class = Node
182
+ if schema.kind in registry.node:
183
+ node_class = registry.node[schema.kind]
184
+
185
+ fields_to_validate = list(data)
186
+ if db.is_transaction:
187
+ obj = await _do_create_node(
188
+ node_class=node_class,
189
+ node_constraint_runner=node_constraint_runner,
190
+ db=db,
191
+ schema=schema,
192
+ branch=branch,
193
+ fields_to_validate=fields_to_validate,
194
+ data=data,
195
+ )
196
+ else:
197
+ async with db.start_transaction() as dbt:
198
+ obj = await _do_create_node(
199
+ node_class=node_class,
200
+ node_constraint_runner=node_constraint_runner,
201
+ db=dbt,
202
+ schema=schema,
203
+ branch=branch,
204
+ fields_to_validate=fields_to_validate,
205
+ data=data,
206
+ )
207
+
208
+ if await get_profile_ids(db=db, obj=obj):
209
+ obj = await refresh_for_profile_update(db=db, branch=branch, schema=schema, obj=obj)
210
+
211
+ return obj
@@ -2,22 +2,44 @@ from __future__ import annotations
2
2
 
3
3
  from typing import TYPE_CHECKING
4
4
 
5
+ from infrahub.core import registry
5
6
  from infrahub.core.query.resource_manager import NumberPoolGetReserved, NumberPoolGetUsed, NumberPoolSetReserved
7
+ from infrahub.core.schema.attribute_parameters import NumberAttributeParameters
6
8
  from infrahub.exceptions import PoolExhaustedError
7
9
 
8
10
  from .. import Node
9
11
 
10
12
  if TYPE_CHECKING:
13
+ from infrahub.core.attribute import BaseAttribute
11
14
  from infrahub.core.branch import Branch
12
15
  from infrahub.database import InfrahubDatabase
13
16
 
14
17
 
15
18
  class CoreNumberPool(Node):
19
+ def get_attribute_nb_excluded_values(self) -> int:
20
+ """
21
+ Returns the number of excluded values for the attribute of the number pool.
22
+ """
23
+
24
+ pool_node = registry.schema.get(name=self.node.value) # type: ignore [attr-defined]
25
+ attribute = [attribute for attribute in pool_node.attributes if attribute.name == self.node_attribute.value][0] # type: ignore [attr-defined]
26
+ if not isinstance(attribute.parameters, NumberAttributeParameters):
27
+ return 0
28
+
29
+ sum_excluded_values = 0
30
+ excluded_ranges = attribute.parameters.get_excluded_ranges()
31
+ for start_range, end_range in excluded_ranges:
32
+ sum_excluded_values += end_range - start_range + 1
33
+
34
+ res = len(attribute.parameters.get_excluded_single_values()) + sum_excluded_values
35
+ return res
36
+
16
37
  async def get_resource(
17
38
  self,
18
39
  db: InfrahubDatabase,
19
40
  branch: Branch,
20
41
  node: Node,
42
+ attribute: BaseAttribute,
21
43
  identifier: str | None = None,
22
44
  ) -> int:
23
45
  identifier = identifier or node.get_id()
@@ -31,23 +53,24 @@ class CoreNumberPool(Node):
31
53
  return reservation
32
54
 
33
55
  # If we have not returned a value we need to find one if avaiable
34
- number = await self.get_next(db=db, branch=branch)
56
+ number = await self.get_next(db=db, branch=branch, attribute=attribute)
35
57
 
36
58
  query_set = await NumberPoolSetReserved.init(
37
59
  db=db, pool_id=self.get_id(), identifier=identifier, reserved=number
38
60
  )
39
61
  await query_set.execute(db=db)
40
-
41
62
  return number
42
63
 
43
- async def get_next(self, db: InfrahubDatabase, branch: Branch) -> int:
64
+ async def get_next(self, db: InfrahubDatabase, branch: Branch, attribute: BaseAttribute) -> int:
44
65
  query = await NumberPoolGetUsed.init(db=db, branch=branch, pool=self, branch_agnostic=True)
45
66
  await query.execute(db=db)
46
67
  taken = [result.get_as_optional_type("av.value", return_type=int) for result in query.results]
68
+ parameters = attribute.schema.parameters
47
69
  next_number = find_next_free(
48
70
  start=self.start_range.value, # type: ignore[attr-defined]
49
71
  end=self.end_range.value, # type: ignore[attr-defined]
50
72
  taken=taken,
73
+ parameters=parameters if isinstance(parameters, NumberAttributeParameters) else None,
51
74
  )
52
75
  if next_number is None:
53
76
  raise PoolExhaustedError("There are no more values available in this pool.")
@@ -55,12 +78,15 @@ class CoreNumberPool(Node):
55
78
  return next_number
56
79
 
57
80
 
58
- def find_next_free(start: int, end: int, taken: list[int | None]) -> int | None:
81
+ def find_next_free(
82
+ start: int, end: int, taken: list[int | None], parameters: NumberAttributeParameters | None
83
+ ) -> int | None:
59
84
  used_numbers = [number for number in taken if number is not None]
60
85
  used_set = set(used_numbers)
61
86
 
62
87
  for num in range(start, end + 1):
63
88
  if num not in used_set:
64
- return num
89
+ if parameters is None or parameters.is_valid_value(num):
90
+ return num
65
91
 
66
92
  return None
@@ -210,7 +210,12 @@ class StandardNode(BaseModel):
210
210
 
211
211
  @classmethod
212
212
  async def get_list(
213
- cls, db: InfrahubDatabase, limit: int = 1000, ids: list[str] | None = None, name: str | None = None, **kwargs
213
+ cls,
214
+ db: InfrahubDatabase,
215
+ limit: int = 1000,
216
+ ids: list[str] | None = None,
217
+ name: str | None = None,
218
+ **kwargs: dict[str, Any],
214
219
  ) -> list[Self]:
215
220
  query: Query = await StandardNodeGetListQuery.init(
216
221
  db=db, node_class=cls, ids=ids, node_name=name, limit=limit, **kwargs
infrahub/core/path.py CHANGED
@@ -63,6 +63,20 @@ class DataPath(InfrahubPath):
63
63
  peer_id: str | None = Field(default=None, description="")
64
64
  value: Any | None = Field(default=None, description="Optional value of the resource")
65
65
 
66
+ def __hash__(self) -> int:
67
+ return hash(
68
+ (
69
+ self.branch,
70
+ self.path_type,
71
+ self.node_id,
72
+ self.kind,
73
+ self.field_name,
74
+ self.property_name,
75
+ self.peer_id,
76
+ str(self.value),
77
+ )
78
+ )
79
+
66
80
  @property
67
81
  def resource_type(self) -> PathResourceType:
68
82
  return PathResourceType.DATA
@@ -125,7 +139,7 @@ class SchemaPath(InfrahubPath):
125
139
  if self.field_name:
126
140
  identifier += f"/{self.field_name}"
127
141
 
128
- if self.property_name and not self.path_type == SchemaPathType.NODE:
142
+ if self.property_name and self.path_type != SchemaPathType.NODE:
129
143
  identifier += f"/{self.property_name}"
130
144
 
131
145
  return identifier
@@ -62,6 +62,12 @@ class BuiltinIPPrefix(CoreNode):
62
62
  children: RelationshipManager
63
63
 
64
64
 
65
+ class CoreAction(CoreNode):
66
+ name: String
67
+ description: StringOptional
68
+ triggers: RelationshipManager
69
+
70
+
65
71
  class CoreArtifactTarget(CoreNode):
66
72
  artifacts: RelationshipManager
67
73
 
@@ -148,6 +154,10 @@ class CoreMenu(CoreNode):
148
154
  children: RelationshipManager
149
155
 
150
156
 
157
+ class CoreNodeTriggerMatch(CoreNode):
158
+ trigger: RelationshipManager
159
+
160
+
151
161
  class CoreObjectComponentTemplate(CoreNode):
152
162
  template_name: String
153
163
 
@@ -189,6 +199,14 @@ class CoreTransformation(CoreNode):
189
199
  tags: RelationshipManager
190
200
 
191
201
 
202
+ class CoreTriggerRule(CoreNode):
203
+ name: String
204
+ description: StringOptional
205
+ active: Boolean
206
+ branch_scope: Dropdown
207
+ action: RelationshipManager
208
+
209
+
192
210
  class CoreValidator(CoreNode):
193
211
  label: StringOptional
194
212
  state: Enum
@@ -326,6 +344,10 @@ class CoreFileThread(CoreThread):
326
344
  repository: RelationshipManager
327
345
 
328
346
 
347
+ class CoreGeneratorAction(CoreAction):
348
+ generator: RelationshipManager
349
+
350
+
329
351
  class CoreGeneratorCheck(CoreCheck):
330
352
  instance: String
331
353
 
@@ -380,6 +402,16 @@ class CoreGraphQLQueryGroup(CoreGroup):
380
402
  query: RelationshipManager
381
403
 
382
404
 
405
+ class CoreGroupAction(CoreAction):
406
+ member_action: Dropdown
407
+ group: RelationshipManager
408
+
409
+
410
+ class CoreGroupTriggerRule(CoreTriggerRule):
411
+ member_update: Dropdown
412
+ group: RelationshipManager
413
+
414
+
383
415
  class CoreIPAddressPool(CoreResourcePool, LineageSource):
384
416
  default_address_type: String
385
417
  default_prefix_length: IntegerOptional
@@ -399,11 +431,31 @@ class CoreMenuItem(CoreMenu):
399
431
  pass
400
432
 
401
433
 
434
+ class CoreNodeTriggerAttributeMatch(CoreNodeTriggerMatch):
435
+ attribute_name: String
436
+ value: StringOptional
437
+ value_previous: StringOptional
438
+ value_match: Dropdown
439
+
440
+
441
+ class CoreNodeTriggerRelationshipMatch(CoreNodeTriggerMatch):
442
+ relationship_name: String
443
+ modification_type: Dropdown
444
+ peer: StringOptional
445
+
446
+
447
+ class CoreNodeTriggerRule(CoreTriggerRule):
448
+ node_kind: String
449
+ mutation_action: Enum
450
+ matches: RelationshipManager
451
+
452
+
402
453
  class CoreNumberPool(CoreResourcePool, LineageSource):
403
454
  node: String
404
455
  node_attribute: String
405
456
  start_range: Integer
406
457
  end_range: Integer
458
+ pool_type: Enum
407
459
 
408
460
 
409
461
  class CoreObjectPermission(CoreBasePermission):
@@ -446,6 +498,11 @@ class CoreRepository(LineageOwner, LineageSource, CoreGenericRepository, CoreTas
446
498
  commit: StringOptional
447
499
 
448
500
 
501
+ class CoreRepositoryGroup(CoreGroup):
502
+ content: Dropdown
503
+ repository: RelationshipManager
504
+
505
+
449
506
  class CoreRepositoryValidator(CoreValidator):
450
507
  repository: RelationshipManager
451
508
 
@@ -7,6 +7,7 @@ from typing_extensions import Self
7
7
  if TYPE_CHECKING:
8
8
  from neo4j import AsyncResult, AsyncSession, AsyncTransaction, Record
9
9
 
10
+ from infrahub.core.schema import NonGenericSchemaTypes
10
11
  from infrahub.core.schema.schema_branch import SchemaBranch
11
12
 
12
13
 
@@ -70,6 +71,8 @@ class CoreNode(Protocol):
70
71
 
71
72
  def get_id(self) -> str: ...
72
73
  def get_kind(self) -> str: ...
74
+ def get_schema(self) -> NonGenericSchemaTypes: ...
75
+
73
76
  @classmethod
74
77
  async def init(
75
78
  cls,
@@ -424,8 +424,8 @@ class Query(ABC):
424
424
  else:
425
425
  self.query_lines.extend([line.strip() for line in query.split("\n") if line.strip()])
426
426
 
427
- def add_subquery(self, subquery: str, with_clause: str | None = None) -> None:
428
- self.add_to_query("CALL {")
427
+ def add_subquery(self, subquery: str, node_alias: str, with_clause: str | None = None) -> None:
428
+ self.add_to_query(f"CALL ({node_alias}) {{")
429
429
  self.add_to_query(subquery)
430
430
  self.add_to_query("}")
431
431
  if with_clause:
@@ -21,7 +21,7 @@ class DeleteAfterTimeQuery(Query):
21
21
  // ---------------------
22
22
  // Reset edges with to time after timestamp
23
23
  // ---------------------
24
- CALL {
24
+ CALL () {
25
25
  OPTIONAL MATCH (p)-[r]-(q)
26
26
  WHERE r.to > $timestamp
27
27
  SET r.to = NULL
@@ -33,7 +33,7 @@ class DeleteAfterTimeQuery(Query):
33
33
  // ---------------------
34
34
  // Delete edges with from time after timestamp timestamp
35
35
  // ---------------------
36
- CALL {
36
+ CALL () {
37
37
  OPTIONAL MATCH (p)-[r]->(q)
38
38
  WHERE r.from > $timestamp
39
39
  DELETE r
@@ -49,7 +49,7 @@ class DeleteAfterTimeQuery(Query):
49
49
  // ---------------------
50
50
  // Delete edges with from time after timestamp timestamp
51
51
  // ---------------------
52
- CALL {
52
+ CALL () {
53
53
  OPTIONAL MATCH (p)-[r]->(q)
54
54
  WHERE r.from > $timestamp
55
55
  DELETE r
@@ -121,10 +121,9 @@ class DiffCalculationQuery(DiffQuery):
121
121
 
122
122
  previous_base_path_query = """
123
123
  WITH DISTINCT diff_path AS diff_path, has_more_data
124
- CALL {
125
- WITH diff_path
126
- WITH diff_path, nodes(diff_path) AS d_nodes, relationships(diff_path) AS d_rels
127
- WITH diff_path, d_rels[0] AS r_root, d_nodes[1] AS n, d_rels[1] AS r_node, d_nodes[2] AS attr_rel, d_rels[2] AS r_prop
124
+ CALL (diff_path) {
125
+ WITH nodes(diff_path) AS d_nodes, relationships(diff_path) AS d_rels
126
+ WITH d_rels[0] AS r_root, d_nodes[1] AS n, d_rels[1] AS r_node, d_nodes[2] AS attr_rel, d_rels[2] AS r_prop
128
127
  // -------------------------------------
129
128
  // add base branch paths before branched_from, if they exist
130
129
  // -------------------------------------
@@ -158,10 +157,9 @@ CALL {
158
157
  WITH diff_path, latest_base_path, has_more_data
159
158
  UNWIND [diff_path, latest_base_path] AS penultimate_path
160
159
  WITH DISTINCT penultimate_path, has_more_data
161
- CALL {
162
- WITH penultimate_path
163
- WITH penultimate_path, nodes(penultimate_path) AS d_nodes, relationships(penultimate_path) AS d_rels
164
- WITH penultimate_path, d_rels[0] AS r_root, d_nodes[1] AS n, d_rels[1] AS r_node, d_nodes[2] AS attr_rel, d_rels[2] AS r_prop
160
+ CALL (penultimate_path) {
161
+ WITH nodes(penultimate_path) AS d_nodes, relationships(penultimate_path) AS d_rels
162
+ WITH d_rels[0] AS r_root, d_nodes[1] AS n, d_rels[1] AS r_node, d_nodes[2] AS attr_rel, d_rels[2] AS r_prop
165
163
  // -------------------------------------
166
164
  // Add peer-side of any relationships to get the peer's ID
167
165
  // -------------------------------------
@@ -299,8 +297,7 @@ WITH one_result[0] AS p, one_result[1] AS q, one_result[2] AS diff_rel, one_resu
299
297
  // -------------------------------------
300
298
  // Exclude nodes added then removed on branch within timeframe
301
299
  // -------------------------------------
302
- CALL {
303
- WITH p, q, row_from_time
300
+ CALL (p, q, row_from_time) {
304
301
  OPTIONAL MATCH (q)<-[is_part_of:IS_PART_OF {branch: $branch_name}]-(p)
305
302
  WHERE row_from_time <= is_part_of.from < $to_time
306
303
  WITH DISTINCT is_part_of.status AS rel_status
@@ -312,8 +309,7 @@ WHERE intra_branch_update = FALSE
312
309
  // -------------------------------------
313
310
  // Get every path on this branch under each node
314
311
  // -------------------------------------
315
- CALL {
316
- WITH p, q, diff_rel, row_from_time
312
+ CALL (p, q, diff_rel, row_from_time) {
317
313
  OPTIONAL MATCH path = (
318
314
  (q)<-[top_diff_rel:IS_PART_OF]-(p)-[r_node]-(node)-[r_prop]-(prop)
319
315
  )
@@ -340,12 +336,11 @@ CALL {
340
336
  p.uuid IS NULL OR prop.uuid IS NULL OR p.uuid <> prop.uuid
341
337
  OR type(r_node) <> "IS_RELATED" OR type(r_prop) <> "IS_RELATED"
342
338
  )
343
- WITH path, p, node, prop, r_prop, r_node, type(r_node) AS rel_type, row_from_time
339
+ WITH path, node, prop, r_prop, r_node, type(r_node) AS rel_type, row_from_time
344
340
  // -------------------------------------
345
341
  // Exclude attributes/relationships added then removed on branch within timeframe
346
342
  // -------------------------------------
347
- CALL {
348
- WITH p, rel_type, node, row_from_time
343
+ CALL (p, rel_type, node, row_from_time) {
349
344
  OPTIONAL MATCH (p)-[rel_to_check {branch: $branch_name}]-(node)
350
345
  WHERE row_from_time <= rel_to_check.from < $to_time
351
346
  AND type(rel_to_check) = rel_type
@@ -484,8 +479,7 @@ END AS row_from_time
484
479
  // Exclude attributes/relationship under nodes deleted on this branch in the timeframe
485
480
  // because those were all handled above at the node level
486
481
  // -------------------------------------
487
- CALL {
488
- WITH root, p, row_from_time
482
+ CALL (root, p, row_from_time) {
489
483
  OPTIONAL MATCH (root)<-[r_root_deleted:IS_PART_OF {branch: $branch_name}]-(p)
490
484
  WHERE row_from_time <= r_root_deleted.from < $to_time
491
485
  WITH r_root_deleted
@@ -499,8 +493,7 @@ WHERE node_deleted = FALSE
499
493
  // Exclude relationships added and deleted within the timeframe
500
494
  // -------------------------------------
501
495
  WITH root, r_root, p, diff_rel, q, has_more_data, row_from_time, type(diff_rel) AS rel_type
502
- CALL {
503
- WITH p, rel_type, q, row_from_time
496
+ CALL (p, rel_type, q, row_from_time) {
504
497
  OPTIONAL MATCH (p)-[rel_to_check {branch: $branch_name}]-(q)
505
498
  WHERE row_from_time <= rel_to_check.from < $to_time
506
499
  AND type(rel_to_check) = rel_type
@@ -513,8 +506,7 @@ WHERE intra_branch_update = FALSE
513
506
  // -------------------------------------
514
507
  // Get every path on this branch under each attribute/relationship
515
508
  // -------------------------------------
516
- CALL {
517
- WITH root, r_root, p, diff_rel, q
509
+ CALL (root, r_root, p, diff_rel, q) {
518
510
  OPTIONAL MATCH path = (
519
511
  (root:Root)<-[mid_r_root:IS_PART_OF]-(p)-[mid_diff_rel]-(q)-[r_prop]-(prop)
520
512
  )
@@ -549,8 +541,7 @@ CALL {
549
541
  // Exclude properties added and deleted within the timeframe
550
542
  // -------------------------------------
551
543
  WITH q, nodes(latest_prop_path)[3] AS prop, type(relationships(latest_prop_path)[2]) AS rel_type, latest_prop_path, has_more_data, row_from_time
552
- CALL {
553
- WITH q, rel_type, prop, row_from_time
544
+ CALL (q, rel_type, prop, row_from_time) {
554
545
  OPTIONAL MATCH (q)-[rel_to_check {branch: $branch_name}]-(prop)
555
546
  WHERE row_from_time <= rel_to_check.from < $to_time
556
547
  AND type(rel_to_check) = rel_type
@@ -717,14 +708,12 @@ ORDER BY
717
708
  r_node.from DESC,
718
709
  r_root.from DESC
719
710
  WITH n, p, row_from_time, diff_rel, diff_rel_path, has_more_data
720
- CALL {
711
+ CALL (n, p, row_from_time){
721
712
  // -------------------------------------
722
713
  // Exclude properties under nodes and attributes/relationships deleted
723
714
  // on this branch in the timeframe because those were all handled above
724
715
  // -------------------------------------
725
- WITH n, p, row_from_time
726
- CALL {
727
- WITH n, row_from_time
716
+ CALL (n, row_from_time) {
728
717
  OPTIONAL MATCH (root:Root)<-[r_root_deleted:IS_PART_OF {branch: $branch_name}]-(n)
729
718
  WHERE r_root_deleted.from < $to_time
730
719
  WITH r_root_deleted
@@ -732,9 +721,8 @@ CALL {
732
721
  LIMIT 1
733
722
  RETURN COALESCE(r_root_deleted.status = "deleted", FALSE) AS node_deleted
734
723
  }
735
- WITH n, p, row_from_time, node_deleted
736
- CALL {
737
- WITH n, p, row_from_time
724
+ WITH node_deleted
725
+ CALL (n, p, row_from_time) {
738
726
  OPTIONAL MATCH (n)-[r_node_deleted {branch: $branch_name}]-(p)
739
727
  WHERE row_from_time <= r_node_deleted.from < $to_time
740
728
  AND type(r_node_deleted) IN ["HAS_ATTRIBUTE", "IS_RELATED"]
@@ -803,8 +791,7 @@ AND (
803
791
  // -------------------------------------
804
792
  // Ignore node created and deleted on this branch
805
793
  // -------------------------------------
806
- CALL {
807
- WITH n
794
+ CALL (n) {
808
795
  OPTIONAL MATCH (:Root)<-[diff_rel:IS_PART_OF {branch: $branch_name}]-(n)
809
796
  WITH diff_rel
810
797
  ORDER BY diff_rel.from ASC