infrahub-server 1.4.13__py3-none-any.whl → 1.5.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 (222) 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/internal.py +2 -0
  5. infrahub/api/query.py +2 -0
  6. infrahub/api/schema.py +27 -3
  7. infrahub/auth.py +5 -5
  8. infrahub/cli/__init__.py +2 -0
  9. infrahub/cli/db.py +160 -157
  10. infrahub/cli/dev.py +118 -0
  11. infrahub/cli/upgrade.py +56 -9
  12. infrahub/computed_attribute/tasks.py +19 -7
  13. infrahub/config.py +7 -2
  14. infrahub/core/attribute.py +35 -24
  15. infrahub/core/branch/enums.py +1 -1
  16. infrahub/core/branch/models.py +9 -5
  17. infrahub/core/branch/needs_rebase_status.py +11 -0
  18. infrahub/core/branch/tasks.py +72 -10
  19. infrahub/core/changelog/models.py +2 -10
  20. infrahub/core/constants/__init__.py +4 -0
  21. infrahub/core/constants/infrahubkind.py +1 -0
  22. infrahub/core/convert_object_type/object_conversion.py +201 -0
  23. infrahub/core/convert_object_type/repository_conversion.py +89 -0
  24. infrahub/core/convert_object_type/schema_mapping.py +27 -3
  25. infrahub/core/diff/model/path.py +4 -0
  26. infrahub/core/diff/payload_builder.py +1 -1
  27. infrahub/core/diff/query/artifact.py +1 -0
  28. infrahub/core/diff/query/field_summary.py +1 -0
  29. infrahub/core/graph/__init__.py +1 -1
  30. infrahub/core/initialization.py +7 -4
  31. infrahub/core/manager.py +3 -81
  32. infrahub/core/migrations/__init__.py +3 -0
  33. infrahub/core/migrations/exceptions.py +4 -0
  34. infrahub/core/migrations/graph/__init__.py +11 -10
  35. infrahub/core/migrations/graph/load_schema_branch.py +21 -0
  36. infrahub/core/migrations/graph/m013_convert_git_password_credential.py +1 -1
  37. infrahub/core/migrations/graph/m037_index_attr_vals.py +11 -30
  38. infrahub/core/migrations/graph/m039_ipam_reconcile.py +9 -7
  39. infrahub/core/migrations/graph/m042_profile_attrs_in_db.py +147 -0
  40. infrahub/core/migrations/graph/m043_create_hfid_display_label_in_db.py +164 -0
  41. infrahub/core/migrations/graph/m044_backfill_hfid_display_label_in_db.py +864 -0
  42. infrahub/core/migrations/query/__init__.py +7 -8
  43. infrahub/core/migrations/query/attribute_add.py +8 -6
  44. infrahub/core/migrations/query/attribute_remove.py +134 -0
  45. infrahub/core/migrations/runner.py +54 -0
  46. infrahub/core/migrations/schema/attribute_kind_update.py +9 -3
  47. infrahub/core/migrations/schema/attribute_supports_profile.py +90 -0
  48. infrahub/core/migrations/schema/node_attribute_add.py +26 -5
  49. infrahub/core/migrations/schema/node_attribute_remove.py +13 -109
  50. infrahub/core/migrations/schema/node_kind_update.py +2 -1
  51. infrahub/core/migrations/schema/node_remove.py +2 -1
  52. infrahub/core/migrations/schema/placeholder_dummy.py +3 -2
  53. infrahub/core/migrations/shared.py +66 -19
  54. infrahub/core/models.py +2 -2
  55. infrahub/core/node/__init__.py +207 -54
  56. infrahub/core/node/create.py +53 -49
  57. infrahub/core/node/lock_utils.py +124 -0
  58. infrahub/core/node/node_property_attribute.py +230 -0
  59. infrahub/core/node/resource_manager/ip_address_pool.py +2 -1
  60. infrahub/core/node/resource_manager/ip_prefix_pool.py +2 -1
  61. infrahub/core/node/resource_manager/number_pool.py +2 -1
  62. infrahub/core/node/standard.py +1 -1
  63. infrahub/core/property.py +11 -0
  64. infrahub/core/protocols.py +8 -1
  65. infrahub/core/query/attribute.py +82 -15
  66. infrahub/core/query/ipam.py +16 -4
  67. infrahub/core/query/node.py +66 -188
  68. infrahub/core/query/relationship.py +44 -26
  69. infrahub/core/query/subquery.py +0 -8
  70. infrahub/core/relationship/model.py +69 -24
  71. infrahub/core/schema/__init__.py +56 -0
  72. infrahub/core/schema/attribute_schema.py +4 -2
  73. infrahub/core/schema/basenode_schema.py +42 -2
  74. infrahub/core/schema/definitions/core/__init__.py +2 -0
  75. infrahub/core/schema/definitions/core/check.py +1 -1
  76. infrahub/core/schema/definitions/core/generator.py +2 -0
  77. infrahub/core/schema/definitions/core/group.py +16 -2
  78. infrahub/core/schema/definitions/core/repository.py +7 -0
  79. infrahub/core/schema/definitions/core/transform.py +1 -1
  80. infrahub/core/schema/definitions/internal.py +12 -3
  81. infrahub/core/schema/generated/attribute_schema.py +2 -2
  82. infrahub/core/schema/generated/base_node_schema.py +6 -1
  83. infrahub/core/schema/manager.py +3 -0
  84. infrahub/core/schema/node_schema.py +1 -0
  85. infrahub/core/schema/relationship_schema.py +0 -1
  86. infrahub/core/schema/schema_branch.py +295 -10
  87. infrahub/core/schema/schema_branch_display.py +135 -0
  88. infrahub/core/schema/schema_branch_hfid.py +120 -0
  89. infrahub/core/validators/aggregated_checker.py +1 -1
  90. infrahub/database/graph.py +21 -0
  91. infrahub/display_labels/__init__.py +0 -0
  92. infrahub/display_labels/gather.py +48 -0
  93. infrahub/display_labels/models.py +240 -0
  94. infrahub/display_labels/tasks.py +192 -0
  95. infrahub/display_labels/triggers.py +22 -0
  96. infrahub/events/branch_action.py +27 -1
  97. infrahub/events/group_action.py +1 -1
  98. infrahub/events/node_action.py +1 -1
  99. infrahub/generators/constants.py +7 -0
  100. infrahub/generators/models.py +38 -12
  101. infrahub/generators/tasks.py +34 -16
  102. infrahub/git/base.py +38 -1
  103. infrahub/git/integrator.py +22 -14
  104. infrahub/graphql/api/dependencies.py +2 -4
  105. infrahub/graphql/api/endpoints.py +16 -6
  106. infrahub/graphql/app.py +2 -4
  107. infrahub/graphql/initialization.py +2 -3
  108. infrahub/graphql/manager.py +213 -137
  109. infrahub/graphql/middleware.py +12 -0
  110. infrahub/graphql/mutations/branch.py +16 -0
  111. infrahub/graphql/mutations/computed_attribute.py +110 -3
  112. infrahub/graphql/mutations/convert_object_type.py +44 -13
  113. infrahub/graphql/mutations/display_label.py +118 -0
  114. infrahub/graphql/mutations/generator.py +25 -7
  115. infrahub/graphql/mutations/hfid.py +125 -0
  116. infrahub/graphql/mutations/ipam.py +73 -41
  117. infrahub/graphql/mutations/main.py +61 -178
  118. infrahub/graphql/mutations/profile.py +195 -0
  119. infrahub/graphql/mutations/proposed_change.py +8 -1
  120. infrahub/graphql/mutations/relationship.py +2 -2
  121. infrahub/graphql/mutations/repository.py +22 -83
  122. infrahub/graphql/mutations/resource_manager.py +2 -2
  123. infrahub/graphql/mutations/webhook.py +1 -1
  124. infrahub/graphql/queries/resource_manager.py +1 -1
  125. infrahub/graphql/registry.py +173 -0
  126. infrahub/graphql/resolvers/resolver.py +2 -0
  127. infrahub/graphql/schema.py +8 -1
  128. infrahub/graphql/schema_sort.py +170 -0
  129. infrahub/graphql/types/branch.py +4 -1
  130. infrahub/graphql/types/enums.py +3 -0
  131. infrahub/groups/tasks.py +1 -1
  132. infrahub/hfid/__init__.py +0 -0
  133. infrahub/hfid/gather.py +48 -0
  134. infrahub/hfid/models.py +240 -0
  135. infrahub/hfid/tasks.py +191 -0
  136. infrahub/hfid/triggers.py +22 -0
  137. infrahub/lock.py +119 -42
  138. infrahub/locks/__init__.py +0 -0
  139. infrahub/locks/tasks.py +37 -0
  140. infrahub/patch/plan_writer.py +2 -2
  141. infrahub/permissions/constants.py +2 -0
  142. infrahub/profiles/__init__.py +0 -0
  143. infrahub/profiles/node_applier.py +101 -0
  144. infrahub/profiles/queries/__init__.py +0 -0
  145. infrahub/profiles/queries/get_profile_data.py +98 -0
  146. infrahub/profiles/tasks.py +63 -0
  147. infrahub/proposed_change/tasks.py +24 -5
  148. infrahub/repositories/__init__.py +0 -0
  149. infrahub/repositories/create_repository.py +113 -0
  150. infrahub/server.py +9 -1
  151. infrahub/services/__init__.py +8 -5
  152. infrahub/services/adapters/workflow/worker.py +5 -2
  153. infrahub/task_manager/event.py +5 -0
  154. infrahub/task_manager/models.py +7 -0
  155. infrahub/tasks/registry.py +6 -4
  156. infrahub/trigger/catalogue.py +4 -0
  157. infrahub/trigger/models.py +2 -0
  158. infrahub/trigger/setup.py +13 -4
  159. infrahub/trigger/tasks.py +6 -0
  160. infrahub/webhook/models.py +1 -1
  161. infrahub/workers/dependencies.py +3 -1
  162. infrahub/workers/infrahub_async.py +5 -1
  163. infrahub/workflows/catalogue.py +118 -3
  164. infrahub/workflows/initialization.py +21 -0
  165. infrahub/workflows/models.py +17 -2
  166. infrahub_sdk/branch.py +17 -8
  167. infrahub_sdk/checks.py +1 -1
  168. infrahub_sdk/client.py +376 -95
  169. infrahub_sdk/config.py +29 -2
  170. infrahub_sdk/convert_object_type.py +61 -0
  171. infrahub_sdk/ctl/branch.py +3 -0
  172. infrahub_sdk/ctl/check.py +2 -3
  173. infrahub_sdk/ctl/cli_commands.py +20 -12
  174. infrahub_sdk/ctl/config.py +8 -2
  175. infrahub_sdk/ctl/generator.py +6 -3
  176. infrahub_sdk/ctl/graphql.py +184 -0
  177. infrahub_sdk/ctl/repository.py +39 -1
  178. infrahub_sdk/ctl/schema.py +40 -10
  179. infrahub_sdk/ctl/task.py +110 -0
  180. infrahub_sdk/ctl/utils.py +4 -0
  181. infrahub_sdk/ctl/validate.py +5 -3
  182. infrahub_sdk/diff.py +4 -5
  183. infrahub_sdk/exceptions.py +2 -0
  184. infrahub_sdk/generator.py +7 -1
  185. infrahub_sdk/graphql/__init__.py +12 -0
  186. infrahub_sdk/graphql/constants.py +1 -0
  187. infrahub_sdk/graphql/plugin.py +85 -0
  188. infrahub_sdk/graphql/query.py +77 -0
  189. infrahub_sdk/{graphql.py → graphql/renderers.py} +88 -75
  190. infrahub_sdk/graphql/utils.py +40 -0
  191. infrahub_sdk/node/attribute.py +2 -0
  192. infrahub_sdk/node/node.py +28 -20
  193. infrahub_sdk/node/relationship.py +1 -3
  194. infrahub_sdk/playback.py +1 -2
  195. infrahub_sdk/protocols.py +54 -6
  196. infrahub_sdk/pytest_plugin/plugin.py +7 -4
  197. infrahub_sdk/pytest_plugin/utils.py +40 -0
  198. infrahub_sdk/repository.py +1 -2
  199. infrahub_sdk/schema/__init__.py +70 -4
  200. infrahub_sdk/schema/main.py +1 -0
  201. infrahub_sdk/schema/repository.py +8 -0
  202. infrahub_sdk/spec/models.py +7 -0
  203. infrahub_sdk/spec/object.py +54 -6
  204. infrahub_sdk/spec/processors/__init__.py +0 -0
  205. infrahub_sdk/spec/processors/data_processor.py +10 -0
  206. infrahub_sdk/spec/processors/factory.py +34 -0
  207. infrahub_sdk/spec/processors/range_expand_processor.py +56 -0
  208. infrahub_sdk/spec/range_expansion.py +118 -0
  209. infrahub_sdk/task/models.py +6 -4
  210. infrahub_sdk/timestamp.py +18 -6
  211. infrahub_sdk/transforms.py +1 -1
  212. {infrahub_server-1.4.13.dist-info → infrahub_server-1.5.0.dist-info}/METADATA +9 -10
  213. {infrahub_server-1.4.13.dist-info → infrahub_server-1.5.0.dist-info}/RECORD +221 -165
  214. infrahub_testcontainers/container.py +114 -2
  215. infrahub_testcontainers/docker-compose-cluster.test.yml +5 -0
  216. infrahub_testcontainers/docker-compose.test.yml +5 -0
  217. infrahub_testcontainers/models.py +2 -2
  218. infrahub_testcontainers/performance_test.py +4 -4
  219. infrahub/core/convert_object_type/conversion.py +0 -134
  220. {infrahub_server-1.4.13.dist-info → infrahub_server-1.5.0.dist-info}/LICENSE.txt +0 -0
  221. {infrahub_server-1.4.13.dist-info → infrahub_server-1.5.0.dist-info}/WHEEL +0 -0
  222. {infrahub_server-1.4.13.dist-info → infrahub_server-1.5.0.dist-info}/entry_points.txt +0 -0
@@ -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,11 +5,16 @@ 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.exceptions import ValidationError
14
+ from infrahub.repositories.create_repository import RepositoryFinalizer
11
15
 
12
16
  if TYPE_CHECKING:
17
+ from infrahub.core.attribute import BaseAttribute
13
18
  from infrahub.graphql.initialization import GraphqlContext
14
19
 
15
20
 
@@ -44,26 +49,52 @@ class ConvertObjectType(Mutation):
44
49
  source_schema = registry.get_node_schema(name=node_to_convert.get_kind(), branch=graphql_context.branch)
45
50
  target_schema = registry.get_node_schema(name=str(data.target_kind), branch=graphql_context.branch)
46
51
 
47
- fields_mapping: dict[str, InputForDestField] = {}
52
+ fields_mapping: dict[str, ConversionFieldInput] = {}
48
53
  if not isinstance(data.fields_mapping, dict):
49
- raise ValueError(f"Expected `fields_mapping` to be a `dict`, got {type(fields_mapping)}")
54
+ raise ValidationError(
55
+ input_value=f"Expected `fields_mapping` to be a `dict`, got {type(data.fields_mapping)}"
56
+ )
50
57
 
51
58
  for field_name, input_for_dest_field_str in data.fields_mapping.items():
52
- fields_mapping[field_name] = InputForDestField(**input_for_dest_field_str)
59
+ fields_mapping[field_name] = ConversionFieldInput(**input_for_dest_field_str)
60
+
61
+ node_to_convert = await NodeManager.get_one(
62
+ id=str(data.node_id), db=graphql_context.db, branch=graphql_context.branch
63
+ )
64
+ for attribute_name in source_schema.attribute_names:
65
+ attribute: BaseAttribute = getattr(node_to_convert, attribute_name)
66
+ if attribute.is_from_profile:
67
+ raise ValidationError(
68
+ input_value=f"The attribute '{attribute_name}' is from a profile, converting objects that use profiles is not yet supported."
69
+ )
53
70
 
54
71
  # Complete fields mapping with auto-mapping.
55
72
  mapping = get_schema_mapping(source_schema=source_schema, target_schema=target_schema)
56
73
  for field_name, mapping_value in mapping.items():
57
74
  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
- )
75
+ fields_mapping[field_name] = ConversionFieldInput(source_field=mapping_value.source_field_name)
76
+
77
+ if target_schema.kind in [REPOSITORY, READONLYREPOSITORY]:
78
+ new_node = await convert_repository_type(
79
+ repository=node_to_convert,
80
+ target_schema=target_schema,
81
+ mapping=fields_mapping,
82
+ branch=graphql_context.branch,
83
+ db=graphql_context.db,
84
+ repository_post_creator=RepositoryFinalizer(
85
+ account_session=graphql_context.active_account_session,
86
+ services=graphql_context.active_service,
87
+ context=graphql_context.get_context(),
88
+ ),
89
+ )
90
+ else:
91
+ new_node = await convert_and_validate_object_type(
92
+ node=node_to_convert,
93
+ target_schema=target_schema,
94
+ mapping=fields_mapping,
95
+ branch=graphql_context.branch,
96
+ db=graphql_context.db,
97
+ )
67
98
 
68
99
  dict_node = await new_node.to_graphql(db=graphql_context.db, fields={})
69
100
  result: dict[str, Any] = {"ok": True, "node": dict_node}
@@ -0,0 +1,118 @@
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 GlobalPermissions, 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.permissions import define_global_permission_from_branch
19
+ from infrahub.worker import WORKER_IDENTITY
20
+
21
+ if TYPE_CHECKING:
22
+ from graphql import GraphQLResolveInfo
23
+
24
+ from infrahub.graphql.initialization import GraphqlContext
25
+
26
+
27
+ class InfrahubDisplayLabelUpdateInput(InputObjectType):
28
+ id = String(required=True)
29
+ kind = String(required=True)
30
+ value = String(required=True)
31
+
32
+
33
+ class UpdateDisplayLabel(Mutation):
34
+ class Arguments:
35
+ data = InfrahubDisplayLabelUpdateInput(required=True)
36
+ context = ContextInput(required=False)
37
+
38
+ ok = Boolean()
39
+
40
+ @classmethod
41
+ @retry_db_transaction(name="update_display_label")
42
+ async def mutate(
43
+ cls,
44
+ _: dict,
45
+ info: GraphQLResolveInfo,
46
+ data: InfrahubDisplayLabelUpdateInput,
47
+ context: ContextInput | None = None,
48
+ ) -> UpdateDisplayLabel:
49
+ graphql_context: GraphqlContext = info.context
50
+ node_schema = registry.schema.get_node_schema(
51
+ name=str(data.kind), branch=graphql_context.branch.name, duplicate=False
52
+ )
53
+ if not node_schema.display_label:
54
+ raise ValidationError(input_value=f"{node_schema.kind}.display_label has not been defined for this kind.")
55
+
56
+ graphql_context.active_permissions.raise_for_permissions(
57
+ permissions=[
58
+ define_global_permission_from_branch(
59
+ permission=GlobalPermissions.UPDATE_OBJECT_HFID_DISPLAY_LABEL,
60
+ branch_name=graphql_context.branch.name,
61
+ ),
62
+ ObjectPermission(
63
+ namespace=node_schema.namespace,
64
+ name=node_schema.name,
65
+ action=PermissionAction.UPDATE.value,
66
+ decision=PermissionDecision.ALLOW_DEFAULT.value
67
+ if graphql_context.branch.name == registry.default_branch
68
+ else PermissionDecision.ALLOW_OTHER.value,
69
+ ),
70
+ ]
71
+ )
72
+ await apply_external_context(graphql_context=graphql_context, context_input=context)
73
+
74
+ if not (
75
+ target_node := await NodeManager.get_one(
76
+ db=graphql_context.db,
77
+ kind=node_schema.kind,
78
+ id=str(data.id),
79
+ branch=graphql_context.branch,
80
+ fields={"display_label": None},
81
+ )
82
+ ):
83
+ raise NodeNotFoundError(
84
+ node_type=node_schema.kind,
85
+ identifier=str(data.id),
86
+ message="The targeted node was not found in the database",
87
+ )
88
+
89
+ existing_label = (
90
+ await target_node.get_display_label(db=graphql_context.db) if target_node.has_display_label() else None
91
+ )
92
+ if str(data.value) != existing_label:
93
+ await target_node.set_display_label(value=str(data.value))
94
+
95
+ async with graphql_context.db.start_transaction() as dbt:
96
+ await target_node.save(db=dbt, fields=["display_label"])
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=["display_label"],
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)
@@ -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,125 @@
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 GlobalPermissions, 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.permissions import define_global_permission_from_branch
19
+ from infrahub.worker import WORKER_IDENTITY
20
+
21
+ if TYPE_CHECKING:
22
+ from graphql import GraphQLResolveInfo
23
+
24
+ from infrahub.graphql.initialization import GraphqlContext
25
+
26
+
27
+ class InfrahubHFIDUpdateInput(InputObjectType):
28
+ id = String(required=True)
29
+ kind = String(required=True)
30
+ value = List(NonNull(String), required=True)
31
+
32
+
33
+ class UpdateHFID(Mutation):
34
+ class Arguments:
35
+ data = InfrahubHFIDUpdateInput(required=True)
36
+ context = ContextInput(required=False)
37
+
38
+ ok = Boolean()
39
+
40
+ @classmethod
41
+ @retry_db_transaction(name="update_hfid")
42
+ async def mutate(
43
+ cls,
44
+ _: dict,
45
+ info: GraphQLResolveInfo,
46
+ data: InfrahubHFIDUpdateInput,
47
+ context: ContextInput | None = None,
48
+ ) -> UpdateHFID:
49
+ graphql_context: GraphqlContext = info.context
50
+ node_schema = registry.schema.get_node_schema(
51
+ name=str(data.kind), branch=graphql_context.branch.name, duplicate=False
52
+ )
53
+ if node_schema.human_friendly_id is None:
54
+ raise ValidationError(
55
+ input_value=f"{node_schema.kind}.human_friendly_id has not been defined for this kind."
56
+ )
57
+
58
+ updated_hfid = cast(list[str], data.value)
59
+
60
+ if len(node_schema.human_friendly_id) != len(updated_hfid):
61
+ raise ValidationError(
62
+ input_value=f"{node_schema.kind}.human_friendly_id requires {len(node_schema.human_friendly_id)} parts data has {len(updated_hfid)}"
63
+ )
64
+
65
+ graphql_context.active_permissions.raise_for_permissions(
66
+ permissions=[
67
+ define_global_permission_from_branch(
68
+ permission=GlobalPermissions.UPDATE_OBJECT_HFID_DISPLAY_LABEL,
69
+ branch_name=graphql_context.branch.name,
70
+ ),
71
+ ObjectPermission(
72
+ namespace=node_schema.namespace,
73
+ name=node_schema.name,
74
+ action=PermissionAction.UPDATE.value,
75
+ decision=PermissionDecision.ALLOW_DEFAULT.value
76
+ if graphql_context.branch.name == registry.default_branch
77
+ else PermissionDecision.ALLOW_OTHER.value,
78
+ ),
79
+ ]
80
+ )
81
+ await apply_external_context(graphql_context=graphql_context, context_input=context)
82
+
83
+ if not (
84
+ target_node := await NodeManager.get_one(
85
+ db=graphql_context.db,
86
+ kind=node_schema.kind,
87
+ id=str(data.id),
88
+ branch=graphql_context.branch,
89
+ fields={"human_friendly_id": None},
90
+ )
91
+ ):
92
+ raise NodeNotFoundError(
93
+ node_type=node_schema.kind,
94
+ identifier=str(data.id),
95
+ message="The targeted node was not found in the database",
96
+ )
97
+
98
+ existing = await target_node.get_hfid(db=graphql_context.db) if target_node.has_human_friendly_id() else None
99
+ if updated_hfid != existing:
100
+ await target_node.set_human_friendly_id(value=updated_hfid)
101
+
102
+ async with graphql_context.db.start_transaction() as dbt:
103
+ await target_node.save(db=dbt, fields=["human_friendly_id"])
104
+
105
+ log_data = get_log_data()
106
+ request_id = log_data.get("request_id", "")
107
+
108
+ event = NodeUpdatedEvent(
109
+ kind=node_schema.kind,
110
+ node_id=target_node.get_id(),
111
+ changelog=target_node.node_changelog.model_dump(),
112
+ fields=["human_friendly_id"],
113
+ meta=EventMeta(
114
+ context=graphql_context.get_context(),
115
+ initiator_id=WORKER_IDENTITY,
116
+ request_id=request_id,
117
+ account_id=graphql_context.active_account_session.account_id,
118
+ branch=graphql_context.branch,
119
+ ),
120
+ )
121
+ await graphql_context.active_service.event.send(event=event)
122
+
123
+ result: dict[str, Any] = {"ok": True}
124
+
125
+ return cls(**result)