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,65 @@
1
+ from pydantic import BaseModel
2
+
3
+ from .constants import InfrahubType
4
+
5
+
6
+ class TelemetryWorkerData(BaseModel):
7
+ total: int
8
+ active: int
9
+
10
+
11
+ class TelemetryBranchData(BaseModel):
12
+ total: int
13
+
14
+
15
+ class TelemetrySchemaData(BaseModel):
16
+ node_count: int
17
+ generic_count: int
18
+ last_update: str
19
+
20
+
21
+ class TelemetryDatabaseServerData(BaseModel):
22
+ name: str
23
+ version: str
24
+
25
+
26
+ class TelemetryDatabaseSystemInfoData(BaseModel):
27
+ memory_total: int
28
+ memory_available: int
29
+ processor_available: int
30
+
31
+
32
+ class TelemetryDatabaseData(BaseModel):
33
+ database_type: str
34
+ relationship_count: dict[str, int]
35
+ node_count: dict[str, int]
36
+ servers: list[TelemetryDatabaseServerData]
37
+ system_info: TelemetryDatabaseSystemInfoData | None
38
+
39
+
40
+ class TelemetryWorkPoolData(BaseModel):
41
+ name: str
42
+ type: str
43
+ total_workers: int
44
+ active_workers: int
45
+
46
+
47
+ class TelemetryPrefectData(BaseModel):
48
+ events: dict[str, int]
49
+ automations: dict[str, int]
50
+ work_pools: list[TelemetryWorkPoolData]
51
+
52
+
53
+ class TelemetryData(BaseModel):
54
+ deployment_id: str | None
55
+ execution_time: float | None
56
+ infrahub_version: str
57
+ infrahub_type: InfrahubType
58
+ python_version: str
59
+ platform: str
60
+ workers: TelemetryWorkerData
61
+ branches: TelemetryBranchData
62
+ features: dict[str, int]
63
+ schema_info: TelemetrySchemaData
64
+ database: TelemetryDatabaseData
65
+ prefect: TelemetryPrefectData
@@ -0,0 +1,77 @@
1
+ from typing import Any
2
+
3
+ from prefect import task
4
+ from prefect.cache_policies import NONE
5
+ from prefect.client.orchestration import PrefectClient, get_client
6
+ from prefect.client.schemas.objects import WorkerStatus
7
+
8
+ from infrahub.events.utils import get_all_events
9
+ from infrahub.trigger.constants import NAME_SEPARATOR
10
+ from infrahub.trigger.models import TriggerType
11
+
12
+ from .models import TelemetryPrefectData, TelemetryWorkPoolData
13
+
14
+
15
+ @task(name="telemetry-gather-work-pools", task_run_name="Gather Work Pools", cache_policy=NONE)
16
+ async def gather_prefect_work_pools(client: PrefectClient) -> list[TelemetryWorkPoolData]:
17
+ work_pools = await client.read_work_pools()
18
+ data: list[TelemetryWorkPoolData] = []
19
+
20
+ for pool in work_pools:
21
+ workers = await client.read_workers_for_work_pool(work_pool_name=pool.name)
22
+ data.append(
23
+ TelemetryWorkPoolData(
24
+ name=pool.name,
25
+ type=pool.type,
26
+ total_workers=len(workers),
27
+ active_workers=len([item for item in workers if item.status == WorkerStatus.ONLINE]),
28
+ )
29
+ )
30
+
31
+ return data
32
+
33
+
34
+ @task(name="telemetry-gather-events", task_run_name="Gather Events", cache_policy=NONE)
35
+ async def gather_prefect_events(client: PrefectClient) -> dict[str, Any]:
36
+ infrahub_events = get_all_events()
37
+ events: dict[str, int] = {}
38
+
39
+ async def count_events(event_name: str) -> int:
40
+ payload = {"filter": {"event": {"name": [event_name]}}}
41
+ response = await client._client.post("/events/count-by/event", json=payload)
42
+ response.raise_for_status()
43
+ data = response.json()
44
+ if not isinstance(data, list) or len(data) == 0:
45
+ return 0
46
+ return data[0]["count"]
47
+
48
+ for event in infrahub_events:
49
+ events[event.event_name] = await count_events(event_name=event.event_name)
50
+
51
+ return events
52
+
53
+
54
+ @task(name="telemetry-gather-automations", task_run_name="Gather Automations", cache_policy=NONE)
55
+ async def gather_prefect_automations(client: PrefectClient) -> dict[str, Any]:
56
+ automations = await client.read_automations()
57
+
58
+ data: dict[str, Any] = {}
59
+
60
+ for trigger_type in TriggerType:
61
+ data[trigger_type.value] = len(
62
+ [item for item in automations if item.name.startswith(f"{trigger_type.value}{NAME_SEPARATOR}")]
63
+ )
64
+
65
+ return data
66
+
67
+
68
+ @task(name="telemetry-gather-prefect-information", task_run_name="Gather Prefect Information", cache_policy=NONE)
69
+ async def gather_prefect_information() -> TelemetryPrefectData:
70
+ async with get_client(sync_client=False) as client:
71
+ data = TelemetryPrefectData(
72
+ work_pools=await gather_prefect_work_pools(client=client),
73
+ events=await gather_prefect_events(client=client),
74
+ automations=await gather_prefect_automations(client=client),
75
+ )
76
+
77
+ return data
@@ -12,44 +12,32 @@ from infrahub import __version__, config
12
12
  from infrahub.core import registry, utils
13
13
  from infrahub.core.branch import Branch
14
14
  from infrahub.core.constants import InfrahubKind
15
- from infrahub.core.graph.schema import GRAPH_SCHEMA
16
15
  from infrahub.services import InfrahubServices
17
16
 
18
- TELEMETRY_KIND: str = "community"
19
- TELEMETRY_VERSION: str = "20240524"
17
+ from .constants import TELEMETRY_KIND, TELEMETRY_VERSION
18
+ from .database import gather_database_information
19
+ from .models import (
20
+ TelemetryBranchData,
21
+ TelemetryData,
22
+ TelemetrySchemaData,
23
+ TelemetryWorkerData,
24
+ )
25
+ from .task_manager import gather_prefect_information
26
+ from .utils import determine_infrahub_type
20
27
 
21
28
 
22
- @task(name="telemetry-gather-db", task_run_name="Gather Database Information", cache_policy=NONE) # type: ignore[arg-type]
23
- async def gather_database_information(service: InfrahubServices) -> dict:
24
- async with service.database.start_session() as db:
25
- data: dict[str, Any] = {
26
- "database_type": db.db_type.value,
27
- "relationship_count": {"total": await utils.count_relationships(db=db)},
28
- "node_count": {"total": await utils.count_nodes(db=db)},
29
- }
30
-
31
- for name in GRAPH_SCHEMA["relationships"]:
32
- data["relationship_count"][name] = await utils.count_relationships(db=db, label=name)
33
-
34
- for name in GRAPH_SCHEMA["nodes"]:
35
- data["node_count"][name] = await utils.count_nodes(db=db, label=name)
36
-
37
- return data
38
-
39
-
40
- @task(name="telemetry-schema-information", task_run_name="Gather Schema Information", cache_policy=NONE) # type: ignore[arg-type]
41
- async def gather_schema_information(branch: Branch) -> dict:
42
- data: dict[str, Any] = {}
29
+ @task(name="telemetry-schema-information", task_run_name="Gather Schema Information", cache_policy=NONE)
30
+ async def gather_schema_information(branch: Branch) -> TelemetrySchemaData:
43
31
  main_schema = registry.schema.get_schema_branch(name=branch.name)
44
- data["node_count"] = len(main_schema.node_names)
45
- data["generic_count"] = len(main_schema.generic_names)
46
- data["last_update"] = branch.schema_changed_at
47
-
48
- return data
32
+ return TelemetrySchemaData(
33
+ node_count=len(main_schema.node_names),
34
+ generic_count=len(main_schema.generic_names),
35
+ last_update=branch.schema_changed_at or "",
36
+ )
49
37
 
50
38
 
51
- @task(name="telemetry-feature-information", task_run_name="Gather Feature Information", cache_policy=NONE) # type: ignore[arg-type]
52
- async def gather_feature_information(service: InfrahubServices) -> dict:
39
+ @task(name="telemetry-feature-information", task_run_name="Gather Feature Information", cache_policy=NONE)
40
+ async def gather_feature_information(service: InfrahubServices) -> dict[str, int]:
53
41
  async with service.database.start_session() as db:
54
42
  data = {}
55
43
  features_to_count = [
@@ -59,7 +47,9 @@ async def gather_feature_information(service: InfrahubServices) -> dict:
59
47
  InfrahubKind.GENERICGROUP,
60
48
  InfrahubKind.PROFILE,
61
49
  InfrahubKind.PROPOSEDCHANGE,
50
+ InfrahubKind.OBJECTTEMPLATE,
62
51
  InfrahubKind.TRANSFORM,
52
+ InfrahubKind.WEBHOOK,
63
53
  ]
64
54
  for kind in features_to_count:
65
55
  data[kind] = await utils.count_nodes(db=db, label=kind)
@@ -67,37 +57,39 @@ async def gather_feature_information(service: InfrahubServices) -> dict:
67
57
  return data
68
58
 
69
59
 
70
- @task(name="telemetry-gather-data", task_run_name="Gather Anonynous Data", cache_policy=NONE) # type: ignore[arg-type]
71
- async def gather_anonymous_telemetry_data(service: InfrahubServices) -> dict:
60
+ @task(name="telemetry-gather-data", task_run_name="Gather Anonynous Data", cache_policy=NONE)
61
+ async def gather_anonymous_telemetry_data(service: InfrahubServices) -> TelemetryData:
72
62
  start_time = time.time()
73
63
 
74
64
  default_branch = registry.get_branch_from_registry()
75
65
  workers = await service.component.list_workers(branch=default_branch.name, schema_hash=False)
76
66
 
77
- data: dict[str, Any] = {
78
- "deployment_id": registry.id,
79
- "execution_time": None,
80
- "infrahub_version": __version__,
81
- "python_version": platform.python_version(),
82
- "platform": platform.machine(),
83
- "workers": {
84
- "total": len(workers),
85
- "active": len([w for w in workers if w.active]),
86
- },
87
- "branches": {
88
- "total": len(registry.branch),
89
- },
90
- "features": await gather_feature_information(service=service),
91
- "schema": await gather_schema_information(branch=default_branch),
92
- "database": await gather_database_information(service=service),
93
- }
94
-
95
- data["execution_time"] = time.time() - start_time
67
+ data = TelemetryData(
68
+ deployment_id=registry.id,
69
+ execution_time=None,
70
+ infrahub_version=__version__,
71
+ infrahub_type=determine_infrahub_type(),
72
+ python_version=platform.python_version(),
73
+ platform=platform.machine(),
74
+ workers=TelemetryWorkerData(
75
+ total=len(workers),
76
+ active=len([w for w in workers if w.active]),
77
+ ),
78
+ branches=TelemetryBranchData(
79
+ total=len(registry.branch),
80
+ ),
81
+ features=await gather_feature_information(service=service),
82
+ schema_info=await gather_schema_information(branch=default_branch),
83
+ database=await gather_database_information(db=service.database),
84
+ prefect=await gather_prefect_information(),
85
+ )
86
+
87
+ data.execution_time = time.time() - start_time
96
88
 
97
89
  return data
98
90
 
99
91
 
100
- @task(name="telemetry-post-data", task_run_name="Upload data", retries=5, cache_policy=NONE) # type: ignore[arg-type]
92
+ @task(name="telemetry-post-data", task_run_name="Upload data", retries=5, cache_policy=NONE)
101
93
  async def post_telemetry_data(service: InfrahubServices, url: str, payload: dict[str, Any]) -> None:
102
94
  """Send the telemetry data to the specified URL, using HTTP POST."""
103
95
  response = await service.http.post(url=url, json=payload)
@@ -114,13 +106,14 @@ async def send_telemetry_push(service: InfrahubServices) -> None:
114
106
  log.info(f"Pushing anonymous telemetry data to {config.SETTINGS.main.telemetry_endpoint}...")
115
107
 
116
108
  data = await gather_anonymous_telemetry_data(service=service)
117
- log.info(f"Anonymous usage telemetry gathered in {data['execution_time']} seconds. | {data}")
109
+ data_dict = data.model_dump(mode="json")
110
+ log.info(f"Anonymous usage telemetry gathered in {data.execution_time} seconds. | {data_dict}")
118
111
 
119
112
  payload = {
120
113
  "kind": TELEMETRY_KIND,
121
114
  "payload_format": TELEMETRY_VERSION,
122
- "data": data,
123
- "checksum": hashlib.sha256(json.dumps(data).encode()).hexdigest(),
115
+ "data": data_dict,
116
+ "checksum": hashlib.sha256(json.dumps(data_dict).encode()).hexdigest(),
124
117
  }
125
118
 
126
119
  await post_telemetry_data(service=service, url=config.SETTINGS.main.telemetry_endpoint, payload=payload)
@@ -0,0 +1,11 @@
1
+ import importlib.metadata
2
+
3
+ from .constants import InfrahubType
4
+
5
+
6
+ def determine_infrahub_type() -> InfrahubType:
7
+ try:
8
+ importlib.metadata.version("infrahub-enterprise")
9
+ return InfrahubType.ENTERPRISE
10
+ except importlib.metadata.PackageNotFoundError:
11
+ return InfrahubType.COMMUNITY
infrahub/trace.py CHANGED
@@ -9,7 +9,7 @@ from opentelemetry.exporter.otlp.proto.http.trace_exporter import (
9
9
  )
10
10
  from opentelemetry.sdk.resources import Resource
11
11
  from opentelemetry.sdk.trace import TracerProvider
12
- from opentelemetry.sdk.trace.export import BatchSpanProcessor, ConsoleSpanExporter
12
+ from opentelemetry.sdk.trace.export import BatchSpanProcessor, ConsoleSpanExporter, SpanExporter
13
13
  from opentelemetry.trace import StatusCode
14
14
 
15
15
  from infrahub.worker import WORKER_IDENTITY
@@ -19,7 +19,7 @@ def get_current_span_with_context() -> trace.Span:
19
19
  return trace.get_current_span()
20
20
 
21
21
 
22
- def get_traceid() -> str:
22
+ def get_traceid() -> str | None:
23
23
  current_span = get_current_span_with_context()
24
24
  trace_id = current_span.get_span_context().trace_id
25
25
  if trace_id == 0:
@@ -63,7 +63,7 @@ def create_tracer_provider(
63
63
  ) -> TracerProvider:
64
64
  # Create a BatchSpanProcessor exporter based on the type
65
65
  if exporter_type == "console":
66
- exporter = ConsoleSpanExporter()
66
+ exporter: SpanExporter = ConsoleSpanExporter()
67
67
  elif exporter_type == "otlp":
68
68
  if not exporter_endpoint:
69
69
  raise ValueError("Exporter type is set to otlp but endpoint is not set")
@@ -76,7 +76,7 @@ def create_tracer_provider(
76
76
 
77
77
  extra_attributes = {}
78
78
  if os.getenv("OTEL_RESOURCE_ATTRIBUTES"):
79
- extra_attributes = dict(attr.split("=") for attr in os.getenv("OTEL_RESOURCE_ATTRIBUTES").split(","))
79
+ extra_attributes = dict(attr.split("=") for attr in os.getenv("OTEL_RESOURCE_ATTRIBUTES", "").split(","))
80
80
 
81
81
  # Resource can be required for some backends, e.g. Jaeger
82
82
  resource = Resource(
@@ -30,7 +30,7 @@ async def transform_python(message: TransformPythonData, service: InfrahubServic
30
30
  location=message.transform_location,
31
31
  data=message.data,
32
32
  client=service.client,
33
- )
33
+ ) # type: ignore[misc]
34
34
 
35
35
  return transformed_data
36
36
 
@@ -49,6 +49,6 @@ async def transform_render_jinja2_template(message: TransformJinjaTemplateData,
49
49
 
50
50
  rendered_template = await repo.render_jinja2_template.with_options(timeout_seconds=message.timeout)(
51
51
  commit=message.commit, location=message.template_location, data={"data": message.data}
52
- )
52
+ ) # type: ignore[misc]
53
53
 
54
54
  return rendered_template
@@ -1,16 +1,13 @@
1
1
  from infrahub.computed_attribute.triggers import (
2
2
  TRIGGER_COMPUTED_ATTRIBUTE_ALL_SCHEMA,
3
- TRIGGER_COMPUTED_ATTRIBUTE_PYTHON_CLEAN_BRANCH,
4
- TRIGGER_COMPUTED_ATTRIBUTE_PYTHON_SETUP_BRANCH,
5
3
  TRIGGER_COMPUTED_ATTRIBUTE_PYTHON_SETUP_COMMIT,
6
4
  )
7
5
  from infrahub.trigger.models import TriggerDefinition
8
- from infrahub.webhook.triggers import TRIGGER_WEBHOOK_SETUP_UPDATE
6
+ from infrahub.webhook.triggers import TRIGGER_WEBHOOK_DELETE, TRIGGER_WEBHOOK_SETUP_UPDATE
9
7
 
10
8
  builtin_triggers: list[TriggerDefinition] = [
11
9
  TRIGGER_COMPUTED_ATTRIBUTE_ALL_SCHEMA,
12
- TRIGGER_COMPUTED_ATTRIBUTE_PYTHON_CLEAN_BRANCH,
13
- TRIGGER_COMPUTED_ATTRIBUTE_PYTHON_SETUP_BRANCH,
14
10
  TRIGGER_COMPUTED_ATTRIBUTE_PYTHON_SETUP_COMMIT,
11
+ TRIGGER_WEBHOOK_DELETE,
15
12
  TRIGGER_WEBHOOK_SETUP_UPDATE,
16
13
  ]
@@ -1,9 +1 @@
1
- DEPRECATED_STATIC_TRIGGER_NAMES = [
2
- "Trigger-branch-create-events", # used in 1.1.x
3
- "Trigger-branch-remove-events", # used in 1.1.x
4
- "Trigger-webhook-update-configuration", # used in 1.1.x
5
- "Trigger-repository-commit-update-event", # used in 1.1.x
6
- "Trigger-schema-update-event", # used in 1.1.x
7
- ]
8
-
9
1
  NAME_SEPARATOR = "::"
@@ -10,6 +10,7 @@ from prefect.events.schemas.automations import Posture
10
10
  from prefect.events.schemas.events import ResourceSpecification
11
11
  from pydantic import BaseModel, Field
12
12
 
13
+ from infrahub import __version__
13
14
  from infrahub.workflows.models import WorkflowDefinition # noqa: TC001
14
15
 
15
16
  from .constants import NAME_SEPARATOR
@@ -21,8 +22,10 @@ if TYPE_CHECKING:
21
22
  class TriggerType(str, Enum):
22
23
  BUILTIN = "builtin"
23
24
  WEBHOOK = "webhook"
25
+ COMPUTED_ATTR_JINJA2 = "computed_attr_jinja2"
26
+ COMPUTED_ATTR_PYTHON = "computed_attr_python"
27
+ COMPUTED_ATTR_PYTHON_QUERY = "computed_attr_python_query"
24
28
  # OBJECT = "object"
25
- # COMPUTED_ATTR = "computed_attr"
26
29
 
27
30
 
28
31
  class EventTrigger(BaseModel):
@@ -93,6 +96,9 @@ class TriggerDefinition(BaseModel):
93
96
  """Return the name of all deployments used by this trigger"""
94
97
  return [action.name for action in self.actions]
95
98
 
99
+ def get_description(self) -> str:
100
+ return f"Automation for Trigger {self.name} of type {self.type.value} (v{__version__})"
101
+
96
102
  def generate_name(self) -> str:
97
103
  return f"{self.type.value}{NAME_SEPARATOR}{self.name}"
98
104
 
@@ -101,5 +107,12 @@ class TriggerDefinition(BaseModel):
101
107
  action.validate_parameters()
102
108
 
103
109
 
110
+ class TriggerBranchDefinition(TriggerDefinition):
111
+ branch: str
112
+
113
+ def generate_name(self) -> str:
114
+ return f"{self.type.value}{NAME_SEPARATOR}{self.branch}{NAME_SEPARATOR}{self.name}"
115
+
116
+
104
117
  class BuiltinTriggerDefinition(TriggerDefinition):
105
118
  type: TriggerType = TriggerType.BUILTIN
@@ -0,0 +1,90 @@
1
+ from typing import TYPE_CHECKING
2
+
3
+ from prefect import get_run_logger, task
4
+ from prefect.automations import AutomationCore
5
+ from prefect.cache_policies import NONE
6
+ from prefect.client.orchestration import PrefectClient
7
+ from prefect.client.schemas.filters import DeploymentFilter, DeploymentFilterName
8
+
9
+ from infrahub.trigger.models import TriggerDefinition
10
+
11
+ from .models import TriggerType
12
+
13
+ if TYPE_CHECKING:
14
+ from uuid import UUID
15
+
16
+
17
+ @task(name="trigger-setup", task_run_name="Setup triggers", cache_policy=NONE) # type: ignore[arg-type]
18
+ async def setup_triggers(
19
+ client: PrefectClient,
20
+ triggers: list[TriggerDefinition],
21
+ trigger_type: TriggerType | None = None,
22
+ ) -> None:
23
+ log = get_run_logger()
24
+
25
+ if trigger_type:
26
+ log.info(f"Setting up triggers of type {trigger_type.value}")
27
+ else:
28
+ log.info("Setting up all triggers")
29
+
30
+ # -------------------------------------------------------------
31
+ # Retrieve existing Deployments and Automation from the server
32
+ # -------------------------------------------------------------
33
+ deployment_names = list({name for trigger in triggers for name in trigger.get_deployment_names()})
34
+ deployments = {
35
+ item.name: item
36
+ for item in await client.read_deployments(
37
+ deployment_filter=DeploymentFilter(name=DeploymentFilterName(any_=deployment_names))
38
+ )
39
+ }
40
+ deployments_mapping: dict[str, UUID] = {name: item.id for name, item in deployments.items()}
41
+ existing_automations = {item.name: item for item in await client.read_automations()}
42
+
43
+ # If a trigger type is provided, narrow down the list of existing triggers to know which one to delete
44
+ if trigger_type:
45
+ trigger_automations = [
46
+ item.name for item in await client.read_automations() if item.name.startswith(trigger_type.value)
47
+ ]
48
+ else:
49
+ trigger_automations = [item.name for item in await client.read_automations()]
50
+
51
+ trigger_names = [trigger.generate_name() for trigger in triggers]
52
+
53
+ log.debug(f"{len(trigger_automations)} existing triggers ({trigger_automations})")
54
+ log.debug(f"{len(trigger_names)} triggers to configure ({trigger_names})")
55
+
56
+ to_delete = set(trigger_automations) - set(trigger_names)
57
+ log.debug(f"{len(trigger_names)} triggers to delete ({to_delete})")
58
+
59
+ # -------------------------------------------------------------
60
+ # Create or Update all triggers
61
+ # -------------------------------------------------------------
62
+ for trigger in triggers:
63
+ automation = AutomationCore(
64
+ name=trigger.generate_name(),
65
+ description=trigger.get_description(),
66
+ enabled=True,
67
+ trigger=trigger.trigger.get_prefect(),
68
+ actions=[action.get_prefect(mapping=deployments_mapping) for action in trigger.actions],
69
+ )
70
+
71
+ existing_automation = existing_automations.get(trigger.generate_name(), None)
72
+
73
+ if existing_automation:
74
+ await client.update_automation(automation_id=existing_automation.id, automation=automation)
75
+ log.info(f"{trigger.generate_name()} Updated")
76
+ else:
77
+ await client.create_automation(automation=automation)
78
+ log.info(f"{trigger.generate_name()} Created")
79
+
80
+ # -------------------------------------------------------------
81
+ # Delete Triggers that shouldn't be there
82
+ # -------------------------------------------------------------
83
+ for item_to_delete in to_delete:
84
+ existing_automation = existing_automations.get(item_to_delete)
85
+
86
+ if not existing_automation:
87
+ continue
88
+
89
+ await client.delete_automation(automation_id=existing_automation.id)
90
+ log.info(f"{item_to_delete} Deleted")
infrahub/trigger/tasks.py CHANGED
@@ -1,91 +1,36 @@
1
- from typing import TYPE_CHECKING
2
-
3
- from prefect import get_run_logger, task
4
- from prefect.automations import AutomationCore
5
- from prefect.cache_policies import NONE
6
- from prefect.client.orchestration import PrefectClient
7
- from prefect.client.schemas.filters import DeploymentFilter, DeploymentFilterName
8
-
9
- from infrahub.trigger.models import TriggerDefinition
10
-
11
- # from .catalogue import triggers
12
- from .models import TriggerType
13
-
14
- if TYPE_CHECKING:
15
- from uuid import UUID
16
-
17
-
18
- @task(name="trigger-setup", task_run_name="Setup triggers of type {trigger_type.value}", cache_policy=NONE) # type: ignore[arg-type]
19
- async def setup_triggers(
20
- client: PrefectClient,
21
- triggers: list[TriggerDefinition],
22
- trigger_type: TriggerType = TriggerType.BUILTIN,
23
- deprecated_triggers: list[str] | None = None,
24
- ) -> None:
25
- log = get_run_logger()
26
-
27
- # -------------------------------------------------------------
28
- # Retrieve existing Deployments and Automation from the server
29
- # -------------------------------------------------------------
30
- deployment_names = list({name for trigger in triggers for name in trigger.get_deployment_names()})
31
- deployments = {
32
- item.name: item
33
- for item in await client.read_deployments(
34
- deployment_filter=DeploymentFilter(name=DeploymentFilterName(any_=deployment_names))
1
+ from prefect import flow
2
+ from prefect.client.orchestration import get_client
3
+
4
+ from infrahub.computed_attribute.gather import (
5
+ gather_trigger_computed_attribute_jinja2,
6
+ gather_trigger_computed_attribute_python,
7
+ )
8
+ from infrahub.services import InfrahubServices # noqa: TC001 needed for prefect flow
9
+ from infrahub.trigger.catalogue import builtin_triggers
10
+ from infrahub.webhook.gather import gather_trigger_webhook
11
+
12
+ from .setup import setup_triggers
13
+
14
+
15
+ @flow(name="trigger-configure-all", flow_run_name="Configure all triggers")
16
+ async def trigger_configure_all(service: InfrahubServices) -> None:
17
+ webhook_trigger = await gather_trigger_webhook(db=service.database)
18
+ computed_attribute_j2_triggers = await gather_trigger_computed_attribute_jinja2()
19
+ (
20
+ computed_attribute_python_triggers,
21
+ computed_attribute_python_query_triggers,
22
+ ) = await gather_trigger_computed_attribute_python(db=service.database)
23
+
24
+ triggers = (
25
+ computed_attribute_j2_triggers
26
+ + computed_attribute_python_triggers
27
+ + computed_attribute_python_query_triggers
28
+ + builtin_triggers
29
+ + webhook_trigger
30
+ )
31
+
32
+ async with get_client(sync_client=False) as prefect_client:
33
+ await setup_triggers(
34
+ client=prefect_client,
35
+ triggers=triggers,
35
36
  )
36
- }
37
- deployments_mapping: dict[str, UUID] = {name: item.id for name, item in deployments.items()}
38
- existing_automations = {item.name: item for item in await client.read_automations()}
39
-
40
- trigger_automations = [
41
- item.name for item in await client.read_automations() if item.name.startswith(trigger_type.value)
42
- ]
43
- trigger_names = [trigger.generate_name() for trigger in triggers]
44
-
45
- to_delete = set(trigger_automations) - set(trigger_names)
46
-
47
- # -------------------------------------------------------------
48
- # Create or Update all triggers
49
- # -------------------------------------------------------------
50
- for trigger in triggers:
51
- automation = AutomationCore(
52
- name=trigger.generate_name(),
53
- description=trigger.description,
54
- enabled=True,
55
- trigger=trigger.trigger.get_prefect(),
56
- actions=[action.get_prefect(mapping=deployments_mapping) for action in trigger.actions],
57
- )
58
-
59
- existing_automation = existing_automations.get(trigger.name, None)
60
-
61
- if existing_automation:
62
- await client.update_automation(automation_id=existing_automation.id, automation=automation)
63
- log.info(f"{trigger.name} Updated")
64
- else:
65
- await client.create_automation(automation=automation)
66
- log.info(f"{trigger.name} Created")
67
-
68
- # -------------------------------------------------------------
69
- # Delete Triggers that shouldn't be there
70
- # -------------------------------------------------------------
71
- for item_to_delete in to_delete:
72
- existing_automation = existing_automations.get(item_to_delete)
73
-
74
- if not existing_automation:
75
- continue
76
-
77
- await client.delete_automation(automation_id=existing_automation.id)
78
- log.info(f"{item_to_delete} Deleted")
79
-
80
- # -------------------------------------------------------------
81
- # Delete Deprecated triggers
82
- # -------------------------------------------------------------
83
- if deprecated_triggers:
84
- for trigger_name in deprecated_triggers:
85
- existing_automation = existing_automations.get(trigger_name)
86
-
87
- if not existing_automation:
88
- continue
89
-
90
- await client.delete_automation(automation_id=existing_automation.id)
91
- log.info(f"{trigger_name} Deleted")