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
@@ -109,7 +109,7 @@ class BranchRebasedEvent(InfrahubEvent):
109
109
 
110
110
  event_name: ClassVar[str] = f"{EVENT_NAMESPACE}.branch.rebased"
111
111
 
112
- branch_id: str = Field(..., description="The ID of the mutated node")
112
+ branch_id: str = Field(..., description="The ID of the branch")
113
113
  branch_name: str = Field(..., description="The name of the branch")
114
114
 
115
115
  def get_resource(self) -> dict[str, str]:
@@ -128,3 +128,29 @@ class BranchRebasedEvent(InfrahubEvent):
128
128
  RefreshRegistryRebasedBranch(branch=self.branch_name),
129
129
  ]
130
130
  return events
131
+
132
+
133
+ class BranchMigratedEvent(InfrahubEvent):
134
+ """Event generated when a branch has been migrated"""
135
+
136
+ event_name: ClassVar[str] = f"{EVENT_NAMESPACE}.branch.migrated"
137
+
138
+ branch_id: str = Field(..., description="The ID of the branch")
139
+ branch_name: str = Field(..., description="The name of the branch")
140
+
141
+ def get_resource(self) -> dict[str, str]:
142
+ return {
143
+ "prefect.resource.id": f"infrahub.branch.{self.branch_name}",
144
+ "infrahub.branch.id": self.branch_id,
145
+ "infrahub.branch.name": self.branch_name,
146
+ }
147
+
148
+ def get_messages(self) -> list[InfrahubMessage]:
149
+ events: list[InfrahubMessage] = [
150
+ # EventBranchMigrated(
151
+ # branch=self.branch,
152
+ # meta=self.get_message_meta(),
153
+ # ),
154
+ RefreshRegistryRebasedBranch(branch=self.branch_name),
155
+ ]
156
+ return events
@@ -22,7 +22,7 @@ class GroupMutatedEvent(InfrahubEvent):
22
22
  def get_related(self) -> list[dict[str, str]]:
23
23
  related = super().get_related()
24
24
 
25
- if self.kind in [InfrahubKind.GENERATORGROUP, InfrahubKind.GRAPHQLQUERYGROUP]:
25
+ if self.kind in [InfrahubKind.GENERATORGROUP, InfrahubKind.GENERATORAWAREGROUP, InfrahubKind.GRAPHQLQUERYGROUP]:
26
26
  # Temporary workaround to avoid too large payloads for the related field
27
27
  return related
28
28
 
@@ -24,7 +24,7 @@ class NodeMutatedEvent(InfrahubEvent):
24
24
 
25
25
  def get_related(self) -> list[dict[str, str]]:
26
26
  related = super().get_related()
27
- if self.kind in [InfrahubKind.GENERATORGROUP, InfrahubKind.GRAPHQLQUERYGROUP]:
27
+ if self.kind in [InfrahubKind.GENERATORGROUP, InfrahubKind.GENERATORAWAREGROUP, InfrahubKind.GRAPHQLQUERYGROUP]:
28
28
  # Temporary workaround to avoid too large payloads for the related field
29
29
  return related
30
30
 
@@ -0,0 +1,7 @@
1
+ from enum import Enum
2
+
3
+
4
+ class GeneratorDefinitionRunSource(Enum):
5
+ PROPOSED_CHANGE = "proposed_change"
6
+ MERGE = "merge"
7
+ UNKNOWN = "unknown"
@@ -6,7 +6,7 @@ from pydantic import BaseModel, ConfigDict, Field
6
6
  class RequestGeneratorRun(BaseModel):
7
7
  """Runs a generator."""
8
8
 
9
- generator_definition: ProposedChangeGeneratorDefinition = Field(..., description="The Generator definition")
9
+ generator_definition: GeneratorDefinitionModel = Field(..., description="The Generator definition")
10
10
  generator_instance: str | None = Field(
11
11
  default=None, description="The id of the generator instance if it previously existed"
12
12
  )
@@ -31,14 +31,40 @@ class RequestGeneratorDefinitionRun(BaseModel):
31
31
  target_members: list[str] = Field(default_factory=list, description="List of targets to run the generator for")
32
32
 
33
33
 
34
- class ProposedChangeGeneratorDefinition(BaseModel):
35
- definition_id: str
36
- definition_name: str
37
- query_name: str
38
- convert_query_response: bool
39
- query_models: list[str]
40
- repository_id: str
41
- class_name: str
42
- file_path: str
43
- parameters: dict
44
- group_id: str
34
+ class GeneratorDefinitionModel(BaseModel):
35
+ definition_id: str = Field(..., description="The id of the generator definition.")
36
+ definition_name: str = Field(..., description="The name of the generator definition.")
37
+ query_name: str = Field(..., description="The name of the query to use when collecting data.")
38
+ convert_query_response: bool = Field(
39
+ ...,
40
+ description="Decide if the generator should convert the result of the GraphQL query to SDK InfrahubNode objects.",
41
+ )
42
+ class_name: str = Field(..., description="The name of the generator class to run.")
43
+ file_path: str = Field(..., description="The file path of the generator in the repository.")
44
+ group_id: str = Field(..., description="The group to target when running this generator")
45
+ parameters: dict = Field(..., description="The input parameters required to run this check")
46
+
47
+ execute_in_proposed_change: bool = Field(
48
+ ..., description="Indicates if the generator should execute in a proposed change."
49
+ )
50
+ execute_after_merge: bool = Field(..., description="Indicates if the generator should execute after a merge.")
51
+
52
+ @classmethod
53
+ def from_pc_generator_definition(cls, model: ProposedChangeGeneratorDefinition) -> GeneratorDefinitionModel:
54
+ return GeneratorDefinitionModel(
55
+ definition_id=model.definition_id,
56
+ definition_name=model.definition_name,
57
+ query_name=model.query_name,
58
+ convert_query_response=model.convert_query_response,
59
+ class_name=model.class_name,
60
+ file_path=model.file_path,
61
+ group_id=model.group_id,
62
+ parameters=model.parameters,
63
+ execute_in_proposed_change=model.execute_in_proposed_change,
64
+ execute_after_merge=model.execute_after_merge,
65
+ )
66
+
67
+
68
+ class ProposedChangeGeneratorDefinition(GeneratorDefinitionModel):
69
+ query_models: list[str] = Field(..., description="The models to use when collecting data.")
70
+ repository_id: str = Field(..., description="The id of the repository.")
@@ -14,7 +14,9 @@ from prefect.states import Completed, Failed
14
14
  from infrahub import lock
15
15
  from infrahub.context import InfrahubContext # noqa: TC001 needed for prefect flow
16
16
  from infrahub.core.constants import GeneratorInstanceStatus, InfrahubKind
17
+ from infrahub.generators.constants import GeneratorDefinitionRunSource
17
18
  from infrahub.generators.models import (
19
+ GeneratorDefinitionModel,
18
20
  ProposedChangeGeneratorDefinition,
19
21
  RequestGeneratorDefinitionRun,
20
22
  RequestGeneratorRun,
@@ -53,9 +55,12 @@ async def run_generator(model: RequestGeneratorRun) -> None:
53
55
  name=model.generator_definition.definition_name,
54
56
  class_name=model.generator_definition.class_name,
55
57
  file_path=model.generator_definition.file_path,
58
+ parameters=model.generator_definition.parameters,
56
59
  query=model.generator_definition.query_name,
57
60
  targets=model.generator_definition.group_id,
58
61
  convert_query_response=model.generator_definition.convert_query_response,
62
+ execute_in_proposed_change=model.generator_definition.execute_in_proposed_change,
63
+ execute_after_merge=model.generator_definition.execute_after_merge,
59
64
  )
60
65
 
61
66
  commit_worktree = repository.get_commit_worktree(commit=model.commit)
@@ -79,6 +84,8 @@ async def run_generator(model: RequestGeneratorRun) -> None:
79
84
  params=model.variables,
80
85
  generator_instance=generator_instance.id,
81
86
  convert_query_response=generator_definition.convert_query_response,
87
+ execute_in_proposed_change=generator_definition.execute_in_proposed_change,
88
+ execute_after_merge=generator_definition.execute_after_merge,
82
89
  infrahub_node=InfrahubNode,
83
90
  )
84
91
  await generator.run(identifier=generator_definition.name)
@@ -128,28 +135,39 @@ async def _define_instance(model: RequestGeneratorRun, client: InfrahubClient) -
128
135
 
129
136
 
130
137
  @flow(name="generator-definition-run", flow_run_name="Run all generators")
131
- async def run_generator_definition(branch: str, context: InfrahubContext) -> None:
138
+ async def run_generator_definition(
139
+ branch: str, context: InfrahubContext, source: GeneratorDefinitionRunSource = GeneratorDefinitionRunSource.UNKNOWN
140
+ ) -> None:
132
141
  await add_tags(branches=[branch])
133
142
 
134
143
  generators = await get_client().filters(
135
144
  kind=InfrahubKind.GENERATORDEFINITION, prefetch_relationships=True, populate_store=True, branch=branch
136
145
  )
137
146
 
138
- generator_definitions = [
139
- ProposedChangeGeneratorDefinition(
140
- definition_id=generator.id,
141
- definition_name=generator.name.value,
142
- class_name=generator.class_name.value,
143
- file_path=generator.file_path.value,
144
- query_name=generator.query.peer.name.value,
145
- query_models=generator.query.peer.models.value,
146
- repository_id=generator.repository.peer.id,
147
- parameters=generator.parameters.value,
148
- group_id=generator.targets.peer.id,
149
- convert_query_response=generator.convert_query_response.value,
147
+ generator_definitions: list[ProposedChangeGeneratorDefinition] = []
148
+
149
+ for generator in generators:
150
+ if (
151
+ source == GeneratorDefinitionRunSource.PROPOSED_CHANGE and not generator.execute_in_proposed_change.value
152
+ ) or (source == GeneratorDefinitionRunSource.MERGE and not generator.execute_after_merge.value):
153
+ continue
154
+
155
+ generator_definitions.append(
156
+ ProposedChangeGeneratorDefinition(
157
+ definition_id=generator.id,
158
+ definition_name=generator.name.value,
159
+ class_name=generator.class_name.value,
160
+ file_path=generator.file_path.value,
161
+ query_name=generator.query.peer.name.value,
162
+ query_models=generator.query.peer.models.value,
163
+ repository_id=generator.repository.peer.id,
164
+ parameters=generator.parameters.value,
165
+ group_id=generator.targets.peer.id,
166
+ convert_query_response=generator.convert_query_response.value,
167
+ execute_in_proposed_change=generator.execute_in_proposed_change.value,
168
+ execute_after_merge=generator.execute_after_merge.value,
169
+ )
150
170
  )
151
- for generator in generators
152
- ]
153
171
 
154
172
  for generator_definition in generator_definitions:
155
173
  model = RequestGeneratorDefinitionRun(branch=branch, generator_definition=generator_definition)
@@ -209,7 +227,7 @@ async def request_generator_definition_run(
209
227
 
210
228
  generator_instance = instance_by_member.get(member.id)
211
229
  request_generator_run_model = RequestGeneratorRun(
212
- generator_definition=model.generator_definition,
230
+ generator_definition=GeneratorDefinitionModel.from_pc_generator_definition(model.generator_definition),
213
231
  commit=repository.commit.value,
214
232
  generator_instance=generator_instance,
215
233
  repository_id=repository.id,
infrahub/git/base.py CHANGED
@@ -7,7 +7,7 @@ from typing import TYPE_CHECKING, NoReturn
7
7
  from uuid import UUID # noqa: TC003
8
8
 
9
9
  import git
10
- from git import Blob, Repo
10
+ from git import BadName, Blob, Repo
11
11
  from git.exc import GitCommandError, InvalidGitRepositoryError
12
12
  from git.refs.remote import RemoteReference
13
13
  from infrahub_sdk import InfrahubClient # noqa: TC002
@@ -67,6 +67,15 @@ class RepoFileInformation(BaseModel):
67
67
  """Extension of the file Example: py """
68
68
 
69
69
 
70
+ class RepoChangedFiles(BaseModel):
71
+ added: list[str] = Field(default_factory=list)
72
+ copied: list[tuple[str, str]] = Field(default_factory=list)
73
+ deleted: list[str] = Field(default_factory=list)
74
+ renamed: list[tuple[str, str]] = Field(default_factory=list)
75
+ modified: list[str] = Field(default_factory=list)
76
+ type_changed: list[tuple[str, str]] = Field(default_factory=list)
77
+
78
+
70
79
  def extract_repo_file_information(
71
80
  full_filename: Path, repo_directory: Path, worktree_directory: Path | None = None
72
81
  ) -> RepoFileInformation:
@@ -973,3 +982,31 @@ class InfrahubRepositoryBase(BaseModel, ABC):
973
982
  if branch_name == self.default_branch and branch_name != registry.default_branch:
974
983
  return registry.default_branch
975
984
  return branch_name
985
+
986
+ def get_changed_files(self, first_commit: str, second_commit: str | None = None) -> RepoChangedFiles:
987
+ """Return the changes between two commits in this repo."""
988
+ changes = RepoChangedFiles()
989
+ repo = self.get_git_repo_main()
990
+
991
+ try:
992
+ commit_a = repo.commit(first_commit)
993
+ commit_b = repo.commit(second_commit) if second_commit else repo.head.commit
994
+ except BadName as exc:
995
+ raise CommitNotFoundError(identifier=str(self.id), commit=exc.args[0]) from exc
996
+
997
+ for diff in commit_a.diff(commit_b):
998
+ match diff.change_type:
999
+ case "A":
1000
+ changes.added.append(diff.b_path)
1001
+ case "C":
1002
+ changes.copied.append((diff.a_path, diff.b_path))
1003
+ case "D":
1004
+ changes.deleted.append(diff.a_path)
1005
+ case "R":
1006
+ changes.renamed.append((diff.a_path, diff.b_path))
1007
+ case "M":
1008
+ changes.modified.append(diff.b_path)
1009
+ case "T":
1010
+ changes.type_changed.append((diff.a_path, diff.b_path))
1011
+
1012
+ return changes
@@ -226,7 +226,7 @@ class InfrahubRepositoryIntegrator(InfrahubRepositoryBase):
226
226
  )
227
227
  )
228
228
 
229
- @task(name="import-jinja2-tansforms", task_run_name="Import Jinja2 transform", cache_policy=NONE) # type: ignore[arg-type]
229
+ @task(name="import-jinja2-transforms", task_run_name="Import Jinja2 transform", cache_policy=NONE)
230
230
  async def import_jinja2_transforms(
231
231
  self,
232
232
  branch_name: str,
@@ -331,7 +331,7 @@ class InfrahubRepositoryIntegrator(InfrahubRepositoryBase):
331
331
 
332
332
  await existing_transform.save()
333
333
 
334
- @task(name="import-artifact-definitions", task_run_name="Import Artifact Definitions", cache_policy=NONE) # type: ignore[arg-type]
334
+ @task(name="import-artifact-definitions", task_run_name="Import Artifact Definitions", cache_policy=NONE)
335
335
  async def import_artifact_definitions(
336
336
  self,
337
337
  branch_name: str,
@@ -432,7 +432,7 @@ class InfrahubRepositoryIntegrator(InfrahubRepositoryBase):
432
432
 
433
433
  await existing_artifact_definition.save()
434
434
 
435
- @task(name="repository-get-config", task_run_name="get repository config", cache_policy=NONE) # type: ignore[arg-type]
435
+ @task(name="repository-get-config", task_run_name="get repository config", cache_policy=NONE)
436
436
  async def get_repository_config(self, branch_name: str, commit: str) -> InfrahubRepositoryConfig | None:
437
437
  branch_wt = self.get_worktree(identifier=commit or branch_name)
438
438
  log = get_run_logger()
@@ -469,7 +469,7 @@ class InfrahubRepositoryIntegrator(InfrahubRepositoryBase):
469
469
  log.error(f"Unable to load the configuration file {config_file_name}, the format is not valid : {exc}")
470
470
  return None
471
471
 
472
- @task(name="import-schema-files", task_run_name="Import schema files", cache_policy=NONE) # type: ignore[arg-type]
472
+ @task(name="import-schema-files", task_run_name="Import schema files", cache_policy=NONE)
473
473
  async def import_schema_files(self, branch_name: str, commit: str, config_file: InfrahubRepositoryConfig) -> None:
474
474
  log = get_run_logger()
475
475
  branch_wt = self.get_worktree(identifier=commit or branch_name)
@@ -541,7 +541,7 @@ class InfrahubRepositoryIntegrator(InfrahubRepositoryBase):
541
541
  for schema_file in schemas_data:
542
542
  log.info(f"schema '{schema_file.identifier}' loaded successfully!")
543
543
 
544
- @task(name="import-graphql-queries", task_run_name="Import GraphQL Queries", cache_policy=NONE) # type: ignore[arg-type]
544
+ @task(name="import-graphql-queries", task_run_name="Import GraphQL Queries", cache_policy=NONE)
545
545
  async def import_all_graphql_query(
546
546
  self, branch_name: str, commit: str, config_file: InfrahubRepositoryConfig
547
547
  ) -> None:
@@ -599,7 +599,7 @@ class InfrahubRepositoryIntegrator(InfrahubRepositoryBase):
599
599
  await obj.save()
600
600
  return obj
601
601
 
602
- @task(name="import-python-check-definitions", task_run_name="Import Python Check Definitions", cache_policy=NONE) # type: ignore[arg-type]
602
+ @task(name="import-python-check-definitions", task_run_name="Import Python Check Definitions", cache_policy=NONE)
603
603
  async def import_python_check_definitions(
604
604
  self, branch_name: str, commit: str, config_file: InfrahubRepositoryConfig
605
605
  ) -> None:
@@ -670,7 +670,7 @@ class InfrahubRepositoryIntegrator(InfrahubRepositoryBase):
670
670
  log.info(f"CheckDefinition '{check_name!r}' not found locally, deleting")
671
671
  await check_definition_in_graph[check_name].delete()
672
672
 
673
- @task(name="import-generator-definitions", task_run_name="Import Generator Definitions", cache_policy=NONE) # type: ignore[arg-type]
673
+ @task(name="import-generator-definitions", task_run_name="Import Generator Definitions", cache_policy=NONE)
674
674
  async def import_generator_definitions(
675
675
  self, branch_name: str, commit: str, config_file: InfrahubRepositoryConfig
676
676
  ) -> None:
@@ -756,11 +756,13 @@ class InfrahubRepositoryIntegrator(InfrahubRepositoryBase):
756
756
  or existing_generator.parameters.value != generator.parameters
757
757
  or existing_generator.convert_query_response.value != generator.convert_query_response
758
758
  or existing_generator.targets.id != generator.targets
759
+ or existing_generator.execute_in_proposed_change.value != generator.execute_in_proposed_change
760
+ or existing_generator.execute_after_merge.value != generator.execute_after_merge
759
761
  ):
760
762
  return True
761
763
  return False
762
764
 
763
- @task(name="import-python-transforms", task_run_name="Import Python Transforms", cache_policy=NONE) # type: ignore[arg-type]
765
+ @task(name="import-python-transforms", task_run_name="Import Python Transforms", cache_policy=NONE)
764
766
  async def import_python_transforms(
765
767
  self, branch_name: str, commit: str, config_file: InfrahubRepositoryConfig
766
768
  ) -> None:
@@ -885,7 +887,7 @@ class InfrahubRepositoryIntegrator(InfrahubRepositoryBase):
885
887
  file_type=file_type,
886
888
  )
887
889
 
888
- @task(name="import-objects", task_run_name="Import Objects", cache_policy=NONE) # type: ignore[arg-type]
890
+ @task(name="import-objects", task_run_name="Import Objects", cache_policy=NONE)
889
891
  async def import_objects(
890
892
  self,
891
893
  branch_name: str,
@@ -905,7 +907,7 @@ class InfrahubRepositoryIntegrator(InfrahubRepositoryBase):
905
907
  object_type=RepositoryObjects.MENU,
906
908
  )
907
909
 
908
- @task(name="check-definition-get", task_run_name="Get Check Definition", cache_policy=NONE) # type: ignore[arg-type]
910
+ @task(name="check-definition-get", task_run_name="Get Check Definition", cache_policy=NONE)
909
911
  async def get_check_definition(
910
912
  self,
911
913
  branch_name: str,
@@ -945,7 +947,7 @@ class InfrahubRepositoryIntegrator(InfrahubRepositoryBase):
945
947
  raise
946
948
  return checks
947
949
 
948
- @task(name="python-transform-get", task_run_name="Get Python Transform", cache_policy=NONE) # type: ignore[arg-type]
950
+ @task(name="python-transform-get", task_run_name="Get Python Transform", cache_policy=NONE)
949
951
  async def get_python_transforms(
950
952
  self, branch_name: str, module: types.ModuleType, file_path: str, transform: InfrahubPythonTransformConfig
951
953
  ) -> list[TransformPythonInformation]:
@@ -1023,6 +1025,12 @@ class InfrahubRepositoryIntegrator(InfrahubRepositoryBase):
1023
1025
  if existing_generator.targets.id != generator.targets:
1024
1026
  existing_generator.targets = {"id": generator.targets, "source": str(self.id), "is_protected": True}
1025
1027
 
1028
+ if existing_generator.execute_in_proposed_change.value != generator.execute_in_proposed_change:
1029
+ existing_generator.execute_in_proposed_change.value = generator.execute_in_proposed_change
1030
+
1031
+ if existing_generator.execute_after_merge.value != generator.execute_after_merge:
1032
+ existing_generator.execute_after_merge.value = generator.execute_after_merge
1033
+
1026
1034
  await existing_generator.save()
1027
1035
 
1028
1036
  async def create_python_check_definition(
@@ -1152,7 +1160,7 @@ class InfrahubRepositoryIntegrator(InfrahubRepositoryBase):
1152
1160
  await self.import_python_transforms(branch_name=branch_name, commit=commit, config_file=config_file) # type: ignore[misc]
1153
1161
  await self.import_generator_definitions(branch_name=branch_name, commit=commit, config_file=config_file) # type: ignore[misc]
1154
1162
 
1155
- @task(name="jinja2-template-render", task_run_name="Render Jinja2 template", cache_policy=NONE) # type: ignore[arg-type]
1163
+ @task(name="jinja2-template-render", task_run_name="Render Jinja2 template", cache_policy=NONE)
1156
1164
  async def render_jinja2_template(self, commit: str, location: str, data: dict) -> str:
1157
1165
  log = get_run_logger()
1158
1166
  commit_worktree = self.get_commit_worktree(commit=commit)
@@ -1168,7 +1176,7 @@ class InfrahubRepositoryIntegrator(InfrahubRepositoryBase):
1168
1176
  repository_name=self.name, commit=commit, location=location, message=exc.message
1169
1177
  ) from exc
1170
1178
 
1171
- @task(name="python-check-execute", task_run_name="Execute Python Check", cache_policy=NONE) # type: ignore[arg-type]
1179
+ @task(name="python-check-execute", task_run_name="Execute Python Check", cache_policy=NONE)
1172
1180
  async def execute_python_check(
1173
1181
  self,
1174
1182
  branch_name: str,
@@ -1227,7 +1235,7 @@ class InfrahubRepositoryIntegrator(InfrahubRepositoryBase):
1227
1235
  repository_name=self.name, class_name=class_name, commit=commit, location=location, message=str(exc)
1228
1236
  ) from exc
1229
1237
 
1230
- @task(name="python-transform-execute", task_run_name="Execute Python Transform", cache_policy=NONE) # type: ignore[arg-type]
1238
+ @task(name="python-transform-execute", task_run_name="Execute Python Transform", cache_policy=NONE)
1231
1239
  async def execute_python_transform(
1232
1240
  self,
1233
1241
  branch_name: str,
@@ -1,5 +1,3 @@
1
- from typing import Any
2
-
3
1
  from infrahub import config
4
2
 
5
3
  from ..app import InfrahubGraphQLApp
@@ -36,5 +34,5 @@ def build_graphql_query_permission_checker() -> GraphQLQueryPermissionChecker:
36
34
  )
37
35
 
38
36
 
39
- def build_graphql_app(**kwargs: Any) -> InfrahubGraphQLApp:
40
- return InfrahubGraphQLApp(build_graphql_query_permission_checker(), **kwargs)
37
+ def build_graphql_app() -> InfrahubGraphQLApp:
38
+ return InfrahubGraphQLApp(build_graphql_query_permission_checker())
@@ -2,14 +2,15 @@ from __future__ import annotations
2
2
 
3
3
  from typing import TYPE_CHECKING
4
4
 
5
- from fastapi import APIRouter, Depends
5
+ from fastapi import APIRouter, Depends, Query
6
6
  from fastapi.responses import PlainTextResponse
7
- from graphql import print_schema
7
+ from graphql import parse, print_ast, print_schema
8
8
  from starlette.routing import Route, WebSocketRoute
9
9
 
10
10
  from infrahub.api.dependencies import get_branch_dep, get_current_user
11
11
  from infrahub.core import registry
12
- from infrahub.graphql.manager import GraphQLSchemaManager
12
+ from infrahub.graphql.registry import registry as graphql_registry
13
+ from infrahub.graphql.schema_sort import sort_schema_ast
13
14
 
14
15
  from .dependencies import build_graphql_app
15
16
 
@@ -27,11 +28,20 @@ router.routes.append(WebSocketRoute(path="/graphql", endpoint=graphql_app))
27
28
  router.routes.append(WebSocketRoute(path="/graphql/{branch_name:str}", endpoint=graphql_app))
28
29
 
29
30
 
30
- @router.get("/schema.graphql", include_in_schema=False)
31
+ @router.get("/schema.graphql")
31
32
  async def get_graphql_schema(
32
- branch: Branch = Depends(get_branch_dep), _: AccountSession = Depends(get_current_user)
33
+ branch: Branch = Depends(get_branch_dep),
34
+ _: AccountSession = Depends(get_current_user),
35
+ sort_schema: bool = Query(default=False, alias="sorted", description="Whether to sort the schema alphabetically."),
33
36
  ) -> PlainTextResponse:
34
37
  schema_branch = registry.schema.get_schema_branch(name=branch.name)
35
- gqlm = GraphQLSchemaManager.get_manager_for_branch(branch=branch, schema_branch=schema_branch)
38
+ gqlm = graphql_registry.get_manager_for_branch(branch=branch, schema_branch=schema_branch)
36
39
  graphql_schema = gqlm.get_graphql_schema()
40
+
41
+ if sort_schema:
42
+ schema_str = print_schema(graphql_schema)
43
+ schema_ast = parse(schema_str)
44
+ sorted_schema_ast = sort_schema_ast(schema_ast)
45
+ return PlainTextResponse(content=print_ast(sorted_schema_ast))
46
+
37
47
  return PlainTextResponse(content=print_schema(graphql_schema))
infrahub/graphql/app.py CHANGED
@@ -23,7 +23,6 @@ from graphql import (
23
23
  ExecutionResult,
24
24
  GraphQLError,
25
25
  GraphQLFormattedError,
26
- Middleware,
27
26
  OperationType,
28
27
  graphql,
29
28
  parse,
@@ -59,6 +58,7 @@ from .metrics import (
59
58
  GRAPHQL_RESPONSE_SIZE_METRICS,
60
59
  GRAPHQL_TOP_LEVEL_QUERIES_METRICS,
61
60
  )
61
+ from .middleware import raise_on_mutation_on_branch_needing_rebase
62
62
 
63
63
  if TYPE_CHECKING:
64
64
  import graphene
@@ -99,7 +99,6 @@ class InfrahubGraphQLApp:
99
99
  *,
100
100
  on_get: Callable[[Request], Response | Awaitable[Response]] | None = None,
101
101
  root_value: RootValue = None,
102
- middleware: Middleware | None = None,
103
102
  error_formatter: Callable[[GraphQLError], GraphQLFormattedError] = format_error,
104
103
  execution_context_class: type[ExecutionContext] | None = None,
105
104
  ) -> None:
@@ -107,7 +106,6 @@ class InfrahubGraphQLApp:
107
106
  self.on_get = on_get
108
107
  self.root_value = root_value
109
108
  self.error_formatter = error_formatter
110
- self.middleware = middleware
111
109
  self.execution_context_class = execution_context_class
112
110
  self.logger = get_logger(name="infrahub.graphql")
113
111
  self.permission_checker = permission_checker
@@ -259,7 +257,7 @@ class InfrahubGraphQLApp:
259
257
  source=query,
260
258
  context_value=graphql_params.context,
261
259
  root_value=self.root_value,
262
- middleware=self.middleware,
260
+ middleware=[raise_on_mutation_on_branch_needing_rebase],
263
261
  variable_values=variable_values,
264
262
  operation_name=operation_name,
265
263
  execution_context_class=self.execution_context_class,
@@ -9,12 +9,11 @@ from infrahub.context import InfrahubContext
9
9
  from infrahub.core import registry
10
10
  from infrahub.core.timestamp import Timestamp
11
11
  from infrahub.exceptions import InitializationError
12
+ from infrahub.graphql.registry import registry as graphql_registry
12
13
  from infrahub.graphql.resolvers.many_relationship import ManyRelationshipResolver
13
14
  from infrahub.graphql.resolvers.single_relationship import SingleRelationshipResolver
14
15
  from infrahub.permissions import PermissionManager
15
16
 
16
- from .manager import GraphQLSchemaManager
17
-
18
17
  if TYPE_CHECKING:
19
18
  from graphql import GraphQLSchema
20
19
  from starlette.requests import HTTPConnection
@@ -91,7 +90,7 @@ async def prepare_graphql_params(
91
90
  ) -> GraphqlParams:
92
91
  branch = registry.get_branch_from_registry(branch=branch)
93
92
  schema_branch = registry.schema.get_schema_branch(name=branch.name)
94
- gqlm = GraphQLSchemaManager.get_manager_for_branch(branch=branch, schema_branch=schema_branch)
93
+ gqlm = graphql_registry.get_manager_for_branch(branch=branch, schema_branch=schema_branch)
95
94
  gql_schema = gqlm.get_graphql_schema(
96
95
  include_query=include_query,
97
96
  include_mutation=include_mutation,