infrahub-server 1.4.12__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 (234) 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/tasks.py +46 -0
  12. infrahub/cli/upgrade.py +56 -9
  13. infrahub/computed_attribute/tasks.py +19 -7
  14. infrahub/config.py +7 -2
  15. infrahub/core/attribute.py +35 -24
  16. infrahub/core/branch/enums.py +1 -1
  17. infrahub/core/branch/models.py +9 -5
  18. infrahub/core/branch/needs_rebase_status.py +11 -0
  19. infrahub/core/branch/tasks.py +72 -10
  20. infrahub/core/changelog/models.py +2 -10
  21. infrahub/core/constants/__init__.py +4 -0
  22. infrahub/core/constants/infrahubkind.py +1 -0
  23. infrahub/core/convert_object_type/object_conversion.py +201 -0
  24. infrahub/core/convert_object_type/repository_conversion.py +89 -0
  25. infrahub/core/convert_object_type/schema_mapping.py +27 -3
  26. infrahub/core/diff/calculator.py +2 -2
  27. infrahub/core/diff/model/path.py +4 -0
  28. infrahub/core/diff/payload_builder.py +1 -1
  29. infrahub/core/diff/query/artifact.py +1 -0
  30. infrahub/core/diff/query/delete_query.py +9 -5
  31. infrahub/core/diff/query/field_summary.py +1 -0
  32. infrahub/core/diff/query/merge.py +39 -23
  33. infrahub/core/graph/__init__.py +1 -1
  34. infrahub/core/initialization.py +7 -4
  35. infrahub/core/manager.py +3 -81
  36. infrahub/core/migrations/__init__.py +3 -0
  37. infrahub/core/migrations/exceptions.py +4 -0
  38. infrahub/core/migrations/graph/__init__.py +13 -10
  39. infrahub/core/migrations/graph/load_schema_branch.py +21 -0
  40. infrahub/core/migrations/graph/m013_convert_git_password_credential.py +1 -1
  41. infrahub/core/migrations/graph/m037_index_attr_vals.py +11 -30
  42. infrahub/core/migrations/graph/m039_ipam_reconcile.py +9 -7
  43. infrahub/core/migrations/graph/m041_deleted_dup_edges.py +149 -0
  44. infrahub/core/migrations/graph/m042_profile_attrs_in_db.py +147 -0
  45. infrahub/core/migrations/graph/m043_create_hfid_display_label_in_db.py +164 -0
  46. infrahub/core/migrations/graph/m044_backfill_hfid_display_label_in_db.py +864 -0
  47. infrahub/core/migrations/query/__init__.py +7 -8
  48. infrahub/core/migrations/query/attribute_add.py +8 -6
  49. infrahub/core/migrations/query/attribute_remove.py +134 -0
  50. infrahub/core/migrations/runner.py +54 -0
  51. infrahub/core/migrations/schema/attribute_kind_update.py +9 -3
  52. infrahub/core/migrations/schema/attribute_supports_profile.py +90 -0
  53. infrahub/core/migrations/schema/node_attribute_add.py +26 -5
  54. infrahub/core/migrations/schema/node_attribute_remove.py +13 -109
  55. infrahub/core/migrations/schema/node_kind_update.py +2 -1
  56. infrahub/core/migrations/schema/node_remove.py +2 -1
  57. infrahub/core/migrations/schema/placeholder_dummy.py +3 -2
  58. infrahub/core/migrations/shared.py +66 -19
  59. infrahub/core/models.py +2 -2
  60. infrahub/core/node/__init__.py +207 -54
  61. infrahub/core/node/create.py +53 -49
  62. infrahub/core/node/lock_utils.py +124 -0
  63. infrahub/core/node/node_property_attribute.py +230 -0
  64. infrahub/core/node/resource_manager/ip_address_pool.py +2 -1
  65. infrahub/core/node/resource_manager/ip_prefix_pool.py +2 -1
  66. infrahub/core/node/resource_manager/number_pool.py +2 -1
  67. infrahub/core/node/standard.py +1 -1
  68. infrahub/core/property.py +11 -0
  69. infrahub/core/protocols.py +8 -1
  70. infrahub/core/query/attribute.py +82 -15
  71. infrahub/core/query/diff.py +61 -16
  72. infrahub/core/query/ipam.py +16 -4
  73. infrahub/core/query/node.py +92 -212
  74. infrahub/core/query/relationship.py +44 -26
  75. infrahub/core/query/subquery.py +0 -8
  76. infrahub/core/relationship/model.py +69 -24
  77. infrahub/core/schema/__init__.py +56 -0
  78. infrahub/core/schema/attribute_schema.py +4 -2
  79. infrahub/core/schema/basenode_schema.py +42 -2
  80. infrahub/core/schema/definitions/core/__init__.py +2 -0
  81. infrahub/core/schema/definitions/core/check.py +1 -1
  82. infrahub/core/schema/definitions/core/generator.py +2 -0
  83. infrahub/core/schema/definitions/core/group.py +16 -2
  84. infrahub/core/schema/definitions/core/repository.py +7 -0
  85. infrahub/core/schema/definitions/core/transform.py +1 -1
  86. infrahub/core/schema/definitions/internal.py +12 -3
  87. infrahub/core/schema/generated/attribute_schema.py +2 -2
  88. infrahub/core/schema/generated/base_node_schema.py +6 -1
  89. infrahub/core/schema/manager.py +3 -0
  90. infrahub/core/schema/node_schema.py +1 -0
  91. infrahub/core/schema/relationship_schema.py +0 -1
  92. infrahub/core/schema/schema_branch.py +295 -10
  93. infrahub/core/schema/schema_branch_display.py +135 -0
  94. infrahub/core/schema/schema_branch_hfid.py +120 -0
  95. infrahub/core/validators/aggregated_checker.py +1 -1
  96. infrahub/database/graph.py +21 -0
  97. infrahub/display_labels/__init__.py +0 -0
  98. infrahub/display_labels/gather.py +48 -0
  99. infrahub/display_labels/models.py +240 -0
  100. infrahub/display_labels/tasks.py +192 -0
  101. infrahub/display_labels/triggers.py +22 -0
  102. infrahub/events/branch_action.py +27 -1
  103. infrahub/events/group_action.py +1 -1
  104. infrahub/events/node_action.py +1 -1
  105. infrahub/generators/constants.py +7 -0
  106. infrahub/generators/models.py +38 -12
  107. infrahub/generators/tasks.py +34 -16
  108. infrahub/git/base.py +42 -2
  109. infrahub/git/integrator.py +22 -14
  110. infrahub/git/tasks.py +52 -2
  111. infrahub/graphql/analyzer.py +9 -0
  112. infrahub/graphql/api/dependencies.py +2 -4
  113. infrahub/graphql/api/endpoints.py +16 -6
  114. infrahub/graphql/app.py +2 -4
  115. infrahub/graphql/initialization.py +2 -3
  116. infrahub/graphql/manager.py +213 -137
  117. infrahub/graphql/middleware.py +12 -0
  118. infrahub/graphql/mutations/branch.py +16 -0
  119. infrahub/graphql/mutations/computed_attribute.py +110 -3
  120. infrahub/graphql/mutations/convert_object_type.py +44 -13
  121. infrahub/graphql/mutations/display_label.py +118 -0
  122. infrahub/graphql/mutations/generator.py +25 -7
  123. infrahub/graphql/mutations/hfid.py +125 -0
  124. infrahub/graphql/mutations/ipam.py +73 -41
  125. infrahub/graphql/mutations/main.py +61 -178
  126. infrahub/graphql/mutations/profile.py +195 -0
  127. infrahub/graphql/mutations/proposed_change.py +8 -1
  128. infrahub/graphql/mutations/relationship.py +2 -2
  129. infrahub/graphql/mutations/repository.py +22 -83
  130. infrahub/graphql/mutations/resource_manager.py +2 -2
  131. infrahub/graphql/mutations/webhook.py +1 -1
  132. infrahub/graphql/queries/resource_manager.py +1 -1
  133. infrahub/graphql/registry.py +173 -0
  134. infrahub/graphql/resolvers/resolver.py +2 -0
  135. infrahub/graphql/schema.py +8 -1
  136. infrahub/graphql/schema_sort.py +170 -0
  137. infrahub/graphql/types/branch.py +4 -1
  138. infrahub/graphql/types/enums.py +3 -0
  139. infrahub/groups/tasks.py +1 -1
  140. infrahub/hfid/__init__.py +0 -0
  141. infrahub/hfid/gather.py +48 -0
  142. infrahub/hfid/models.py +240 -0
  143. infrahub/hfid/tasks.py +191 -0
  144. infrahub/hfid/triggers.py +22 -0
  145. infrahub/lock.py +119 -42
  146. infrahub/locks/__init__.py +0 -0
  147. infrahub/locks/tasks.py +37 -0
  148. infrahub/message_bus/types.py +1 -0
  149. infrahub/patch/plan_writer.py +2 -2
  150. infrahub/permissions/constants.py +2 -0
  151. infrahub/profiles/__init__.py +0 -0
  152. infrahub/profiles/node_applier.py +101 -0
  153. infrahub/profiles/queries/__init__.py +0 -0
  154. infrahub/profiles/queries/get_profile_data.py +98 -0
  155. infrahub/profiles/tasks.py +63 -0
  156. infrahub/proposed_change/tasks.py +67 -14
  157. infrahub/repositories/__init__.py +0 -0
  158. infrahub/repositories/create_repository.py +113 -0
  159. infrahub/server.py +9 -1
  160. infrahub/services/__init__.py +8 -5
  161. infrahub/services/adapters/http/__init__.py +5 -0
  162. infrahub/services/adapters/workflow/worker.py +14 -3
  163. infrahub/task_manager/event.py +5 -0
  164. infrahub/task_manager/models.py +7 -0
  165. infrahub/task_manager/task.py +73 -0
  166. infrahub/tasks/registry.py +6 -4
  167. infrahub/trigger/catalogue.py +4 -0
  168. infrahub/trigger/models.py +2 -0
  169. infrahub/trigger/setup.py +13 -4
  170. infrahub/trigger/tasks.py +6 -0
  171. infrahub/webhook/models.py +1 -1
  172. infrahub/workers/dependencies.py +3 -1
  173. infrahub/workers/infrahub_async.py +10 -2
  174. infrahub/workflows/catalogue.py +118 -3
  175. infrahub/workflows/initialization.py +21 -0
  176. infrahub/workflows/models.py +17 -2
  177. infrahub/workflows/utils.py +2 -1
  178. infrahub_sdk/branch.py +17 -8
  179. infrahub_sdk/checks.py +1 -1
  180. infrahub_sdk/client.py +376 -95
  181. infrahub_sdk/config.py +29 -2
  182. infrahub_sdk/convert_object_type.py +61 -0
  183. infrahub_sdk/ctl/branch.py +3 -0
  184. infrahub_sdk/ctl/check.py +2 -3
  185. infrahub_sdk/ctl/cli_commands.py +20 -12
  186. infrahub_sdk/ctl/config.py +8 -2
  187. infrahub_sdk/ctl/generator.py +6 -3
  188. infrahub_sdk/ctl/graphql.py +184 -0
  189. infrahub_sdk/ctl/repository.py +39 -1
  190. infrahub_sdk/ctl/schema.py +40 -10
  191. infrahub_sdk/ctl/task.py +110 -0
  192. infrahub_sdk/ctl/utils.py +4 -0
  193. infrahub_sdk/ctl/validate.py +5 -3
  194. infrahub_sdk/diff.py +4 -5
  195. infrahub_sdk/exceptions.py +2 -0
  196. infrahub_sdk/generator.py +7 -1
  197. infrahub_sdk/graphql/__init__.py +12 -0
  198. infrahub_sdk/graphql/constants.py +1 -0
  199. infrahub_sdk/graphql/plugin.py +85 -0
  200. infrahub_sdk/graphql/query.py +77 -0
  201. infrahub_sdk/{graphql.py → graphql/renderers.py} +88 -75
  202. infrahub_sdk/graphql/utils.py +40 -0
  203. infrahub_sdk/node/attribute.py +2 -0
  204. infrahub_sdk/node/node.py +28 -20
  205. infrahub_sdk/node/relationship.py +1 -3
  206. infrahub_sdk/playback.py +1 -2
  207. infrahub_sdk/protocols.py +54 -6
  208. infrahub_sdk/pytest_plugin/plugin.py +7 -4
  209. infrahub_sdk/pytest_plugin/utils.py +40 -0
  210. infrahub_sdk/repository.py +1 -2
  211. infrahub_sdk/schema/__init__.py +70 -4
  212. infrahub_sdk/schema/main.py +1 -0
  213. infrahub_sdk/schema/repository.py +8 -0
  214. infrahub_sdk/spec/models.py +7 -0
  215. infrahub_sdk/spec/object.py +54 -6
  216. infrahub_sdk/spec/processors/__init__.py +0 -0
  217. infrahub_sdk/spec/processors/data_processor.py +10 -0
  218. infrahub_sdk/spec/processors/factory.py +34 -0
  219. infrahub_sdk/spec/processors/range_expand_processor.py +56 -0
  220. infrahub_sdk/spec/range_expansion.py +118 -0
  221. infrahub_sdk/task/models.py +6 -4
  222. infrahub_sdk/timestamp.py +18 -6
  223. infrahub_sdk/transforms.py +1 -1
  224. {infrahub_server-1.4.12.dist-info → infrahub_server-1.5.0.dist-info}/METADATA +9 -10
  225. {infrahub_server-1.4.12.dist-info → infrahub_server-1.5.0.dist-info}/RECORD +233 -176
  226. infrahub_testcontainers/container.py +114 -2
  227. infrahub_testcontainers/docker-compose-cluster.test.yml +5 -0
  228. infrahub_testcontainers/docker-compose.test.yml +5 -0
  229. infrahub_testcontainers/models.py +2 -2
  230. infrahub_testcontainers/performance_test.py +4 -4
  231. infrahub/core/convert_object_type/conversion.py +0 -134
  232. {infrahub_server-1.4.12.dist-info → infrahub_server-1.5.0.dist-info}/LICENSE.txt +0 -0
  233. {infrahub_server-1.4.12.dist-info → infrahub_server-1.5.0.dist-info}/WHEEL +0 -0
  234. {infrahub_server-1.4.12.dist-info → infrahub_server-1.5.0.dist-info}/entry_points.txt +0 -0
infrahub_sdk/node/node.py CHANGED
@@ -234,15 +234,10 @@ class InfrahubNodeBase:
234
234
 
235
235
  rel: RelatedNodeBase | RelationshipManagerBase = getattr(self, item_name)
236
236
 
237
- # BLOCKED by https://github.com/opsmill/infrahub/issues/330
238
- # if (
239
- # item is None
240
- # and item_name in self._relationships
241
- # and self._schema.get_relationship(item_name).cardinality == "one"
242
- # ):
243
- # data[item_name] = None
244
- # continue
245
- # el
237
+ if rel_schema.cardinality == RelationshipCardinality.ONE and rel_schema.optional and not rel.initialized:
238
+ data[item_name] = None
239
+ continue
240
+
246
241
  if rel is None or not rel.initialized:
247
242
  continue
248
243
 
@@ -315,7 +310,16 @@ class InfrahubNodeBase:
315
310
  variables.pop(variable_key)
316
311
 
317
312
  # TODO: I do not feel _great_ about this
318
- if not data_item and data_item != [] and item in data:
313
+ # -> I don't even know who you are (but this is not great indeed) -- gmazoyer (quoting Thanos)
314
+ original_data_item = original_data.get(item)
315
+ original_data_item_is_none = original_data_item is None
316
+ if isinstance(original_data_item, dict):
317
+ if "node" in original_data_item:
318
+ original_data_item_is_none = original_data_item["node"] is None
319
+ elif "id" not in original_data_item:
320
+ original_data_item_is_none = True
321
+
322
+ if item in data and (data_item in ({}, []) or (data_item is None and original_data_item_is_none)):
319
323
  data.pop(item)
320
324
 
321
325
  def _strip_unmodified(self, data: dict, variables: dict) -> tuple[dict, dict]:
@@ -324,7 +328,9 @@ class InfrahubNodeBase:
324
328
  relationship_property = getattr(self, relationship)
325
329
  if not relationship_property or relationship not in data:
326
330
  continue
327
- if not relationship_property.initialized:
331
+ if not relationship_property.initialized and (
332
+ not isinstance(relationship_property, RelatedNodeBase) or not relationship_property.schema.optional
333
+ ):
328
334
  data.pop(relationship)
329
335
  elif isinstance(relationship_property, RelationshipManagerBase) and not relationship_property.has_update:
330
336
  data.pop(relationship)
@@ -573,8 +579,7 @@ class InfrahubNode(InfrahubNodeBase):
573
579
  self._validate_artifact_support(ARTIFACT_GENERATE_FEATURE_NOT_SUPPORTED_MESSAGE)
574
580
 
575
581
  artifact = await self._client.get(kind="CoreArtifact", name__value=name, object__ids=[self.id])
576
- content = await self._client.object_store.get(identifier=artifact._get_attribute(name="storage_id").value)
577
- return content
582
+ return await self._client.object_store.get(identifier=artifact._get_attribute(name="storage_id").value)
578
583
 
579
584
  async def delete(self, timeout: int | None = None, request_context: RequestContext | None = None) -> None:
580
585
  input_data = {"data": {"id": self.id}}
@@ -742,12 +747,11 @@ class InfrahubNode(InfrahubNodeBase):
742
747
  continue
743
748
 
744
749
  peer_data: dict[str, Any] = {}
745
- if rel_schema and prefetch_relationships:
750
+ should_fetch_relationship = prefetch_relationships or (include is not None and rel_name in include)
751
+ if rel_schema and should_fetch_relationship:
746
752
  peer_schema = await self._client.schema.get(kind=rel_schema.peer, branch=self._branch)
747
753
  peer_node = InfrahubNode(client=self._client, schema=peer_schema, branch=self._branch)
748
754
  peer_data = await peer_node.generate_query_data_node(
749
- include=include,
750
- exclude=exclude,
751
755
  property=property,
752
756
  )
753
757
 
@@ -886,7 +890,11 @@ class InfrahubNode(InfrahubNodeBase):
886
890
  await self._process_mutation_result(mutation_name=mutation_name, response=response, timeout=timeout)
887
891
 
888
892
  async def _process_relationships(
889
- self, node_data: dict[str, Any], branch: str, related_nodes: list[InfrahubNode], timeout: int | None = None
893
+ self,
894
+ node_data: dict[str, Any],
895
+ branch: str,
896
+ related_nodes: list[InfrahubNode],
897
+ timeout: int | None = None,
890
898
  ) -> None:
891
899
  """Processes the Relationships of a InfrahubNode and add Related Nodes to a list.
892
900
 
@@ -1199,8 +1207,7 @@ class InfrahubNodeSync(InfrahubNodeBase):
1199
1207
  def artifact_fetch(self, name: str) -> str | dict[str, Any]:
1200
1208
  self._validate_artifact_support(ARTIFACT_FETCH_FEATURE_NOT_SUPPORTED_MESSAGE)
1201
1209
  artifact = self._client.get(kind="CoreArtifact", name__value=name, object__ids=[self.id])
1202
- content = self._client.object_store.get(identifier=artifact._get_attribute(name="storage_id").value)
1203
- return content
1210
+ return self._client.object_store.get(identifier=artifact._get_attribute(name="storage_id").value)
1204
1211
 
1205
1212
  def delete(self, timeout: int | None = None, request_context: RequestContext | None = None) -> None:
1206
1213
  input_data = {"data": {"id": self.id}}
@@ -1363,7 +1370,8 @@ class InfrahubNodeSync(InfrahubNodeBase):
1363
1370
  continue
1364
1371
 
1365
1372
  peer_data: dict[str, Any] = {}
1366
- if rel_schema and prefetch_relationships:
1373
+ should_fetch_relationship = prefetch_relationships or (include is not None and rel_name in include)
1374
+ if rel_schema and should_fetch_relationship:
1367
1375
  peer_schema = self._client.schema.get(kind=rel_schema.peer, branch=self._branch)
1368
1376
  peer_node = InfrahubNodeSync(client=self._client, schema=peer_schema, branch=self._branch)
1369
1377
  peer_data = peer_node.generate_query_data_node(include=include, exclude=exclude, property=property)
@@ -4,7 +4,6 @@ from collections import defaultdict
4
4
  from collections.abc import Iterable
5
5
  from typing import TYPE_CHECKING, Any
6
6
 
7
- from ..batch import InfrahubBatch
8
7
  from ..exceptions import (
9
8
  Error,
10
9
  UninitializedError,
@@ -166,7 +165,7 @@ class RelationshipManager(RelationshipManagerBase):
166
165
  raise Error("Unable to fetch the peer, id and/or typename are not defined")
167
166
  ids_per_kind_map[peer.typename].append(peer.id)
168
167
 
169
- batch = InfrahubBatch(max_concurrent_execution=self.client.max_concurrent_execution)
168
+ batch = await self.client.create_batch()
170
169
  for kind, ids in ids_per_kind_map.items():
171
170
  batch.add(
172
171
  task=self.client.filters,
@@ -289,7 +288,6 @@ class RelationshipManagerSync(RelationshipManagerBase):
289
288
  raise Error("Unable to fetch the peer, id and/or typename are not defined")
290
289
  ids_per_kind_map[peer.typename].append(peer.id)
291
290
 
292
- # Unlike Async, no need to create a new batch from scratch because we are not using a semaphore
293
291
  batch = self.client.create_batch()
294
292
  for kind, ids in ids_per_kind_map.items():
295
293
  batch.add(
infrahub_sdk/playback.py CHANGED
@@ -56,5 +56,4 @@ class JSONPlayback(BaseSettings):
56
56
  with Path(f"{self.directory}/{filename}.json").open(encoding="utf-8") as fobj:
57
57
  data = ujson.load(fobj)
58
58
 
59
- response = httpx.Response(status_code=data["status_code"], content=data["response_content"], request=request)
60
- return response
59
+ return httpx.Response(status_code=data["status_code"], content=data["response_content"], request=request)
infrahub_sdk/protocols.py CHANGED
@@ -131,6 +131,7 @@ class CoreGenericRepository(CoreNode):
131
131
  queries: RelationshipManager
132
132
  checks: RelationshipManager
133
133
  generators: RelationshipManager
134
+ groups_objects: RelationshipManager
134
135
 
135
136
 
136
137
  class CoreGroup(CoreNode):
@@ -233,6 +234,10 @@ class CoreWebhook(CoreNode):
233
234
  validate_certificates: BooleanOptional
234
235
 
235
236
 
237
+ class CoreWeightedPoolResource(CoreNode):
238
+ allocation_weight: IntegerOptional
239
+
240
+
236
241
  class LineageOwner(CoreNode):
237
242
  pass
238
243
 
@@ -321,6 +326,7 @@ class CoreCheckDefinition(CoreTaskTarget):
321
326
 
322
327
 
323
328
  class CoreCustomWebhook(CoreWebhook, CoreTaskTarget):
329
+ shared_key: StringOptional
324
330
  transformation: RelatedNode
325
331
 
326
332
 
@@ -350,6 +356,10 @@ class CoreGeneratorAction(CoreAction):
350
356
  generator: RelatedNode
351
357
 
352
358
 
359
+ class CoreGeneratorAwareGroup(CoreGroup):
360
+ pass
361
+
362
+
353
363
  class CoreGeneratorCheck(CoreCheck):
354
364
  instance: String
355
365
 
@@ -361,6 +371,8 @@ class CoreGeneratorDefinition(CoreTaskTarget):
361
371
  file_path: String
362
372
  class_name: String
363
373
  convert_query_response: BooleanOptional
374
+ execute_in_proposed_change: BooleanOptional
375
+ execute_after_merge: BooleanOptional
364
376
  query: RelatedNode
365
377
  repository: RelatedNode
366
378
  targets: RelatedNode
@@ -405,12 +417,12 @@ class CoreGraphQLQueryGroup(CoreGroup):
405
417
 
406
418
 
407
419
  class CoreGroupAction(CoreAction):
408
- add_members: Boolean
420
+ member_action: Dropdown
409
421
  group: RelatedNode
410
422
 
411
423
 
412
424
  class CoreGroupTriggerRule(CoreTriggerRule):
413
- members_added: Boolean
425
+ member_update: Dropdown
414
426
  group: RelatedNode
415
427
 
416
428
 
@@ -442,7 +454,7 @@ class CoreNodeTriggerAttributeMatch(CoreNodeTriggerMatch):
442
454
 
443
455
  class CoreNodeTriggerRelationshipMatch(CoreNodeTriggerMatch):
444
456
  relationship_name: String
445
- added: Boolean
457
+ modification_type: Dropdown
446
458
  peer: StringOptional
447
459
 
448
460
 
@@ -457,6 +469,7 @@ class CoreNumberPool(CoreResourcePool, LineageSource):
457
469
  node_attribute: String
458
470
  start_range: Integer
459
471
  end_range: Integer
472
+ pool_type: Enum
460
473
 
461
474
 
462
475
  class CoreObjectPermission(CoreBasePermission):
@@ -481,7 +494,10 @@ class CoreProposedChange(CoreTaskTarget):
481
494
  source_branch: String
482
495
  destination_branch: String
483
496
  state: Enum
497
+ is_draft: Boolean
498
+ total_comments: IntegerOptional
484
499
  approved_by: RelationshipManager
500
+ rejected_by: RelationshipManager
485
501
  reviewers: RelationshipManager
486
502
  created_by: RelatedNode
487
503
  comments: RelationshipManager
@@ -555,6 +571,14 @@ class InternalAccountToken(CoreNode):
555
571
  account: RelatedNode
556
572
 
557
573
 
574
+ class InternalIPPrefixAvailable(BuiltinIPPrefix):
575
+ pass
576
+
577
+
578
+ class InternalIPRangeAvailable(BuiltinIPAddress):
579
+ last_address: IPHost
580
+
581
+
558
582
  class InternalRefreshToken(CoreNode):
559
583
  expiration: DateTime
560
584
  account: RelatedNode
@@ -664,6 +688,7 @@ class CoreGenericRepositorySync(CoreNodeSync):
664
688
  queries: RelationshipManagerSync
665
689
  checks: RelationshipManagerSync
666
690
  generators: RelationshipManagerSync
691
+ groups_objects: RelationshipManagerSync
667
692
 
668
693
 
669
694
  class CoreGroupSync(CoreNodeSync):
@@ -766,6 +791,10 @@ class CoreWebhookSync(CoreNodeSync):
766
791
  validate_certificates: BooleanOptional
767
792
 
768
793
 
794
+ class CoreWeightedPoolResourceSync(CoreNodeSync):
795
+ allocation_weight: IntegerOptional
796
+
797
+
769
798
  class LineageOwnerSync(CoreNodeSync):
770
799
  pass
771
800
 
@@ -854,6 +883,7 @@ class CoreCheckDefinitionSync(CoreTaskTargetSync):
854
883
 
855
884
 
856
885
  class CoreCustomWebhookSync(CoreWebhookSync, CoreTaskTargetSync):
886
+ shared_key: StringOptional
857
887
  transformation: RelatedNodeSync
858
888
 
859
889
 
@@ -883,6 +913,10 @@ class CoreGeneratorActionSync(CoreActionSync):
883
913
  generator: RelatedNodeSync
884
914
 
885
915
 
916
+ class CoreGeneratorAwareGroupSync(CoreGroupSync):
917
+ pass
918
+
919
+
886
920
  class CoreGeneratorCheckSync(CoreCheckSync):
887
921
  instance: String
888
922
 
@@ -894,6 +928,8 @@ class CoreGeneratorDefinitionSync(CoreTaskTargetSync):
894
928
  file_path: String
895
929
  class_name: String
896
930
  convert_query_response: BooleanOptional
931
+ execute_in_proposed_change: BooleanOptional
932
+ execute_after_merge: BooleanOptional
897
933
  query: RelatedNodeSync
898
934
  repository: RelatedNodeSync
899
935
  targets: RelatedNodeSync
@@ -938,12 +974,12 @@ class CoreGraphQLQueryGroupSync(CoreGroupSync):
938
974
 
939
975
 
940
976
  class CoreGroupActionSync(CoreActionSync):
941
- add_members: Boolean
977
+ member_action: Dropdown
942
978
  group: RelatedNodeSync
943
979
 
944
980
 
945
981
  class CoreGroupTriggerRuleSync(CoreTriggerRuleSync):
946
- members_added: Boolean
982
+ member_update: Dropdown
947
983
  group: RelatedNodeSync
948
984
 
949
985
 
@@ -975,7 +1011,7 @@ class CoreNodeTriggerAttributeMatchSync(CoreNodeTriggerMatchSync):
975
1011
 
976
1012
  class CoreNodeTriggerRelationshipMatchSync(CoreNodeTriggerMatchSync):
977
1013
  relationship_name: String
978
- added: Boolean
1014
+ modification_type: Dropdown
979
1015
  peer: StringOptional
980
1016
 
981
1017
 
@@ -990,6 +1026,7 @@ class CoreNumberPoolSync(CoreResourcePoolSync, LineageSourceSync):
990
1026
  node_attribute: String
991
1027
  start_range: Integer
992
1028
  end_range: Integer
1029
+ pool_type: Enum
993
1030
 
994
1031
 
995
1032
  class CoreObjectPermissionSync(CoreBasePermissionSync):
@@ -1014,7 +1051,10 @@ class CoreProposedChangeSync(CoreTaskTargetSync):
1014
1051
  source_branch: String
1015
1052
  destination_branch: String
1016
1053
  state: Enum
1054
+ is_draft: Boolean
1055
+ total_comments: IntegerOptional
1017
1056
  approved_by: RelationshipManagerSync
1057
+ rejected_by: RelationshipManagerSync
1018
1058
  reviewers: RelationshipManagerSync
1019
1059
  created_by: RelatedNodeSync
1020
1060
  comments: RelationshipManagerSync
@@ -1088,6 +1128,14 @@ class InternalAccountTokenSync(CoreNodeSync):
1088
1128
  account: RelatedNodeSync
1089
1129
 
1090
1130
 
1131
+ class InternalIPPrefixAvailableSync(BuiltinIPPrefixSync):
1132
+ pass
1133
+
1134
+
1135
+ class InternalIPRangeAvailableSync(BuiltinIPAddressSync):
1136
+ last_address: IPHost
1137
+
1138
+
1091
1139
  class InternalRefreshTokenSync(CoreNodeSync):
1092
1140
  expiration: DateTime
1093
1141
  account: RelatedNodeSync
@@ -9,7 +9,7 @@ from pytest import exit as exit_test
9
9
  from .. import InfrahubClientSync
10
10
  from ..utils import is_valid_url
11
11
  from .loader import InfrahubYamlFile
12
- from .utils import load_repository_config
12
+ from .utils import find_repository_config_file, load_repository_config
13
13
 
14
14
 
15
15
  def pytest_addoption(parser: Parser) -> None:
@@ -18,9 +18,9 @@ def pytest_addoption(parser: Parser) -> None:
18
18
  "--infrahub-repo-config",
19
19
  action="store",
20
20
  dest="infrahub_repo_config",
21
- default=".infrahub.yml",
21
+ default=None,
22
22
  metavar="INFRAHUB_REPO_CONFIG_FILE",
23
- help="Infrahub configuration file for the repository (default: %(default)s)",
23
+ help="Infrahub configuration file for the repository (.infrahub.yml or .infrahub.yaml)",
24
24
  )
25
25
  group.addoption(
26
26
  "--infrahub-address",
@@ -63,7 +63,10 @@ def pytest_addoption(parser: Parser) -> None:
63
63
 
64
64
 
65
65
  def pytest_sessionstart(session: Session) -> None:
66
- session.infrahub_config_path = Path(session.config.option.infrahub_repo_config) # type: ignore[attr-defined]
66
+ if session.config.option.infrahub_repo_config:
67
+ session.infrahub_config_path = Path(session.config.option.infrahub_repo_config) # type: ignore[attr-defined]
68
+ else:
69
+ session.infrahub_config_path = find_repository_config_file() # type: ignore[attr-defined]
67
70
 
68
71
  if session.infrahub_config_path.is_file(): # type: ignore[attr-defined]
69
72
  session.infrahub_repo_config = load_repository_config(repo_config_file=session.infrahub_config_path) # type: ignore[attr-defined]
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from pathlib import Path
2
4
 
3
5
  import yaml
@@ -6,7 +8,45 @@ from ..schema.repository import InfrahubRepositoryConfig
6
8
  from .exceptions import FileNotValidError
7
9
 
8
10
 
11
+ def find_repository_config_file(base_path: Path | None = None) -> Path:
12
+ """Find the repository config file, checking for both .yml and .yaml extensions.
13
+
14
+ Args:
15
+ base_path: Base directory to search in. If None, uses current directory.
16
+
17
+ Returns:
18
+ Path to the config file.
19
+
20
+ Raises:
21
+ FileNotFoundError: If neither .infrahub.yml nor .infrahub.yaml exists.
22
+ """
23
+ if base_path is None:
24
+ base_path = Path()
25
+
26
+ yml_path = base_path / ".infrahub.yml"
27
+ yaml_path = base_path / ".infrahub.yaml"
28
+
29
+ # Prefer .yml if both exist
30
+ if yml_path.exists():
31
+ return yml_path
32
+ if yaml_path.exists():
33
+ return yaml_path
34
+ # For backward compatibility, return .yml path for error messages
35
+ return yml_path
36
+
37
+
9
38
  def load_repository_config(repo_config_file: Path) -> InfrahubRepositoryConfig:
39
+ # If the file doesn't exist, try to find it with alternate extension
40
+ if not repo_config_file.exists():
41
+ if repo_config_file.name == ".infrahub.yml":
42
+ alt_path = repo_config_file.parent / ".infrahub.yaml"
43
+ if alt_path.exists():
44
+ repo_config_file = alt_path
45
+ elif repo_config_file.name == ".infrahub.yaml":
46
+ alt_path = repo_config_file.parent / ".infrahub.yml"
47
+ if alt_path.exists():
48
+ repo_config_file = alt_path
49
+
10
50
  if not repo_config_file.is_file():
11
51
  raise FileNotFoundError(repo_config_file)
12
52
 
@@ -29,5 +29,4 @@ class GitRepoManager:
29
29
 
30
30
  @property
31
31
  def active_branch(self) -> str | None:
32
- active_branch = porcelain.active_branch(self.root_directory).decode("utf-8")
33
- return active_branch
32
+ return porcelain.active_branch(self.root_directory).decode("utf-8")
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import asyncio
4
+ import inspect
4
5
  import json
5
6
  import warnings
6
7
  from collections.abc import MutableMapping
@@ -90,6 +91,26 @@ MainSchemaTypesAll: TypeAlias = Union[
90
91
  ]
91
92
 
92
93
 
94
+ class SchemaWarningType(Enum):
95
+ DEPRECATION = "deprecation"
96
+
97
+
98
+ class SchemaWarningKind(BaseModel):
99
+ kind: str = Field(..., description="The kind impacted by the warning")
100
+ field: str | None = Field(default=None, description="The attribute or relationship impacted by the warning")
101
+
102
+ @property
103
+ def display(self) -> str:
104
+ suffix = f".{self.field}" if self.field else ""
105
+ return f"{self.kind}{suffix}"
106
+
107
+
108
+ class SchemaWarning(BaseModel):
109
+ type: SchemaWarningType = Field(..., description="The type of warning")
110
+ kinds: list[SchemaWarningKind] = Field(default_factory=list, description="The kinds impacted by the warning")
111
+ message: str = Field(..., description="The message that describes the warning")
112
+
113
+
93
114
  class InfrahubSchemaBase:
94
115
  client: InfrahubClient | InfrahubClientSync
95
116
  cache: dict[str, BranchSchema]
@@ -169,7 +190,9 @@ class InfrahubSchemaBase:
169
190
  def _validate_load_schema_response(response: httpx.Response) -> SchemaLoadResponse:
170
191
  if response.status_code == httpx.codes.OK:
171
192
  status = response.json()
172
- return SchemaLoadResponse(hash=status["hash"], previous_hash=status["previous_hash"])
193
+ return SchemaLoadResponse(
194
+ hash=status["hash"], previous_hash=status["previous_hash"], warnings=status.get("warnings") or []
195
+ )
173
196
 
174
197
  if response.status_code in [
175
198
  httpx.codes.BAD_REQUEST,
@@ -185,12 +208,16 @@ class InfrahubSchemaBase:
185
208
 
186
209
  @staticmethod
187
210
  def _get_schema_name(schema: type[SchemaType | SchemaTypeSync] | str) -> str:
188
- if hasattr(schema, "_is_runtime_protocol") and schema._is_runtime_protocol: # type: ignore[union-attr]
189
- return schema.__name__ # type: ignore[union-attr]
190
-
191
211
  if isinstance(schema, str):
192
212
  return schema
193
213
 
214
+ if hasattr(schema, "_is_runtime_protocol") and getattr(schema, "_is_runtime_protocol", None):
215
+ if inspect.iscoroutinefunction(schema.save):
216
+ return schema.__name__
217
+ if schema.__name__[-4:] == "Sync":
218
+ return schema.__name__[:-4]
219
+ return schema.__name__
220
+
194
221
  raise ValueError("schema must be a protocol or a string")
195
222
 
196
223
  @staticmethod
@@ -474,6 +501,25 @@ class InfrahubSchema(InfrahubSchemaBase):
474
501
 
475
502
  return branch_schema.nodes
476
503
 
504
+ async def get_graphql_schema(self, branch: str | None = None) -> str:
505
+ """Get the GraphQL schema as a string.
506
+
507
+ Args:
508
+ branch: The branch to get the schema for. Defaults to default_branch.
509
+
510
+ Returns:
511
+ The GraphQL schema as a string.
512
+ """
513
+ branch = branch or self.client.default_branch
514
+ url = f"{self.client.address}/schema.graphql?branch={branch}"
515
+
516
+ response = await self.client._get(url=url)
517
+
518
+ if response.status_code != 200:
519
+ raise ValueError(f"Failed to fetch GraphQL schema: HTTP {response.status_code} - {response.text}")
520
+
521
+ return response.text
522
+
477
523
  async def _fetch(self, branch: str, namespaces: list[str] | None = None) -> BranchSchema:
478
524
  url_parts = [("branch", branch)]
479
525
  if namespaces:
@@ -697,6 +743,25 @@ class InfrahubSchemaSync(InfrahubSchemaBase):
697
743
 
698
744
  return branch_schema.nodes
699
745
 
746
+ def get_graphql_schema(self, branch: str | None = None) -> str:
747
+ """Get the GraphQL schema as a string.
748
+
749
+ Args:
750
+ branch: The branch to get the schema for. Defaults to default_branch.
751
+
752
+ Returns:
753
+ The GraphQL schema as a string.
754
+ """
755
+ branch = branch or self.client.default_branch
756
+ url = f"{self.client.address}/schema.graphql?branch={branch}"
757
+
758
+ response = self.client._get(url=url)
759
+
760
+ if response.status_code != 200:
761
+ raise ValueError(f"Failed to fetch GraphQL schema: HTTP {response.status_code} - {response.text}")
762
+
763
+ return response.text
764
+
700
765
  def _fetch(self, branch: str, namespaces: list[str] | None = None) -> BranchSchema:
701
766
  url_parts = [("branch", branch)]
702
767
  if namespaces:
@@ -764,6 +829,7 @@ class SchemaLoadResponse(BaseModel):
764
829
  hash: str = Field(default="", description="The new hash for the entire schema")
765
830
  previous_hash: str = Field(default="", description="The previous hash for the entire schema")
766
831
  errors: dict = Field(default_factory=dict, description="Errors reported by the server")
832
+ warnings: list[SchemaWarning] = Field(default_factory=list, description="Warnings reported by the server")
767
833
 
768
834
  @property
769
835
  def schema_updated(self) -> bool:
@@ -267,6 +267,7 @@ class BaseSchema(BaseModel):
267
267
  description: str | None = None
268
268
  include_in_menu: bool | None = None
269
269
  menu_placement: str | None = None
270
+ display_label: str | None = None
270
271
  display_labels: list[str] | None = None
271
272
  human_friendly_id: list[str] | None = None
272
273
  icon: str | None = None
@@ -96,6 +96,14 @@ class InfrahubGeneratorDefinitionConfig(InfrahubRepositoryConfigElement):
96
96
  default=False,
97
97
  description="Decide if the generator should convert the result of the GraphQL query to SDK InfrahubNode objects.",
98
98
  )
99
+ execute_in_proposed_change: bool = Field(
100
+ default=True,
101
+ description="Decide if the generator should execute in a proposed change.",
102
+ )
103
+ execute_after_merge: bool = Field(
104
+ default=True,
105
+ description="Decide if the generator should execute after a merge.",
106
+ )
99
107
 
100
108
  def load_class(self, import_root: str | None = None, relative_path: str | None = None) -> type[InfrahubGenerator]:
101
109
  module = import_module(module_path=self.file_path, import_root=import_root, relative_path=relative_path)
@@ -0,0 +1,7 @@
1
+ from __future__ import annotations
2
+
3
+ from pydantic import BaseModel
4
+
5
+
6
+ class InfrahubObjectParameters(BaseModel):
7
+ expand_range: bool = False