infrahub-server 1.1.0b2__py3-none-any.whl → 1.1.2__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 (244) hide show
  1. infrahub/api/artifact.py +8 -15
  2. infrahub/api/dependencies.py +15 -2
  3. infrahub/api/diff/diff.py +2 -2
  4. infrahub/api/file.py +2 -7
  5. infrahub/api/menu.py +9 -7
  6. infrahub/api/oauth2.py +3 -3
  7. infrahub/api/oidc.py +3 -3
  8. infrahub/api/query.py +4 -4
  9. infrahub/api/schema.py +24 -41
  10. infrahub/api/transformation.py +3 -3
  11. infrahub/auth.py +0 -2
  12. infrahub/cli/db.py +9 -11
  13. infrahub/cli/tasks.py +3 -1
  14. infrahub/computed_attribute/constants.py +2 -2
  15. infrahub/computed_attribute/models.py +20 -8
  16. infrahub/computed_attribute/tasks.py +136 -61
  17. infrahub/config.py +18 -4
  18. infrahub/core/account.py +8 -11
  19. infrahub/core/attribute.py +15 -0
  20. infrahub/core/branch/constants.py +2 -0
  21. infrahub/core/branch/models.py +3 -2
  22. infrahub/core/branch/tasks.py +150 -52
  23. infrahub/core/constraint/node/runner.py +11 -10
  24. infrahub/core/diff/calculator.py +4 -3
  25. infrahub/core/diff/combiner.py +1 -0
  26. infrahub/core/diff/coordinator.py +309 -105
  27. infrahub/core/diff/data_check_synchronizer.py +33 -4
  28. infrahub/core/diff/enricher/hierarchy.py +2 -0
  29. infrahub/core/diff/merger/merger.py +1 -1
  30. infrahub/core/diff/model/path.py +101 -3
  31. infrahub/core/diff/query/artifact.py +52 -28
  32. infrahub/core/diff/query/field_specifiers.py +35 -0
  33. infrahub/core/diff/query/roots_metadata.py +48 -0
  34. infrahub/core/diff/query/save.py +1 -0
  35. infrahub/core/diff/query_parser.py +52 -19
  36. infrahub/core/diff/repository/deserializer.py +7 -3
  37. infrahub/core/diff/repository/repository.py +98 -7
  38. infrahub/core/diff/tasks.py +29 -25
  39. infrahub/core/graph/__init__.py +1 -1
  40. infrahub/core/graph/constraints.py +4 -2
  41. infrahub/core/integrity/object_conflict/conflict_recorder.py +6 -1
  42. infrahub/core/manager.py +157 -43
  43. infrahub/core/merge.py +3 -2
  44. infrahub/core/migrations/graph/__init__.py +4 -0
  45. infrahub/core/migrations/graph/m012_convert_account_generic.py +12 -6
  46. infrahub/core/migrations/graph/m013_convert_git_password_credential.py +7 -5
  47. infrahub/core/migrations/graph/m014_remove_index_attr_value.py +1 -1
  48. infrahub/core/migrations/graph/m015_diff_format_update.py +1 -1
  49. infrahub/core/migrations/graph/m016_diff_delete_bug_fix.py +1 -1
  50. infrahub/core/migrations/graph/m017_add_core_profile.py +44 -0
  51. infrahub/core/migrations/graph/m018_uniqueness_nulls.py +101 -0
  52. infrahub/core/migrations/query/attribute_add.py +2 -1
  53. infrahub/core/migrations/query/delete_element_in_schema.py +2 -1
  54. infrahub/core/migrations/query/node_duplicate.py +2 -1
  55. infrahub/core/migrations/query/relationship_duplicate.py +2 -1
  56. infrahub/core/migrations/query/schema_attribute_update.py +1 -1
  57. infrahub/core/migrations/schema/tasks.py +2 -2
  58. infrahub/core/migrations/shared.py +3 -3
  59. infrahub/core/node/__init__.py +34 -7
  60. infrahub/core/node/constraints/grouped_uniqueness.py +9 -2
  61. infrahub/core/query/__init__.py +12 -6
  62. infrahub/core/query/diff.py +23 -17
  63. infrahub/core/query/ipam.py +9 -4
  64. infrahub/core/query/node.py +161 -65
  65. infrahub/core/query/relationship.py +19 -17
  66. infrahub/core/query/resource_manager.py +21 -11
  67. infrahub/core/query/standard_node.py +13 -19
  68. infrahub/core/registry.py +0 -1
  69. infrahub/core/relationship/model.py +17 -5
  70. infrahub/core/schema/definitions/internal.py +1 -1
  71. infrahub/core/schema/generated/attribute_schema.py +2 -2
  72. infrahub/core/schema/generated/base_node_schema.py +2 -2
  73. infrahub/core/schema/generated/relationship_schema.py +1 -1
  74. infrahub/core/schema/schema_branch.py +16 -48
  75. infrahub/core/schema/schema_branch_computed.py +15 -4
  76. infrahub/core/task/task_log.py +1 -1
  77. infrahub/core/utils.py +5 -4
  78. infrahub/core/validators/interface.py +1 -2
  79. infrahub/core/validators/shared.py +2 -2
  80. infrahub/core/validators/tasks.py +8 -7
  81. infrahub/core/validators/uniqueness/index.py +2 -4
  82. infrahub/core/validators/uniqueness/query.py +2 -1
  83. infrahub/database/__init__.py +37 -20
  84. infrahub/database/index.py +1 -1
  85. infrahub/database/neo4j.py +3 -1
  86. infrahub/dependencies/builder/diff/data_check_synchronizer.py +2 -0
  87. infrahub/events/branch_action.py +13 -14
  88. infrahub/events/node_action.py +10 -10
  89. infrahub/events/schema_action.py +6 -5
  90. infrahub/exceptions.py +29 -16
  91. infrahub/generators/tasks.py +2 -1
  92. infrahub/git/base.py +106 -32
  93. infrahub/git/integrator.py +34 -3
  94. infrahub/git/models.py +5 -1
  95. infrahub/git/repository.py +19 -65
  96. infrahub/git/tasks.py +43 -22
  97. infrahub/graphql/app.py +2 -2
  98. infrahub/graphql/auth/query_permission_checker/default_branch_checker.py +2 -17
  99. infrahub/graphql/auth/query_permission_checker/merge_operation_checker.py +1 -12
  100. infrahub/graphql/auth/query_permission_checker/object_permission_checker.py +6 -40
  101. infrahub/graphql/auth/query_permission_checker/super_admin_checker.py +5 -8
  102. infrahub/graphql/enums.py +2 -2
  103. infrahub/graphql/initialization.py +30 -8
  104. infrahub/graphql/loaders/node.py +82 -0
  105. infrahub/graphql/manager.py +10 -4
  106. infrahub/graphql/models.py +6 -0
  107. infrahub/graphql/mutations/account.py +10 -6
  108. infrahub/graphql/mutations/branch.py +0 -2
  109. infrahub/graphql/mutations/computed_attribute.py +14 -23
  110. infrahub/graphql/mutations/diff.py +2 -0
  111. infrahub/graphql/mutations/ipam.py +13 -8
  112. infrahub/graphql/mutations/main.py +83 -8
  113. infrahub/graphql/mutations/proposed_change.py +11 -20
  114. infrahub/graphql/mutations/repository.py +3 -0
  115. infrahub/graphql/mutations/resource_manager.py +3 -0
  116. infrahub/graphql/mutations/schema.py +8 -7
  117. infrahub/graphql/mutations/tasks.py +36 -37
  118. infrahub/graphql/permissions.py +3 -4
  119. infrahub/graphql/queries/account.py +2 -11
  120. infrahub/graphql/queries/task.py +2 -12
  121. infrahub/graphql/query.py +3 -1
  122. infrahub/graphql/{resolver.py → resolvers/resolver.py} +14 -61
  123. infrahub/graphql/resolvers/single_relationship.py +169 -0
  124. infrahub/graphql/schema.py +0 -6
  125. infrahub/graphql/subscription/graphql_query.py +7 -1
  126. infrahub/graphql/types/task.py +14 -2
  127. infrahub/groups/tasks.py +9 -3
  128. infrahub/log.py +4 -0
  129. infrahub/menu/generator.py +6 -18
  130. infrahub/message_bus/messages/check_generator_run.py +1 -0
  131. infrahub/message_bus/messages/event_node_mutated.py +2 -2
  132. infrahub/message_bus/messages/refresh_git_fetch.py +1 -0
  133. infrahub/message_bus/messages/request_repository_userchecks.py +2 -1
  134. infrahub/message_bus/operations/check/artifact.py +3 -1
  135. infrahub/message_bus/operations/check/generator.py +20 -2
  136. infrahub/message_bus/operations/check/repository.py +31 -21
  137. infrahub/message_bus/operations/event/branch.py +2 -4
  138. infrahub/message_bus/operations/git/file.py +1 -3
  139. infrahub/message_bus/operations/git/repository.py +7 -2
  140. infrahub/message_bus/operations/requests/artifact_definition.py +6 -5
  141. infrahub/message_bus/operations/requests/generator_definition.py +10 -6
  142. infrahub/message_bus/operations/requests/proposed_change.py +31 -13
  143. infrahub/message_bus/operations/requests/repository.py +15 -13
  144. infrahub/message_bus/types.py +1 -1
  145. infrahub/permissions/__init__.py +12 -3
  146. infrahub/permissions/backend.py +2 -17
  147. infrahub/permissions/constants.py +12 -8
  148. infrahub/permissions/local_backend.py +5 -102
  149. infrahub/permissions/manager.py +135 -0
  150. infrahub/permissions/report.py +14 -25
  151. infrahub/permissions/types.py +6 -0
  152. infrahub/proposed_change/tasks.py +12 -9
  153. infrahub/server.py +3 -1
  154. infrahub/services/scheduler.py +1 -3
  155. infrahub/task_manager/models.py +34 -5
  156. infrahub/task_manager/task.py +10 -6
  157. infrahub/tasks/dummy.py +3 -0
  158. infrahub/tasks/telemetry.py +26 -24
  159. infrahub/transformations/tasks.py +2 -0
  160. infrahub/visuals.py +1 -3
  161. infrahub/webhook/models.py +6 -2
  162. infrahub/webhook/tasks.py +1 -1
  163. infrahub/workers/infrahub_async.py +14 -0
  164. infrahub/workflows/catalogue.py +24 -5
  165. infrahub/workflows/utils.py +11 -3
  166. infrahub_sdk/_importer.py +13 -4
  167. infrahub_sdk/analyzer.py +9 -7
  168. infrahub_sdk/batch.py +54 -8
  169. infrahub_sdk/branch.py +22 -20
  170. infrahub_sdk/checks.py +14 -41
  171. infrahub_sdk/client.py +769 -575
  172. infrahub_sdk/code_generator.py +21 -17
  173. infrahub_sdk/config.py +15 -13
  174. infrahub_sdk/ctl/branch.py +1 -1
  175. infrahub_sdk/ctl/check.py +15 -11
  176. infrahub_sdk/ctl/cli_commands.py +122 -21
  177. infrahub_sdk/ctl/client.py +18 -16
  178. infrahub_sdk/ctl/config.py +6 -7
  179. infrahub_sdk/ctl/exporter.py +2 -3
  180. infrahub_sdk/ctl/generator.py +11 -8
  181. infrahub_sdk/ctl/render.py +1 -1
  182. infrahub_sdk/ctl/repository.py +9 -7
  183. infrahub_sdk/ctl/schema.py +8 -4
  184. infrahub_sdk/ctl/transform.py +1 -1
  185. infrahub_sdk/ctl/utils.py +13 -9
  186. infrahub_sdk/ctl/validate.py +3 -2
  187. infrahub_sdk/data.py +5 -4
  188. infrahub_sdk/exceptions.py +14 -14
  189. infrahub_sdk/generator.py +9 -10
  190. infrahub_sdk/graphql.py +9 -4
  191. infrahub_sdk/groups.py +1 -3
  192. infrahub_sdk/node.py +188 -152
  193. infrahub_sdk/object_store.py +5 -5
  194. infrahub_sdk/playback.py +11 -7
  195. infrahub_sdk/protocols_base.py +30 -30
  196. infrahub_sdk/pytest_plugin/items/base.py +21 -7
  197. infrahub_sdk/pytest_plugin/items/check.py +11 -7
  198. infrahub_sdk/pytest_plugin/items/graphql_query.py +2 -2
  199. infrahub_sdk/pytest_plugin/items/jinja2_transform.py +5 -5
  200. infrahub_sdk/pytest_plugin/items/python_transform.py +12 -7
  201. infrahub_sdk/pytest_plugin/loader.py +2 -2
  202. infrahub_sdk/pytest_plugin/models.py +10 -10
  203. infrahub_sdk/pytest_plugin/plugin.py +3 -2
  204. infrahub_sdk/pytest_plugin/utils.py +1 -1
  205. infrahub_sdk/queries.py +69 -0
  206. infrahub_sdk/query_groups.py +17 -17
  207. infrahub_sdk/schema/__init__.py +687 -0
  208. infrahub_sdk/schema/main.py +343 -0
  209. infrahub_sdk/schema/repository.py +237 -0
  210. infrahub_sdk/spec/menu.py +2 -2
  211. infrahub_sdk/spec/object.py +14 -10
  212. infrahub_sdk/store.py +21 -23
  213. infrahub_sdk/task_report.py +20 -22
  214. infrahub_sdk/testing/__init__.py +0 -0
  215. infrahub_sdk/testing/docker.py +18 -0
  216. infrahub_sdk/testing/repository.py +107 -0
  217. infrahub_sdk/testing/schemas/__init__.py +0 -0
  218. infrahub_sdk/testing/schemas/animal.py +182 -0
  219. infrahub_sdk/testing/schemas/car_person.py +252 -0
  220. infrahub_sdk/timestamp.py +1 -2
  221. infrahub_sdk/transfer/exporter/interface.py +3 -2
  222. infrahub_sdk/transfer/exporter/json.py +13 -10
  223. infrahub_sdk/transfer/importer/json.py +9 -6
  224. infrahub_sdk/transfer/schema_sorter.py +8 -4
  225. infrahub_sdk/transforms.py +5 -46
  226. infrahub_sdk/types.py +19 -10
  227. infrahub_sdk/utils.py +22 -5
  228. infrahub_sdk/uuidt.py +5 -6
  229. infrahub_sdk/yaml.py +8 -6
  230. {infrahub_server-1.1.0b2.dist-info → infrahub_server-1.1.2.dist-info}/METADATA +3 -2
  231. {infrahub_server-1.1.0b2.dist-info → infrahub_server-1.1.2.dist-info}/RECORD +240 -225
  232. infrahub_testcontainers/__init__.py +6 -0
  233. {infrahub/testing → infrahub_testcontainers}/container.py +28 -22
  234. {infrahub/testing → infrahub_testcontainers}/docker-compose.test.yml +33 -40
  235. {infrahub/testing → infrahub_testcontainers}/helpers.py +31 -26
  236. infrahub/core/diff/query/empty_roots.py +0 -33
  237. infrahub/graphql/mutations/task.py +0 -147
  238. infrahub/testing/schemas/car_person.py +0 -125
  239. infrahub_sdk/schema.py +0 -1080
  240. /infrahub/{testing → graphql/loaders}/__init__.py +0 -0
  241. /infrahub/{testing/schemas → graphql/resolvers}/__init__.py +0 -0
  242. {infrahub_server-1.1.0b2.dist-info → infrahub_server-1.1.2.dist-info}/LICENSE.txt +0 -0
  243. {infrahub_server-1.1.0b2.dist-info → infrahub_server-1.1.2.dist-info}/WHEEL +0 -0
  244. {infrahub_server-1.1.0b2.dist-info → infrahub_server-1.1.2.dist-info}/entry_points.txt +0 -0
@@ -1,7 +1,10 @@
1
+ from __future__ import annotations
2
+
1
3
  from datetime import timedelta
2
4
  from typing import TYPE_CHECKING, Any
3
5
 
4
- from infrahub_sdk.protocols import CoreNode
6
+ import ujson
7
+ from infrahub_sdk.protocols import CoreNode # noqa: TC002
5
8
  from prefect import flow
6
9
  from prefect.automations import AutomationCore
7
10
  from prefect.client.orchestration import get_client
@@ -25,7 +28,6 @@ from infrahub.workflows.catalogue import (
25
28
  TRIGGER_UPDATE_PYTHON_COMPUTED_ATTRIBUTES,
26
29
  UPDATE_COMPUTED_ATTRIBUTE_TRANSFORM,
27
30
  )
28
- from infrahub.workflows.constants import TAG_NAMESPACE, WorkflowTag
29
31
  from infrahub.workflows.utils import add_tags, wait_for_schema_to_converge
30
32
 
31
33
  from .constants import (
@@ -38,7 +40,10 @@ from .constants import (
38
40
  from .models import ComputedAttributeAutomations, PythonTransformComputedAttribute, PythonTransformTarget
39
41
 
40
42
  if TYPE_CHECKING:
43
+ import logging
44
+
41
45
  from infrahub.core.schema.computed_attribute import ComputedAttribute
46
+ from infrahub.services import InfrahubServices
42
47
 
43
48
  UPDATE_ATTRIBUTE = """
44
49
  mutation UpdateAttribute(
@@ -104,10 +109,12 @@ async def process_transform(
104
109
  name=transform.repository.peer.name.value,
105
110
  service=service,
106
111
  repository_kind=transform.repository.peer.typename,
112
+ commit=repo_node.commit.value,
107
113
  )
108
114
 
109
115
  data = await service.client.query_gql_query(
110
116
  name=transform.query.peer.name.value,
117
+ branch_name=branch_name,
111
118
  variables={"id": object_id},
112
119
  update_group=True,
113
120
  subscribers=[object_id],
@@ -170,7 +177,7 @@ async def update_computed_attribute_value_jinja2(
170
177
  log = get_run_logger()
171
178
  service = services.service
172
179
 
173
- await add_tags(branches=[branch_name], nodes=[obj.id], others=[TAG_NAMESPACE, WorkflowTag.DATABASE_CHANGE.render()])
180
+ await add_tags(branches=[branch_name], nodes=[obj.id], db_change=True)
174
181
 
175
182
  macro_definition = MacroDefinition(macro=template_value)
176
183
  my_filter = {}
@@ -221,12 +228,15 @@ async def process_jinja2(
221
228
  object_id: str,
222
229
  computed_attribute_name: str,
223
230
  computed_attribute_kind: str,
224
- updated_fields: list[str] | None = None,
231
+ updated_fields: str | None = None,
225
232
  ) -> None:
226
233
  log = get_run_logger()
227
234
  service = services.service
228
235
 
229
236
  await add_tags(branches=[branch_name])
237
+ updates: list[str] = []
238
+ if isinstance(updated_fields, str):
239
+ updates = ujson.loads(updated_fields)
230
240
 
231
241
  target_branch_schema = (
232
242
  branch_name if branch_name in registry.get_altered_schema_branches() else registry.default_branch
@@ -236,9 +246,7 @@ async def process_jinja2(
236
246
 
237
247
  computed_macros = [
238
248
  attrib
239
- for attrib in schema_branch.computed_attributes.get_impacted_jinja2_targets(
240
- kind=node_kind, updates=updated_fields
241
- )
249
+ for attrib in schema_branch.computed_attributes.get_impacted_jinja2_targets(kind=node_kind, updates=updates)
242
250
  if attrib.kind == computed_attribute_kind and attrib.attribute.name == computed_attribute_name
243
251
  ]
244
252
  for computed_macro in computed_macros:
@@ -363,6 +371,7 @@ async def computed_attribute_setup(branch_name: str | None = None) -> None: # p
363
371
  "object_id": "{{ event.resource['infrahub.node.id'] }}",
364
372
  "computed_attribute_name": computed_attribute.attribute.name,
365
373
  "computed_attribute_kind": computed_attribute.kind,
374
+ "updated_fields": "{{ event.payload['fields'] | tojson }}",
366
375
  },
367
376
  job_variables={},
368
377
  )
@@ -426,6 +435,7 @@ async def computed_attribute_setup(branch_name: str | None = None) -> None: # p
426
435
  "object_id": "{{ event.resource['infrahub.node.id'] }}",
427
436
  "computed_attribute_name": computed_attribute.attribute.name,
428
437
  "computed_attribute_kind": computed_attribute.kind,
438
+ "updated_fields": "{{ event.payload['fields'] | tojson }}",
429
439
  },
430
440
  job_variables={},
431
441
  )
@@ -464,51 +474,20 @@ async def computed_attribute_setup(branch_name: str | None = None) -> None: # p
464
474
  flow_run_name="Setup computed attributes for Python transforms in task-manager",
465
475
  )
466
476
  async def computed_attribute_setup_python(
467
- branch_name: str | None = None, # pylint: disable=unused-argument
477
+ branch_name: str | None = None,
478
+ commit: str | None = None, # pylint: disable=unused-argument
479
+ trigger_updates: bool = True,
468
480
  ) -> None:
469
481
  log = get_run_logger()
470
482
  service = services.service
471
483
 
472
- if branch_name:
473
- await add_tags(branches=[branch_name])
474
-
475
- await wait_for_schema_to_converge(branch_name=registry.default_branch, service=service, log=log)
476
-
477
- schema_branch = registry.schema.get_schema_branch(name=registry.default_branch)
478
-
479
- transform_attributes = schema_branch.computed_attributes.python_attributes_by_transform
480
-
481
- transform_names = list(transform_attributes.keys())
484
+ branch_name = branch_name or registry.default_branch
482
485
 
483
- transforms = await service.client.filters(
484
- kind="CoreTransformPython",
485
- branch=registry.default_branch,
486
- prefetch_relationships=True,
487
- populate_store=True,
488
- name__values=transform_names,
489
- )
486
+ await add_tags(branches=[branch_name])
490
487
 
491
- found_transforms_names = [transform.name.value for transform in transforms]
492
- for transform_name in transform_names:
493
- if transform_name not in found_transforms_names:
494
- log.warning(
495
- msg=f"The transform {transform_name} is assigned to a computed attribute but the transform could not be found in the database."
496
- )
488
+ await wait_for_schema_to_converge(branch_name=branch_name, service=service, log=log)
497
489
 
498
- computed_attributes: list[PythonTransformComputedAttribute] = []
499
- for transform in transforms:
500
- for attribute in transform_attributes[transform.name.value]:
501
- computed_attributes.append(
502
- PythonTransformComputedAttribute(
503
- name=transform.name.value,
504
- repository_id=transform.repository.peer.id,
505
- repository_name=transform.repository.peer.name.value,
506
- repository_kind=transform.repository.peer.typename,
507
- query_name=transform.query.peer.name.value,
508
- query_models=transform.query.peer.models.value,
509
- computed_attribute=attribute,
510
- )
511
- )
490
+ computed_attributes = await _gather_python_transform_attributes(branch_name=branch_name, service=service, log=log)
512
491
 
513
492
  async with get_client(sync_client=False) as client:
514
493
  deployments = {
@@ -531,15 +510,16 @@ async def computed_attribute_setup_python(
531
510
 
532
511
  automations = await client.read_automations()
533
512
  existing_computed_attr_process_automations = ComputedAttributeAutomations.from_prefect(
534
- automations=automations, prefix=PROCESS_PYTHON_AUTOMATION_NAME_PREFIX
513
+ automations=automations, prefix=f"{PROCESS_PYTHON_AUTOMATION_NAME_PREFIX}::{branch_name}::"
535
514
  )
536
515
  existing_computed_attr_query_automations = ComputedAttributeAutomations.from_prefect(
537
- automations=automations, prefix=QUERY_AUTOMATION_NAME_PREFIX
516
+ automations=automations, prefix=f"{QUERY_AUTOMATION_NAME_PREFIX}::{branch_name}::"
538
517
  )
539
518
 
519
+ automations_to_keep = []
540
520
  for computed_attribute in computed_attributes:
541
521
  log.info(f"processing {computed_attribute.computed_attribute.key_name}")
542
- scope = "default"
522
+ scope = branch_name
543
523
 
544
524
  automation = AutomationCore(
545
525
  name=PROCESS_AUTOMATION_NAME.format(
@@ -553,7 +533,12 @@ async def computed_attribute_setup_python(
553
533
  posture=Posture.Reactive,
554
534
  expect={"infrahub.node.*"},
555
535
  within=timedelta(0),
556
- match=ResourceSpecification({"infrahub.node.kind": [computed_attribute.computed_attribute.kind]}),
536
+ match=ResourceSpecification(
537
+ {
538
+ "infrahub.node.kind": [computed_attribute.computed_attribute.kind],
539
+ "infrahub.branch.name": branch_name,
540
+ }
541
+ ),
557
542
  threshold=1,
558
543
  ),
559
544
  actions=[
@@ -580,8 +565,10 @@ async def computed_attribute_setup_python(
580
565
  )
581
566
  await client.update_automation(automation_id=existing.id, automation=automation)
582
567
  log.info(f"Process {computed_attribute.computed_attribute.key_name} Updated")
568
+ automations_to_keep.append(existing.id)
583
569
  else:
584
- await client.create_automation(automation=automation)
570
+ automation_id = await client.create_automation(automation=automation)
571
+ automations_to_keep.append(automation_id)
585
572
  log.info(f"Process {computed_attribute.computed_attribute.key_name} Created")
586
573
 
587
574
  automation = AutomationCore(
@@ -596,7 +583,12 @@ async def computed_attribute_setup_python(
596
583
  posture=Posture.Reactive,
597
584
  expect={"infrahub.node.*"},
598
585
  within=timedelta(0),
599
- match=ResourceSpecification({"infrahub.node.kind": computed_attribute.query_models}),
586
+ match=ResourceSpecification(
587
+ {
588
+ "infrahub.node.kind": computed_attribute.query_models,
589
+ "infrahub.branch.name": branch_name,
590
+ }
591
+ ),
600
592
  threshold=1,
601
593
  ),
602
594
  actions=[
@@ -620,19 +612,53 @@ async def computed_attribute_setup_python(
620
612
  identifier=computed_attribute.computed_attribute.key_name, scope=scope
621
613
  )
622
614
  await client.update_automation(automation_id=existing.id, automation=automation)
615
+ automations_to_keep.append(existing.id)
623
616
  log.info(f"Query {computed_attribute.computed_attribute.key_name} Updated")
624
617
  else:
625
- await client.create_automation(automation=automation)
618
+ automation_id = await client.create_automation(automation=automation)
619
+ automations_to_keep.append(automation_id)
626
620
  log.info(f"Query {computed_attribute.computed_attribute.key_name} Created")
627
621
 
628
- await service.workflow.submit_workflow(
629
- workflow=TRIGGER_UPDATE_PYTHON_COMPUTED_ATTRIBUTES,
630
- parameters={
631
- "branch_name": registry.default_branch,
632
- "computed_attribute_name": computed_attribute.computed_attribute.attribute.name,
633
- "computed_attribute_kind": computed_attribute.computed_attribute.kind,
634
- },
635
- )
622
+ if trigger_updates:
623
+ await service.workflow.submit_workflow(
624
+ workflow=TRIGGER_UPDATE_PYTHON_COMPUTED_ATTRIBUTES,
625
+ parameters={
626
+ "branch_name": branch_name,
627
+ "computed_attribute_name": computed_attribute.computed_attribute.attribute.name,
628
+ "computed_attribute_kind": computed_attribute.computed_attribute.kind,
629
+ },
630
+ )
631
+
632
+ automations_to_remove = existing_computed_attr_process_automations.return_obsolete(keep=automations_to_keep)
633
+ for automation_to_remove in automations_to_remove:
634
+ await client.delete_automation(automation_id=automation_to_remove)
635
+
636
+ automations_to_remove = existing_computed_attr_query_automations.return_obsolete(keep=automations_to_keep)
637
+ for automation_to_remove in automations_to_remove:
638
+ await client.delete_automation(automation_id=automation_to_remove)
639
+
640
+
641
+ @flow(
642
+ name="computed-attribute-remove-python",
643
+ flow_run_name="Remove Python based computed attributes on branch={branch_name}",
644
+ )
645
+ async def computed_attribute_remove_python(
646
+ branch_name: str,
647
+ ) -> None:
648
+ async with get_client(sync_client=False) as client:
649
+ automations = await client.read_automations()
650
+ existing_computed_attr_process_automations = ComputedAttributeAutomations.from_prefect(
651
+ automations=automations, prefix=f"{PROCESS_PYTHON_AUTOMATION_NAME_PREFIX}::{branch_name}::"
652
+ )
653
+ existing_computed_attr_query_automations = ComputedAttributeAutomations.from_prefect(
654
+ automations=automations, prefix=f"{QUERY_AUTOMATION_NAME_PREFIX}::{branch_name}::"
655
+ )
656
+
657
+ for automation_id in existing_computed_attr_process_automations.all_automation_ids:
658
+ await client.delete_automation(automation_id=automation_id)
659
+
660
+ for automation_id in existing_computed_attr_query_automations.all_automation_ids:
661
+ await client.delete_automation(automation_id=automation_id)
636
662
 
637
663
 
638
664
  @flow(
@@ -648,7 +674,7 @@ async def query_transform_targets(
648
674
  service = services.service
649
675
  schema_branch = registry.schema.get_schema_branch(name=branch_name)
650
676
  targets = await service.client.execute_graphql(
651
- query=GATHER_GRAPHQL_QUERY_SUBSCRIBERS, variables={"members": [object_id]}
677
+ query=GATHER_GRAPHQL_QUERY_SUBSCRIBERS, variables={"members": [object_id]}, branch_name=branch_name
652
678
  )
653
679
 
654
680
  subscribers: list[PythonTransformTarget] = []
@@ -675,6 +701,55 @@ async def query_transform_targets(
675
701
  )
676
702
 
677
703
 
704
+ async def _gather_python_transform_attributes(
705
+ branch_name: str, service: InfrahubServices, log: logging.Logger | logging.LoggerAdapter
706
+ ) -> list[PythonTransformComputedAttribute]:
707
+ schema_branch = registry.schema.get_schema_branch(name=branch_name)
708
+ branches_with_diff_from_main = registry.get_altered_schema_branches()
709
+
710
+ transform_attributes = schema_branch.computed_attributes.python_attributes_by_transform
711
+
712
+ transform_names = list(transform_attributes.keys())
713
+ if not transform_names:
714
+ return []
715
+
716
+ transforms = await service.client.filters(
717
+ kind="CoreTransformPython",
718
+ branch=branch_name,
719
+ prefetch_relationships=True,
720
+ populate_store=True,
721
+ name__values=transform_names,
722
+ )
723
+
724
+ found_transforms_names = [transform.name.value for transform in transforms]
725
+ for transform_name in transform_names:
726
+ if transform_name not in found_transforms_names:
727
+ log.warning(
728
+ msg=f"The transform {transform_name} is assigned to a computed attribute but the transform could not be found in the database."
729
+ )
730
+
731
+ repositories = await service.client.get_list_repositories()
732
+ computed_attributes: list[PythonTransformComputedAttribute] = []
733
+ for transform in transforms:
734
+ for attribute in transform_attributes[transform.name.value]:
735
+ python_transform_computed_attribute = PythonTransformComputedAttribute(
736
+ name=transform.name.value,
737
+ repository_id=transform.repository.peer.id,
738
+ repository_name=transform.repository.peer.name.value,
739
+ repository_kind=transform.repository.peer.typename,
740
+ query_name=transform.query.peer.name.value,
741
+ query_models=transform.query.peer.models.value,
742
+ computed_attribute=attribute,
743
+ default_schema=branch_name not in branches_with_diff_from_main,
744
+ )
745
+ python_transform_computed_attribute.populate_branch_commit(
746
+ repository_data=repositories.get(transform.repository.peer.name.value)
747
+ )
748
+ computed_attributes.append(python_transform_computed_attribute)
749
+
750
+ return computed_attributes
751
+
752
+
678
753
  GATHER_GRAPHQL_QUERY_SUBSCRIBERS = """
679
754
  query GatherGraphQLQuerySubscribers($members: [ID!]) {
680
755
  CoreGraphQLQueryGroup(members__ids: $members) {
infrahub/config.py CHANGED
@@ -126,6 +126,14 @@ class WorkflowDriver(str, Enum):
126
126
  WORKER = "worker"
127
127
 
128
128
 
129
+ class ExtraLogLevel(str, Enum):
130
+ CRITICAL = "CRITICAL"
131
+ ERROR = "ERROR"
132
+ WARNING = "WARNING"
133
+ INFO = "INFO"
134
+ DEBUG = "DEBUG"
135
+
136
+
129
137
  class MainSettings(BaseSettings):
130
138
  model_config = SettingsConfigDict(env_prefix="INFRAHUB_")
131
139
  docs_index_path: Path = Field(
@@ -145,6 +153,10 @@ class MainSettings(BaseSettings):
145
153
  default=["infrahub.permissions.LocalPermissionBackend"],
146
154
  description="List of modules to handle permissions, they will be run in the given order",
147
155
  )
156
+ public_url: Optional[str] = Field(
157
+ default=None,
158
+ description="Define the public URL of the Infrahub, might be required for OAuth2 and OIDC depending on your infrastructure.",
159
+ )
148
160
 
149
161
  @field_validator("docs_index_path", mode="before")
150
162
  @classmethod
@@ -248,10 +260,6 @@ class DevelopmentSettings(BaseSettings):
248
260
 
249
261
  model_config = SettingsConfigDict(env_prefix="INFRAHUB_DEV_")
250
262
 
251
- frontend_url: Optional[str] = Field(
252
- default=None,
253
- description="Define the URL of the frontend, useful for OAuth2 development when the frontend and backend use different ports.",
254
- )
255
263
  frontend_redirect_sso: bool = Field(
256
264
  default=False,
257
265
  description="Indicates of the frontend should be responsible for the SSO redirection",
@@ -318,6 +326,12 @@ class WorkflowSettings(BaseSettings):
318
326
  tls_enabled: bool = Field(default=False, description="Indicates if TLS is enabled for the connection")
319
327
  driver: WorkflowDriver = WorkflowDriver.WORKER
320
328
  default_worker_type: str = "infrahubasync"
329
+ extra_loggers: list[str] = Field(
330
+ default_factory=list, description="A list of additional logger that will be captured during task execution."
331
+ )
332
+ extra_log_level: ExtraLogLevel = Field(
333
+ default=ExtraLogLevel.INFO, description="Log level applied to all extra loggers."
334
+ )
321
335
  worker_polling_interval: int = Field(
322
336
  default=2, ge=1, le=30, description="Specify how often the worker should poll the server for tasks (sec)"
323
337
  )
infrahub/core/account.py CHANGED
@@ -6,13 +6,13 @@ from typing import TYPE_CHECKING, Any, Optional, Union
6
6
  from typing_extensions import Self
7
7
 
8
8
  from infrahub.core.constants import InfrahubKind, PermissionDecision
9
- from infrahub.core.query import Query
9
+ from infrahub.core.query import Query, QueryType
10
10
  from infrahub.core.registry import registry
11
11
 
12
12
  if TYPE_CHECKING:
13
13
  from infrahub.core.branch import Branch
14
14
  from infrahub.database import InfrahubDatabase
15
- from infrahub.permissions.constants import AssignedPermissions
15
+ from infrahub.permissions import AssignedPermissions
16
16
 
17
17
 
18
18
  # pylint: disable=redefined-builtin
@@ -35,15 +35,7 @@ class GlobalPermission:
35
35
  if len(parts) != 3 and parts[0] != "global":
36
36
  raise ValueError(f"{input} is not a valid format for a Global permission")
37
37
 
38
- # FIXME there is probably a better way to convert the decision
39
- decision = PermissionDecision.DENY
40
- if parts[2] == "allow_default":
41
- decision = PermissionDecision.ALLOW_DEFAULT
42
- elif parts[2] == "allow_all":
43
- decision = PermissionDecision.ALLOW_ALL
44
- elif parts[2] == "allow_other":
45
- decision = PermissionDecision.ALLOW_OTHER
46
- return cls(action=str(parts[1]), decision=decision)
38
+ return cls(action=parts[1], decision=PermissionDecision[parts[2].upper()])
47
39
 
48
40
 
49
41
  @dataclass
@@ -62,6 +54,7 @@ class ObjectPermission:
62
54
 
63
55
  class AccountGlobalPermissionQuery(Query):
64
56
  name: str = "account_global_permissions"
57
+ type: QueryType = QueryType.READ
65
58
 
66
59
  def __init__(self, account_id: str, **kwargs: Any):
67
60
  self.account_id = account_id
@@ -176,6 +169,7 @@ class AccountGlobalPermissionQuery(Query):
176
169
 
177
170
  class AccountObjectPermissionQuery(Query):
178
171
  name: str = "account_object_permissions"
172
+ type: QueryType = QueryType.READ
179
173
 
180
174
  def __init__(self, account_id: str, **kwargs: Any):
181
175
  self.account_id = account_id
@@ -327,6 +321,7 @@ async def fetch_permissions(account_id: str, db: InfrahubDatabase, branch: Branc
327
321
 
328
322
  class AccountRoleGlobalPermissionQuery(Query):
329
323
  name: str = "account_role_global_permissions"
324
+ type: QueryType = QueryType.READ
330
325
 
331
326
  def __init__(self, role_id: str, **kwargs: Any):
332
327
  self.role_id = role_id
@@ -416,6 +411,7 @@ class AccountRoleGlobalPermissionQuery(Query):
416
411
 
417
412
  class AccountRoleObjectPermissionQuery(Query):
418
413
  name: str = "account_role_object_permissions"
414
+ type: QueryType = QueryType.READ
419
415
 
420
416
  def __init__(self, role_id: str, **kwargs: Any):
421
417
  self.role_id = role_id
@@ -542,6 +538,7 @@ async def fetch_role_permissions(role_id: str, db: InfrahubDatabase, branch: Bra
542
538
 
543
539
  class AccountTokenValidateQuery(Query):
544
540
  name: str = "account_token_validate"
541
+ type: QueryType = QueryType.READ
545
542
 
546
543
  def __init__(self, token: str, **kwargs: Any):
547
544
  self.token = token
@@ -666,6 +666,21 @@ class Integer(BaseAttribute):
666
666
  value: int
667
667
  from_pool: Optional[str] = None
668
668
 
669
+ @classmethod
670
+ def validate_format(cls, value: Any, name: str, schema: AttributeSchema) -> None:
671
+ """
672
+ Make sure boolean objects are not accepted as value. Need to override `validate_format`
673
+ as `isinstance(True, int)` is True.
674
+ """
675
+
676
+ value_to_check = value
677
+ if schema.enum and isinstance(value, Enum):
678
+ value_to_check = value.value
679
+
680
+ # Note that we might want to do this check directly in parent function.
681
+ if value_to_check.__class__ != cls.type:
682
+ raise ValidationError({name: f"{value} is not a valid {schema.kind}"})
683
+
669
684
 
670
685
  class IntegerOptional(Integer):
671
686
  value: Optional[int]
@@ -0,0 +1,2 @@
1
+ AUTOMATION_NAME_CREATE = "Trigger-branch-create-events"
2
+ AUTOMATION_NAME_REMOVE = "Trigger-branch-remove-events"
@@ -8,8 +8,9 @@ from pydantic import Field, field_validator
8
8
  from infrahub.core.constants import (
9
9
  GLOBAL_BRANCH_NAME,
10
10
  )
11
- from infrahub.core.models import SchemaBranchHash # noqa: TCH001
11
+ from infrahub.core.models import SchemaBranchHash # noqa: TC001
12
12
  from infrahub.core.node.standard import StandardNode
13
+ from infrahub.core.query import QueryType
13
14
  from infrahub.core.query.branch import (
14
15
  DeleteBranchRelationshipsQuery,
15
16
  GetAllBranchInternalRelationshipQuery,
@@ -139,7 +140,7 @@ class Branch(StandardNode): # pylint: disable=too-many-public-methods
139
140
 
140
141
  params = {"name": name}
141
142
 
142
- results = await db.execute_query(query=query, params=params, name="branch_get_by_name")
143
+ results = await db.execute_query(query=query, params=params, name="branch_get_by_name", type=QueryType.READ)
143
144
 
144
145
  if len(results) == 0:
145
146
  raise BranchNotFoundError(identifier=name)