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
@@ -0,0 +1,146 @@
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+ import os
5
+ from typing import TYPE_CHECKING
6
+
7
+ import typer
8
+ from deepdiff import DeepDiff
9
+ from infrahub_sdk.async_typer import AsyncTyper
10
+ from prefect.client.orchestration import get_client
11
+ from rich import print as rprint
12
+
13
+ from infrahub import config
14
+ from infrahub.core.constants import InfrahubKind
15
+ from infrahub.core.initialization import (
16
+ create_anonymous_role,
17
+ create_default_roles,
18
+ create_super_administrator_role,
19
+ create_super_administrators_group,
20
+ initialize_registry,
21
+ )
22
+ from infrahub.core.manager import NodeManager
23
+ from infrahub.menu.menu import default_menu
24
+ from infrahub.menu.models import MenuDict
25
+ from infrahub.menu.repository import MenuRepository
26
+ from infrahub.menu.utils import create_default_menu
27
+ from infrahub.services import InfrahubServices
28
+ from infrahub.services.adapters.message_bus.local import BusSimulator
29
+ from infrahub.services.adapters.workflow.local import WorkflowLocalExecution
30
+ from infrahub.trigger.tasks import trigger_configure_all
31
+ from infrahub.workflows.initialization import (
32
+ setup_blocks,
33
+ setup_deployments,
34
+ setup_worker_pools,
35
+ )
36
+
37
+ from .db import initialize_internal_schema, migrate_database, update_core_schema
38
+
39
+ if TYPE_CHECKING:
40
+ from infrahub.cli.context import CliContext
41
+ from infrahub.database import InfrahubDatabase
42
+
43
+ app = AsyncTyper()
44
+
45
+
46
+ @app.command(name="upgrade")
47
+ async def upgrade_cmd(
48
+ ctx: typer.Context,
49
+ config_file: str = typer.Argument("infrahub.toml", envvar="INFRAHUB_CONFIG"),
50
+ check: bool = typer.Option(False, help="Check the state of the system without upgrading."),
51
+ ) -> None:
52
+ """Upgrade Infrahub to the latest version."""
53
+
54
+ logging.getLogger("infrahub").setLevel(logging.WARNING)
55
+ logging.getLogger("neo4j").setLevel(logging.ERROR)
56
+ logging.getLogger("prefect").setLevel(logging.ERROR)
57
+ os.environ["PREFECT_SERVER_ANALYTICS_ENABLED"] = "false"
58
+
59
+ config.load_and_exit(config_file_name=config_file)
60
+
61
+ context: CliContext = ctx.obj
62
+ dbdriver = await context.init_db(retry=1)
63
+
64
+ service = await InfrahubServices.new(
65
+ database=dbdriver, message_bus=BusSimulator(), workflow=WorkflowLocalExecution()
66
+ )
67
+ await initialize_registry(db=dbdriver)
68
+
69
+ # NOTE add step to validate if the database and the task manager are reachable
70
+
71
+ # -------------------------------------------
72
+ # Add pre-upgrade validation
73
+ # -------------------------------------------
74
+
75
+ # -------------------------------------------
76
+ # Upgrade Infrahub Database and Schema
77
+ # -------------------------------------------
78
+ await migrate_database(db=dbdriver, initialize=False, check=check)
79
+
80
+ await initialize_internal_schema()
81
+ await update_core_schema(db=dbdriver, service=service, initialize=False)
82
+
83
+ # -------------------------------------------
84
+ # Upgrade Internal Objects, generated and managed by Infrahub
85
+ # -------------------------------------------
86
+ await upgrade_menu(db=dbdriver)
87
+ await upgrade_permissions(db=dbdriver)
88
+
89
+ # -------------------------------------------
90
+ # Upgrade External system : Task Manager
91
+ # -------------------------------------------
92
+ async with get_client(sync_client=False) as client:
93
+ await setup_blocks()
94
+ await setup_worker_pools(client=client)
95
+ await setup_deployments(client=client)
96
+ await trigger_configure_all(service=service)
97
+
98
+ await dbdriver.close()
99
+
100
+
101
+ async def upgrade_menu(db: InfrahubDatabase) -> None:
102
+ menu_repository = MenuRepository(db=db)
103
+ menu_nodes = await menu_repository.get_menu_db()
104
+ menu_items = await menu_repository.get_menu(nodes=menu_nodes)
105
+ default_menu_dict = MenuDict.from_definition_list(default_menu)
106
+
107
+ if not menu_nodes:
108
+ await create_default_menu(db=db)
109
+ return
110
+
111
+ diff_menu = DeepDiff(menu_items.to_rest(), default_menu_dict.to_rest(), ignore_order=True)
112
+
113
+ if not diff_menu:
114
+ rprint("Menu Up to date, nothing to update")
115
+ return
116
+
117
+ await menu_repository.update_menu(existing_menu=menu_items, new_menu=default_menu_dict, menu_nodes=menu_nodes)
118
+ rprint("Menu has been updated")
119
+
120
+
121
+ async def upgrade_permissions(db: InfrahubDatabase) -> None:
122
+ existing_permissions = await NodeManager.query(
123
+ schema=InfrahubKind.OBJECTPERMISSION,
124
+ db=db,
125
+ limit=1,
126
+ )
127
+ if existing_permissions:
128
+ rprint("Permissions Up to date, nothing to update")
129
+ return
130
+
131
+ await setup_permissions(db=db)
132
+ rprint("Permissions have been updated")
133
+
134
+
135
+ async def setup_permissions(db: InfrahubDatabase) -> None:
136
+ existing_accounts = await NodeManager.query(
137
+ schema=InfrahubKind.ACCOUNT,
138
+ db=db,
139
+ limit=1,
140
+ )
141
+ administrator_role = await create_super_administrator_role(db=db)
142
+ await create_super_administrators_group(db=db, role=administrator_role, admin_accounts=existing_accounts)
143
+ await create_default_roles(db=db)
144
+
145
+ if config.SETTINGS.main.allow_anonymous_access:
146
+ await create_anonymous_role(db=db)
@@ -0,0 +1,185 @@
1
+ from __future__ import annotations
2
+
3
+ from collections import defaultdict
4
+ from typing import TYPE_CHECKING
5
+
6
+ from prefect import task
7
+ from prefect.cache_policies import NONE
8
+ from prefect.logging import get_run_logger
9
+
10
+ from infrahub.core.constants import InfrahubKind
11
+ from infrahub.core.manager import NodeManager
12
+ from infrahub.core.protocols import CoreGenericRepository, CoreGraphQLQuery
13
+ from infrahub.core.registry import registry
14
+ from infrahub.database import InfrahubDatabase # noqa: TC001 needed for prefect flow
15
+ from infrahub.git.utils import get_repositories_commit_per_branch
16
+ from infrahub.graphql.analyzer import InfrahubGraphQLQueryAnalyzer
17
+ from infrahub.graphql.initialization import prepare_graphql_params
18
+
19
+ from .models import (
20
+ ComputedAttrJinja2TriggerDefinition,
21
+ ComputedAttrPythonQueryTriggerDefinition,
22
+ ComputedAttrPythonTriggerDefinition,
23
+ PythonTransformComputedAttribute,
24
+ )
25
+
26
+ if TYPE_CHECKING:
27
+ from infrahub.core.protocols import CoreTransformPython as CoreTransformPythonNode
28
+ from infrahub.git.models import RepositoryData
29
+
30
+
31
+ @task(
32
+ name="gather-python-transform-attributes",
33
+ task_run_name="Gather Python transform attributes for {branch_name}",
34
+ cache_policy=NONE,
35
+ )
36
+ async def gather_python_transform_attributes(
37
+ db: InfrahubDatabase, branch_name: str, repositories: dict[str, RepositoryData] | None = None
38
+ ) -> list[PythonTransformComputedAttribute]:
39
+ log = get_run_logger()
40
+ schema_branch = registry.schema.get_schema_branch(name=branch_name)
41
+ branches_with_diff_from_main = registry.get_altered_schema_branches()
42
+ branch = registry.get_branch_from_registry(branch=branch_name)
43
+
44
+ transform_attributes = schema_branch.computed_attributes.python_attributes_by_transform
45
+
46
+ transform_names = list(transform_attributes.keys())
47
+
48
+ if not transform_names:
49
+ return []
50
+
51
+ transforms: list[CoreTransformPythonNode] = await NodeManager.query(
52
+ db=db,
53
+ schema=InfrahubKind.TRANSFORMPYTHON,
54
+ branch=branch_name,
55
+ fields={"id": None, "name": None, "repository": None, "query": None},
56
+ filters={"name__values": transform_names},
57
+ prefetch_relationships=True,
58
+ )
59
+
60
+ found_transforms_names = [transform.name.value for transform in transforms]
61
+ for transform_name in transform_names:
62
+ if transform_name not in found_transforms_names:
63
+ log.warning(
64
+ msg=f"The transform {transform_name} is assigned to a computed attribute but the transform could not be found in the database."
65
+ )
66
+ repositories = repositories or await get_repositories_commit_per_branch(db=db)
67
+ graphql_params = await prepare_graphql_params(db=db, branch=branch)
68
+
69
+ computed_attributes: list[PythonTransformComputedAttribute] = []
70
+ for transform in transforms:
71
+ repository = await transform.repository.get_peer(db=db, peer_type=CoreGenericRepository, raise_on_error=True)
72
+ query = await transform.query.get_peer(db=db, peer_type=CoreGraphQLQuery, raise_on_error=True)
73
+ query_analyzer = InfrahubGraphQLQueryAnalyzer(
74
+ query=query.query.value,
75
+ branch=branch,
76
+ schema_branch=schema_branch,
77
+ schema=graphql_params.schema,
78
+ )
79
+ for attribute in transform_attributes[transform.name.value]:
80
+ python_transform_computed_attribute = PythonTransformComputedAttribute(
81
+ name=transform.name.value,
82
+ branch_name=branch_name,
83
+ repository_id=repository.get_id(),
84
+ repository_name=repository.name.value,
85
+ repository_kind=repository.get_kind(),
86
+ query_analyzer=query_analyzer,
87
+ query_name=query.name.value,
88
+ computed_attribute=attribute,
89
+ default_schema=branch_name not in branches_with_diff_from_main,
90
+ )
91
+ python_transform_computed_attribute.populate_branch_commit(
92
+ repository_data=repositories.get(repository.name.value)
93
+ )
94
+ computed_attributes.append(python_transform_computed_attribute)
95
+
96
+ return computed_attributes
97
+
98
+
99
+ @task(
100
+ name="gather-trigger-computed-attribute-jinja2",
101
+ cache_policy=NONE,
102
+ )
103
+ async def gather_trigger_computed_attribute_jinja2() -> list[ComputedAttrJinja2TriggerDefinition]:
104
+ log = get_run_logger()
105
+
106
+ # Build a list of all branches to process based on which branch is different from main
107
+ branches_with_diff_from_main = registry.get_altered_schema_branches()
108
+ branches_to_process: list[tuple[str, list[str]]] = [(branch, []) for branch in branches_with_diff_from_main]
109
+ branches_to_process.append((registry.default_branch, branches_with_diff_from_main))
110
+
111
+ triggers: list[ComputedAttrJinja2TriggerDefinition] = []
112
+
113
+ for branch_scope, branches_out_of_scope in branches_to_process:
114
+ schema_branch = registry.schema.get_schema_branch(name=branch_scope)
115
+ mapping = schema_branch.computed_attributes.get_jinja2_trigger_nodes()
116
+
117
+ log.info(f"Generating {len(mapping)} Jinja2 trigger for {branch_scope} (except {branches_out_of_scope})")
118
+
119
+ for computed_attribute, trigger_nodes in mapping.items():
120
+ for trigger_node in trigger_nodes:
121
+ trigger = ComputedAttrJinja2TriggerDefinition.from_computed_attribute(
122
+ branch=branch_scope,
123
+ computed_attribute=computed_attribute,
124
+ trigger_node=trigger_node,
125
+ branches_out_of_scope=branches_out_of_scope,
126
+ )
127
+ triggers.append(trigger)
128
+
129
+ return triggers
130
+
131
+
132
+ @task(
133
+ name="gather-trigger-computed-attribute-python",
134
+ cache_policy=NONE,
135
+ )
136
+ async def gather_trigger_computed_attribute_python(
137
+ db: InfrahubDatabase,
138
+ ) -> tuple[list[ComputedAttrPythonTriggerDefinition], list[ComputedAttrPythonQueryTriggerDefinition]]:
139
+ triggers_python = []
140
+ triggers_python_query = []
141
+
142
+ repositories = await get_repositories_commit_per_branch(db=db)
143
+
144
+ all_computed_attributes: dict[str, dict[str, PythonTransformComputedAttribute]] = defaultdict(dict)
145
+ for branch in list(registry.branch.values()):
146
+ if branch.is_global:
147
+ continue
148
+
149
+ computed_attributes = await gather_python_transform_attributes(
150
+ db=db, branch_name=branch.name, repositories=repositories
151
+ )
152
+ for computed_attribute in computed_attributes:
153
+ all_computed_attributes[computed_attribute.name][branch.name] = computed_attribute
154
+
155
+ for branches in all_computed_attributes.values():
156
+ branches_with_diff_from_main = []
157
+ if registry.default_branch in branches.keys():
158
+ commit_main = branches[registry.default_branch].repository_commit
159
+ branches_with_diff_from_main = [
160
+ branch_name for branch_name, item in branches.items() if item.repository_commit != commit_main
161
+ ]
162
+ else:
163
+ branches_with_diff_from_main = list(branches.keys())
164
+
165
+ branches_to_process: list[tuple[str, list[str]]] = [(branch, []) for branch in branches_with_diff_from_main]
166
+ branches_to_process.append((registry.default_branch, branches_with_diff_from_main))
167
+
168
+ for branch_scope, branches_out_of_scope in branches_to_process:
169
+ trigger_python = ComputedAttrPythonTriggerDefinition.from_object(
170
+ computed_attribute=branches[branch_scope],
171
+ branch=branch_scope,
172
+ branches_out_of_scope=branches_out_of_scope,
173
+ )
174
+ triggers_python.append(trigger_python)
175
+
176
+ for kind in branches[branch_scope].query_analyzer.query_report.requested_read.keys():
177
+ trigger_python_query = ComputedAttrPythonQueryTriggerDefinition.from_object(
178
+ kind=kind,
179
+ computed_attribute=branches[branch_scope],
180
+ branch=branch_scope,
181
+ branches_out_of_scope=branches_out_of_scope,
182
+ )
183
+ triggers_python_query.append(trigger_python_query)
184
+
185
+ return triggers_python, triggers_python_query
@@ -5,15 +5,34 @@ from dataclasses import dataclass, field
5
5
  from typing import TYPE_CHECKING
6
6
 
7
7
  from prefect.events.schemas.automations import Automation # noqa: TC002
8
- from pydantic import BaseModel, Field
8
+ from pydantic import BaseModel, ConfigDict, Field, computed_field
9
9
  from typing_extensions import Self
10
10
 
11
+ from infrahub.core import registry
12
+ from infrahub.core.schema.schema_branch_computed import ( # noqa: TC001
13
+ ComputedAttributeTarget,
14
+ ComputedAttributeTriggerNode,
15
+ PythonDefinition,
16
+ )
17
+ from infrahub.events import NodeCreatedEvent, NodeUpdatedEvent
18
+ from infrahub.graphql.analyzer import InfrahubGraphQLQueryAnalyzer # noqa: TC001
19
+ from infrahub.trigger.constants import NAME_SEPARATOR
20
+ from infrahub.trigger.models import (
21
+ EventTrigger,
22
+ ExecuteWorkflow,
23
+ TriggerBranchDefinition,
24
+ TriggerType,
25
+ )
26
+ from infrahub.workflows.catalogue import (
27
+ COMPUTED_ATTRIBUTE_PROCESS_JINJA2,
28
+ COMPUTED_ATTRIBUTE_PROCESS_TRANSFORM,
29
+ QUERY_COMPUTED_ATTRIBUTE_TRANSFORM_TARGETS,
30
+ )
31
+
11
32
  if TYPE_CHECKING:
12
33
  from uuid import UUID
13
34
 
14
- from infrahub_sdk.data import RepositoryData
15
-
16
- from infrahub.core.schema.schema_branch_computed import PythonDefinition
35
+ from infrahub.git.models import RepositoryData
17
36
 
18
37
 
19
38
  class ComputedAttributeAutomations(BaseModel):
@@ -26,7 +45,7 @@ class ComputedAttributeAutomations(BaseModel):
26
45
  if not automation.name.startswith(prefix):
27
46
  continue
28
47
 
29
- name_split = automation.name.split("::")
48
+ name_split = automation.name.split(NAME_SEPARATOR)
30
49
  if len(name_split) != 3:
31
50
  continue
32
51
 
@@ -47,9 +66,6 @@ class ComputedAttributeAutomations(BaseModel):
47
66
  return True
48
67
  return False
49
68
 
50
- def return_obsolete(self, keep: list[UUID]) -> list[UUID]:
51
- return [automation_id for automation_id in self.all_automation_ids if automation_id not in keep]
52
-
53
69
  @property
54
70
  def all_automation_ids(self) -> list[UUID]:
55
71
  automation_ids: list[UUID] = []
@@ -59,25 +75,237 @@ class ComputedAttributeAutomations(BaseModel):
59
75
  return automation_ids
60
76
 
61
77
 
62
- @dataclass
63
- class PythonTransformComputedAttribute:
78
+ class PythonTransformComputedAttribute(BaseModel):
79
+ model_config = ConfigDict(arbitrary_types_allowed=True)
64
80
  name: str
65
81
  repository_id: str
66
82
  repository_name: str
67
83
  repository_kind: str
68
84
  query_name: str
69
- query_models: list[str]
85
+ query_analyzer: InfrahubGraphQLQueryAnalyzer
70
86
  computed_attribute: PythonDefinition
71
87
  default_schema: bool
88
+ branch_name: str
72
89
  branch_commit: dict[str, str] = field(default_factory=dict)
73
90
 
91
+ @computed_field
92
+ def repository_commit(self) -> str:
93
+ return self.branch_commit[self.branch_name]
94
+
74
95
  def populate_branch_commit(self, repository_data: RepositoryData | None = None) -> None:
75
96
  if repository_data:
76
97
  for branch, commit in repository_data.branches.items():
77
98
  self.branch_commit[branch] = commit
78
99
 
100
+ def get_altered_branches(self) -> list[str]:
101
+ if registry.default_branch in self.branch_commit:
102
+ default_branch_commit = self.branch_commit[registry.default_branch]
103
+ return [
104
+ branch_name for branch_name, commit in self.branch_commit.items() if commit != default_branch_commit
105
+ ]
106
+ return list(self.branch_commit.keys())
107
+
79
108
 
80
109
  @dataclass
81
110
  class PythonTransformTarget:
82
111
  kind: str
83
112
  object_id: str
113
+
114
+
115
+ class ComputedAttrJinja2TriggerDefinition(TriggerBranchDefinition):
116
+ type: TriggerType = TriggerType.COMPUTED_ATTR_JINJA2
117
+ computed_attribute: ComputedAttributeTarget
118
+
119
+ @classmethod
120
+ def from_computed_attribute(
121
+ cls,
122
+ branch: str,
123
+ computed_attribute: ComputedAttributeTarget,
124
+ trigger_node: ComputedAttributeTriggerNode,
125
+ branches_out_of_scope: list[str] | None = None,
126
+ ) -> Self:
127
+ """
128
+ This function is used to create a trigger definition for a computed attribute of type Jinja2.
129
+ """
130
+ event_trigger = EventTrigger()
131
+ event_trigger.events.add(NodeUpdatedEvent.event_name)
132
+ if computed_attribute.attribute.optional:
133
+ # If the computed attribute not optional it means that Infrahub will have assigned the value during
134
+ # the creation of the node so we don't need to match on node creation events as it would only add
135
+ # extra work that doesn't accomplish anything. For this reason the filter should only match on the
136
+ # node creation events if the attribute is optional.
137
+ event_trigger.events.add(NodeCreatedEvent.event_name)
138
+
139
+ event_trigger.match = {"infrahub.node.kind": trigger_node.kind}
140
+ if branches_out_of_scope:
141
+ event_trigger.match["infrahub.branch.name"] = [f"!{branch}" for branch in branches_out_of_scope]
142
+ elif not branches_out_of_scope and branch != registry.default_branch:
143
+ event_trigger.match["infrahub.branch.name"] = branch
144
+
145
+ event_trigger.match_related = {
146
+ "prefect.resource.role": ["infrahub.node.attribute_update", "infrahub.node.relationship_update"],
147
+ "infrahub.field.name": trigger_node.fields,
148
+ }
149
+
150
+ workflow = ExecuteWorkflow(
151
+ workflow=COMPUTED_ATTRIBUTE_PROCESS_JINJA2,
152
+ parameters={
153
+ "branch_name": "{{ event.resource['infrahub.branch.name'] }}",
154
+ "node_kind": "{{ event.resource['infrahub.node.kind'] }}",
155
+ "object_id": "{{ event.resource['infrahub.node.id'] }}",
156
+ "computed_attribute_name": computed_attribute.attribute.name,
157
+ "computed_attribute_kind": computed_attribute.kind,
158
+ "updated_fields": {
159
+ "__prefect_kind": "json",
160
+ "value": {
161
+ "__prefect_kind": "jinja",
162
+ "template": "{{ event.payload['data']['fields'] | tojson }}",
163
+ },
164
+ },
165
+ "context": {
166
+ "__prefect_kind": "json",
167
+ "value": {
168
+ "__prefect_kind": "jinja",
169
+ "template": "{{ event.payload['context'] | tojson }}",
170
+ },
171
+ },
172
+ },
173
+ )
174
+
175
+ definition = cls(
176
+ name=f"{computed_attribute.key_name}{NAME_SEPARATOR}kind{NAME_SEPARATOR}{trigger_node.kind}",
177
+ branch=branch,
178
+ computed_attribute=computed_attribute,
179
+ trigger=event_trigger,
180
+ actions=[workflow],
181
+ )
182
+
183
+ return definition
184
+
185
+
186
+ class ComputedAttrPythonTriggerDefinition(TriggerBranchDefinition):
187
+ type: TriggerType = TriggerType.COMPUTED_ATTR_PYTHON
188
+ computed_attribute: PythonTransformComputedAttribute
189
+
190
+ @classmethod
191
+ def from_object(
192
+ cls,
193
+ branch: str,
194
+ computed_attribute: PythonTransformComputedAttribute,
195
+ branches_out_of_scope: list[str] | None = None,
196
+ ) -> Self:
197
+ # scope = registry.default_branch
198
+
199
+ event_trigger = EventTrigger()
200
+ event_trigger.events.update({NodeCreatedEvent.event_name, NodeUpdatedEvent.event_name})
201
+ event_trigger.match = {
202
+ "infrahub.node.kind": [computed_attribute.computed_attribute.kind],
203
+ }
204
+
205
+ if branches_out_of_scope:
206
+ event_trigger.match["infrahub.branch.name"] = [f"!{branch}" for branch in branches_out_of_scope]
207
+ elif not branches_out_of_scope and branch != registry.default_branch:
208
+ event_trigger.match["infrahub.branch.name"] = branch
209
+
210
+ update_fields = computed_attribute.query_analyzer.query_report.fields_by_kind(
211
+ kind=computed_attribute.computed_attribute.kind
212
+ )
213
+ event_trigger.match_related = {
214
+ "prefect.resource.role": ["infrahub.node.attribute_update", "infrahub.node.relationship_update"],
215
+ }
216
+
217
+ if update_fields and "display_label" not in update_fields:
218
+ # The GraphQLQuery analyzer doesn't yet support figuring out which updates would match the "display label"
219
+ # of a query. Because of this we temporarily match any field if the display_label is part of the computed
220
+ # attribute query
221
+ event_trigger.match_related["infrahub.field.name"] = update_fields
222
+
223
+ definition = cls(
224
+ name=computed_attribute.computed_attribute.key_name,
225
+ branch=branch,
226
+ computed_attribute=computed_attribute,
227
+ trigger=event_trigger,
228
+ actions=[
229
+ ExecuteWorkflow(
230
+ workflow=COMPUTED_ATTRIBUTE_PROCESS_TRANSFORM,
231
+ parameters={
232
+ "branch_name": "{{ event.resource['infrahub.branch.name'] }}",
233
+ "node_kind": "{{ event.resource['infrahub.node.kind'] }}",
234
+ "object_id": "{{ event.resource['infrahub.node.id'] }}",
235
+ "computed_attribute_name": computed_attribute.computed_attribute.attribute.name,
236
+ "computed_attribute_kind": computed_attribute.computed_attribute.kind,
237
+ "context": {
238
+ "__prefect_kind": "json",
239
+ "value": {
240
+ "__prefect_kind": "jinja",
241
+ "template": "{{ event.payload['context'] | tojson }}",
242
+ },
243
+ },
244
+ },
245
+ ),
246
+ ],
247
+ )
248
+
249
+ return definition
250
+
251
+
252
+ class ComputedAttrPythonQueryTriggerDefinition(TriggerBranchDefinition):
253
+ type: TriggerType = TriggerType.COMPUTED_ATTR_PYTHON_QUERY
254
+
255
+ @classmethod
256
+ def from_object(
257
+ cls,
258
+ branch: str,
259
+ kind: str,
260
+ computed_attribute: PythonTransformComputedAttribute,
261
+ branches_out_of_scope: list[str] | None = None,
262
+ ) -> Self:
263
+ # Only matching on node updated events, before nodes are created they won't be a member of the GraphQL query
264
+ # group regardless so it doesn't make sense to trigger the query on node creation. For the initial object
265
+ # where the computed attribute belongs that to will need to be created first which will trigger its own initial
266
+ # process to populated the original members of the GraphQL query group
267
+ event_trigger = EventTrigger(events={NodeUpdatedEvent.event_name})
268
+ event_trigger.match = {
269
+ "infrahub.node.kind": kind,
270
+ }
271
+
272
+ update_fields = computed_attribute.query_analyzer.query_report.fields_by_kind(kind=kind)
273
+ event_trigger.match_related = {
274
+ "prefect.resource.role": ["infrahub.node.attribute_update", "infrahub.node.relationship_update"],
275
+ }
276
+
277
+ if update_fields and "display_label" not in update_fields:
278
+ # The GraphQLQuery analyzer doesn't yet support figuring out which updates would match the "display label"
279
+ # of a query. Because of this we temporarily match any field if the display_label is part of the computed
280
+ # attribute query
281
+ event_trigger.match_related["infrahub.field.name"] = update_fields
282
+
283
+ if branches_out_of_scope:
284
+ event_trigger.match["infrahub.branch.name"] = [f"!{branch}" for branch in branches_out_of_scope]
285
+ elif not branches_out_of_scope and branch != registry.default_branch:
286
+ event_trigger.match["infrahub.branch.name"] = branch
287
+
288
+ definition = cls(
289
+ name=f"{computed_attribute.computed_attribute.key_name}{NAME_SEPARATOR}kind{NAME_SEPARATOR}{kind}",
290
+ branch=branch,
291
+ trigger=event_trigger,
292
+ actions=[
293
+ ExecuteWorkflow(
294
+ workflow=QUERY_COMPUTED_ATTRIBUTE_TRANSFORM_TARGETS,
295
+ parameters={
296
+ "branch_name": "{{ event.resource['infrahub.branch.name'] }}",
297
+ "node_kind": "{{ event.resource['infrahub.node.kind'] }}",
298
+ "object_id": "{{ event.resource['infrahub.node.id'] }}",
299
+ "context": {
300
+ "__prefect_kind": "json",
301
+ "value": {
302
+ "__prefect_kind": "jinja",
303
+ "template": "{{ event.payload['context'] | tojson }}",
304
+ },
305
+ },
306
+ },
307
+ ),
308
+ ],
309
+ )
310
+
311
+ return definition