infrahub-server 1.2.0b1__py3-none-any.whl → 1.2.1__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 (297) hide show
  1. infrahub/api/dependencies.py +6 -6
  2. infrahub/api/diff/validation_models.py +7 -7
  3. infrahub/api/schema.py +1 -1
  4. infrahub/artifacts/models.py +1 -3
  5. infrahub/artifacts/tasks.py +1 -3
  6. infrahub/cli/__init__.py +13 -9
  7. infrahub/cli/constants.py +3 -0
  8. infrahub/cli/db.py +165 -183
  9. infrahub/cli/upgrade.py +146 -0
  10. infrahub/computed_attribute/gather.py +185 -0
  11. infrahub/computed_attribute/models.py +239 -11
  12. infrahub/computed_attribute/tasks.py +77 -442
  13. infrahub/computed_attribute/triggers.py +11 -45
  14. infrahub/config.py +43 -32
  15. infrahub/context.py +14 -0
  16. infrahub/core/account.py +4 -4
  17. infrahub/core/attribute.py +57 -57
  18. infrahub/core/branch/tasks.py +12 -9
  19. infrahub/core/changelog/diff.py +16 -8
  20. infrahub/core/changelog/models.py +189 -26
  21. infrahub/core/constants/__init__.py +5 -1
  22. infrahub/core/constants/infrahubkind.py +2 -0
  23. infrahub/core/constraint/node/runner.py +9 -8
  24. infrahub/core/diff/branch_differ.py +10 -10
  25. infrahub/core/diff/ipam_diff_parser.py +4 -5
  26. infrahub/core/diff/model/diff.py +27 -27
  27. infrahub/core/diff/model/path.py +3 -3
  28. infrahub/core/diff/query/merge.py +20 -17
  29. infrahub/core/diff/query_parser.py +4 -4
  30. infrahub/core/graph/__init__.py +1 -1
  31. infrahub/core/initialization.py +1 -10
  32. infrahub/core/ipam/constants.py +3 -4
  33. infrahub/core/ipam/reconciler.py +12 -12
  34. infrahub/core/ipam/utilization.py +10 -13
  35. infrahub/core/manager.py +34 -34
  36. infrahub/core/merge.py +7 -7
  37. infrahub/core/migrations/__init__.py +2 -3
  38. infrahub/core/migrations/graph/__init__.py +9 -4
  39. infrahub/core/migrations/graph/m017_add_core_profile.py +1 -5
  40. infrahub/core/migrations/graph/m018_uniqueness_nulls.py +4 -4
  41. infrahub/core/migrations/graph/m020_duplicate_edges.py +160 -0
  42. infrahub/core/migrations/graph/m021_missing_hierarchy_merge.py +51 -0
  43. infrahub/core/migrations/graph/{m020_add_generate_template_attr.py → m022_add_generate_template_attr.py} +3 -3
  44. infrahub/core/migrations/graph/m023_deduplicate_cardinality_one_relationships.py +96 -0
  45. infrahub/core/migrations/query/attribute_add.py +2 -2
  46. infrahub/core/migrations/query/node_duplicate.py +18 -21
  47. infrahub/core/migrations/query/schema_attribute_update.py +2 -2
  48. infrahub/core/migrations/schema/models.py +19 -4
  49. infrahub/core/migrations/schema/tasks.py +2 -2
  50. infrahub/core/migrations/shared.py +16 -16
  51. infrahub/core/models.py +15 -6
  52. infrahub/core/node/__init__.py +29 -28
  53. infrahub/core/node/base.py +2 -4
  54. infrahub/core/node/constraints/attribute_uniqueness.py +2 -2
  55. infrahub/core/node/constraints/grouped_uniqueness.py +99 -47
  56. infrahub/core/node/constraints/interface.py +1 -2
  57. infrahub/core/node/delete_validator.py +3 -5
  58. infrahub/core/node/ipam.py +4 -4
  59. infrahub/core/node/permissions.py +7 -7
  60. infrahub/core/node/resource_manager/ip_address_pool.py +6 -6
  61. infrahub/core/node/resource_manager/ip_prefix_pool.py +6 -6
  62. infrahub/core/node/resource_manager/number_pool.py +3 -3
  63. infrahub/core/path.py +12 -12
  64. infrahub/core/property.py +11 -11
  65. infrahub/core/protocols.py +5 -0
  66. infrahub/core/protocols_base.py +21 -21
  67. infrahub/core/query/__init__.py +33 -33
  68. infrahub/core/query/attribute.py +6 -4
  69. infrahub/core/query/diff.py +3 -3
  70. infrahub/core/query/node.py +82 -32
  71. infrahub/core/query/relationship.py +24 -24
  72. infrahub/core/query/resource_manager.py +2 -0
  73. infrahub/core/query/standard_node.py +3 -3
  74. infrahub/core/query/subquery.py +9 -9
  75. infrahub/core/registry.py +13 -15
  76. infrahub/core/relationship/constraints/count.py +3 -4
  77. infrahub/core/relationship/constraints/peer_kind.py +3 -4
  78. infrahub/core/relationship/constraints/profiles_kind.py +2 -2
  79. infrahub/core/relationship/model.py +40 -46
  80. infrahub/core/schema/attribute_schema.py +9 -9
  81. infrahub/core/schema/basenode_schema.py +93 -44
  82. infrahub/core/schema/computed_attribute.py +3 -3
  83. infrahub/core/schema/definitions/core/__init__.py +13 -19
  84. infrahub/core/schema/definitions/core/account.py +151 -148
  85. infrahub/core/schema/definitions/core/artifact.py +122 -113
  86. infrahub/core/schema/definitions/core/builtin.py +19 -16
  87. infrahub/core/schema/definitions/core/check.py +61 -53
  88. infrahub/core/schema/definitions/core/core.py +17 -0
  89. infrahub/core/schema/definitions/core/generator.py +89 -85
  90. infrahub/core/schema/definitions/core/graphql_query.py +72 -70
  91. infrahub/core/schema/definitions/core/group.py +96 -93
  92. infrahub/core/schema/definitions/core/ipam.py +176 -235
  93. infrahub/core/schema/definitions/core/lineage.py +18 -16
  94. infrahub/core/schema/definitions/core/menu.py +42 -40
  95. infrahub/core/schema/definitions/core/permission.py +144 -142
  96. infrahub/core/schema/definitions/core/profile.py +16 -27
  97. infrahub/core/schema/definitions/core/propose_change.py +88 -79
  98. infrahub/core/schema/definitions/core/propose_change_comment.py +170 -165
  99. infrahub/core/schema/definitions/core/propose_change_validator.py +290 -288
  100. infrahub/core/schema/definitions/core/repository.py +231 -225
  101. infrahub/core/schema/definitions/core/resource_pool.py +156 -166
  102. infrahub/core/schema/definitions/core/template.py +27 -12
  103. infrahub/core/schema/definitions/core/transform.py +85 -76
  104. infrahub/core/schema/definitions/core/webhook.py +127 -101
  105. infrahub/core/schema/definitions/internal.py +16 -16
  106. infrahub/core/schema/dropdown.py +3 -4
  107. infrahub/core/schema/generated/attribute_schema.py +15 -18
  108. infrahub/core/schema/generated/base_node_schema.py +12 -14
  109. infrahub/core/schema/generated/node_schema.py +3 -5
  110. infrahub/core/schema/generated/relationship_schema.py +9 -11
  111. infrahub/core/schema/generic_schema.py +2 -2
  112. infrahub/core/schema/manager.py +20 -9
  113. infrahub/core/schema/node_schema.py +4 -2
  114. infrahub/core/schema/relationship_schema.py +7 -7
  115. infrahub/core/schema/schema_branch.py +276 -138
  116. infrahub/core/schema/schema_branch_computed.py +41 -4
  117. infrahub/core/task/task.py +3 -3
  118. infrahub/core/task/user_task.py +15 -15
  119. infrahub/core/utils.py +20 -18
  120. infrahub/core/validators/__init__.py +1 -3
  121. infrahub/core/validators/aggregated_checker.py +2 -2
  122. infrahub/core/validators/attribute/choices.py +2 -2
  123. infrahub/core/validators/attribute/enum.py +2 -2
  124. infrahub/core/validators/attribute/kind.py +2 -2
  125. infrahub/core/validators/attribute/length.py +2 -2
  126. infrahub/core/validators/attribute/optional.py +2 -2
  127. infrahub/core/validators/attribute/regex.py +2 -2
  128. infrahub/core/validators/attribute/unique.py +2 -2
  129. infrahub/core/validators/checks_runner.py +25 -2
  130. infrahub/core/validators/determiner.py +1 -3
  131. infrahub/core/validators/interface.py +6 -2
  132. infrahub/core/validators/model.py +22 -3
  133. infrahub/core/validators/models/validate_migration.py +17 -4
  134. infrahub/core/validators/node/attribute.py +2 -2
  135. infrahub/core/validators/node/generate_profile.py +2 -2
  136. infrahub/core/validators/node/hierarchy.py +3 -5
  137. infrahub/core/validators/node/inherit_from.py +27 -5
  138. infrahub/core/validators/node/relationship.py +2 -2
  139. infrahub/core/validators/relationship/count.py +4 -4
  140. infrahub/core/validators/relationship/optional.py +2 -2
  141. infrahub/core/validators/relationship/peer.py +2 -2
  142. infrahub/core/validators/shared.py +2 -2
  143. infrahub/core/validators/tasks.py +8 -0
  144. infrahub/core/validators/uniqueness/checker.py +22 -21
  145. infrahub/core/validators/uniqueness/index.py +2 -2
  146. infrahub/core/validators/uniqueness/model.py +11 -11
  147. infrahub/database/__init__.py +26 -22
  148. infrahub/database/metrics.py +7 -1
  149. infrahub/dependencies/builder/constraint/grouped/node_runner.py +1 -3
  150. infrahub/dependencies/component/registry.py +2 -2
  151. infrahub/events/__init__.py +25 -2
  152. infrahub/events/artifact_action.py +13 -25
  153. infrahub/events/branch_action.py +26 -18
  154. infrahub/events/generator.py +71 -0
  155. infrahub/events/group_action.py +10 -24
  156. infrahub/events/models.py +10 -16
  157. infrahub/events/node_action.py +87 -32
  158. infrahub/events/repository_action.py +5 -18
  159. infrahub/events/schema_action.py +4 -9
  160. infrahub/events/utils.py +16 -0
  161. infrahub/events/validator_action.py +55 -0
  162. infrahub/exceptions.py +23 -24
  163. infrahub/generators/models.py +1 -3
  164. infrahub/git/base.py +7 -7
  165. infrahub/git/integrator.py +26 -25
  166. infrahub/git/models.py +22 -9
  167. infrahub/git/repository.py +3 -3
  168. infrahub/git/tasks.py +67 -49
  169. infrahub/git/utils.py +48 -0
  170. infrahub/git/worktree.py +1 -2
  171. infrahub/git_credential/askpass.py +1 -2
  172. infrahub/graphql/analyzer.py +12 -0
  173. infrahub/graphql/app.py +13 -15
  174. infrahub/graphql/context.py +6 -0
  175. infrahub/graphql/initialization.py +3 -0
  176. infrahub/graphql/loaders/node.py +2 -12
  177. infrahub/graphql/loaders/peers.py +77 -0
  178. infrahub/graphql/loaders/shared.py +13 -0
  179. infrahub/graphql/manager.py +13 -10
  180. infrahub/graphql/mutations/artifact_definition.py +5 -5
  181. infrahub/graphql/mutations/computed_attribute.py +4 -5
  182. infrahub/graphql/mutations/graphql_query.py +5 -5
  183. infrahub/graphql/mutations/ipam.py +50 -70
  184. infrahub/graphql/mutations/main.py +164 -141
  185. infrahub/graphql/mutations/menu.py +5 -5
  186. infrahub/graphql/mutations/models.py +2 -4
  187. infrahub/graphql/mutations/node_getter/by_default_filter.py +10 -10
  188. infrahub/graphql/mutations/node_getter/by_hfid.py +1 -3
  189. infrahub/graphql/mutations/node_getter/by_id.py +1 -3
  190. infrahub/graphql/mutations/node_getter/interface.py +1 -2
  191. infrahub/graphql/mutations/proposed_change.py +7 -7
  192. infrahub/graphql/mutations/relationship.py +67 -35
  193. infrahub/graphql/mutations/repository.py +8 -8
  194. infrahub/graphql/mutations/resource_manager.py +3 -3
  195. infrahub/graphql/mutations/schema.py +4 -4
  196. infrahub/graphql/mutations/webhook.py +137 -0
  197. infrahub/graphql/parser.py +4 -4
  198. infrahub/graphql/queries/diff/tree.py +4 -4
  199. infrahub/graphql/queries/ipam.py +2 -2
  200. infrahub/graphql/queries/relationship.py +2 -2
  201. infrahub/graphql/queries/search.py +2 -2
  202. infrahub/graphql/resolvers/many_relationship.py +264 -0
  203. infrahub/graphql/resolvers/resolver.py +13 -110
  204. infrahub/graphql/subscription/graphql_query.py +2 -0
  205. infrahub/graphql/types/event.py +20 -11
  206. infrahub/graphql/types/node.py +2 -2
  207. infrahub/graphql/utils.py +2 -2
  208. infrahub/groups/ancestors.py +29 -0
  209. infrahub/groups/parsers.py +107 -0
  210. infrahub/menu/generator.py +7 -7
  211. infrahub/menu/menu.py +0 -10
  212. infrahub/menu/models.py +117 -16
  213. infrahub/menu/repository.py +111 -0
  214. infrahub/menu/utils.py +5 -8
  215. infrahub/message_bus/messages/__init__.py +1 -11
  216. infrahub/message_bus/messages/check_generator_run.py +2 -0
  217. infrahub/message_bus/messages/finalize_validator_execution.py +3 -0
  218. infrahub/message_bus/messages/request_generatordefinition_check.py +2 -0
  219. infrahub/message_bus/operations/__init__.py +0 -2
  220. infrahub/message_bus/operations/check/generator.py +1 -0
  221. infrahub/message_bus/operations/event/__init__.py +2 -2
  222. infrahub/message_bus/operations/finalize/validator.py +51 -1
  223. infrahub/message_bus/operations/requests/generator_definition.py +19 -19
  224. infrahub/message_bus/operations/requests/proposed_change.py +3 -1
  225. infrahub/pools/number.py +2 -4
  226. infrahub/proposed_change/tasks.py +37 -28
  227. infrahub/pytest_plugin.py +13 -10
  228. infrahub/server.py +1 -2
  229. infrahub/services/adapters/event/__init__.py +1 -1
  230. infrahub/task_manager/event.py +23 -9
  231. infrahub/tasks/artifact.py +2 -4
  232. infrahub/telemetry/__init__.py +0 -0
  233. infrahub/telemetry/constants.py +9 -0
  234. infrahub/telemetry/database.py +86 -0
  235. infrahub/telemetry/models.py +65 -0
  236. infrahub/telemetry/task_manager.py +77 -0
  237. infrahub/{tasks/telemetry.py → telemetry/tasks.py} +49 -56
  238. infrahub/telemetry/utils.py +11 -0
  239. infrahub/trace.py +4 -4
  240. infrahub/transformations/tasks.py +2 -2
  241. infrahub/trigger/catalogue.py +2 -5
  242. infrahub/trigger/constants.py +0 -8
  243. infrahub/trigger/models.py +14 -1
  244. infrahub/trigger/setup.py +90 -0
  245. infrahub/trigger/tasks.py +35 -90
  246. infrahub/utils.py +11 -1
  247. infrahub/validators/__init__.py +0 -0
  248. infrahub/validators/events.py +42 -0
  249. infrahub/validators/tasks.py +41 -0
  250. infrahub/webhook/gather.py +17 -0
  251. infrahub/webhook/models.py +22 -5
  252. infrahub/webhook/tasks.py +44 -19
  253. infrahub/webhook/triggers.py +22 -5
  254. infrahub/workers/infrahub_async.py +2 -2
  255. infrahub/workers/utils.py +2 -2
  256. infrahub/workflows/catalogue.py +28 -20
  257. infrahub/workflows/initialization.py +1 -3
  258. infrahub/workflows/models.py +1 -1
  259. infrahub/workflows/utils.py +10 -1
  260. infrahub_sdk/client.py +27 -8
  261. infrahub_sdk/config.py +3 -0
  262. infrahub_sdk/context.py +13 -0
  263. infrahub_sdk/exceptions.py +6 -0
  264. infrahub_sdk/generator.py +4 -1
  265. infrahub_sdk/graphql.py +45 -13
  266. infrahub_sdk/node.py +69 -20
  267. infrahub_sdk/protocols_base.py +32 -11
  268. infrahub_sdk/query_groups.py +6 -35
  269. infrahub_sdk/schema/__init__.py +55 -26
  270. infrahub_sdk/schema/main.py +8 -0
  271. infrahub_sdk/task/__init__.py +10 -0
  272. infrahub_sdk/task/manager.py +12 -6
  273. infrahub_sdk/testing/schemas/animal.py +9 -0
  274. infrahub_sdk/timestamp.py +12 -4
  275. {infrahub_server-1.2.0b1.dist-info → infrahub_server-1.2.1.dist-info}/METADATA +3 -2
  276. {infrahub_server-1.2.0b1.dist-info → infrahub_server-1.2.1.dist-info}/RECORD +289 -260
  277. {infrahub_server-1.2.0b1.dist-info → infrahub_server-1.2.1.dist-info}/entry_points.txt +1 -0
  278. infrahub_testcontainers/constants.py +2 -0
  279. infrahub_testcontainers/container.py +157 -12
  280. infrahub_testcontainers/docker-compose.test.yml +31 -6
  281. infrahub_testcontainers/helpers.py +18 -73
  282. infrahub_testcontainers/host.py +41 -0
  283. infrahub_testcontainers/measurements.py +93 -0
  284. infrahub_testcontainers/models.py +38 -0
  285. infrahub_testcontainers/performance_test.py +166 -0
  286. infrahub_testcontainers/plugin.py +136 -0
  287. infrahub_testcontainers/prometheus.yml +30 -0
  288. infrahub/message_bus/messages/event_branch_create.py +0 -11
  289. infrahub/message_bus/messages/event_branch_delete.py +0 -11
  290. infrahub/message_bus/messages/event_branch_rebased.py +0 -9
  291. infrahub/message_bus/messages/event_node_mutated.py +0 -15
  292. infrahub/message_bus/messages/event_schema_update.py +0 -9
  293. infrahub/message_bus/operations/event/node.py +0 -20
  294. infrahub/message_bus/operations/event/schema.py +0 -17
  295. infrahub/webhook/constants.py +0 -1
  296. {infrahub_server-1.2.0b1.dist-info → infrahub_server-1.2.1.dist-info}/LICENSE.txt +0 -0
  297. {infrahub_server-1.2.0b1.dist-info → infrahub_server-1.2.1.dist-info}/WHEEL +0 -0
infrahub/git/tasks.py CHANGED
@@ -1,5 +1,12 @@
1
1
  from infrahub_sdk import InfrahubClient
2
- from infrahub_sdk.protocols import CoreArtifact, CoreArtifactDefinition, CoreCheckDefinition, CoreRepository
2
+ from infrahub_sdk.protocols import (
3
+ CoreArtifact,
4
+ CoreArtifactDefinition,
5
+ CoreCheckDefinition,
6
+ CoreRepository,
7
+ CoreRepositoryValidator,
8
+ CoreUserValidator,
9
+ )
3
10
  from infrahub_sdk.uuidt import UUIDT
4
11
  from prefect import flow, task
5
12
  from prefect.cache_policies import NONE
@@ -7,11 +14,12 @@ from prefect.logging import get_run_logger
7
14
 
8
15
  from infrahub import lock
9
16
  from infrahub.context import InfrahubContext
10
- from infrahub.core.constants import InfrahubKind, RepositoryInternalStatus, ValidatorConclusion, ValidatorState
17
+ from infrahub.core.constants import InfrahubKind, RepositoryInternalStatus, ValidatorConclusion
11
18
  from infrahub.core.registry import registry
12
19
  from infrahub.exceptions import CheckError, RepositoryError
13
20
  from infrahub.message_bus import Meta, messages
14
21
  from infrahub.services import InfrahubServices
22
+ from infrahub.validators.tasks import start_validator
15
23
  from infrahub.worker import WORKER_IDENTITY
16
24
 
17
25
  from ..core.manager import NodeManager
@@ -509,7 +517,9 @@ async def git_repository_diff_names_only(
509
517
  name="git-repository-user-checks-definition-trigger",
510
518
  flow_run_name="Trigger user defined checks for repository {model.repository_name}",
511
519
  )
512
- async def trigger_repository_user_checks_definitions(model: UserCheckDefinitionData, service: InfrahubServices) -> None:
520
+ async def trigger_repository_user_checks_definitions(
521
+ model: UserCheckDefinitionData, context: InfrahubContext, service: InfrahubServices
522
+ ) -> None:
513
523
  await add_tags(branches=[model.branch_name], nodes=[model.proposed_change])
514
524
  log = get_run_logger()
515
525
 
@@ -520,8 +530,8 @@ async def trigger_repository_user_checks_definitions(model: UserCheckDefinitionD
520
530
  validator_execution_id = str(UUIDT())
521
531
  check_execution_ids: list[str] = []
522
532
  await proposed_change.validations.fetch()
523
- validator = None
524
533
 
534
+ previous_validator: CoreUserValidator | None = None
525
535
  for relationship in proposed_change.validations.peers:
526
536
  existing_validator = relationship.peer
527
537
 
@@ -530,26 +540,21 @@ async def trigger_repository_user_checks_definitions(model: UserCheckDefinitionD
530
540
  and existing_validator.repository.id == model.repository_id
531
541
  and existing_validator.check_definition.id == model.check_definition_id
532
542
  ):
533
- validator = existing_validator
534
- service.log.info("Found the same validator", validator=validator)
535
-
536
- if validator:
537
- validator.conclusion.value = ValidatorConclusion.UNKNOWN.value
538
- validator.state.value = ValidatorState.QUEUED.value
539
- validator.started_at.value = ""
540
- validator.completed_at.value = ""
541
- await validator.save()
542
- else:
543
- validator = await service.client.create(
544
- kind=InfrahubKind.USERVALIDATOR,
545
- data={
546
- "label": f"Check: {definition.name.value}",
547
- "proposed_change": model.proposed_change,
548
- "repository": model.repository_id,
549
- "check_definition": model.check_definition_id,
550
- },
551
- )
552
- await validator.save()
543
+ previous_validator = existing_validator
544
+ service.log.info("Found the same validator", validator=previous_validator)
545
+
546
+ validator = await start_validator(
547
+ service=service,
548
+ validator=previous_validator,
549
+ validator_type=CoreUserValidator,
550
+ proposed_change=model.proposed_change,
551
+ data={
552
+ "label": f"Check: {definition.name.value}",
553
+ "repository": model.repository_id,
554
+ "check_definition": model.check_definition_id,
555
+ },
556
+ context=context,
557
+ )
553
558
 
554
559
  if definition.targets.id:
555
560
  # Check against a group of targets
@@ -612,14 +617,22 @@ async def trigger_repository_user_checks_definitions(model: UserCheckDefinitionD
612
617
  for model in check_models
613
618
  ]
614
619
 
615
- await run_checks_and_update_validator(checks_coroutines, validator)
620
+ await run_checks_and_update_validator(
621
+ checks=checks_coroutines,
622
+ validator=validator,
623
+ context=context,
624
+ service=service,
625
+ proposed_change_id=model.proposed_change,
626
+ )
616
627
 
617
628
 
618
629
  @flow(
619
630
  name="git-repository-trigger-user-checks",
620
631
  flow_run_name="Evaluating user-defined checks on repository {model.repository_name}",
621
632
  )
622
- async def trigger_user_checks(model: TriggerRepositoryUserChecks, service: InfrahubServices) -> None:
633
+ async def trigger_user_checks(
634
+ model: TriggerRepositoryUserChecks, service: InfrahubServices, context: InfrahubContext
635
+ ) -> None:
623
636
  """Request to start validation checks on a specific repository for User-defined checks."""
624
637
 
625
638
  await add_tags(branches=[model.source_branch], nodes=[model.proposed_change])
@@ -645,7 +658,9 @@ async def trigger_user_checks(model: TriggerRepositoryUserChecks, service: Infra
645
658
  branch_diff=model.branch_diff,
646
659
  )
647
660
  await service.workflow.submit_workflow(
648
- workflow=GIT_REPOSITORY_USER_CHECKS_DEFINITIONS_TRIGGER, parameters={"model": user_check_definition_model}
661
+ workflow=GIT_REPOSITORY_USER_CHECKS_DEFINITIONS_TRIGGER,
662
+ context=context,
663
+ parameters={"model": user_check_definition_model},
649
664
  )
650
665
 
651
666
 
@@ -653,7 +668,9 @@ async def trigger_user_checks(model: TriggerRepositoryUserChecks, service: Infra
653
668
  name="git-repository-trigger-internal-checks",
654
669
  flow_run_name="Running repository checks for repository {model.repository}",
655
670
  )
656
- async def trigger_internal_checks(model: TriggerRepositoryInternalChecks, service: InfrahubServices) -> None:
671
+ async def trigger_internal_checks(
672
+ model: TriggerRepositoryInternalChecks, service: InfrahubServices, context: InfrahubContext
673
+ ) -> None:
657
674
  """Request to start validation checks on a specific repository."""
658
675
  await add_tags(branches=[model.source_branch], nodes=[model.proposed_change])
659
676
  log = get_run_logger()
@@ -669,7 +686,7 @@ async def trigger_internal_checks(model: TriggerRepositoryInternalChecks, servic
669
686
  await repository.checks.fetch()
670
687
 
671
688
  validator_name = f"Repository Validator: {repository.name.value}"
672
- validator = None
689
+ previous_validator: CoreRepositoryValidator | None = None
673
690
  for relationship in proposed_change.validations.peers:
674
691
  existing_validator = relationship.peer
675
692
 
@@ -677,24 +694,19 @@ async def trigger_internal_checks(model: TriggerRepositoryInternalChecks, servic
677
694
  existing_validator.typename == InfrahubKind.REPOSITORYVALIDATOR
678
695
  and existing_validator.repository.id == model.repository
679
696
  ):
680
- validator = existing_validator
681
-
682
- if validator:
683
- validator.conclusion.value = ValidatorConclusion.UNKNOWN.value
684
- validator.state.value = ValidatorState.QUEUED.value
685
- validator.started_at.value = ""
686
- validator.completed_at.value = ""
687
- await validator.save()
688
- else:
689
- validator = await service.client.create(
690
- kind=InfrahubKind.REPOSITORYVALIDATOR,
691
- data={
692
- "label": validator_name,
693
- "proposed_change": model.proposed_change,
694
- "repository": model.repository,
695
- },
696
- )
697
- await validator.save()
697
+ previous_validator = existing_validator
698
+
699
+ validator = await start_validator(
700
+ service=service,
701
+ validator=previous_validator,
702
+ validator_type=CoreRepositoryValidator,
703
+ proposed_change=model.proposed_change,
704
+ data={
705
+ "label": validator_name,
706
+ "repository": model.repository,
707
+ },
708
+ context=context,
709
+ )
698
710
 
699
711
  check_execution_id = str(UUIDT())
700
712
  check_execution_ids.append(check_execution_id)
@@ -718,7 +730,13 @@ async def trigger_internal_checks(model: TriggerRepositoryInternalChecks, servic
718
730
  expected_return=ValidatorConclusion,
719
731
  )
720
732
 
721
- await run_checks_and_update_validator(checks=[check_coroutine], validator=validator)
733
+ await run_checks_and_update_validator(
734
+ checks=[check_coroutine],
735
+ validator=validator,
736
+ context=context,
737
+ service=service,
738
+ proposed_change_id=model.proposed_change,
739
+ )
722
740
 
723
741
 
724
742
  @flow(
@@ -822,7 +840,7 @@ async def run_user_check(model: UserCheckData, service: InfrahubServices) -> Val
822
840
  client=service.client,
823
841
  commit=model.commit,
824
842
  params=model.variables,
825
- )
843
+ ) # type: ignore[misc]
826
844
  if check_run.passed:
827
845
  conclusion = ValidatorConclusion.SUCCESS
828
846
  severity = "info"
infrahub/git/utils.py ADDED
@@ -0,0 +1,48 @@
1
+ from typing import TYPE_CHECKING
2
+
3
+ from infrahub.core import registry
4
+ from infrahub.core.constants import InfrahubKind
5
+ from infrahub.core.manager import NodeManager
6
+ from infrahub.database import InfrahubDatabase
7
+
8
+ from .models import RepositoryBranchInfo, RepositoryData
9
+
10
+ if TYPE_CHECKING:
11
+ from infrahub.core.protocols import CoreGenericRepository
12
+
13
+
14
+ async def get_repositories_commit_per_branch(
15
+ db: InfrahubDatabase,
16
+ ) -> dict[str, RepositoryData]:
17
+ """Get a list of all repositories and their commit on each branches.
18
+
19
+ This method is similar to 'get_list_repositories' method in the Python SDK.
20
+
21
+ NOTE: At some point, we should refactor this function to use a single Database query instead of one per branch
22
+ """
23
+
24
+ repositories: dict[str, RepositoryData] = {}
25
+
26
+ for branch in list(registry.branch.values()):
27
+ repos: list[CoreGenericRepository] = await NodeManager.query(
28
+ db=db,
29
+ branch=branch,
30
+ fields={"id": None, "name": None, "commit": None, "internal_status": None},
31
+ schema=InfrahubKind.GENERICREPOSITORY,
32
+ )
33
+
34
+ for repository in repos:
35
+ repo_name = repository.name.value
36
+ if repo_name not in repositories:
37
+ repositories[repo_name] = RepositoryData(
38
+ repository_id=repository.get_id(),
39
+ repository_name=repo_name,
40
+ branches={},
41
+ )
42
+
43
+ repositories[repo_name].branches[branch.name] = repository.commit.value # type: ignore[attr-defined]
44
+ repositories[repo_name].branch_info[branch.name] = RepositoryBranchInfo(
45
+ internal_status=repository.internal_status.value
46
+ )
47
+
48
+ return repositories
infrahub/git/worktree.py CHANGED
@@ -1,7 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from pathlib import Path
4
- from typing import Optional
5
4
 
6
5
  from pydantic import BaseModel
7
6
 
@@ -14,7 +13,7 @@ class Worktree(BaseModel):
14
13
  identifier: str
15
14
  directory: Path
16
15
  commit: str
17
- branch: Optional[str] = None
16
+ branch: str | None = None
18
17
 
19
18
  @classmethod
20
19
  def init(cls, text: str) -> Worktree:
@@ -1,6 +1,5 @@
1
1
  import re
2
2
  import sys
3
- from typing import Optional
4
3
 
5
4
  import typer
6
5
  from infrahub_sdk import Config, InfrahubClientSync
@@ -16,7 +15,7 @@ REGEX_PASSWORD = r"^Password.*\'(.*\:\/\/)(.*)@(.*)\'"
16
15
 
17
16
  @app.command()
18
17
  def askpass(
19
- text: Optional[list[str]] = typer.Argument(None),
18
+ text: list[str] | None = typer.Argument(None),
20
19
  config_file: str = typer.Option("infrahub.toml", envvar="INFRAHUB_CONFIG"),
21
20
  ) -> None:
22
21
  config.SETTINGS.initialize_and_exit(config_file=config_file)
@@ -258,6 +258,18 @@ class GraphQLQueryReport:
258
258
 
259
259
  return access
260
260
 
261
+ def fields_by_kind(self, kind: str) -> list[str]:
262
+ fields: list[str] = []
263
+ if access := self.requested_read.get(kind):
264
+ fields.extend(list(access.attributes))
265
+ fields.extend(list(access.relationships))
266
+
267
+ return fields
268
+
269
+ @cached_property
270
+ def top_level_kinds(self) -> list[str]:
271
+ return [query.infrahub_model.kind for query in self.queries if query.infrahub_model]
272
+
261
273
  @cached_property
262
274
  def kind_action_map(self) -> dict[str, set[MutateAction]]:
263
275
  access: dict[str, set[MutateAction]] = {}
infrahub/graphql/app.py CHANGED
@@ -13,9 +13,7 @@ from typing import (
13
13
  AsyncGenerator,
14
14
  Awaitable,
15
15
  Callable,
16
- Optional,
17
16
  Sequence,
18
- Union,
19
17
  cast,
20
18
  )
21
19
 
@@ -87,7 +85,7 @@ GQL_ERROR = "error"
87
85
  GQL_START = "start"
88
86
  GQL_STOP = "stop"
89
87
 
90
- ContextValue = Union[Any, Callable[[HTTPConnection], Any]]
88
+ ContextValue = Any | Callable[[HTTPConnection], Any]
91
89
  RootValue = Any
92
90
 
93
91
 
@@ -95,13 +93,13 @@ class InfrahubGraphQLApp:
95
93
  def __init__(
96
94
  self,
97
95
  permission_checker: GraphQLQueryPermissionChecker,
98
- schema: Optional[graphene.Schema] = None,
96
+ schema: graphene.Schema | None = None,
99
97
  *,
100
- on_get: Optional[Callable[[Request], Union[Response, Awaitable[Response]]]] = None,
98
+ on_get: Callable[[Request], Response | Awaitable[Response]] | None = None,
101
99
  root_value: RootValue = None,
102
- middleware: Optional[Middleware] = None,
100
+ middleware: Middleware | None = None,
103
101
  error_formatter: Callable[[GraphQLError], GraphQLFormattedError] = format_error,
104
- execution_context_class: Optional[type[ExecutionContext]] = None,
102
+ execution_context_class: type[ExecutionContext] | None = None,
105
103
  ) -> None:
106
104
  self._schema = schema
107
105
  self.on_get = on_get
@@ -116,7 +114,7 @@ class InfrahubGraphQLApp:
116
114
  db: InfrahubDatabase
117
115
  if scope["type"] == "http":
118
116
  request = Request(scope=scope, receive=receive)
119
- response: Optional[Response] = None
117
+ response: Response | None = None
120
118
  jwt_auth = await jwt_scheme(request)
121
119
  api_key = await api_key_scheme(request)
122
120
  cookie_auth = await cookie_auth_scheme(request)
@@ -166,7 +164,7 @@ class InfrahubGraphQLApp:
166
164
  else:
167
165
  raise ValueError(f"Unsupported scope type: ${scope['type']}")
168
166
 
169
- async def _get_on_get(self, request: Request) -> Optional[Response]:
167
+ async def _get_on_get(self, request: Request) -> Response | None:
170
168
  handler = self.on_get
171
169
 
172
170
  if handler is None:
@@ -388,8 +386,8 @@ class InfrahubGraphQLApp:
388
386
  graphql_params = await prepare_graphql_params(db=db, branch=branch)
389
387
 
390
388
  errors: list[GraphQLError] = []
391
- operation: Optional[OperationDefinitionNode] = None
392
- document: Optional[DocumentNode] = None
389
+ operation: OperationDefinitionNode | None = None
390
+ document: DocumentNode | None = None
393
391
 
394
392
  try:
395
393
  document = parse(query)
@@ -474,11 +472,11 @@ class InfrahubGraphQLApp:
474
472
  await websocket.send_json({"type": GQL_COMPLETE, "id": operation_id})
475
473
 
476
474
 
477
- async def _get_operation_from_request(request: Request) -> Union[dict[str, Any], list[Any]]:
475
+ async def _get_operation_from_request(request: Request) -> dict[str, Any] | list[Any]:
478
476
  content_type = request.headers.get("Content-Type", "").split(";")[0]
479
477
  if content_type == "application/json":
480
478
  try:
481
- return cast(Union[dict[str, Any], list[Any]], await request.json())
479
+ return cast(dict[str, Any] | list[Any], await request.json())
482
480
  except (TypeError, ValueError) as err:
483
481
  raise ValueError("Request body is not a valid JSON") from err
484
482
  elif content_type == "multipart/form-data":
@@ -487,7 +485,7 @@ async def _get_operation_from_request(request: Request) -> Union[dict[str, Any],
487
485
  raise ValueError("Content-type must be application/json or multipart/form-data")
488
486
 
489
487
 
490
- async def _get_operation_from_multipart(request: Request) -> Union[dict[str, Any], list[Any]]:
488
+ async def _get_operation_from_multipart(request: Request) -> dict[str, Any] | list[Any]:
491
489
  try:
492
490
  request_body = await request.form()
493
491
  except Exception as err:
@@ -526,7 +524,7 @@ async def _get_operation_from_multipart(request: Request) -> Union[dict[str, Any
526
524
 
527
525
  def _inject_file_to_operations(ops_tree: Any, _file: UploadFile, path: Sequence[str]) -> None:
528
526
  k = path[0]
529
- key: Union[str, int]
527
+ key: str | int
530
528
  try:
531
529
  key = int(k)
532
530
  except ValueError:
@@ -17,6 +17,12 @@ async def apply_external_context(graphql_context: GraphqlContext, context_input:
17
17
  if not context_input or not context_input.account:
18
18
  return
19
19
 
20
+ if graphql_context.active_account_session.account_id == context_input.account.id:
21
+ # If the account_id from the request context is the same as the current account
22
+ # there's no point moving forward with other checks to override the current
23
+ # context we can just continue with what is already there.
24
+ return
25
+
20
26
  permission = define_global_permission_from_branch(
21
27
  permission=GlobalPermissions.OVERRIDE_CONTEXT, branch_name=graphql_context.branch.name
22
28
  )
@@ -9,6 +9,7 @@ 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.resolvers.many_relationship import ManyRelationshipResolver
12
13
  from infrahub.graphql.resolvers.single_relationship import SingleRelationshipResolver
13
14
  from infrahub.permissions import PermissionManager
14
15
 
@@ -36,6 +37,7 @@ class GraphqlContext:
36
37
  branch: Branch
37
38
  types: dict
38
39
  single_relationship_resolver: SingleRelationshipResolver
40
+ many_relationship_resolver: ManyRelationshipResolver
39
41
  service: InfrahubServices | None = None
40
42
  at: Timestamp | None = None
41
43
  related_node_ids: set | None = None
@@ -111,6 +113,7 @@ async def prepare_graphql_params(
111
113
  db=db,
112
114
  branch=branch,
113
115
  single_relationship_resolver=SingleRelationshipResolver(),
116
+ many_relationship_resolver=ManyRelationshipResolver(),
114
117
  at=Timestamp(at),
115
118
  types=gqlm.get_graphql_types(),
116
119
  related_node_ids=set(),
@@ -10,6 +10,8 @@ from infrahub.core.node import Node
10
10
  from infrahub.core.timestamp import Timestamp
11
11
  from infrahub.database import InfrahubDatabase
12
12
 
13
+ from .shared import to_frozen_set
14
+
13
15
 
14
16
  @dataclass
15
17
  class GetManyParams:
@@ -68,15 +70,3 @@ class NodeDataLoader(DataLoader[str, Node | None]):
68
70
  for node_id in keys:
69
71
  results.append(nodes_by_id.get(node_id, None))
70
72
  return results
71
-
72
-
73
- def to_frozen_set(to_freeze: dict[str, Any]) -> frozenset:
74
- freezing_dict = {}
75
- for k, v in to_freeze.items():
76
- if isinstance(v, dict):
77
- freezing_dict[k] = to_frozen_set(v)
78
- elif isinstance(v, list | set):
79
- freezing_dict[k] = frozenset(v)
80
- else:
81
- freezing_dict[k] = v
82
- return frozenset(freezing_dict)
@@ -0,0 +1,77 @@
1
+ from dataclasses import dataclass
2
+ from typing import Any
3
+
4
+ from aiodataloader import DataLoader
5
+
6
+ from infrahub.core.branch.models import Branch
7
+ from infrahub.core.manager import NodeManager
8
+ from infrahub.core.relationship.model import Relationship
9
+ from infrahub.core.schema.relationship_schema import RelationshipSchema
10
+ from infrahub.core.timestamp import Timestamp
11
+ from infrahub.database import InfrahubDatabase
12
+
13
+ from .shared import to_frozen_set
14
+
15
+
16
+ @dataclass
17
+ class QueryPeerParams:
18
+ branch: Branch | str
19
+ source_kind: str
20
+ schema: RelationshipSchema
21
+ filters: dict[str, Any]
22
+ fields: dict | None = None
23
+ at: Timestamp | str | None = None
24
+ branch_agnostic: bool = False
25
+
26
+ def __hash__(self) -> int:
27
+ frozen_fields: frozenset | None = None
28
+ if self.fields:
29
+ frozen_fields = to_frozen_set(self.fields)
30
+ frozen_filters = to_frozen_set(self.filters)
31
+ timestamp = Timestamp(self.at)
32
+ branch = self.branch.name if isinstance(self.branch, Branch) else self.branch
33
+ hash_str = "|".join(
34
+ [
35
+ str(hash(frozen_fields)),
36
+ str(hash(frozen_filters)),
37
+ timestamp.to_string(),
38
+ branch,
39
+ self.schema.name,
40
+ str(self.source_kind),
41
+ str(self.branch_agnostic),
42
+ ]
43
+ )
44
+ return hash(hash_str)
45
+
46
+
47
+ class PeerRelationshipsDataLoader(DataLoader[str, list[Relationship]]):
48
+ def __init__(self, db: InfrahubDatabase, query_params: QueryPeerParams, *args: Any, **kwargs: Any) -> None:
49
+ super().__init__(*args, **kwargs)
50
+ self.query_params = query_params
51
+ self.db = db
52
+
53
+ async def batch_load_fn(self, keys: list[Any]) -> list[list[Relationship]]: # pylint: disable=method-hidden
54
+ async with self.db.start_session() as db:
55
+ peer_rels = await NodeManager.query_peers(
56
+ db=db,
57
+ ids=keys,
58
+ source_kind=self.query_params.source_kind,
59
+ schema=self.query_params.schema,
60
+ filters=self.query_params.filters,
61
+ fields=self.query_params.fields,
62
+ at=self.query_params.at,
63
+ branch=self.query_params.branch,
64
+ branch_agnostic=self.query_params.branch_agnostic,
65
+ fetch_peers=True,
66
+ )
67
+ peer_rels_by_node_id: dict[str, list[Relationship]] = {}
68
+ for rel in peer_rels:
69
+ node_id = rel.node_id
70
+ if node_id not in peer_rels_by_node_id:
71
+ peer_rels_by_node_id[node_id] = []
72
+ peer_rels_by_node_id[node_id].append(rel)
73
+
74
+ results = []
75
+ for node_id in keys:
76
+ results.append(peer_rels_by_node_id.get(node_id, []))
77
+ return results
@@ -0,0 +1,13 @@
1
+ from typing import Any
2
+
3
+
4
+ def to_frozen_set(to_freeze: dict[str, Any]) -> frozenset:
5
+ freezing_dict = {}
6
+ for k, v in to_freeze.items():
7
+ if isinstance(v, dict):
8
+ freezing_dict[k] = to_frozen_set(v)
9
+ elif isinstance(v, list | set):
10
+ freezing_dict[k] = frozenset(v)
11
+ else:
12
+ freezing_dict[k] = v
13
+ return frozenset(freezing_dict)
@@ -1,7 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from dataclasses import dataclass
4
- from typing import TYPE_CHECKING, Any, Iterable, Optional, Union
4
+ from typing import TYPE_CHECKING, Any, Iterable
5
5
 
6
6
  import graphene
7
7
 
@@ -38,6 +38,7 @@ from .mutations.repository import InfrahubRepositoryMutation
38
38
  from .mutations.resource_manager import (
39
39
  InfrahubNumberPoolMutation,
40
40
  )
41
+ from .mutations.webhook import InfrahubWebhookMutation
41
42
  from .resolvers.resolver import (
42
43
  account_resolver,
43
44
  ancestors_resolver,
@@ -75,9 +76,7 @@ class DeleteInput(graphene.InputObjectType):
75
76
  hfid = graphene.List(of_type=graphene.String, required=False)
76
77
 
77
78
 
78
- GraphQLTypes = Union[
79
- type[InfrahubMutation], type[BaseAttributeType], type[graphene.Interface], type[graphene.ObjectType]
80
- ]
79
+ GraphQLTypes = type[InfrahubMutation] | type[BaseAttributeType] | type[graphene.Interface] | type[graphene.ObjectType]
81
80
 
82
81
 
83
82
  class OrderInput(graphene.InputObjectType):
@@ -513,6 +512,8 @@ class GraphQLSchemaManager:
513
512
  InfrahubKind.NAMESPACE: InfrahubIPNamespaceMutation,
514
513
  InfrahubKind.NUMBERPOOL: InfrahubNumberPoolMutation,
515
514
  InfrahubKind.MENUITEM: InfrahubCoreMenuMutation,
515
+ InfrahubKind.STANDARDWEBHOOK: InfrahubWebhookMutation,
516
+ InfrahubKind.CUSTOMWEBHOOK: InfrahubWebhookMutation,
516
517
  }
517
518
 
518
519
  if isinstance(node_schema, NodeSchema) and node_schema.is_ip_prefix():
@@ -643,7 +644,9 @@ class GraphQLSchemaManager:
643
644
  self.set_type(name=type_name, graphql_type=relationship_property)
644
645
 
645
646
  def generate_graphql_mutations(
646
- self, schema: NodeSchema | ProfileSchema | TemplateSchema, base_class: type[InfrahubMutation]
647
+ self,
648
+ schema: NodeSchema | ProfileSchema | TemplateSchema,
649
+ base_class: type[InfrahubMutation],
647
650
  ) -> GraphqlMutations:
648
651
  graphql_mutation_create_input = self.generate_graphql_mutation_create_input(schema)
649
652
  graphql_mutation_update_input = self.generate_graphql_mutation_update_input(schema)
@@ -682,7 +685,7 @@ class GraphQLSchemaManager:
682
685
  slug = InputField(StringAttributeCreate, required=True)
683
686
  description = InputField(StringAttributeCreate, required=False)
684
687
  """
685
- attrs: dict[str, Union[graphene.String, graphene.InputField]] = {"id": graphene.String(required=False)}
688
+ attrs: dict[str, graphene.String | graphene.InputField] = {"id": graphene.String(required=False)}
686
689
 
687
690
  for attr in schema.attributes:
688
691
  if attr.read_only:
@@ -718,7 +721,7 @@ class GraphQLSchemaManager:
718
721
  slug = InputField(StringAttributeUpdate, required=False)
719
722
  description = InputField(StringAttributeUpdate, required=False)
720
723
  """
721
- attrs: dict[str, Union[graphene.String, graphene.InputField]] = {
724
+ attrs: dict[str, graphene.String | graphene.InputField] = {
722
725
  "id": graphene.String(required=False),
723
726
  "hfid": graphene.List(of_type=graphene.String, required=False),
724
727
  }
@@ -759,7 +762,7 @@ class GraphQLSchemaManager:
759
762
  slug = InputField(StringAttributeUpdate, required=True)
760
763
  description = InputField(StringAttributeUpdate, required=False)
761
764
  """
762
- attrs: dict[str, Union[graphene.String, graphene.InputField]] = {
765
+ attrs: dict[str, graphene.String | graphene.InputField] = {
763
766
  "id": graphene.String(required=False),
764
767
  "hfid": graphene.List(of_type=graphene.String, required=False),
765
768
  }
@@ -855,7 +858,7 @@ class GraphQLSchemaManager:
855
858
 
856
859
  def generate_filters(
857
860
  self, schema: MainSchemaTypes, top_level: bool = False, include_properties: bool = True
858
- ) -> dict[str, Union[graphene.Scalar, graphene.List]]:
861
+ ) -> dict[str, graphene.Scalar | graphene.List]:
859
862
  """Generate the GraphQL filters for a given Schema object.
860
863
 
861
864
  The generated filter will be different if we are at the top_level (query)
@@ -920,7 +923,7 @@ class GraphQLSchemaManager:
920
923
  self,
921
924
  schema: MainSchemaTypes,
922
925
  node: type[InfrahubObject],
923
- relation_property: Optional[type[InfrahubObject]] = None,
926
+ relation_property: type[InfrahubObject] | None = None,
924
927
  populate_cache: bool = False,
925
928
  ) -> type[InfrahubObject]:
926
929
  """Generate a edged GraphQL object Type from a Infrahub NodeSchema for pagination."""