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
@@ -1,7 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from dataclasses import dataclass, field
4
- from typing import TYPE_CHECKING, Any, Mapping
4
+ from typing import TYPE_CHECKING, Any
5
5
 
6
6
  from graphene import InputObjectType, Mutation
7
7
  from graphene.types.mutation import MutationOptions
@@ -9,12 +9,15 @@ from infrahub_sdk.utils import extract_fields
9
9
  from typing_extensions import Self
10
10
 
11
11
  from infrahub import config, lock
12
- from infrahub.core import registry
13
- from infrahub.core.constants import InfrahubKind, MutationAction, RelationshipCardinality, RelationshipKind
12
+ from infrahub.core.constants import InfrahubKind, MutationAction
14
13
  from infrahub.core.constraint.node.runner import NodeConstraintRunner
15
14
  from infrahub.core.manager import NodeManager
16
- from infrahub.core.node import Node
17
- from infrahub.core.schema import MainSchemaTypes, NodeSchema, RelationshipSchema
15
+ from infrahub.core.node.create import (
16
+ create_node,
17
+ get_profile_ids,
18
+ refresh_for_profile_update,
19
+ )
20
+ from infrahub.core.schema import MainSchemaTypes, NodeSchema
18
21
  from infrahub.core.schema.generic_schema import GenericSchema
19
22
  from infrahub.core.schema.profile_schema import ProfileSchema
20
23
  from infrahub.core.schema.template_schema import TemplateSchema
@@ -33,8 +36,7 @@ if TYPE_CHECKING:
33
36
  from graphql import GraphQLResolveInfo
34
37
 
35
38
  from infrahub.core.branch import Branch
36
- from infrahub.core.protocols import CoreObjectTemplate
37
- from infrahub.core.relationship.model import RelationshipManager
39
+ from infrahub.core.node import Node
38
40
  from infrahub.core.schema.schema_branch import SchemaBranch
39
41
  from infrahub.database import InfrahubDatabase
40
42
  from infrahub.graphql.types.context import ContextInput
@@ -143,33 +145,6 @@ class InfrahubMutationMixin:
143
145
 
144
146
  return mutation
145
147
 
146
- @classmethod
147
- async def _get_profile_ids(cls, db: InfrahubDatabase, obj: Node) -> set[str]:
148
- if not hasattr(obj, "profiles"):
149
- return set()
150
- profile_rels = await obj.profiles.get_relationships(db=db)
151
- return {pr.peer_id for pr in profile_rels}
152
-
153
- @classmethod
154
- async def _refresh_for_profile_update(
155
- cls, db: InfrahubDatabase, branch: Branch, obj: Node, previous_profile_ids: set[str] | None = None
156
- ) -> Node:
157
- if not hasattr(obj, "profiles"):
158
- return obj
159
- current_profile_ids = await cls._get_profile_ids(db=db, obj=obj)
160
- if previous_profile_ids is None or previous_profile_ids != current_profile_ids:
161
- refreshed_node = await NodeManager.get_one_by_id_or_default_filter(
162
- db=db,
163
- kind=cls._meta.active_schema.kind,
164
- id=obj.get_id(),
165
- branch=branch,
166
- include_owner=True,
167
- include_source=True,
168
- )
169
- refreshed_node._node_changelog = obj.node_changelog
170
- return refreshed_node
171
- return obj
172
-
173
148
  @classmethod
174
149
  async def _call_mutate_create_object(cls, data: InputObjectType, db: InfrahubDatabase, branch: Branch) -> Node:
175
150
  """
@@ -185,100 +160,6 @@ class InfrahubMutationMixin:
185
160
 
186
161
  return await cls.mutate_create_object(data=data, db=db, branch=branch)
187
162
 
188
- @classmethod
189
- async def _get_template_relationship_peers(
190
- cls, db: InfrahubDatabase, template: CoreObjectTemplate, relationship: RelationshipSchema
191
- ) -> Mapping[str, Node]:
192
- """For a given relationship on the template, fetch the related peers."""
193
- template_relationship_manager: RelationshipManager = getattr(template, relationship.name)
194
- if relationship.cardinality == RelationshipCardinality.MANY:
195
- return await template_relationship_manager.get_peers(db=db)
196
-
197
- peers: dict[str, Node] = {}
198
- template_relationship_peer = await template_relationship_manager.get_peer(db=db)
199
- if template_relationship_peer:
200
- peers[template_relationship_peer.id] = template_relationship_peer
201
- return peers
202
-
203
- @classmethod
204
- async def _extract_peer_data(
205
- cls,
206
- db: InfrahubDatabase,
207
- template_peer: Node,
208
- obj_peer_schema: MainSchemaTypes,
209
- parent_obj: Node,
210
- current_template: CoreObjectTemplate,
211
- ) -> Mapping[str, Any]:
212
- obj_peer_data: dict[str, Any] = {}
213
-
214
- for attr in template_peer.get_schema().attribute_names:
215
- if attr not in obj_peer_schema.attribute_names:
216
- continue
217
- obj_peer_data[attr] = {"value": getattr(template_peer, attr).value, "source": template_peer.id}
218
-
219
- for rel in template_peer.get_schema().relationship_names:
220
- rel_manager: RelationshipManager = getattr(template_peer, rel)
221
- if (
222
- rel_manager.schema.kind not in [RelationshipKind.COMPONENT, RelationshipKind.PARENT]
223
- or rel_manager.schema.name not in obj_peer_schema.relationship_names
224
- ):
225
- continue
226
-
227
- if list(await rel_manager.get_peers(db=db)) == [current_template.id]:
228
- obj_peer_data[rel] = {"id": parent_obj.id}
229
-
230
- return obj_peer_data
231
-
232
- @classmethod
233
- async def _handle_template_relationships(
234
- cls,
235
- db: InfrahubDatabase,
236
- branch: Branch,
237
- obj: Node,
238
- template: CoreObjectTemplate,
239
- data: InputObjectType,
240
- constraint_runner: NodeConstraintRunner | None = None,
241
- ) -> None:
242
- if constraint_runner is None:
243
- component_registry = get_component_registry()
244
- constraint_runner = await component_registry.get_component(NodeConstraintRunner, db=db, branch=branch)
245
-
246
- for relationship in obj.get_relationships(kind=RelationshipKind.COMPONENT, exclude=list(data)):
247
- template_relationship_peers = await cls._get_template_relationship_peers(
248
- db=db, template=template, relationship=relationship
249
- )
250
- if not template_relationship_peers:
251
- continue
252
-
253
- for template_relationship_peer in template_relationship_peers.values():
254
- # We retrieve peer schema for each peer in case we are processing a relationship which is based on a generic
255
- obj_peer_schema = registry.schema.get_node_schema(
256
- name=template_relationship_peer.get_schema().kind.removeprefix("Template"),
257
- branch=branch,
258
- duplicate=False,
259
- )
260
- obj_peer_data = await cls._extract_peer_data(
261
- db=db,
262
- template_peer=template_relationship_peer,
263
- obj_peer_schema=obj_peer_schema,
264
- parent_obj=obj,
265
- current_template=template,
266
- )
267
-
268
- obj_peer = await Node.init(schema=obj_peer_schema, db=db, branch=branch)
269
- await obj_peer.new(db=db, **obj_peer_data)
270
- await constraint_runner.check(node=obj_peer, field_filters=list(obj_peer_data))
271
- await obj_peer.save(db=db)
272
-
273
- await cls._handle_template_relationships(
274
- db=db,
275
- branch=branch,
276
- constraint_runner=constraint_runner,
277
- obj=obj_peer,
278
- template=template_relationship_peer,
279
- data=data,
280
- )
281
-
282
163
  @classmethod
283
164
  async def mutate_create(
284
165
  cls,
@@ -301,51 +182,15 @@ class InfrahubMutationMixin:
301
182
  db: InfrahubDatabase,
302
183
  branch: Branch,
303
184
  ) -> Node:
304
- component_registry = get_component_registry()
305
- node_constraint_runner = await component_registry.get_component(
306
- NodeConstraintRunner, db=db.start_session(), branch=branch
185
+ schema = cls._meta.active_schema
186
+ if isinstance(schema, GenericSchema):
187
+ raise ValueError(f"Node of generic schema `{schema.name=}` can not be instantiated.")
188
+ return await create_node(
189
+ data=dict(data),
190
+ db=db,
191
+ branch=branch,
192
+ schema=schema,
307
193
  )
308
- node_class = Node
309
- if cls._meta.active_schema.kind in registry.node:
310
- node_class = registry.node[cls._meta.active_schema.kind]
311
-
312
- fields_to_validate = list(data)
313
- if db.is_transaction:
314
- obj = await node_class.init(db=db, schema=cls._meta.schema, branch=branch)
315
- await obj.new(db=db, **data)
316
- await node_constraint_runner.check(node=obj, field_filters=fields_to_validate)
317
- await obj.save(db=db)
318
-
319
- object_template = await obj.get_object_template(db=db)
320
- if object_template:
321
- await cls._handle_template_relationships(
322
- db=db,
323
- branch=branch,
324
- template=object_template,
325
- obj=obj,
326
- data=data,
327
- )
328
- else:
329
- async with db.start_transaction() as dbt:
330
- obj = await node_class.init(db=dbt, schema=cls._meta.schema, branch=branch)
331
- await obj.new(db=dbt, **data)
332
- await node_constraint_runner.check(node=obj, field_filters=fields_to_validate)
333
- await obj.save(db=dbt)
334
-
335
- object_template = await obj.get_object_template(db=dbt)
336
- if object_template:
337
- await cls._handle_template_relationships(
338
- db=dbt,
339
- branch=branch,
340
- template=object_template,
341
- obj=obj,
342
- data=data,
343
- )
344
-
345
- if await cls._get_profile_ids(db=db, obj=obj):
346
- obj = await cls._refresh_for_profile_update(db=db, branch=branch, obj=obj)
347
-
348
- return obj
349
194
 
350
195
  @classmethod
351
196
  async def mutate_create_to_graphql(cls, info: GraphQLResolveInfo, db: InfrahubDatabase, obj: Node) -> Self:
@@ -439,7 +284,7 @@ class InfrahubMutationMixin:
439
284
  component_registry = get_component_registry()
440
285
  node_constraint_runner = await component_registry.get_component(NodeConstraintRunner, db=db, branch=branch)
441
286
 
442
- before_mutate_profile_ids = await cls._get_profile_ids(db=db, obj=obj)
287
+ before_mutate_profile_ids = await get_profile_ids(db=db, obj=obj)
443
288
  await obj.from_graphql(db=db, data=data)
444
289
  fields_to_validate = list(data)
445
290
  await node_constraint_runner.check(
@@ -453,8 +298,12 @@ class InfrahubMutationMixin:
453
298
 
454
299
  await obj.save(db=db, fields=fields)
455
300
 
456
- obj = await cls._refresh_for_profile_update(
457
- db=db, branch=branch, obj=obj, previous_profile_ids=before_mutate_profile_ids
301
+ obj = await refresh_for_profile_update(
302
+ db=db,
303
+ branch=branch,
304
+ obj=obj,
305
+ previous_profile_ids=before_mutate_profile_ids,
306
+ schema=cls._meta.active_schema,
458
307
  )
459
308
  return obj
460
309
 
@@ -18,10 +18,10 @@ from infrahub.database import InfrahubDatabase, retry_db_transaction
18
18
  from infrahub.exceptions import BranchNotFoundError, PermissionDeniedError, ValidationError
19
19
  from infrahub.graphql.mutations.main import InfrahubMutationMixin
20
20
  from infrahub.graphql.types.enums import CheckType as GraphQLCheckType
21
- from infrahub.message_bus import messages
22
21
  from infrahub.proposed_change.constants import ProposedChangeState
23
- from infrahub.workflows.catalogue import PROPOSED_CHANGE_MERGE
22
+ from infrahub.workflows.catalogue import PROPOSED_CHANGE_MERGE, REQUEST_PROPOSED_CHANGE_PIPELINE
24
23
 
24
+ from ...proposed_change.models import RequestProposedChangePipeline
25
25
  from ..types.task import TaskInfo
26
26
  from .main import InfrahubMutationOptions
27
27
 
@@ -65,18 +65,18 @@ class InfrahubProposedChangeMutation(InfrahubMutationMixin, Mutation):
65
65
  )
66
66
 
67
67
  if graphql_context.service:
68
- message_list = [
69
- messages.RequestProposedChangePipeline(
70
- proposed_change=proposed_change.id,
71
- source_branch=source_branch.name,
72
- source_branch_sync_with_git=source_branch.sync_with_git,
73
- destination_branch=destination_branch,
74
- context=graphql_context.get_context(),
75
- ),
76
- ]
77
-
78
- for message in message_list:
79
- await graphql_context.service.message_bus.send(message=message)
68
+ request_proposed_change_model = RequestProposedChangePipeline(
69
+ proposed_change=proposed_change.id,
70
+ source_branch=source_branch.name,
71
+ source_branch_sync_with_git=source_branch.sync_with_git,
72
+ destination_branch=destination_branch,
73
+ )
74
+
75
+ await graphql_context.service.workflow.submit_workflow(
76
+ workflow=REQUEST_PROPOSED_CHANGE_PIPELINE,
77
+ parameters={"model": request_proposed_change_model},
78
+ context=graphql_context.get_context(),
79
+ )
80
80
 
81
81
  return proposed_change, result
82
82
 
@@ -175,16 +175,19 @@ class ProposedChangeRequestRunCheck(Mutation):
175
175
  destination_branch = proposed_change.destination_branch.value
176
176
  source_branch = await _get_source_branch(db=graphql_context.db, name=proposed_change.source_branch.value)
177
177
 
178
- message = messages.RequestProposedChangePipeline(
178
+ request_proposed_change_model = RequestProposedChangePipeline(
179
179
  proposed_change=proposed_change.id,
180
180
  source_branch=source_branch.name,
181
181
  source_branch_sync_with_git=source_branch.sync_with_git,
182
182
  destination_branch=destination_branch,
183
183
  check_type=check_type,
184
- context=graphql_context.get_context(),
185
184
  )
186
185
  if graphql_context.service:
187
- await graphql_context.service.message_bus.send(message=message)
186
+ await graphql_context.service.workflow.submit_workflow(
187
+ workflow=REQUEST_PROPOSED_CHANGE_PIPELINE,
188
+ parameters={"model": request_proposed_change_model},
189
+ context=graphql_context.get_context(),
190
+ )
188
191
 
189
192
  return {"ok": True}
190
193
 
@@ -85,6 +85,7 @@ class RelationshipAdd(Mutation):
85
85
  nodes = await _validate_peers(info=info, data=data)
86
86
  await _validate_permissions(info=info, source_node=source, peers=nodes)
87
87
  await _validate_peer_types(info=info, data=data, source_node=source, peers=nodes)
88
+ await _validate_peer_parents(info=info, data=data, source_node=source, peers=nodes)
88
89
 
89
90
  # This has to be done after validating the permissions
90
91
  await apply_external_context(graphql_context=graphql_context, context_input=context)
@@ -406,6 +407,37 @@ async def _validate_peer_types(
406
407
  )
407
408
 
408
409
 
410
+ async def _validate_peer_parents(
411
+ info: GraphQLResolveInfo, data: RelationshipNodesInput, source_node: Node, peers: dict[str, Node]
412
+ ) -> None:
413
+ relationship_name = str(data.name)
414
+ rel_schema = source_node.get_schema().get_relationship(name=relationship_name)
415
+ if not rel_schema.common_parent:
416
+ return
417
+
418
+ graphql_context: GraphqlContext = info.context
419
+
420
+ source_node_parent = await source_node.get_parent_relationship_peer(
421
+ db=graphql_context.db, name=rel_schema.common_parent
422
+ )
423
+ if not source_node_parent:
424
+ # If the schema is properly validated we are not expecting this to happen
425
+ raise ValidationError(f"Node {source_node.id} ({source_node.get_kind()!r}) does not have a parent peer")
426
+
427
+ parents: set[str] = {source_node_parent.id}
428
+ for peer in peers.values():
429
+ peer_parent = await peer.get_parent_relationship_peer(db=graphql_context.db, name=rel_schema.common_parent)
430
+ if not peer_parent:
431
+ # If the schema is properly validated we are not expecting this to happen
432
+ raise ValidationError(f"Peer {peer.id} ({peer.get_kind()!r}) does not have a parent peer")
433
+ parents.add(peer_parent.id)
434
+
435
+ if len(parents) > 1:
436
+ raise ValidationError(
437
+ f"Cannot relate {source_node.id!r} to '{relationship_name}' peers that do not have the same parent"
438
+ )
439
+
440
+
409
441
  async def _collect_current_peers(
410
442
  info: GraphQLResolveInfo, data: RelationshipNodesInput, source_node: Node
411
443
  ) -> dict[str, RelationshipPeerData]:
@@ -6,15 +6,18 @@ from graphene import Boolean, Field, InputField, InputObjectType, Int, List, Mut
6
6
  from graphene.types.generic import GenericScalar
7
7
  from typing_extensions import Self
8
8
 
9
- from infrahub.core import registry
10
- from infrahub.core.constants import InfrahubKind
9
+ from infrahub.core import protocols, registry
10
+ from infrahub.core.constants import InfrahubKind, NumberPoolType
11
11
  from infrahub.core.ipam.constants import PrefixMemberType
12
+ from infrahub.core.manager import NodeManager
12
13
  from infrahub.core.schema import NodeSchema
14
+ from infrahub.core.schema.attribute_parameters import NumberAttributeParameters
13
15
  from infrahub.database import retry_db_transaction
14
16
  from infrahub.exceptions import QueryValidationError, SchemaNotFoundError, ValidationError
17
+ from infrahub.pools.registration import get_branches_with_schema_number_pool
15
18
 
16
19
  from ..queries.resource_manager import PoolAllocatedNode
17
- from .main import InfrahubMutationMixin, InfrahubMutationOptions
20
+ from .main import DeleteResult, InfrahubMutationMixin, InfrahubMutationOptions
18
21
 
19
22
  if TYPE_CHECKING:
20
23
  from graphql import GraphQLResolveInfo
@@ -177,14 +180,16 @@ class InfrahubNumberPoolMutation(InfrahubMutationMixin, Mutation):
177
180
  database: InfrahubDatabase | None = None, # noqa: ARG003
178
181
  ) -> Any:
179
182
  try:
180
- pool_node = registry.schema.get(name=data["node"].value)
181
- if not pool_node.is_generic_schema and not pool_node.is_node_schema:
183
+ schema_node = registry.schema.get(name=data["node"].value)
184
+ if not schema_node.is_generic_schema and not schema_node.is_node_schema:
182
185
  raise ValidationError(input_value="The selected model is not a Node or a Generic")
183
186
  except SchemaNotFoundError as exc:
184
187
  exc.message = "The selected model does not exist"
185
188
  raise exc
186
189
 
187
- attributes = [attribute for attribute in pool_node.attributes if attribute.name == data["node_attribute"].value]
190
+ attributes = [
191
+ attribute for attribute in schema_node.attributes if attribute.name == data["node_attribute"].value
192
+ ]
188
193
  if not attributes:
189
194
  raise ValidationError(input_value="The selected attribute doesn't exist in the selected model")
190
195
 
@@ -192,9 +197,22 @@ class InfrahubNumberPoolMutation(InfrahubMutationMixin, Mutation):
192
197
  if attribute.kind != "Number":
193
198
  raise ValidationError(input_value="The selected attribute is not of the kind Number")
194
199
 
195
- if data["start_range"].value > data["end_range"].value:
200
+ start_range = data["start_range"].value
201
+ end_range = data["end_range"].value
202
+ if start_range > end_range:
196
203
  raise ValidationError(input_value="start_range can't be larger than end_range")
197
204
 
205
+ if not isinstance(attribute.parameters, NumberAttributeParameters):
206
+ raise ValidationError(
207
+ input_value="The selected attribute parameters are not of the kind NumberAttributeParameters"
208
+ )
209
+
210
+ if attribute.parameters.min_value is not None and start_range < attribute.parameters.min_value:
211
+ raise ValidationError(input_value="start_range can't be less than min_value")
212
+
213
+ if attribute.parameters.max_value is not None and end_range > attribute.parameters.max_value:
214
+ raise ValidationError(input_value="end_range can't be larger than max_value")
215
+
198
216
  return await super().mutate_create(info=info, data=data, branch=branch)
199
217
 
200
218
  @classmethod
@@ -217,7 +235,45 @@ class InfrahubNumberPoolMutation(InfrahubMutationMixin, Mutation):
217
235
  number_pool, result = await super().mutate_update(
218
236
  info=info, data=data, branch=branch, database=dbt, node=node
219
237
  )
238
+
239
+ if number_pool.pool_type.value.value == NumberPoolType.SCHEMA.value and ( # type: ignore[attr-defined]
240
+ "start_range" in data.keys() or "end_range" in data.keys()
241
+ ):
242
+ raise ValidationError(
243
+ input_value="start_range or end_range can't be updated on schema defined pools, update the schema in the default branch instead"
244
+ )
245
+
220
246
  if number_pool.start_range.value > number_pool.end_range.value: # type: ignore[attr-defined]
221
247
  raise ValidationError(input_value="start_range can't be larger than end_range")
222
248
 
223
249
  return number_pool, result
250
+
251
+ @classmethod
252
+ @retry_db_transaction(name="resource_manager_update")
253
+ async def mutate_delete(
254
+ cls,
255
+ info: GraphQLResolveInfo,
256
+ data: InputObjectType,
257
+ branch: Branch,
258
+ ) -> DeleteResult:
259
+ graphql_context: GraphqlContext = info.context
260
+
261
+ number_pool = await NodeManager.find_object(
262
+ db=graphql_context.db,
263
+ kind=protocols.CoreNumberPool,
264
+ id=data.get("id"),
265
+ hfid=data.get("hfid"),
266
+ branch=branch,
267
+ )
268
+
269
+ violating_branches = get_branches_with_schema_number_pool(
270
+ kind=number_pool.node.value, attribute_name=number_pool.node_attribute.value
271
+ )
272
+
273
+ if violating_branches:
274
+ raise ValidationError(
275
+ input_value=f"Unable to delete number pool {number_pool.node.value}.{number_pool.node_attribute.value}"
276
+ f" is in use (branches: {','.join(violating_branches)})"
277
+ )
278
+
279
+ return await super().mutate_delete(info=info, data=data, branch=branch)
@@ -0,0 +1,34 @@
1
+ from graphene import Field, ObjectType, String
2
+ from graphene.types.generic import GenericScalar
3
+ from graphql import GraphQLResolveInfo
4
+
5
+ from infrahub.core import registry
6
+ from infrahub.core.convert_object_type.schema_mapping import get_schema_mapping
7
+
8
+
9
+ class FieldsMapping(ObjectType):
10
+ mapping = GenericScalar(required=True)
11
+
12
+
13
+ async def fields_mapping_type_conversion_resolver(
14
+ root: dict, # noqa: ARG001
15
+ info: GraphQLResolveInfo,
16
+ source_kind: str,
17
+ target_kind: str,
18
+ ) -> dict:
19
+ source_schema = registry.get_node_schema(name=source_kind, branch=info.context.branch)
20
+ target_schema = registry.get_node_schema(name=target_kind, branch=info.context.branch)
21
+
22
+ mapping = get_schema_mapping(source_schema=source_schema, target_schema=target_schema)
23
+ mapping_dict = {field_name: model.model_dump(mode="json") for field_name, model in mapping.items()}
24
+ return {"mapping": mapping_dict}
25
+
26
+
27
+ FieldsMappingTypeConversion = Field(
28
+ FieldsMapping,
29
+ source_kind=String(),
30
+ target_kind=String(),
31
+ description="Retrieve fields mapping for converting object type",
32
+ resolver=fields_mapping_type_conversion_resolver,
33
+ required=True,
34
+ )
@@ -306,7 +306,13 @@ async def resolve_number_pool_allocation(
306
306
  async def resolve_number_pool_utilization(
307
307
  db: InfrahubDatabase, pool: CoreNode, at: Timestamp | str | None, branch: Branch
308
308
  ) -> dict:
309
- number_pool = NumberUtilizationGetter(db=db, pool=pool, at=at, branch=branch)
309
+ """
310
+ Returns a mapping containg utilization info of a number pool.
311
+ The utilization is calculated as the percentage of the total number of values in the pool that are not excluded for the corresponding attribute.
312
+ """
313
+
314
+ core_number_pool = await registry.manager.get_one_by_id_or_default_filter(db=db, id=pool.id, kind="CoreNumberPool")
315
+ number_pool = NumberUtilizationGetter(db=db, pool=core_number_pool, at=at, branch=branch)
310
316
  await number_pool.load_data()
311
317
 
312
318
  return {
@@ -99,7 +99,7 @@ class ManyRelationshipResolver:
99
99
  filters = {
100
100
  f"{info.field_name}__{key}": value
101
101
  for key, value in kwargs.items()
102
- if "__" in key and value or key in ["id", "ids"]
102
+ if ("__" in key and value) or key in ["id", "ids"]
103
103
  }
104
104
 
105
105
  response: dict[str, Any] = {"edges": [], "count": None}
@@ -99,7 +99,7 @@ async def default_resolver(*args: Any, **kwargs) -> dict | list[dict] | None:
99
99
  filters = {
100
100
  f"{info.field_name}__{key}": value
101
101
  for key, value in kwargs.items()
102
- if "__" in key and value or key in ["id", "ids"]
102
+ if ("__" in key and value) or key in ["id", "ids"]
103
103
  }
104
104
 
105
105
  async with graphql_context.db.start_session(read_only=True) as db:
@@ -288,7 +288,7 @@ async def hierarchy_resolver(
288
288
  filters = {
289
289
  f"{info.field_name}__{key}": value
290
290
  for key, value in kwargs.items()
291
- if "__" in key and value or key in ["id", "ids"]
291
+ if ("__" in key and value) or key in ["id", "ids"]
292
292
  }
293
293
 
294
294
  response: dict[str, Any] = {"edges": [], "count": None}
@@ -107,7 +107,7 @@ class SingleRelationshipResolver:
107
107
  filters = {
108
108
  f"{field_name}__{key}": value
109
109
  for key, value in kwargs.items()
110
- if "__" in key and value or key in ["id", "ids"]
110
+ if ("__" in key and value) or key in ["id", "ids"]
111
111
  }
112
112
  async with db.start_session(read_only=True) as dbs:
113
113
  objs = await NodeManager.query_peers(
@@ -16,6 +16,7 @@ from .mutations.branch import (
16
16
  BranchValidate,
17
17
  )
18
18
  from .mutations.computed_attribute import UpdateComputedAttribute
19
+ from .mutations.convert_object_type import ConvertObjectType
19
20
  from .mutations.diff import DiffUpdateMutation
20
21
  from .mutations.diff_conflict import ResolveDiffConflict
21
22
  from .mutations.generator import GeneratorDefinitionRequestRun
@@ -48,6 +49,7 @@ from .queries import (
48
49
  InfrahubStatus,
49
50
  Relationship,
50
51
  )
52
+ from .queries.convert_object_type_mapping import FieldsMappingTypeConversion
51
53
  from .queries.diff.tree import DiffTreeQuery, DiffTreeSummaryQuery
52
54
  from .queries.event import Event
53
55
  from .queries.task import Task, TaskBranchStatus
@@ -77,6 +79,8 @@ class InfrahubBaseQuery(ObjectType):
77
79
  InfrahubResourcePoolAllocated = InfrahubResourcePoolAllocated
78
80
  InfrahubResourcePoolUtilization = InfrahubResourcePoolUtilization
79
81
 
82
+ FieldsMappingTypeConversion = FieldsMappingTypeConversion
83
+
80
84
 
81
85
  class InfrahubBaseMutation(ObjectType):
82
86
  InfrahubAccountTokenCreate = InfrahubAccountTokenCreate.Field()
@@ -109,3 +113,5 @@ class InfrahubBaseMutation(ObjectType):
109
113
  SchemaEnumAdd = SchemaEnumAdd.Field()
110
114
  SchemaEnumRemove = SchemaEnumRemove.Field()
111
115
  ResolveDiffConflict = ResolveDiffConflict.Field()
116
+
117
+ ConvertObjectType = ConvertObjectType.Field()
infrahub/menu/menu.py CHANGED
@@ -57,7 +57,7 @@ default_menu = [
57
57
  name="IPPrefix",
58
58
  label="IP Prefixes",
59
59
  kind=InfrahubKind.IPPREFIX,
60
- path="/ipam/prefixes",
60
+ path="/ipam",
61
61
  icon=_extract_node_icon(infrahub_schema.get(InfrahubKind.IPPREFIX)),
62
62
  protected=True,
63
63
  section=MenuSection.INTERNAL,
@@ -68,7 +68,7 @@ default_menu = [
68
68
  name="IPAddress",
69
69
  label="IP Addresses",
70
70
  kind=InfrahubKind.IPPREFIX,
71
- path="/ipam/addresses?ipam-tab=ip-details",
71
+ path="/ipam/ip_addresses",
72
72
  icon=_extract_node_icon(infrahub_schema.get(InfrahubKind.IPADDRESS)),
73
73
  protected=True,
74
74
  section=MenuSection.INTERNAL,
@@ -79,6 +79,7 @@ default_menu = [
79
79
  name="Namespaces",
80
80
  label="Namespaces",
81
81
  kind=InfrahubKind.IPNAMESPACE,
82
+ path="/ipam/namespaces",
82
83
  icon=_extract_node_icon(infrahub_schema.get(InfrahubKind.IPNAMESPACE)),
83
84
  protected=True,
84
85
  section=MenuSection.INTERNAL,
@@ -246,6 +247,37 @@ default_menu = [
246
247
  section=MenuSection.INTERNAL,
247
248
  order_weight=2000,
248
249
  ),
250
+ MenuItemDefinition(
251
+ namespace="Builtin",
252
+ name="TriggerDefinition",
253
+ label="Events",
254
+ icon=_extract_node_icon(infrahub_schema.get(InfrahubKind.TRIGGERRULE)),
255
+ protected=True,
256
+ section=MenuSection.INTERNAL,
257
+ order_weight=6000,
258
+ children=[
259
+ MenuItemDefinition(
260
+ namespace="Builtin",
261
+ name="TriggerRule",
262
+ label="Rules",
263
+ kind=InfrahubKind.TRIGGERRULE,
264
+ icon=_extract_node_icon(infrahub_schema.get(InfrahubKind.TRIGGERRULE)),
265
+ protected=True,
266
+ section=MenuSection.INTERNAL,
267
+ order_weight=1000,
268
+ ),
269
+ MenuItemDefinition(
270
+ namespace="Builtin",
271
+ name="Action",
272
+ label="Actions",
273
+ kind=InfrahubKind.ACTION,
274
+ icon=_extract_node_icon(infrahub_schema.get(InfrahubKind.ACTION)),
275
+ protected=True,
276
+ section=MenuSection.INTERNAL,
277
+ order_weight=2000,
278
+ ),
279
+ ],
280
+ ),
249
281
  ],
250
282
  ),
251
283
  MenuItemDefinition(