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,107 @@
1
+ from dataclasses import dataclass
2
+
3
+ from infrahub.core.branch import Branch
4
+ from infrahub.core.changelog.models import RelationshipCardinalityManyChangelog
5
+ from infrahub.core.constants import DiffAction, InfrahubKind
6
+ from infrahub.core.schema import NodeSchema
7
+ from infrahub.database import InfrahubDatabase
8
+ from infrahub.events.group_action import GroupMemberAddedEvent, GroupMemberRemovedEvent, GroupMutatedEvent
9
+ from infrahub.events.models import EventMeta, EventNode, InfrahubEvent
10
+ from infrahub.events.node_action import NodeCreatedEvent, NodeMutatedEvent, NodeUpdatedEvent
11
+ from infrahub.exceptions import SchemaNotFoundError
12
+
13
+ from .ancestors import collect_ancestors
14
+
15
+
16
+ @dataclass
17
+ class ApplicableEvent:
18
+ event: NodeMutatedEvent
19
+ node_schema: NodeSchema
20
+ relationship: RelationshipCardinalityManyChangelog
21
+
22
+
23
+ class GroupNodeMutationParser:
24
+ def __init__(self, db: InfrahubDatabase, branch: Branch) -> None:
25
+ self._db = db
26
+ self._branch = branch
27
+
28
+ def _get_schema(self, kind: str) -> NodeSchema | None:
29
+ try:
30
+ node_schema = self._db.schema.get_node_schema(name=kind, branch=self._branch, duplicate=False)
31
+ except (ValueError, SchemaNotFoundError):
32
+ return None
33
+ return node_schema
34
+
35
+ def _get_applicable_events(self, events: list[NodeMutatedEvent]) -> list[ApplicableEvent]:
36
+ applicable: list[ApplicableEvent] = []
37
+ for event in events:
38
+ if event_kind := self._get_schema(kind=event.kind):
39
+ if (
40
+ InfrahubKind.GENERICGROUP in event_kind.inherit_from
41
+ and "members" in event.changelog.relationships
42
+ and isinstance(
43
+ event.changelog.relationships["members"],
44
+ RelationshipCardinalityManyChangelog,
45
+ )
46
+ ):
47
+ applicable.append(
48
+ ApplicableEvent(
49
+ event=event,
50
+ node_schema=event_kind,
51
+ relationship=event.changelog.relationships["members"],
52
+ )
53
+ )
54
+ return applicable
55
+
56
+ async def group_events_from_node_actions(self, events: list[NodeMutatedEvent]) -> list[InfrahubEvent]:
57
+ group_events: list[InfrahubEvent] = []
58
+
59
+ for applicable_event in self._get_applicable_events(events=events):
60
+ added_peers = [
61
+ EventNode(id=peer.peer_id, kind=peer.peer_kind)
62
+ for peer in applicable_event.relationship.peers
63
+ if peer.peer_status == DiffAction.ADDED
64
+ ]
65
+ removed_peers = [
66
+ EventNode(id=peer.peer_id, kind=peer.peer_kind)
67
+ for peer in applicable_event.relationship.peers
68
+ if peer.peer_status == DiffAction.REMOVED
69
+ ]
70
+ if added_peers:
71
+ group_events.append(
72
+ await self._define_group_event(
73
+ event_type=GroupMemberAddedEvent, parent=applicable_event.event, peers=added_peers
74
+ )
75
+ )
76
+
77
+ if removed_peers:
78
+ group_events.append(
79
+ await self._define_group_event(
80
+ event_type=GroupMemberRemovedEvent, parent=applicable_event.event, peers=removed_peers
81
+ )
82
+ )
83
+
84
+ return group_events
85
+
86
+ async def _define_group_event(
87
+ self,
88
+ event_type: type[GroupMemberAddedEvent | GroupMemberRemovedEvent],
89
+ parent: NodeMutatedEvent,
90
+ peers: list[EventNode],
91
+ ) -> GroupMutatedEvent:
92
+ event_meta = EventMeta.from_parent(parent=parent)
93
+ group_event = event_type(
94
+ meta=event_meta,
95
+ kind=parent.kind,
96
+ node_id=parent.node_id,
97
+ members=peers,
98
+ )
99
+ if isinstance(parent, NodeCreatedEvent | NodeUpdatedEvent):
100
+ # Avoid trying to find ancestors for deleted nodes
101
+ group_event.ancestors = await collect_ancestors(
102
+ db=self._db,
103
+ branch=self._branch,
104
+ node_kind=parent.kind,
105
+ node_id=parent.node_id,
106
+ )
107
+ return group_event
@@ -70,7 +70,7 @@ async def generate_menu(db: InfrahubDatabase, branch: Branch, menu_items: list[C
70
70
  menu_item = structure.find_item(name=parent_full_name)
71
71
  if menu_item:
72
72
  child_item = MenuItemDict.from_node(obj=item)
73
- menu_item.children[child_item.identifier] = child_item
73
+ menu_item.children[str(child_item.identifier)] = child_item
74
74
  else:
75
75
  log.warning(
76
76
  "new_menu_request: unable to find the parent menu item",
@@ -90,20 +90,20 @@ async def generate_menu(db: InfrahubDatabase, branch: Branch, menu_items: list[C
90
90
 
91
91
  schema = full_schema[item_name]
92
92
  menu_item = MenuItemDict.from_schema(model=schema)
93
- already_in_schema = bool(structure.find_item(name=menu_item.identifier))
93
+ already_in_schema = bool(structure.find_item(name=str(menu_item.identifier)))
94
94
  if already_in_schema:
95
95
  items_to_add[item_name] = True
96
96
  continue
97
97
 
98
98
  if not schema.menu_placement:
99
99
  first_element = MenuItemDict.from_schema(model=schema)
100
- first_element.identifier = f"{first_element.identifier}Sub"
100
+ first_element.name = f"{first_element.name}Sub"
101
101
  first_element.order_weight = 1
102
- menu_item.children[first_element.identifier] = first_element
103
- structure.data[menu_item.identifier] = menu_item
102
+ menu_item.children[str(first_element.identifier)] = first_element
103
+ structure.data[str(menu_item.identifier)] = menu_item
104
104
  items_to_add[item_name] = True
105
105
  elif menu_placement := structure.find_item(name=schema.menu_placement):
106
- menu_placement.children[menu_item.identifier] = menu_item
106
+ menu_placement.children[str(menu_item.identifier)] = menu_item
107
107
  items_to_add[item_name] = True
108
108
  continue
109
109
 
@@ -130,7 +130,7 @@ async def generate_menu(db: InfrahubDatabase, branch: Branch, menu_items: list[C
130
130
  item=schema.kind,
131
131
  menu_placement=schema.menu_placement,
132
132
  )
133
- default_menu.children[menu_item.identifier] = menu_item
133
+ default_menu.children[str(menu_item.identifier)] = menu_item
134
134
  items_to_add[item_name] = True
135
135
 
136
136
  return structure
infrahub/menu/menu.py CHANGED
@@ -185,16 +185,6 @@ default_menu = [
185
185
  section=MenuSection.INTERNAL,
186
186
  order_weight=3500,
187
187
  ),
188
- MenuItemDefinition(
189
- namespace="Builtin",
190
- name="ActivityLogs",
191
- label="Activity Logs",
192
- path="/activities",
193
- icon="mdi:format-list-bulleted",
194
- protected=True,
195
- section=MenuSection.INTERNAL,
196
- order_weight=4000,
197
- ),
198
188
  ],
199
189
  ),
200
190
  MenuItemDefinition(
infrahub/menu/models.py CHANGED
@@ -1,9 +1,9 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from dataclasses import dataclass, field
4
- from typing import TYPE_CHECKING
4
+ from typing import TYPE_CHECKING, Any
5
5
 
6
- from pydantic import BaseModel, Field
6
+ from pydantic import BaseModel, Field, computed_field
7
7
  from typing_extensions import Self
8
8
 
9
9
  from infrahub.core.account import GlobalPermission
@@ -35,21 +35,26 @@ def _get_full_name_schema(node: MainSchemaTypes) -> str:
35
35
  class MenuDict:
36
36
  data: dict[str, MenuItemDict] = field(default_factory=dict)
37
37
 
38
+ def get_item_location(self, name: str) -> list[str]:
39
+ location, _ = self._find_child_item(name=name, children=self.data)
40
+ return location
41
+
38
42
  def find_item(self, name: str) -> MenuItemDict | None:
39
- return self._find_child_item(name=name, children=self.data)
43
+ _, item = self._find_child_item(name=name, children=self.data)
44
+ return item
40
45
 
41
46
  @classmethod
42
- def _find_child_item(cls, name: str, children: dict[str, MenuItemDict]) -> MenuItemDict | None:
47
+ def _find_child_item(cls, name: str, children: dict[str, MenuItemDict]) -> tuple[list[str], MenuItemDict | None]:
43
48
  if name in children.keys():
44
- return children[name]
49
+ return [], children[name]
45
50
 
46
51
  for child in children.values():
47
52
  if not child.children:
48
53
  continue
49
- found = cls._find_child_item(name=name, children=child.children)
54
+ position, found = cls._find_child_item(name=name, children=child.children)
50
55
  if found:
51
- return found
52
- return None
56
+ return [str(child.identifier)] + position, found
57
+ return [], None
53
58
 
54
59
  def to_rest(self) -> Menu:
55
60
  data: dict[str, list[MenuItemList]] = {}
@@ -62,10 +67,15 @@ class MenuDict:
62
67
 
63
68
  return Menu(sections=data)
64
69
 
65
- # @staticmethod
66
- # def _sort_menu_items(items: dict[str, MenuItem]) -> dict[str, MenuItem]:
67
- # sorted_dict = dict(sorted(items.items(), key=lambda x: (x[1].order_weight, x[0]), reverse=False))
68
- # return sorted_dict
70
+ @classmethod
71
+ def from_definition_list(cls, definitions: list[MenuItemDefinition]) -> Self:
72
+ menu = cls()
73
+ for definition in definitions:
74
+ menu.data[definition.full_name] = MenuItemDict.from_definition(definition=definition)
75
+ return menu
76
+
77
+ def get_all_identifiers(self) -> set[str]:
78
+ return {identifier for item in self.data.values() for identifier in item.get_all_identifiers()}
69
79
 
70
80
 
71
81
  @dataclass
@@ -74,7 +84,11 @@ class Menu:
74
84
 
75
85
 
76
86
  class MenuItem(BaseModel):
77
- identifier: str = Field(..., description="Unique identifier for this menu item")
87
+ id: str | None = None
88
+ namespace: str = Field(..., description="Namespace of the menu item")
89
+ name: str = Field(..., description="Name of the menu item")
90
+ description: str = Field(default="", description="Description of the menu item")
91
+ protected: bool = Field(default=False, description="Whether the menu item is protected")
78
92
  label: str = Field(..., description="Title of the menu item")
79
93
  path: str = Field(default="", description="URL endpoint if applicable")
80
94
  icon: str = Field(default="", description="The icon to show for the current view")
@@ -83,10 +97,27 @@ class MenuItem(BaseModel):
83
97
  section: MenuSection = MenuSection.OBJECT
84
98
  permissions: list[str] = Field(default_factory=list)
85
99
 
100
+ @computed_field
101
+ def identifier(self) -> str:
102
+ return f"{self.namespace}{self.name}"
103
+
104
+ def get_path(self) -> str | None:
105
+ if self.path:
106
+ return self.path
107
+
108
+ if self.kind:
109
+ return f"/objects/{self.kind}"
110
+
111
+ return None
112
+
86
113
  @classmethod
87
114
  def from_node(cls, obj: CoreMenuItem) -> Self:
88
115
  return cls(
89
- identifier=get_full_name(obj),
116
+ id=obj.get_id(),
117
+ name=obj.name.value,
118
+ namespace=obj.namespace.value,
119
+ protected=obj.protected.value,
120
+ description=obj.description.value or "",
90
121
  label=obj.label.value or "",
91
122
  icon=obj.icon.value or "",
92
123
  order_weight=obj.order_weight.value,
@@ -96,10 +127,30 @@ class MenuItem(BaseModel):
96
127
  permissions=obj.required_permissions.value or [],
97
128
  )
98
129
 
130
+ async def to_node(self, db: InfrahubDatabase, parent: CoreMenuItem | None = None) -> CoreMenuItem:
131
+ obj = await Node.init(db=db, schema=CoreMenuItem)
132
+ await obj.new(
133
+ db=db,
134
+ namespace=self.namespace,
135
+ name=self.name,
136
+ label=self.label,
137
+ kind=self.kind,
138
+ path=self.get_path(),
139
+ description=self.description or None,
140
+ icon=self.icon or None,
141
+ protected=self.protected,
142
+ section=self.section.value,
143
+ order_weight=self.order_weight,
144
+ parent=parent.id if parent else None,
145
+ required_permissions=self.permissions,
146
+ )
147
+ return obj
148
+
99
149
  @classmethod
100
150
  def from_schema(cls, model: NodeSchema | GenericSchema | ProfileSchema | TemplateSchema) -> Self:
101
151
  return cls(
102
- identifier=get_full_name(model),
152
+ name=model.name,
153
+ namespace=model.namespace,
103
154
  label=model.label or model.kind,
104
155
  path=f"/objects/{model.kind}",
105
156
  icon=model.icon or "",
@@ -111,8 +162,14 @@ class MenuItemDict(MenuItem):
111
162
  hidden: bool = False
112
163
  children: dict[str, MenuItemDict] = Field(default_factory=dict, description="Child objects")
113
164
 
165
+ def get_all_identifiers(self) -> set[str]:
166
+ identifiers: set[str] = {str(self.identifier)}
167
+ for child in self.children.values():
168
+ identifiers.update(child.get_all_identifiers())
169
+ return identifiers
170
+
114
171
  def to_list(self) -> MenuItemList:
115
- data = self.model_dump(exclude={"children"})
172
+ data = self.model_dump(exclude={"children", "id"})
116
173
  unsorted_children = [child.to_list() for child in self.children.values() if child.hidden is False]
117
174
  data["children"] = sorted(unsorted_children, key=lambda d: d.order_weight)
118
175
  return MenuItemList(**data)
@@ -125,6 +182,35 @@ class MenuItemDict(MenuItem):
125
182
  permissions.append(GlobalPermission.from_string(input=permission))
126
183
  return permissions
127
184
 
185
+ def diff_attributes(self, other: Self) -> dict[str, Any]:
186
+ other_attributes = other.model_dump(exclude={"children"})
187
+ self_attributes = self.model_dump(exclude={"children"})
188
+ return {
189
+ key: value
190
+ for key, value in other_attributes.items()
191
+ if value != self_attributes[key] and key not in ["id", "children"]
192
+ }
193
+
194
+ @classmethod
195
+ def from_definition(cls, definition: MenuItemDefinition) -> Self:
196
+ menu_item = cls(
197
+ name=definition.name,
198
+ namespace=definition.namespace,
199
+ label=definition.label,
200
+ path=definition.get_path() or "",
201
+ icon=definition.icon,
202
+ kind=definition.kind,
203
+ protected=definition.protected,
204
+ section=definition.section,
205
+ permissions=definition.permissions,
206
+ order_weight=definition.order_weight,
207
+ )
208
+
209
+ for child in definition.children:
210
+ menu_item.children[child.full_name] = MenuItemDict.from_definition(definition=child)
211
+
212
+ return menu_item
213
+
128
214
 
129
215
  class MenuItemList(MenuItem):
130
216
  children: list[MenuItemList] = Field(default_factory=list, description="Child objects")
@@ -162,6 +248,21 @@ class MenuItemDefinition(BaseModel):
162
248
  )
163
249
  return obj
164
250
 
251
+ @classmethod
252
+ async def from_node(cls, node: CoreMenuItem) -> Self:
253
+ return cls(
254
+ namespace=node.namespace.value,
255
+ name=node.name.value,
256
+ label=node.label.value or "",
257
+ description=node.description.value or "",
258
+ icon=node.icon.value or "",
259
+ protected=node.protected.value,
260
+ path=node.path.value or "",
261
+ kind=node.kind.value or "",
262
+ section=node.section.value,
263
+ order_weight=node.order_weight.value,
264
+ )
265
+
165
266
  def get_path(self) -> str | None:
166
267
  if self.path:
167
268
  return self.path
@@ -0,0 +1,111 @@
1
+ from infrahub.core.manager import NodeManager
2
+ from infrahub.core.protocols import CoreMenuItem
3
+ from infrahub.database import InfrahubDatabase
4
+
5
+ from .models import MenuDict, MenuItemDefinition, MenuItemDict
6
+
7
+
8
+ class MenuRepository:
9
+ def __init__(self, db: InfrahubDatabase):
10
+ self.db = db
11
+
12
+ async def get_menu(self, nodes: dict[str, CoreMenuItem] | None = None) -> MenuDict:
13
+ menu_nodes = nodes or await self.get_menu_db()
14
+ return await self._convert_menu_from_db(nodes=menu_nodes)
15
+
16
+ async def _convert_menu_from_db(self, nodes: dict[str, CoreMenuItem]) -> MenuDict:
17
+ menu = MenuDict()
18
+ menu_by_ids = {menu_node.get_id(): MenuItemDict.from_node(menu_node) for menu_node in nodes.values()}
19
+
20
+ async def add_children(menu_item: MenuItemDict, menu_node: CoreMenuItem) -> MenuItemDict:
21
+ children = await menu_node.children.get_peers(db=self.db, peer_type=CoreMenuItem)
22
+ for child_id, child_node in children.items():
23
+ child_menu_item = menu_by_ids[child_id]
24
+ child = await add_children(child_menu_item, child_node)
25
+ menu_item.children[str(child.identifier)] = child
26
+ return menu_item
27
+
28
+ for menu_node in nodes.values():
29
+ menu_item = menu_by_ids[menu_node.get_id()]
30
+ parent = await menu_node.parent.get_peer(db=self.db, peer_type=CoreMenuItem)
31
+ if parent:
32
+ continue
33
+
34
+ children = await menu_node.children.get_peers(db=self.db, peer_type=CoreMenuItem)
35
+ for child_id, child_node in children.items():
36
+ child_menu_item = menu_by_ids[child_id]
37
+ child = await add_children(child_menu_item, child_node)
38
+ menu_item.children[str(child.identifier)] = child
39
+
40
+ menu.data[str(menu_item.identifier)] = menu_item
41
+
42
+ return menu
43
+
44
+ async def get_menu_db(self) -> dict[str, CoreMenuItem]:
45
+ menu_nodes = await NodeManager.query(
46
+ schema=CoreMenuItem,
47
+ filters={"namespace__value": "Builtin"},
48
+ prefetch_relationships=True,
49
+ db=self.db,
50
+ )
51
+ return {node.get_id(): node for node in menu_nodes}
52
+
53
+ async def create_menu(self, menu: list[MenuItemDefinition]) -> None:
54
+ for item in menu:
55
+ obj = await item.to_node(db=self.db)
56
+ await obj.save(db=self.db)
57
+ if item.children:
58
+ await self.create_menu_children(parent=obj, children=item.children)
59
+
60
+ async def create_menu_children(self, parent: CoreMenuItem, children: list[MenuItemDefinition]) -> None:
61
+ for child in children:
62
+ obj = await child.to_node(db=self.db, parent=parent)
63
+ await obj.save(db=self.db)
64
+ if child.children:
65
+ await self.create_menu_children(parent=obj, children=child.children)
66
+
67
+ async def update_menu(
68
+ self, existing_menu: MenuDict, new_menu: MenuDict, menu_nodes: dict[str, CoreMenuItem]
69
+ ) -> None:
70
+ async def process_menu_item(menu_item: MenuItemDict, parent: CoreMenuItem | None) -> None:
71
+ existing_item = existing_menu.find_item(name=str(menu_item.identifier))
72
+ if existing_item and existing_item.id:
73
+ node = menu_nodes[existing_item.id]
74
+ await self.update_menu_item(
75
+ node=node, existing_menu_item=existing_item, new_menu_item=menu_item, parent=parent
76
+ )
77
+ else:
78
+ node = await self.create_menu_item(new_menu_item=menu_item, parent=parent)
79
+
80
+ for child_item in menu_item.children.values():
81
+ await process_menu_item(menu_item=child_item, parent=node)
82
+
83
+ for top_level_item in new_menu.data.values():
84
+ await process_menu_item(menu_item=top_level_item, parent=None)
85
+
86
+ # Delete items that are not in the new menu
87
+ menu_to_delete = existing_menu.get_all_identifiers() - new_menu.get_all_identifiers()
88
+ for item_to_delete in menu_to_delete:
89
+ existing_item = existing_menu.find_item(name=item_to_delete)
90
+ if existing_item and existing_item.id:
91
+ node = menu_nodes[existing_item.id]
92
+ await node.delete(db=self.db)
93
+
94
+ async def update_menu_item(
95
+ self,
96
+ node: CoreMenuItem,
97
+ existing_menu_item: MenuItemDict,
98
+ new_menu_item: MenuItemDict,
99
+ parent: CoreMenuItem | None,
100
+ ) -> None:
101
+ attrs_to_update = existing_menu_item.diff_attributes(new_menu_item)
102
+ for attr_name, value in attrs_to_update.items():
103
+ attr = getattr(node, attr_name)
104
+ attr.value = value
105
+ await node.parent.update(data=parent, db=self.db) # type: ignore[arg-type]
106
+ await node.save(db=self.db)
107
+
108
+ async def create_menu_item(self, new_menu_item: MenuItemDict, parent: CoreMenuItem | None) -> CoreMenuItem:
109
+ obj = await new_menu_item.to_node(db=self.db, parent=parent)
110
+ await obj.save(db=self.db)
111
+ return obj
infrahub/menu/utils.py CHANGED
@@ -1,12 +1,9 @@
1
- from infrahub.core.protocols import CoreMenuItem
2
1
  from infrahub.database import InfrahubDatabase
3
2
 
4
- from .models import MenuItemDefinition
3
+ from .menu import default_menu
4
+ from .repository import MenuRepository
5
5
 
6
6
 
7
- async def create_menu_children(db: InfrahubDatabase, parent: CoreMenuItem, children: list[MenuItemDefinition]) -> None:
8
- for child in children:
9
- obj = await child.to_node(db=db, parent=parent)
10
- await obj.save(db=db)
11
- if child.children:
12
- await create_menu_children(db=db, parent=obj, children=child.children)
7
+ async def create_default_menu(db: InfrahubDatabase) -> None:
8
+ repository = MenuRepository(db=db)
9
+ await repository.create_menu(menu=default_menu)
@@ -1,12 +1,7 @@
1
1
  from infrahub.message_bus import InfrahubMessage, InfrahubResponse
2
2
 
3
3
  from .check_generator_run import CheckGeneratorRun
4
- from .event_branch_create import EventBranchCreate
5
- from .event_branch_delete import EventBranchDelete
6
4
  from .event_branch_merge import EventBranchMerge
7
- from .event_branch_rebased import EventBranchRebased
8
- from .event_node_mutated import EventNodeMutated
9
- from .event_schema_update import EventSchemaUpdate
10
5
  from .event_worker_newprimaryapi import EventWorkerNewPrimaryAPI
11
6
  from .finalize_validator_execution import FinalizeValidatorExecution
12
7
  from .git_file_get import GitFileGet, GitFileGetResponse
@@ -21,12 +16,7 @@ from .send_echo_request import SendEchoRequest, SendEchoRequestResponse
21
16
 
22
17
  MESSAGE_MAP: dict[str, type[InfrahubMessage]] = {
23
18
  "check.generator.run": CheckGeneratorRun,
24
- "event.branch.create": EventBranchCreate,
25
- "event.branch.delete": EventBranchDelete,
26
19
  "event.branch.merge": EventBranchMerge,
27
- "event.branch.rebased": EventBranchRebased,
28
- "event.node.mutated": EventNodeMutated,
29
- "event.schema.update": EventSchemaUpdate,
30
20
  "event.worker.new_primary_api": EventWorkerNewPrimaryAPI,
31
21
  "finalize.validator.execution": FinalizeValidatorExecution,
32
22
  "git.file.get": GitFileGet,
@@ -49,7 +39,7 @@ PRIORITY_MAP = {
49
39
  "check.artifact.create": 2,
50
40
  "check.repository.check_definition": 2,
51
41
  "check.repository.merge_conflicts": 2,
52
- "event.branch.create": 5,
42
+ "send.echo.request": 5, # Currently only for testing purposes, will be removed once all message bus have been migrated to prefect
53
43
  "event.branch.delete": 5,
54
44
  "event.branch.merge": 5,
55
45
  "event.schema.update": 5,
@@ -1,5 +1,6 @@
1
1
  from pydantic import Field
2
2
 
3
+ from infrahub.context import InfrahubContext
3
4
  from infrahub.generators.models import ProposedChangeGeneratorDefinition
4
5
  from infrahub.message_bus import InfrahubMessage
5
6
 
@@ -22,3 +23,4 @@ class CheckGeneratorRun(InfrahubMessage):
22
23
  variables: dict = Field(..., description="Input variables when running the generator")
23
24
  validator_id: str = Field(..., description="The ID of the validator")
24
25
  proposed_change: str | None = Field(None, description="The unique ID of the Proposed Change")
26
+ context: InfrahubContext = Field(..., description="The Infrahub context")
@@ -1,5 +1,6 @@
1
1
  from pydantic import Field
2
2
 
3
+ from infrahub.context import InfrahubContext
3
4
  from infrahub.message_bus import InfrahubMessage
4
5
 
5
6
 
@@ -10,3 +11,5 @@ class FinalizeValidatorExecution(InfrahubMessage):
10
11
  validator_execution_id: str = Field(..., description="The id of current execution of the associated validator")
11
12
  start_time: str = Field(..., description="Start time when the message was first created")
12
13
  validator_type: str = Field(..., description="The type of validator to complete")
14
+ context: InfrahubContext = Field(..., description="The Infrahub context")
15
+ proposed_change: str = Field(..., description="The ID of the proposed change")
@@ -1,5 +1,6 @@
1
1
  from pydantic import ConfigDict, Field
2
2
 
3
+ from infrahub.context import InfrahubContext
3
4
  from infrahub.generators.models import ProposedChangeGeneratorDefinition
4
5
  from infrahub.message_bus import InfrahubMessage
5
6
  from infrahub.message_bus.types import ProposedChangeBranchDiff
@@ -16,3 +17,4 @@ class RequestGeneratorDefinitionCheck(InfrahubMessage):
16
17
  source_branch: str = Field(..., description="The source branch")
17
18
  source_branch_sync_with_git: bool = Field(..., description="Indicates if the source branch should sync with git")
18
19
  destination_branch: str = Field(..., description="The target branch")
20
+ context: InfrahubContext = Field(..., description="The Infrahub context")
@@ -18,8 +18,6 @@ from infrahub.tasks.check import set_check_status
18
18
  COMMAND_MAP = {
19
19
  "check.generator.run": check.generator.run,
20
20
  "event.branch.merge": event.branch.merge,
21
- "event.node.mutated": event.node.mutated,
22
- "event.schema.update": event.schema.update,
23
21
  "event.worker.new_primary_api": event.worker.new_primary_api,
24
22
  "finalize.validator.execution": finalize.validator.execution,
25
23
  "git.file.get": git.file.get,
@@ -72,6 +72,7 @@ async def run(message: messages.CheckGeneratorRun, service: InfrahubServices) ->
72
72
  convert_query_response=generator_definition.convert_query_response,
73
73
  infrahub_node=InfrahubNode,
74
74
  )
75
+ generator._init_client.request_context = message.context.to_request_context()
75
76
  await generator.run(identifier=generator_definition.name)
76
77
  generator_instance.status.value = GeneratorInstanceStatus.READY.value
77
78
  except ModuleImportError as exc:
@@ -1,3 +1,3 @@
1
- from . import branch, node, schema, worker
1
+ from . import branch, worker
2
2
 
3
- __all__ = ["branch", "node", "schema", "worker"]
3
+ __all__ = ["branch", "worker"]