infrahub-server 1.4.10__py3-none-any.whl → 1.5.0b1__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 (178) hide show
  1. infrahub/actions/tasks.py +208 -16
  2. infrahub/api/artifact.py +3 -0
  3. infrahub/api/diff/diff.py +1 -1
  4. infrahub/api/query.py +2 -0
  5. infrahub/api/schema.py +3 -0
  6. infrahub/auth.py +5 -5
  7. infrahub/cli/db.py +26 -2
  8. infrahub/cli/db_commands/clean_duplicate_schema_fields.py +212 -0
  9. infrahub/config.py +7 -2
  10. infrahub/core/attribute.py +25 -22
  11. infrahub/core/branch/models.py +2 -2
  12. infrahub/core/branch/needs_rebase_status.py +11 -0
  13. infrahub/core/branch/tasks.py +4 -3
  14. infrahub/core/changelog/models.py +4 -12
  15. infrahub/core/constants/__init__.py +1 -0
  16. infrahub/core/constants/infrahubkind.py +1 -0
  17. infrahub/core/convert_object_type/object_conversion.py +201 -0
  18. infrahub/core/convert_object_type/repository_conversion.py +89 -0
  19. infrahub/core/convert_object_type/schema_mapping.py +27 -3
  20. infrahub/core/diff/model/path.py +4 -0
  21. infrahub/core/diff/payload_builder.py +1 -1
  22. infrahub/core/diff/query/artifact.py +1 -1
  23. infrahub/core/graph/__init__.py +1 -1
  24. infrahub/core/initialization.py +2 -2
  25. infrahub/core/ipam/utilization.py +1 -1
  26. infrahub/core/manager.py +9 -84
  27. infrahub/core/migrations/graph/__init__.py +6 -0
  28. infrahub/core/migrations/graph/m040_profile_attrs_in_db.py +166 -0
  29. infrahub/core/migrations/graph/m041_create_hfid_display_label_in_db.py +97 -0
  30. infrahub/core/migrations/graph/m042_backfill_hfid_display_label_in_db.py +86 -0
  31. infrahub/core/migrations/schema/node_attribute_add.py +5 -2
  32. infrahub/core/migrations/shared.py +5 -6
  33. infrahub/core/node/__init__.py +165 -42
  34. infrahub/core/node/constraints/attribute_uniqueness.py +3 -1
  35. infrahub/core/node/create.py +67 -35
  36. infrahub/core/node/lock_utils.py +98 -0
  37. infrahub/core/node/node_property_attribute.py +230 -0
  38. infrahub/core/node/standard.py +1 -1
  39. infrahub/core/property.py +11 -0
  40. infrahub/core/protocols.py +8 -1
  41. infrahub/core/query/attribute.py +27 -15
  42. infrahub/core/query/node.py +61 -185
  43. infrahub/core/query/relationship.py +43 -26
  44. infrahub/core/query/subquery.py +0 -8
  45. infrahub/core/registry.py +2 -2
  46. infrahub/core/relationship/constraints/count.py +1 -1
  47. infrahub/core/relationship/model.py +60 -20
  48. infrahub/core/schema/attribute_schema.py +0 -2
  49. infrahub/core/schema/basenode_schema.py +42 -2
  50. infrahub/core/schema/definitions/core/__init__.py +2 -0
  51. infrahub/core/schema/definitions/core/generator.py +2 -0
  52. infrahub/core/schema/definitions/core/group.py +16 -2
  53. infrahub/core/schema/definitions/core/repository.py +7 -0
  54. infrahub/core/schema/definitions/internal.py +14 -1
  55. infrahub/core/schema/generated/base_node_schema.py +6 -1
  56. infrahub/core/schema/node_schema.py +5 -2
  57. infrahub/core/schema/relationship_schema.py +0 -1
  58. infrahub/core/schema/schema_branch.py +137 -2
  59. infrahub/core/schema/schema_branch_display.py +123 -0
  60. infrahub/core/schema/schema_branch_hfid.py +114 -0
  61. infrahub/core/validators/aggregated_checker.py +1 -1
  62. infrahub/core/validators/determiner.py +12 -1
  63. infrahub/core/validators/relationship/peer.py +1 -1
  64. infrahub/core/validators/tasks.py +1 -1
  65. infrahub/display_labels/__init__.py +0 -0
  66. infrahub/display_labels/gather.py +48 -0
  67. infrahub/display_labels/models.py +240 -0
  68. infrahub/display_labels/tasks.py +186 -0
  69. infrahub/display_labels/triggers.py +22 -0
  70. infrahub/events/group_action.py +1 -1
  71. infrahub/events/node_action.py +1 -1
  72. infrahub/generators/constants.py +7 -0
  73. infrahub/generators/models.py +38 -12
  74. infrahub/generators/tasks.py +34 -16
  75. infrahub/git/base.py +38 -1
  76. infrahub/git/integrator.py +22 -14
  77. infrahub/graphql/analyzer.py +1 -1
  78. infrahub/graphql/api/dependencies.py +2 -4
  79. infrahub/graphql/api/endpoints.py +2 -2
  80. infrahub/graphql/app.py +2 -4
  81. infrahub/graphql/initialization.py +2 -3
  82. infrahub/graphql/manager.py +212 -137
  83. infrahub/graphql/middleware.py +12 -0
  84. infrahub/graphql/mutations/branch.py +11 -0
  85. infrahub/graphql/mutations/computed_attribute.py +110 -3
  86. infrahub/graphql/mutations/convert_object_type.py +34 -13
  87. infrahub/graphql/mutations/display_label.py +111 -0
  88. infrahub/graphql/mutations/generator.py +25 -7
  89. infrahub/graphql/mutations/hfid.py +118 -0
  90. infrahub/graphql/mutations/ipam.py +21 -8
  91. infrahub/graphql/mutations/main.py +37 -153
  92. infrahub/graphql/mutations/profile.py +195 -0
  93. infrahub/graphql/mutations/proposed_change.py +2 -1
  94. infrahub/graphql/mutations/relationship.py +2 -2
  95. infrahub/graphql/mutations/repository.py +22 -83
  96. infrahub/graphql/mutations/resource_manager.py +2 -2
  97. infrahub/graphql/mutations/schema.py +5 -5
  98. infrahub/graphql/mutations/webhook.py +1 -1
  99. infrahub/graphql/queries/resource_manager.py +1 -1
  100. infrahub/graphql/registry.py +173 -0
  101. infrahub/graphql/resolvers/resolver.py +2 -0
  102. infrahub/graphql/schema.py +8 -1
  103. infrahub/groups/tasks.py +1 -1
  104. infrahub/hfid/__init__.py +0 -0
  105. infrahub/hfid/gather.py +48 -0
  106. infrahub/hfid/models.py +240 -0
  107. infrahub/hfid/tasks.py +185 -0
  108. infrahub/hfid/triggers.py +22 -0
  109. infrahub/lock.py +67 -30
  110. infrahub/locks/__init__.py +0 -0
  111. infrahub/locks/tasks.py +37 -0
  112. infrahub/middleware.py +26 -1
  113. infrahub/patch/plan_writer.py +2 -2
  114. infrahub/profiles/__init__.py +0 -0
  115. infrahub/profiles/node_applier.py +101 -0
  116. infrahub/profiles/queries/__init__.py +0 -0
  117. infrahub/profiles/queries/get_profile_data.py +99 -0
  118. infrahub/profiles/tasks.py +63 -0
  119. infrahub/proposed_change/tasks.py +10 -1
  120. infrahub/repositories/__init__.py +0 -0
  121. infrahub/repositories/create_repository.py +113 -0
  122. infrahub/server.py +16 -3
  123. infrahub/services/__init__.py +8 -5
  124. infrahub/tasks/registry.py +6 -4
  125. infrahub/trigger/catalogue.py +4 -0
  126. infrahub/trigger/models.py +2 -0
  127. infrahub/trigger/tasks.py +3 -0
  128. infrahub/webhook/models.py +1 -1
  129. infrahub/workflows/catalogue.py +110 -3
  130. infrahub/workflows/initialization.py +16 -0
  131. infrahub/workflows/models.py +17 -2
  132. infrahub_sdk/branch.py +5 -8
  133. infrahub_sdk/checks.py +1 -1
  134. infrahub_sdk/client.py +364 -84
  135. infrahub_sdk/convert_object_type.py +61 -0
  136. infrahub_sdk/ctl/check.py +2 -3
  137. infrahub_sdk/ctl/cli_commands.py +18 -12
  138. infrahub_sdk/ctl/config.py +8 -2
  139. infrahub_sdk/ctl/generator.py +6 -3
  140. infrahub_sdk/ctl/graphql.py +184 -0
  141. infrahub_sdk/ctl/repository.py +39 -1
  142. infrahub_sdk/ctl/schema.py +18 -3
  143. infrahub_sdk/ctl/utils.py +4 -0
  144. infrahub_sdk/ctl/validate.py +5 -3
  145. infrahub_sdk/diff.py +4 -5
  146. infrahub_sdk/exceptions.py +2 -0
  147. infrahub_sdk/generator.py +7 -1
  148. infrahub_sdk/graphql/__init__.py +12 -0
  149. infrahub_sdk/graphql/constants.py +1 -0
  150. infrahub_sdk/graphql/plugin.py +85 -0
  151. infrahub_sdk/graphql/query.py +77 -0
  152. infrahub_sdk/{graphql.py → graphql/renderers.py} +88 -75
  153. infrahub_sdk/graphql/utils.py +40 -0
  154. infrahub_sdk/node/attribute.py +2 -0
  155. infrahub_sdk/node/node.py +28 -20
  156. infrahub_sdk/playback.py +1 -2
  157. infrahub_sdk/protocols.py +54 -6
  158. infrahub_sdk/pytest_plugin/plugin.py +7 -4
  159. infrahub_sdk/pytest_plugin/utils.py +40 -0
  160. infrahub_sdk/repository.py +1 -2
  161. infrahub_sdk/schema/__init__.py +38 -0
  162. infrahub_sdk/schema/main.py +1 -0
  163. infrahub_sdk/schema/repository.py +8 -0
  164. infrahub_sdk/spec/object.py +120 -7
  165. infrahub_sdk/spec/range_expansion.py +118 -0
  166. infrahub_sdk/timestamp.py +18 -6
  167. infrahub_sdk/transforms.py +1 -1
  168. {infrahub_server-1.4.10.dist-info → infrahub_server-1.5.0b1.dist-info}/METADATA +9 -11
  169. {infrahub_server-1.4.10.dist-info → infrahub_server-1.5.0b1.dist-info}/RECORD +177 -134
  170. infrahub_testcontainers/container.py +1 -1
  171. infrahub_testcontainers/docker-compose-cluster.test.yml +1 -1
  172. infrahub_testcontainers/docker-compose.test.yml +1 -1
  173. infrahub_testcontainers/models.py +2 -2
  174. infrahub_testcontainers/performance_test.py +4 -4
  175. infrahub/core/convert_object_type/conversion.py +0 -134
  176. {infrahub_server-1.4.10.dist-info → infrahub_server-1.5.0b1.dist-info}/LICENSE.txt +0 -0
  177. {infrahub_server-1.4.10.dist-info → infrahub_server-1.5.0b1.dist-info}/WHEEL +0 -0
  178. {infrahub_server-1.4.10.dist-info → infrahub_server-1.5.0b1.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,12 @@
1
+ from infrahub.core.branch.needs_rebase_status import check_need_rebase_status
2
+
3
+ ALLOWED_MUTATIONS_ON_NEED_REBASE_BRANCH = ["BranchRebase", "BranchDelete", "BranchCreate", "ProposedChangeCreate"]
4
+
5
+
6
+ def raise_on_mutation_on_branch_needing_rebase(next, root, info, **kwargs): # type: ignore # noqa
7
+ if info.operation.operation.value == "mutation":
8
+ mutation_name = info.operation.selection_set.selections[0].name.value
9
+ if mutation_name not in ALLOWED_MUTATIONS_ON_NEED_REBASE_BRANCH:
10
+ check_need_rebase_status(branch=info.context.branch)
11
+
12
+ return next(root, info, **kwargs)
@@ -7,8 +7,10 @@ from opentelemetry import trace
7
7
  from typing_extensions import Self
8
8
 
9
9
  from infrahub.branch.merge_mutation_checker import verify_branch_merge_mutation_allowed
10
+ from infrahub.core import registry
10
11
  from infrahub.core.branch import Branch
11
12
  from infrahub.database import retry_db_transaction
13
+ from infrahub.exceptions import BranchNotFoundError, ValidationError
12
14
  from infrahub.graphql.context import apply_external_context
13
15
  from infrahub.graphql.field_extractor import extract_graphql_fields
14
16
  from infrahub.graphql.types.context import ContextInput
@@ -66,12 +68,21 @@ class BranchCreate(Mutation):
66
68
  background_execution: bool = False,
67
69
  wait_until_completion: bool = True,
68
70
  ) -> Self:
71
+ if data.origin_branch and data.origin_branch != registry.default_branch:
72
+ raise ValueError(f"origin_branch must be '{registry.default_branch}'")
73
+
69
74
  graphql_context: GraphqlContext = info.context
70
75
  task: dict | None = None
71
76
 
72
77
  model = BranchCreateModel(**data)
73
78
  await apply_external_context(graphql_context=graphql_context, context_input=context)
74
79
 
80
+ try:
81
+ await Branch.get_by_name(db=graphql_context.db, name=model.name)
82
+ raise ValidationError(f"The branch {model.name} already exists")
83
+ except BranchNotFoundError:
84
+ pass
85
+
75
86
  if background_execution or not wait_until_completion:
76
87
  workflow = await graphql_context.active_service.workflow.submit_workflow(
77
88
  workflow=BRANCH_CREATE, context=graphql_context.get_context(), parameters={"model": model}
@@ -2,20 +2,28 @@ from __future__ import annotations
2
2
 
3
3
  from typing import TYPE_CHECKING, Any
4
4
 
5
- from graphene import Boolean, InputObjectType, Mutation, String
5
+ from graphene import Boolean, InputObjectType, List, Mutation, NonNull, String
6
6
 
7
7
  from infrahub.core.account import ObjectPermission
8
8
  from infrahub.core.constants import ComputedAttributeKind, PermissionAction, PermissionDecision
9
9
  from infrahub.core.manager import NodeManager
10
+ from infrahub.core.protocols import CoreTransformPython
10
11
  from infrahub.core.registry import registry
11
12
  from infrahub.database import retry_db_transaction
12
13
  from infrahub.events import EventMeta
13
14
  from infrahub.events.node_action import NodeUpdatedEvent
14
- from infrahub.exceptions import NodeNotFoundError, ValidationError
15
+ from infrahub.exceptions import NodeNotFoundError, ProcessingError, ValidationError
15
16
  from infrahub.graphql.context import apply_external_context
16
17
  from infrahub.graphql.types.context import ContextInput
17
18
  from infrahub.log import get_log_data
18
19
  from infrahub.worker import WORKER_IDENTITY
20
+ from infrahub.workers.dependencies import get_workflow
21
+ from infrahub.workflows.catalogue import (
22
+ COMPUTED_ATTRIBUTE_PROCESS_JINJA2,
23
+ COMPUTED_ATTRIBUTE_PROCESS_TRANSFORM,
24
+ TRIGGER_UPDATE_JINJA_COMPUTED_ATTRIBUTES,
25
+ TRIGGER_UPDATE_PYTHON_COMPUTED_ATTRIBUTES,
26
+ )
19
27
 
20
28
  if TYPE_CHECKING:
21
29
  from graphql import GraphQLResolveInfo
@@ -89,7 +97,7 @@ class UpdateComputedAttribute(Mutation):
89
97
  raise NodeNotFoundError(
90
98
  node_type="target_node",
91
99
  identifier=str(data.id),
92
- message="The indicated not does not have the specified attribute_name",
100
+ message="The indicated node does not have the specified attribute_name",
93
101
  )
94
102
  if attribute_field.value != str(data.value):
95
103
  attribute_field.value = str(data.value)
@@ -117,3 +125,102 @@ class UpdateComputedAttribute(Mutation):
117
125
  result: dict[str, Any] = {"ok": True}
118
126
 
119
127
  return cls(**result)
128
+
129
+
130
+ class InfrahubComputedAttributeRecomputeInput(InputObjectType):
131
+ kind = String(required=True, description="Kind of the node to update")
132
+ attribute = String(required=True, description="Name of the computed attribute that must be recomputed")
133
+ node_ids = List(NonNull(String), description="ID of the nodes for which the attribute must be recomputed")
134
+
135
+
136
+ class RecomputeComputedAttribute(Mutation):
137
+ class Arguments:
138
+ data = InfrahubComputedAttributeRecomputeInput(required=True)
139
+ context = ContextInput(required=False)
140
+
141
+ ok = Boolean()
142
+
143
+ @classmethod
144
+ @retry_db_transaction(name="update_computed_attribute")
145
+ async def mutate(
146
+ cls,
147
+ _: dict,
148
+ info: GraphQLResolveInfo,
149
+ data: InfrahubComputedAttributeRecomputeInput,
150
+ context: ContextInput | None = None,
151
+ ) -> RecomputeComputedAttribute:
152
+ graphql_context: GraphqlContext = info.context
153
+ node_schema = registry.schema.get_node_schema(
154
+ name=str(data.kind), branch=graphql_context.branch.name, duplicate=False
155
+ )
156
+
157
+ graphql_context.active_permissions.raise_for_permission(
158
+ permission=ObjectPermission(
159
+ namespace=node_schema.namespace,
160
+ name=node_schema.name,
161
+ action=PermissionAction.UPDATE.value,
162
+ decision=PermissionDecision.ALLOW_DEFAULT.value
163
+ if graphql_context.branch.name == registry.default_branch
164
+ else PermissionDecision.ALLOW_OTHER.value,
165
+ )
166
+ )
167
+ await apply_external_context(graphql_context=graphql_context, context_input=context)
168
+
169
+ attribute = node_schema.get_attribute(name=str(data.attribute))
170
+
171
+ if not attribute:
172
+ raise ProcessingError(
173
+ message=f"The indicated node does not have the specified attribute '{data.attribute}'"
174
+ )
175
+ if not attribute.computed_attribute:
176
+ raise ProcessingError(
177
+ message=f"The indicated node does not use a computed attribute for the specified attribute '{data.attribute}'"
178
+ )
179
+
180
+ recalculate_single_workflow = COMPUTED_ATTRIBUTE_PROCESS_JINJA2
181
+ recalculate_all_workflow = TRIGGER_UPDATE_JINJA_COMPUTED_ATTRIBUTES
182
+ if attribute.computed_attribute.kind == ComputedAttributeKind.TRANSFORM_PYTHON:
183
+ if not await NodeManager.query(
184
+ db=graphql_context.db,
185
+ branch=graphql_context.branch,
186
+ schema=CoreTransformPython,
187
+ filters={"name__value": attribute.computed_attribute.transform},
188
+ ):
189
+ raise ProcessingError(
190
+ message=f"The transform for the indicated node computed attribute for the specified attribute '{data.attribute}' does not exist"
191
+ )
192
+
193
+ recalculate_single_workflow = COMPUTED_ATTRIBUTE_PROCESS_TRANSFORM
194
+ recalculate_all_workflow = TRIGGER_UPDATE_PYTHON_COMPUTED_ATTRIBUTES
195
+
196
+ if data.node_ids:
197
+ nodes = await NodeManager.get_many(
198
+ db=graphql_context.db, branch=graphql_context.branch, ids=list(data.node_ids)
199
+ )
200
+ for node in nodes.values():
201
+ await get_workflow().submit_workflow(
202
+ workflow=recalculate_single_workflow,
203
+ context=graphql_context.get_context(),
204
+ parameters={
205
+ "branch_name": graphql_context.branch.name,
206
+ "computed_attribute_name": str(data.attribute),
207
+ "computed_attribute_kind": node_schema.kind,
208
+ "node_kind": node_schema.kind,
209
+ "object_id": node.id,
210
+ "context": context,
211
+ },
212
+ )
213
+ else:
214
+ await get_workflow().submit_workflow(
215
+ workflow=recalculate_all_workflow,
216
+ context=graphql_context.get_context(),
217
+ parameters={
218
+ "branch_name": graphql_context.branch.name,
219
+ "computed_attribute_name": str(data.attribute),
220
+ "computed_attribute_kind": node_schema.kind,
221
+ "context": context,
222
+ },
223
+ )
224
+
225
+ result: dict[str, Any] = {"ok": True}
226
+ return cls(**result)
@@ -5,9 +5,12 @@ from graphene.types.generic import GenericScalar
5
5
  from graphql import GraphQLResolveInfo
6
6
 
7
7
  from infrahub.core import registry
8
- from infrahub.core.convert_object_type.conversion import InputForDestField, convert_object_type
8
+ from infrahub.core.constants.infrahubkind import READONLYREPOSITORY, REPOSITORY
9
+ from infrahub.core.convert_object_type.object_conversion import ConversionFieldInput, convert_and_validate_object_type
10
+ from infrahub.core.convert_object_type.repository_conversion import convert_repository_type
9
11
  from infrahub.core.convert_object_type.schema_mapping import get_schema_mapping
10
12
  from infrahub.core.manager import NodeManager
13
+ from infrahub.repositories.create_repository import RepositoryFinalizer
11
14
 
12
15
  if TYPE_CHECKING:
13
16
  from infrahub.graphql.initialization import GraphqlContext
@@ -44,26 +47,44 @@ class ConvertObjectType(Mutation):
44
47
  source_schema = registry.get_node_schema(name=node_to_convert.get_kind(), branch=graphql_context.branch)
45
48
  target_schema = registry.get_node_schema(name=str(data.target_kind), branch=graphql_context.branch)
46
49
 
47
- fields_mapping: dict[str, InputForDestField] = {}
50
+ fields_mapping: dict[str, ConversionFieldInput] = {}
48
51
  if not isinstance(data.fields_mapping, dict):
49
- raise ValueError(f"Expected `fields_mapping` to be a `dict`, got {type(fields_mapping)}")
52
+ raise ValueError(f"Expected `fields_mapping` to be a `dict`, got {type(data.fields_mapping)}")
50
53
 
51
54
  for field_name, input_for_dest_field_str in data.fields_mapping.items():
52
- fields_mapping[field_name] = InputForDestField(**input_for_dest_field_str)
55
+ fields_mapping[field_name] = ConversionFieldInput(**input_for_dest_field_str)
56
+
57
+ node_to_convert = await NodeManager.get_one(
58
+ id=str(data.node_id), db=graphql_context.db, branch=graphql_context.branch
59
+ )
53
60
 
54
61
  # Complete fields mapping with auto-mapping.
55
62
  mapping = get_schema_mapping(source_schema=source_schema, target_schema=target_schema)
56
63
  for field_name, mapping_value in mapping.items():
57
64
  if mapping_value.source_field_name is not None and field_name not in fields_mapping:
58
- fields_mapping[field_name] = InputForDestField(source_field=mapping_value.source_field_name)
59
-
60
- new_node = await convert_object_type(
61
- node=node_to_convert,
62
- target_schema=target_schema,
63
- mapping=fields_mapping,
64
- branch=graphql_context.branch,
65
- db=graphql_context.db,
66
- )
65
+ fields_mapping[field_name] = ConversionFieldInput(source_field=mapping_value.source_field_name)
66
+
67
+ if target_schema.kind in [REPOSITORY, READONLYREPOSITORY]:
68
+ new_node = await convert_repository_type(
69
+ repository=node_to_convert,
70
+ target_schema=target_schema,
71
+ mapping=fields_mapping,
72
+ branch=graphql_context.branch,
73
+ db=graphql_context.db,
74
+ repository_post_creator=RepositoryFinalizer(
75
+ account_session=graphql_context.active_account_session,
76
+ services=graphql_context.active_service,
77
+ context=graphql_context.get_context(),
78
+ ),
79
+ )
80
+ else:
81
+ new_node = await convert_and_validate_object_type(
82
+ node=node_to_convert,
83
+ target_schema=target_schema,
84
+ mapping=fields_mapping,
85
+ branch=graphql_context.branch,
86
+ db=graphql_context.db,
87
+ )
67
88
 
68
89
  dict_node = await new_node.to_graphql(db=graphql_context.db, fields={})
69
90
  result: dict[str, Any] = {"ok": True, "node": dict_node}
@@ -0,0 +1,111 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Any
4
+
5
+ from graphene import Boolean, InputObjectType, Mutation, String
6
+
7
+ from infrahub.core.account import ObjectPermission
8
+ from infrahub.core.constants import PermissionAction, PermissionDecision
9
+ from infrahub.core.manager import NodeManager
10
+ from infrahub.core.registry import registry
11
+ from infrahub.database import retry_db_transaction
12
+ from infrahub.events import EventMeta
13
+ from infrahub.events.node_action import NodeUpdatedEvent
14
+ from infrahub.exceptions import NodeNotFoundError, ValidationError
15
+ from infrahub.graphql.context import apply_external_context
16
+ from infrahub.graphql.types.context import ContextInput
17
+ from infrahub.log import get_log_data
18
+ from infrahub.worker import WORKER_IDENTITY
19
+
20
+ if TYPE_CHECKING:
21
+ from graphql import GraphQLResolveInfo
22
+
23
+ from infrahub.graphql.initialization import GraphqlContext
24
+
25
+
26
+ class InfrahubDisplayLabelUpdateInput(InputObjectType):
27
+ id = String(required=True)
28
+ kind = String(required=True)
29
+ value = String(required=True)
30
+
31
+
32
+ class UpdateDisplayLabel(Mutation):
33
+ class Arguments:
34
+ data = InfrahubDisplayLabelUpdateInput(required=True)
35
+ context = ContextInput(required=False)
36
+
37
+ ok = Boolean()
38
+
39
+ @classmethod
40
+ @retry_db_transaction(name="update_display_label")
41
+ async def mutate(
42
+ cls,
43
+ _: dict,
44
+ info: GraphQLResolveInfo,
45
+ data: InfrahubDisplayLabelUpdateInput,
46
+ context: ContextInput | None = None,
47
+ ) -> UpdateDisplayLabel:
48
+ graphql_context: GraphqlContext = info.context
49
+ node_schema = registry.schema.get_node_schema(
50
+ name=str(data.kind), branch=graphql_context.branch.name, duplicate=False
51
+ )
52
+ if not node_schema.display_label:
53
+ raise ValidationError(input_value=f"{node_schema.kind}.display_label has not been defined for this kind.")
54
+
55
+ graphql_context.active_permissions.raise_for_permission(
56
+ permission=ObjectPermission(
57
+ namespace=node_schema.namespace,
58
+ name=node_schema.name,
59
+ action=PermissionAction.UPDATE.value,
60
+ decision=PermissionDecision.ALLOW_DEFAULT.value
61
+ if graphql_context.branch.name == registry.default_branch
62
+ else PermissionDecision.ALLOW_OTHER.value,
63
+ )
64
+ )
65
+ await apply_external_context(graphql_context=graphql_context, context_input=context)
66
+
67
+ if not (
68
+ target_node := await NodeManager.get_one(
69
+ db=graphql_context.db,
70
+ kind=node_schema.kind,
71
+ id=str(data.id),
72
+ branch=graphql_context.branch,
73
+ fields={"display_label": None},
74
+ )
75
+ ):
76
+ raise NodeNotFoundError(
77
+ node_type=node_schema.kind,
78
+ identifier=str(data.id),
79
+ message="The targeted node was not found in the database",
80
+ )
81
+
82
+ existing_label = (
83
+ await target_node.get_display_label(db=graphql_context.db) if target_node.has_display_label() else None
84
+ )
85
+ if str(data.value) != existing_label:
86
+ await target_node.set_display_label(value=str(data.value))
87
+
88
+ async with graphql_context.db.start_transaction() as dbt:
89
+ await target_node.save(db=dbt, fields=["display_label"])
90
+
91
+ log_data = get_log_data()
92
+ request_id = log_data.get("request_id", "")
93
+
94
+ event = NodeUpdatedEvent(
95
+ kind=node_schema.kind,
96
+ node_id=target_node.get_id(),
97
+ changelog=target_node.node_changelog.model_dump(),
98
+ fields=["display_label"],
99
+ meta=EventMeta(
100
+ context=graphql_context.get_context(),
101
+ initiator_id=WORKER_IDENTITY,
102
+ request_id=request_id,
103
+ account_id=graphql_context.active_account_session.account_id,
104
+ branch=graphql_context.branch,
105
+ ),
106
+ )
107
+ await graphql_context.active_service.event.send(event=event)
108
+
109
+ result: dict[str, Any] = {"ok": True}
110
+
111
+ return cls(**result)
@@ -5,6 +5,7 @@ from typing import TYPE_CHECKING
5
5
  from graphene import Boolean, Field, InputField, InputObjectType, List, Mutation, NonNull, String
6
6
 
7
7
  from infrahub.core.manager import NodeManager
8
+ from infrahub.core.protocols import CoreGeneratorDefinition, CoreGenericRepository, CoreGraphQLQuery, CoreStandardGroup
8
9
  from infrahub.generators.models import ProposedChangeGeneratorDefinition, RequestGeneratorDefinitionRun
9
10
  from infrahub.graphql.context import apply_external_context
10
11
  from infrahub.graphql.types.context import ContextInput
@@ -44,11 +45,18 @@ class GeneratorDefinitionRequestRun(Mutation):
44
45
  db = graphql_context.db
45
46
  await apply_external_context(graphql_context=graphql_context, context_input=context)
46
47
  generator_definition = await NodeManager.get_one(
47
- id=str(data.id), db=db, branch=graphql_context.branch, prefetch_relationships=True, raise_on_error=True
48
+ id=str(data.id),
49
+ kind=CoreGeneratorDefinition,
50
+ db=db,
51
+ branch=graphql_context.branch,
52
+ prefetch_relationships=True,
53
+ raise_on_error=True,
48
54
  )
49
- query = await generator_definition.query.get_peer(db=db)
50
- repository = await generator_definition.repository.get_peer(db=db)
51
- group = await generator_definition.targets.get_peer(db=db)
55
+ query = await generator_definition.query.get_peer(db=db, peer_type=CoreGraphQLQuery, raise_on_error=True)
56
+ repository = await generator_definition.repository.get_peer(
57
+ db=db, peer_type=CoreGenericRepository, raise_on_error=True
58
+ )
59
+ group = await generator_definition.targets.get_peer(db=db, peer_type=CoreStandardGroup, raise_on_error=True)
52
60
 
53
61
  request_model = RequestGeneratorDefinitionRun(
54
62
  generator_definition=ProposedChangeGeneratorDefinition(
@@ -57,11 +65,21 @@ class GeneratorDefinitionRequestRun(Mutation):
57
65
  class_name=generator_definition.class_name.value,
58
66
  file_path=generator_definition.file_path.value,
59
67
  query_name=query.name.value,
60
- query_models=query.models.value,
68
+ query_models=query.models.value or [],
61
69
  repository_id=repository.id,
62
- parameters=generator_definition.parameters.value,
70
+ parameters=generator_definition.parameters.value
71
+ if isinstance(generator_definition.parameters.value, dict)
72
+ else {},
63
73
  group_id=group.id,
64
- convert_query_response=generator_definition.convert_query_response.value or False,
74
+ convert_query_response=generator_definition.convert_query_response.value
75
+ if generator_definition.convert_query_response.value is not None
76
+ else False,
77
+ execute_in_proposed_change=generator_definition.execute_in_proposed_change.value
78
+ if generator_definition.execute_in_proposed_change.value is not None
79
+ else True,
80
+ execute_after_merge=generator_definition.execute_after_merge.value
81
+ if generator_definition.execute_after_merge.value is not None
82
+ else True,
65
83
  ),
66
84
  branch=graphql_context.branch.name,
67
85
  target_members=data.get("nodes", []),
@@ -0,0 +1,118 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Any, cast
4
+
5
+ from graphene import Boolean, InputObjectType, List, Mutation, NonNull, String
6
+
7
+ from infrahub.core.account import ObjectPermission
8
+ from infrahub.core.constants import PermissionAction, PermissionDecision
9
+ from infrahub.core.manager import NodeManager
10
+ from infrahub.core.registry import registry
11
+ from infrahub.database import retry_db_transaction
12
+ from infrahub.events import EventMeta
13
+ from infrahub.events.node_action import NodeUpdatedEvent
14
+ from infrahub.exceptions import NodeNotFoundError, ValidationError
15
+ from infrahub.graphql.context import apply_external_context
16
+ from infrahub.graphql.types.context import ContextInput
17
+ from infrahub.log import get_log_data
18
+ from infrahub.worker import WORKER_IDENTITY
19
+
20
+ if TYPE_CHECKING:
21
+ from graphql import GraphQLResolveInfo
22
+
23
+ from infrahub.graphql.initialization import GraphqlContext
24
+
25
+
26
+ class InfrahubHFIDUpdateInput(InputObjectType):
27
+ id = String(required=True)
28
+ kind = String(required=True)
29
+ value = List(NonNull(String), required=True)
30
+
31
+
32
+ class UpdateHFID(Mutation):
33
+ class Arguments:
34
+ data = InfrahubHFIDUpdateInput(required=True)
35
+ context = ContextInput(required=False)
36
+
37
+ ok = Boolean()
38
+
39
+ @classmethod
40
+ @retry_db_transaction(name="update_hfid")
41
+ async def mutate(
42
+ cls,
43
+ _: dict,
44
+ info: GraphQLResolveInfo,
45
+ data: InfrahubHFIDUpdateInput,
46
+ context: ContextInput | None = None,
47
+ ) -> UpdateHFID:
48
+ graphql_context: GraphqlContext = info.context
49
+ node_schema = registry.schema.get_node_schema(
50
+ name=str(data.kind), branch=graphql_context.branch.name, duplicate=False
51
+ )
52
+ if node_schema.human_friendly_id is None:
53
+ raise ValidationError(
54
+ input_value=f"{node_schema.kind}.human_friendly_id has not been defined for this kind."
55
+ )
56
+
57
+ updated_hfid = cast(list[str], data.value)
58
+
59
+ if len(node_schema.human_friendly_id) != len(updated_hfid):
60
+ raise ValidationError(
61
+ input_value=f"{node_schema.kind}.human_friendly_id requires {len(node_schema.human_friendly_id)} parts data has {len(updated_hfid)}"
62
+ )
63
+
64
+ graphql_context.active_permissions.raise_for_permission(
65
+ permission=ObjectPermission(
66
+ namespace=node_schema.namespace,
67
+ name=node_schema.name,
68
+ action=PermissionAction.UPDATE.value,
69
+ decision=PermissionDecision.ALLOW_DEFAULT.value
70
+ if graphql_context.branch.name == registry.default_branch
71
+ else PermissionDecision.ALLOW_OTHER.value,
72
+ )
73
+ )
74
+ await apply_external_context(graphql_context=graphql_context, context_input=context)
75
+
76
+ if not (
77
+ target_node := await NodeManager.get_one(
78
+ db=graphql_context.db,
79
+ kind=node_schema.kind,
80
+ id=str(data.id),
81
+ branch=graphql_context.branch,
82
+ fields={"human_friendly_id": None},
83
+ )
84
+ ):
85
+ raise NodeNotFoundError(
86
+ node_type=node_schema.kind,
87
+ identifier=str(data.id),
88
+ message="The targeted node was not found in the database",
89
+ )
90
+
91
+ existing = await target_node.get_hfid(db=graphql_context.db) if target_node.has_human_friendly_id() else None
92
+ if updated_hfid != existing:
93
+ await target_node.set_human_friendly_id(value=updated_hfid)
94
+
95
+ async with graphql_context.db.start_transaction() as dbt:
96
+ await target_node.save(db=dbt, fields=["human_friendly_id"])
97
+
98
+ log_data = get_log_data()
99
+ request_id = log_data.get("request_id", "")
100
+
101
+ event = NodeUpdatedEvent(
102
+ kind=node_schema.kind,
103
+ node_id=target_node.get_id(),
104
+ changelog=target_node.node_changelog.model_dump(),
105
+ fields=["human_friendly_id"],
106
+ meta=EventMeta(
107
+ context=graphql_context.get_context(),
108
+ initiator_id=WORKER_IDENTITY,
109
+ request_id=request_id,
110
+ account_id=graphql_context.active_account_session.account_id,
111
+ branch=graphql_context.branch,
112
+ ),
113
+ )
114
+ await graphql_context.active_service.event.send(event=event)
115
+
116
+ result: dict[str, Any] = {"ok": True}
117
+
118
+ return cls(**result)
@@ -16,10 +16,12 @@ from infrahub.core.node import Node
16
16
  from infrahub.core.schema import NodeSchema
17
17
  from infrahub.database import InfrahubDatabase, retry_db_transaction
18
18
  from infrahub.exceptions import NodeNotFoundError, ValidationError
19
- from infrahub.lock import InfrahubMultiLock, build_object_lock_name
19
+ from infrahub.lock import InfrahubMultiLock
20
20
  from infrahub.log import get_logger
21
21
 
22
- from .main import DeleteResult, InfrahubMutationMixin, InfrahubMutationOptions
22
+ from ...core.node.create import create_node
23
+ from ...core.node.lock_utils import build_object_lock_name
24
+ from .main import DeleteResult, InfrahubMutationMixin, InfrahubMutationOptions, build_graphql_response
23
25
  from .node_getter.by_default_filter import MutationNodeGetterByDefaultFilter
24
26
 
25
27
  if TYPE_CHECKING:
@@ -121,7 +123,13 @@ class InfrahubIPAddressMutation(InfrahubMutationMixin, Mutation):
121
123
  ip_address: IPv4Interface | ipaddress.IPv6Interface,
122
124
  namespace_id: str,
123
125
  ) -> Node:
124
- address = await cls.mutate_create_object(data=data, db=db, branch=branch)
126
+ address = await create_node(
127
+ data=dict(data),
128
+ db=db,
129
+ branch=branch,
130
+ schema=cls._meta.active_schema,
131
+ )
132
+
125
133
  reconciler = IpamReconciler(db=db, branch=branch)
126
134
  reconciled_address = await reconciler.reconcile(
127
135
  ip_value=ip_address, namespace=namespace_id, node_uuid=address.get_id()
@@ -152,9 +160,9 @@ class InfrahubIPAddressMutation(InfrahubMutationMixin, Mutation):
152
160
  reconciled_address = await cls._mutate_create_object_and_reconcile(
153
161
  data=data, branch=branch, db=dbt, ip_address=ip_address, namespace_id=namespace_id
154
162
  )
155
- result = await cls.mutate_create_to_graphql(info=info, db=dbt, obj=reconciled_address)
163
+ graphql_response = await build_graphql_response(info=info, db=dbt, obj=reconciled_address)
156
164
 
157
- return reconciled_address, result
165
+ return reconciled_address, cls(**graphql_response)
158
166
 
159
167
  @classmethod
160
168
  async def _mutate_update_object_and_reconcile(
@@ -275,7 +283,12 @@ class InfrahubIPPrefixMutation(InfrahubMutationMixin, Mutation):
275
283
  db: InfrahubDatabase,
276
284
  namespace_id: str,
277
285
  ) -> Node:
278
- prefix = await cls.mutate_create_object(data=data, db=db, branch=branch)
286
+ prefix = await create_node(
287
+ data=dict(data),
288
+ db=db,
289
+ branch=branch,
290
+ schema=cls._meta.active_schema,
291
+ )
279
292
  return await cls._reconcile_prefix(
280
293
  branch=branch, db=db, prefix=prefix, namespace_id=namespace_id, is_delete=False
281
294
  )
@@ -300,9 +313,9 @@ class InfrahubIPPrefixMutation(InfrahubMutationMixin, Mutation):
300
313
  data=data, branch=branch, db=dbt, namespace_id=namespace_id
301
314
  )
302
315
 
303
- result = await cls.mutate_create_to_graphql(info=info, db=dbt, obj=reconciled_prefix)
316
+ graphql_response = await build_graphql_response(info=info, db=dbt, obj=reconciled_prefix)
304
317
 
305
- return reconciled_prefix, result
318
+ return reconciled_prefix, cls(**graphql_response)
306
319
 
307
320
  @classmethod
308
321
  async def _mutate_update_object_and_reconcile(