infrahub-server 1.3.8__py3-none-any.whl → 1.4.0__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 (172) hide show
  1. infrahub/api/internal.py +5 -0
  2. infrahub/artifacts/tasks.py +17 -22
  3. infrahub/branch/merge_mutation_checker.py +38 -0
  4. infrahub/cli/__init__.py +2 -2
  5. infrahub/cli/context.py +7 -3
  6. infrahub/cli/db.py +5 -16
  7. infrahub/cli/upgrade.py +10 -29
  8. infrahub/computed_attribute/tasks.py +36 -46
  9. infrahub/config.py +57 -6
  10. infrahub/constants/environment.py +1 -0
  11. infrahub/core/attribute.py +15 -7
  12. infrahub/core/branch/tasks.py +43 -41
  13. infrahub/core/constants/__init__.py +21 -6
  14. infrahub/core/constants/infrahubkind.py +2 -0
  15. infrahub/core/diff/coordinator.py +3 -1
  16. infrahub/core/diff/repository/repository.py +0 -8
  17. infrahub/core/diff/tasks.py +11 -8
  18. infrahub/core/graph/__init__.py +1 -1
  19. infrahub/core/graph/index.py +1 -2
  20. infrahub/core/graph/schema.py +50 -29
  21. infrahub/core/initialization.py +81 -47
  22. infrahub/core/ipam/tasks.py +4 -3
  23. infrahub/core/merge.py +8 -10
  24. infrahub/core/migrations/__init__.py +2 -0
  25. infrahub/core/migrations/graph/__init__.py +4 -0
  26. infrahub/core/migrations/graph/m036_drop_attr_value_index.py +45 -0
  27. infrahub/core/migrations/graph/m037_index_attr_vals.py +577 -0
  28. infrahub/core/migrations/query/attribute_add.py +27 -2
  29. infrahub/core/migrations/schema/attribute_kind_update.py +156 -0
  30. infrahub/core/migrations/schema/tasks.py +6 -5
  31. infrahub/core/models.py +5 -1
  32. infrahub/core/node/proposed_change.py +43 -0
  33. infrahub/core/protocols.py +12 -0
  34. infrahub/core/query/attribute.py +32 -14
  35. infrahub/core/query/diff.py +11 -0
  36. infrahub/core/query/ipam.py +13 -7
  37. infrahub/core/query/node.py +51 -10
  38. infrahub/core/query/resource_manager.py +3 -3
  39. infrahub/core/schema/basenode_schema.py +8 -0
  40. infrahub/core/schema/definitions/core/__init__.py +10 -1
  41. infrahub/core/schema/definitions/core/ipam.py +28 -2
  42. infrahub/core/schema/definitions/core/propose_change.py +15 -0
  43. infrahub/core/schema/definitions/core/webhook.py +3 -0
  44. infrahub/core/schema/definitions/internal.py +1 -1
  45. infrahub/core/schema/generated/attribute_schema.py +1 -1
  46. infrahub/core/schema/generic_schema.py +10 -0
  47. infrahub/core/schema/manager.py +10 -1
  48. infrahub/core/schema/node_schema.py +22 -17
  49. infrahub/core/schema/profile_schema.py +8 -0
  50. infrahub/core/schema/schema_branch.py +9 -5
  51. infrahub/core/schema/template_schema.py +8 -0
  52. infrahub/core/validators/attribute/kind.py +5 -1
  53. infrahub/core/validators/checks_runner.py +5 -5
  54. infrahub/core/validators/determiner.py +22 -2
  55. infrahub/core/validators/tasks.py +6 -7
  56. infrahub/core/validators/uniqueness/checker.py +4 -2
  57. infrahub/core/validators/uniqueness/model.py +1 -0
  58. infrahub/core/validators/uniqueness/query.py +57 -7
  59. infrahub/database/__init__.py +2 -1
  60. infrahub/events/__init__.py +20 -0
  61. infrahub/events/constants.py +7 -0
  62. infrahub/events/generator.py +29 -2
  63. infrahub/events/proposed_change_action.py +203 -0
  64. infrahub/generators/tasks.py +24 -20
  65. infrahub/git/base.py +4 -7
  66. infrahub/git/integrator.py +21 -12
  67. infrahub/git/repository.py +15 -30
  68. infrahub/git/tasks.py +121 -106
  69. infrahub/graphql/field_extractor.py +69 -0
  70. infrahub/graphql/manager.py +15 -11
  71. infrahub/graphql/mutations/account.py +2 -2
  72. infrahub/graphql/mutations/action.py +8 -2
  73. infrahub/graphql/mutations/artifact_definition.py +4 -1
  74. infrahub/graphql/mutations/branch.py +10 -5
  75. infrahub/graphql/mutations/graphql_query.py +2 -1
  76. infrahub/graphql/mutations/main.py +14 -8
  77. infrahub/graphql/mutations/menu.py +2 -1
  78. infrahub/graphql/mutations/proposed_change.py +230 -8
  79. infrahub/graphql/mutations/relationship.py +5 -0
  80. infrahub/graphql/mutations/repository.py +2 -1
  81. infrahub/graphql/mutations/tasks.py +7 -9
  82. infrahub/graphql/mutations/webhook.py +4 -1
  83. infrahub/graphql/parser.py +15 -6
  84. infrahub/graphql/queries/__init__.py +10 -1
  85. infrahub/graphql/queries/account.py +3 -3
  86. infrahub/graphql/queries/branch.py +2 -2
  87. infrahub/graphql/queries/diff/tree.py +3 -3
  88. infrahub/graphql/queries/event.py +13 -3
  89. infrahub/graphql/queries/ipam.py +23 -1
  90. infrahub/graphql/queries/proposed_change.py +84 -0
  91. infrahub/graphql/queries/relationship.py +2 -2
  92. infrahub/graphql/queries/resource_manager.py +3 -3
  93. infrahub/graphql/queries/search.py +3 -2
  94. infrahub/graphql/queries/status.py +3 -2
  95. infrahub/graphql/queries/task.py +2 -2
  96. infrahub/graphql/resolvers/ipam.py +440 -0
  97. infrahub/graphql/resolvers/many_relationship.py +4 -3
  98. infrahub/graphql/resolvers/resolver.py +5 -5
  99. infrahub/graphql/resolvers/single_relationship.py +3 -2
  100. infrahub/graphql/schema.py +25 -5
  101. infrahub/graphql/types/__init__.py +2 -2
  102. infrahub/graphql/types/attribute.py +3 -3
  103. infrahub/graphql/types/event.py +68 -0
  104. infrahub/groups/tasks.py +6 -6
  105. infrahub/lock.py +3 -2
  106. infrahub/menu/generator.py +8 -0
  107. infrahub/message_bus/operations/__init__.py +9 -12
  108. infrahub/message_bus/operations/git/file.py +6 -5
  109. infrahub/message_bus/operations/git/repository.py +12 -20
  110. infrahub/message_bus/operations/refresh/registry.py +15 -9
  111. infrahub/message_bus/operations/send/echo.py +7 -4
  112. infrahub/message_bus/types.py +1 -0
  113. infrahub/permissions/__init__.py +2 -1
  114. infrahub/permissions/constants.py +13 -0
  115. infrahub/permissions/globals.py +31 -2
  116. infrahub/permissions/manager.py +8 -5
  117. infrahub/pools/prefix.py +7 -5
  118. infrahub/prefect_server/app.py +31 -0
  119. infrahub/prefect_server/bootstrap.py +18 -0
  120. infrahub/proposed_change/action_checker.py +206 -0
  121. infrahub/proposed_change/approval_revoker.py +40 -0
  122. infrahub/proposed_change/branch_diff.py +3 -1
  123. infrahub/proposed_change/checker.py +45 -0
  124. infrahub/proposed_change/constants.py +32 -2
  125. infrahub/proposed_change/tasks.py +182 -150
  126. infrahub/py.typed +0 -0
  127. infrahub/server.py +29 -17
  128. infrahub/services/__init__.py +13 -28
  129. infrahub/services/adapters/cache/__init__.py +4 -0
  130. infrahub/services/adapters/cache/nats.py +2 -0
  131. infrahub/services/adapters/cache/redis.py +3 -0
  132. infrahub/services/adapters/message_bus/__init__.py +0 -2
  133. infrahub/services/adapters/message_bus/local.py +1 -2
  134. infrahub/services/adapters/message_bus/nats.py +6 -8
  135. infrahub/services/adapters/message_bus/rabbitmq.py +7 -9
  136. infrahub/services/adapters/workflow/__init__.py +1 -0
  137. infrahub/services/adapters/workflow/local.py +1 -8
  138. infrahub/services/component.py +2 -1
  139. infrahub/task_manager/event.py +56 -0
  140. infrahub/task_manager/models.py +9 -0
  141. infrahub/tasks/artifact.py +6 -7
  142. infrahub/tasks/check.py +4 -7
  143. infrahub/telemetry/tasks.py +15 -18
  144. infrahub/transformations/tasks.py +10 -6
  145. infrahub/trigger/tasks.py +4 -3
  146. infrahub/types.py +4 -0
  147. infrahub/validators/events.py +7 -7
  148. infrahub/validators/tasks.py +6 -7
  149. infrahub/webhook/models.py +45 -45
  150. infrahub/webhook/tasks.py +25 -24
  151. infrahub/workers/dependencies.py +143 -0
  152. infrahub/workers/infrahub_async.py +19 -43
  153. infrahub/workflows/catalogue.py +16 -2
  154. infrahub/workflows/initialization.py +5 -4
  155. infrahub/workflows/models.py +2 -0
  156. infrahub_sdk/client.py +2 -2
  157. infrahub_sdk/ctl/repository.py +51 -0
  158. infrahub_sdk/ctl/schema.py +9 -9
  159. infrahub_sdk/node/node.py +2 -2
  160. infrahub_sdk/pytest_plugin/items/graphql_query.py +1 -1
  161. infrahub_sdk/schema/repository.py +1 -1
  162. infrahub_sdk/testing/docker.py +1 -1
  163. infrahub_sdk/utils.py +2 -2
  164. {infrahub_server-1.3.8.dist-info → infrahub_server-1.4.0.dist-info}/METADATA +7 -5
  165. {infrahub_server-1.3.8.dist-info → infrahub_server-1.4.0.dist-info}/RECORD +172 -156
  166. infrahub_testcontainers/container.py +17 -0
  167. infrahub_testcontainers/docker-compose-cluster.test.yml +56 -1
  168. infrahub_testcontainers/docker-compose.test.yml +56 -1
  169. infrahub_testcontainers/helpers.py +4 -1
  170. {infrahub_server-1.3.8.dist-info → infrahub_server-1.4.0.dist-info}/LICENSE.txt +0 -0
  171. {infrahub_server-1.3.8.dist-info → infrahub_server-1.4.0.dist-info}/WHEEL +0 -0
  172. {infrahub_server-1.3.8.dist-info → infrahub_server-1.4.0.dist-info}/entry_points.txt +0 -0
@@ -23,6 +23,14 @@ class GraphRelationship(BaseModel):
23
23
  direction: GraphRelDirection
24
24
 
25
25
 
26
+ class GraphVertex(BaseModel):
27
+ default_label: str = "NULL"
28
+
29
+ @classmethod
30
+ def get_default_label(cls) -> str:
31
+ return cls.model_fields["default_label"].default
32
+
33
+
26
34
  # -----------------------------------------------------
27
35
  # Node
28
36
  # -----------------------------------------------------
@@ -61,7 +69,7 @@ class GraphNodeRelationships(BaseModel):
61
69
  )
62
70
 
63
71
 
64
- class GraphNodeNode(BaseModel):
72
+ class GraphNodeNode(GraphVertex):
65
73
  default_label: str = "Node" # Most node also have CoreNode
66
74
  properties: GraphNodeProperties
67
75
  relationships: GraphNodeRelationships
@@ -95,7 +103,7 @@ class GraphRelationshipRelationships(BaseModel):
95
103
  )
96
104
 
97
105
 
98
- class GraphRelationshipNode(BaseModel):
106
+ class GraphRelationshipNode(GraphVertex):
99
107
  default_label: str = "Relationship"
100
108
  properties: GraphRelationshipProperties
101
109
  relationships: GraphRelationshipRelationships
@@ -133,7 +141,7 @@ class GraphAttributeRelationships(BaseModel):
133
141
  )
134
142
 
135
143
 
136
- class GraphAttributeNode(BaseModel):
144
+ class GraphAttributeNode(GraphVertex):
137
145
  default_label: str = "Attribute"
138
146
  properties: GraphAttributeProperties
139
147
  relationships: GraphAttributeRelationships
@@ -168,19 +176,25 @@ class GraphAttributeValueRelationships(BaseModel):
168
176
  )
169
177
 
170
178
 
171
- class GraphAttributeValueNode(BaseModel):
179
+ class GraphAttributeValueNode(GraphVertex):
172
180
  default_label: str = "AttributeValue"
173
181
  properties: GraphAttributeValueProperties
174
182
  relationships: GraphAttributeValueRelationships
175
183
 
176
184
 
177
- class GraphAttributeIPNetworkNode(BaseModel):
185
+ class GraphAttributeValueIndexedNode(GraphVertex):
186
+ default_label: str = "AttributeValueIndexed"
187
+ properties: GraphAttributeValueProperties
188
+ relationships: GraphAttributeValueRelationships
189
+
190
+
191
+ class GraphAttributeIPNetworkNode(GraphVertex):
178
192
  default_label: str = "AttributeIPNetwork"
179
193
  properties: GraphAttributeIPNetworkProperties
180
194
  relationships: GraphAttributeValueRelationships
181
195
 
182
196
 
183
- class GraphAttributeIPHostNode(BaseModel):
197
+ class GraphAttributeIPHostNode(GraphVertex):
184
198
  default_label: str = "AttributeIPHost"
185
199
  properties: GraphAttributeIPHostProperties
186
200
  relationships: GraphAttributeValueRelationships
@@ -202,7 +216,7 @@ class GraphBooleanRelationships(BaseModel):
202
216
  )
203
217
 
204
218
 
205
- class GraphBooleanNode(BaseModel):
219
+ class GraphBooleanNode(GraphVertex):
206
220
  default_label: str = "Boolean"
207
221
  properties: GraphBooleanProperties
208
222
  relationships: GraphBooleanRelationships
@@ -229,25 +243,32 @@ class GraphRelationshipDefault(BaseModel):
229
243
  hierarchy: Optional[str] = Field(None, description="Name of the hierarchy this relationship is part of")
230
244
 
231
245
 
232
- GRAPH_SCHEMA: dict[str, dict[str, Any]] = {
233
- "nodes": {
234
- "Node": GraphNodeNode,
235
- "Relationship": GraphRelationshipNode,
236
- "Attribute": GraphAttributeNode,
237
- "AttributeValue": GraphAttributeValueNode,
238
- "AttributeIPNetwork": GraphAttributeIPNetworkNode,
239
- "AttributeIPHost": GraphAttributeIPHostNode,
240
- "Boolean": GraphBooleanNode,
241
- },
242
- "relationships": {
243
- # Ignoring IS_PART_OF for now, because there is a bit of cleanup required
244
- # "IS_PART_OF": GraphRelationshipIsPartOf,
245
- "HAS_VALUE": GraphRelationshipDefault,
246
- "HAS_ATTRIBUTE": GraphRelationshipDefault,
247
- "IS_RELATED": GraphRelationshipDefault,
248
- "HAS_SOURCE": GraphRelationshipDefault,
249
- "HAS_OWNER": GraphRelationshipDefault,
250
- "IS_VISIBLE": GraphRelationshipDefault,
251
- "IS_PROTECTED": GraphRelationshipDefault,
252
- },
253
- }
246
+ def get_graph_schema() -> dict[str, dict[str, Any]]:
247
+ """Generate the graph schema dictionary containing nodes and relationships."""
248
+ graph_node_list: list[type[GraphVertex]] = [
249
+ GraphNodeNode,
250
+ GraphRelationshipNode,
251
+ GraphAttributeNode,
252
+ GraphAttributeValueNode,
253
+ GraphAttributeValueIndexedNode,
254
+ GraphAttributeIPNetworkNode,
255
+ GraphAttributeIPHostNode,
256
+ GraphBooleanNode,
257
+ ]
258
+ return {
259
+ "nodes": {graph_node.get_default_label(): graph_node for graph_node in graph_node_list},
260
+ "relationships": {
261
+ # Ignoring IS_PART_OF for now, because there is a bit of cleanup required
262
+ # "IS_PART_OF": GraphRelationshipIsPartOf,
263
+ "HAS_VALUE": GraphRelationshipDefault,
264
+ "HAS_ATTRIBUTE": GraphRelationshipDefault,
265
+ "IS_RELATED": GraphRelationshipDefault,
266
+ "HAS_SOURCE": GraphRelationshipDefault,
267
+ "HAS_OWNER": GraphRelationshipDefault,
268
+ "IS_VISIBLE": GraphRelationshipDefault,
269
+ "IS_PROTECTED": GraphRelationshipDefault,
270
+ },
271
+ }
272
+
273
+
274
+ GRAPH_SCHEMA: dict[str, dict[str, Any]] = get_graph_schema()
@@ -1,4 +1,5 @@
1
1
  import importlib
2
+ from collections.abc import Sequence
2
3
  from typing import TYPE_CHECKING
3
4
  from uuid import uuid4
4
5
 
@@ -16,15 +17,16 @@ from infrahub.core.constants import (
16
17
  PermissionDecision,
17
18
  )
18
19
  from infrahub.core.graph import GRAPH_VERSION
19
- from infrahub.core.graph.index import attr_value_index, node_indexes, rel_indexes
20
+ from infrahub.core.graph.index import node_indexes, rel_indexes
20
21
  from infrahub.core.manager import NodeManager
21
22
  from infrahub.core.node import Node
22
23
  from infrahub.core.node.ipam import BuiltinIPPrefix
23
24
  from infrahub.core.node.permissions import CoreGlobalPermission, CoreObjectPermission
25
+ from infrahub.core.node.proposed_change import CoreProposedChange
24
26
  from infrahub.core.node.resource_manager.ip_address_pool import CoreIPAddressPool
25
27
  from infrahub.core.node.resource_manager.ip_prefix_pool import CoreIPPrefixPool
26
28
  from infrahub.core.node.resource_manager.number_pool import CoreNumberPool
27
- from infrahub.core.protocols import CoreAccount
29
+ from infrahub.core.protocols import CoreAccount, CoreAccountGroup, CoreAccountRole
28
30
  from infrahub.core.root import Root
29
31
  from infrahub.core.schema import SchemaRoot, core_models, internal_schema
30
32
  from infrahub.core.schema.manager import SchemaManager
@@ -35,7 +37,7 @@ from infrahub.exceptions import DatabaseError
35
37
  from infrahub.graphql.manager import GraphQLSchemaManager
36
38
  from infrahub.log import get_logger
37
39
  from infrahub.menu.utils import create_default_menu
38
- from infrahub.permissions import PermissionBackend
40
+ from infrahub.permissions import PermissionBackend, get_or_create_global_permission
39
41
  from infrahub.storage import InfrahubObjectStorage
40
42
 
41
43
  if TYPE_CHECKING:
@@ -117,6 +119,7 @@ async def initialize_registry(db: InfrahubDatabase, initialize: bool = False) ->
117
119
  registry.node[InfrahubKind.NUMBERPOOL] = CoreNumberPool
118
120
  registry.node[InfrahubKind.GLOBALPERMISSION] = CoreGlobalPermission
119
121
  registry.node[InfrahubKind.OBJECTPERMISSION] = CoreObjectPermission
122
+ registry.node[InfrahubKind.PROPOSEDCHANGE] = CoreProposedChange
120
123
 
121
124
  # ---------------------------------------------------
122
125
  # Instantiate permission backends
@@ -129,8 +132,6 @@ async def add_indexes(db: InfrahubDatabase) -> None:
129
132
  index_manager: IndexManagerBase = IndexManagerMemgraph(db=db)
130
133
  index_manager = IndexManagerNeo4j(db=db)
131
134
 
132
- if config.SETTINGS.experimental_features.value_db_index:
133
- node_indexes.append(attr_value_index)
134
135
  index_manager.init(nodes=node_indexes, rels=rel_indexes)
135
136
  log.debug("Loading database indexes ..")
136
137
  await index_manager.add()
@@ -321,7 +322,7 @@ async def create_ipam_namespace(
321
322
  return obj
322
323
 
323
324
 
324
- async def create_super_administrator_role(db: InfrahubDatabase) -> Node:
325
+ async def create_super_administrator_role(db: InfrahubDatabase) -> CoreAccountRole:
325
326
  permission = await Node.init(db=db, schema=InfrahubKind.GLOBALPERMISSION)
326
327
  await permission.new(
327
328
  db=db,
@@ -333,15 +334,15 @@ async def create_super_administrator_role(db: InfrahubDatabase) -> Node:
333
334
  log.info(f"Created global permission: {GlobalPermissions.SUPER_ADMIN}")
334
335
 
335
336
  role_name = "Super Administrator"
336
- obj = await Node.init(db=db, schema=InfrahubKind.ACCOUNTROLE)
337
- await obj.new(db=db, name=role_name, permissions=[permission])
338
- await obj.save(db=db)
337
+ role = await Node.init(db=db, schema=CoreAccountRole)
338
+ await role.new(db=db, name=role_name, permissions=[permission])
339
+ await role.save(db=db)
339
340
  log.info(f"Created account role: {role_name}")
340
341
 
341
- return obj
342
+ return role
342
343
 
343
344
 
344
- async def create_default_roles(db: InfrahubDatabase) -> Node:
345
+ async def create_default_role(db: InfrahubDatabase) -> CoreAccountRole:
345
346
  repo_permission = await Node.init(db=db, schema=InfrahubKind.GLOBALPERMISSION)
346
347
  await repo_permission.new(
347
348
  db=db,
@@ -370,20 +371,13 @@ async def create_default_roles(db: InfrahubDatabase) -> Node:
370
371
  await proposed_change_permission.save(db=db)
371
372
 
372
373
  # Other permissions, created to keep references of them from the start
373
- for permission_action, permission_description in (
374
- (GlobalPermissions.EDIT_DEFAULT_BRANCH, "Allow a user to change data in the default branch"),
375
- (GlobalPermissions.MANAGE_ACCOUNTS, "Allow a user to manage accounts, account roles and account groups"),
376
- (GlobalPermissions.MANAGE_PERMISSIONS, "Allow a user to manage permissions"),
377
- (GlobalPermissions.MERGE_BRANCH, "Allow a user to merge branches"),
374
+ for permission_action in (
375
+ GlobalPermissions.EDIT_DEFAULT_BRANCH,
376
+ GlobalPermissions.MANAGE_ACCOUNTS,
377
+ GlobalPermissions.MANAGE_PERMISSIONS,
378
+ GlobalPermissions.MERGE_BRANCH,
378
379
  ):
379
- permission = await Node.init(db=db, schema=InfrahubKind.GLOBALPERMISSION)
380
- await permission.new(
381
- db=db,
382
- action=permission_action.value,
383
- decision=PermissionDecision.ALLOW_ALL.value,
384
- description=permission_description,
385
- )
386
- await permission.save(db=db)
380
+ await get_or_create_global_permission(db=db, permission=permission_action)
387
381
 
388
382
  view_permission = await Node.init(db=db, schema=InfrahubKind.OBJECTPERMISSION)
389
383
  await view_permission.new(
@@ -408,7 +402,7 @@ async def create_default_roles(db: InfrahubDatabase) -> Node:
408
402
  await modify_permission.save(db=db)
409
403
 
410
404
  role_name = "General Access"
411
- role = await Node.init(db=db, schema=InfrahubKind.ACCOUNTROLE)
405
+ role = await Node.init(db=db, schema=CoreAccountRole)
412
406
  await role.new(
413
407
  db=db,
414
408
  name=role_name,
@@ -423,16 +417,42 @@ async def create_default_roles(db: InfrahubDatabase) -> Node:
423
417
  await role.save(db=db)
424
418
  log.info(f"Created account role: {role_name}")
425
419
 
426
- group_name = "Infrahub Users"
427
- group = await Node.init(db=db, schema=InfrahubKind.ACCOUNTGROUP)
428
- await group.new(db=db, name=group_name, roles=[role])
429
- await group.save(db=db)
430
- log.info(f"Created account group: {group_name}")
420
+ return role
421
+
422
+
423
+ async def create_proposed_change_reviewer_role(db: InfrahubDatabase) -> CoreAccountRole:
424
+ edit_default_branch_permission = await get_or_create_global_permission(
425
+ db=db, permission=GlobalPermissions.EDIT_DEFAULT_BRANCH
426
+ )
427
+ reviewer_permission = await get_or_create_global_permission(
428
+ db=db, permission=GlobalPermissions.REVIEW_PROPOSED_CHANGE
429
+ )
430
+
431
+ proposed_change_update_permission = await Node.init(db=db, schema=InfrahubKind.OBJECTPERMISSION)
432
+ await proposed_change_update_permission.new(
433
+ db=db,
434
+ name="ProposedChange",
435
+ namespace="Core",
436
+ action=PermissionAction.UPDATE.value,
437
+ decision=PermissionDecision.ALLOW_ALL.value,
438
+ description="Allow a user to update proposed changes",
439
+ )
440
+ await proposed_change_update_permission.save(db=db)
441
+
442
+ role_name = "Proposed Change Reviewer"
443
+ role = await Node.init(db=db, schema=CoreAccountRole)
444
+ await role.new(
445
+ db=db,
446
+ name=role_name,
447
+ permissions=[edit_default_branch_permission, reviewer_permission, proposed_change_update_permission],
448
+ )
449
+ await role.save(db=db)
450
+ log.info(f"Created account role: {role_name}")
431
451
 
432
452
  return role
433
453
 
434
454
 
435
- async def create_anonymous_role(db: InfrahubDatabase) -> Node:
455
+ async def create_anonymous_role(db: InfrahubDatabase) -> CoreAccountRole:
436
456
  deny_permission = await Node.init(db=db, schema=InfrahubKind.OBJECTPERMISSION)
437
457
  await deny_permission.new(
438
458
  db=db, name="*", namespace="*", action=PermissionAction.ANY.value, decision=PermissionDecision.DENY.value
@@ -445,7 +465,7 @@ async def create_anonymous_role(db: InfrahubDatabase) -> Node:
445
465
  hfid=["*", "*", PermissionAction.VIEW.value, str(PermissionDecision.ALLOW_ALL.value)],
446
466
  )
447
467
 
448
- role = await Node.init(db=db, schema=InfrahubKind.ACCOUNTROLE)
468
+ role = await Node.init(db=db, schema=CoreAccountRole)
449
469
  await role.new(
450
470
  db=db, name=config.SETTINGS.main.anonymous_access_role, permissions=[deny_permission, view_permission]
451
471
  )
@@ -455,23 +475,39 @@ async def create_anonymous_role(db: InfrahubDatabase) -> Node:
455
475
  return role
456
476
 
457
477
 
458
- async def create_super_administrators_group(
459
- db: InfrahubDatabase, role: Node, admin_accounts: list[CoreAccount]
460
- ) -> Node:
461
- group_name = "Super Administrators"
462
- group = await Node.init(db=db, schema=InfrahubKind.ACCOUNTGROUP)
463
- await group.new(db=db, name=group_name, roles=[role])
478
+ async def create_accounts_group(
479
+ db: InfrahubDatabase, name: str, roles: Sequence[CoreAccountRole], accounts: Sequence[CoreAccount]
480
+ ) -> CoreAccountGroup:
481
+ group = await Node.init(db=db, schema=CoreAccountGroup)
482
+ await group.new(db=db, name=name, roles=list(roles))
464
483
  await group.save(db=db)
465
- log.info(f"Created account group: {group_name}")
484
+ log.info(f"Created account group: {name}")
466
485
 
467
- for admin_account in admin_accounts:
468
- await group.members.add(db=db, data=admin_account) # type: ignore[attr-defined]
469
- await group.members.save(db=db) # type: ignore[attr-defined]
470
- log.info(f"Assigned account group: {group_name} to {admin_account.name.value}")
486
+ for account in accounts:
487
+ await group.members.add(db=db, data=account) # type: ignore[arg-type]
488
+ await group.members.save(db=db)
489
+ log.info(f"Assigned account group: {name} to {account.name.value}")
471
490
 
472
491
  return group
473
492
 
474
493
 
494
+ async def create_default_account_groups(
495
+ db: InfrahubDatabase,
496
+ admin_accounts: Sequence[CoreAccount] | None = None,
497
+ accounts: Sequence[CoreAccount] | None = None,
498
+ ) -> None:
499
+ administrator_role = await create_super_administrator_role(db=db)
500
+ await create_accounts_group(
501
+ db=db, name="Super Administrators", roles=[administrator_role], accounts=admin_accounts or []
502
+ )
503
+
504
+ default_role = await create_default_role(db=db)
505
+ proposed_change_reviewer_role = await create_proposed_change_reviewer_role(db=db)
506
+ await create_accounts_group(
507
+ db=db, name="Infrahub Users", roles=[default_role, proposed_change_reviewer_role], accounts=accounts or []
508
+ )
509
+
510
+
475
511
  async def first_time_initialization(db: InfrahubDatabase) -> None:
476
512
  # --------------------------------------------------
477
513
  # Create the default Branch
@@ -522,12 +558,10 @@ async def first_time_initialization(db: InfrahubDatabase) -> None:
522
558
  )
523
559
 
524
560
  # --------------------------------------------------
525
- # Create Global Permissions and assign them
561
+ # Create default account roles, groups and permissions
526
562
  # --------------------------------------------------
527
- administrator_role = await create_super_administrator_role(db=db)
528
- await create_super_administrators_group(db=db, role=administrator_role, admin_accounts=admin_accounts)
563
+ await create_default_account_groups(db=db, admin_accounts=admin_accounts)
529
564
 
530
- await create_default_roles(db=db)
531
565
  if config.SETTINGS.main.allow_anonymous_access:
532
566
  await create_anonymous_role(db=db)
533
567
 
@@ -5,7 +5,7 @@ from prefect import flow
5
5
 
6
6
  from infrahub.core import registry
7
7
  from infrahub.core.ipam.reconciler import IpamReconciler
8
- from infrahub.services import InfrahubServices
8
+ from infrahub.workers.dependencies import get_database
9
9
  from infrahub.workflows.utils import add_branch_tag
10
10
 
11
11
  from .model import IpamNodeDetails
@@ -20,8 +20,9 @@ if TYPE_CHECKING:
20
20
  description="Ensure the IPAM Tree is up to date",
21
21
  persist_result=False,
22
22
  )
23
- async def ipam_reconciliation(branch: str, ipam_node_details: list[IpamNodeDetails], service: InfrahubServices) -> None:
24
- async with service.database.start_session() as db:
23
+ async def ipam_reconciliation(branch: str, ipam_node_details: list[IpamNodeDetails]) -> None:
24
+ database = await get_database()
25
+ async with database.start_session() as db:
25
26
  branch_obj = await registry.get_branch(db=db, branch=branch)
26
27
 
27
28
  await add_branch_tag(branch_name=branch_obj.name)
infrahub/core/merge.py CHANGED
@@ -26,7 +26,7 @@ if TYPE_CHECKING:
26
26
  from infrahub.core.schema.manager import SchemaDiff
27
27
  from infrahub.core.schema.schema_branch import SchemaBranch
28
28
  from infrahub.database import InfrahubDatabase
29
- from infrahub.services import InfrahubServices
29
+ from infrahub.services.adapters.workflow import InfrahubWorkflow
30
30
 
31
31
 
32
32
  log = get_logger()
@@ -42,7 +42,7 @@ class BranchMerger:
42
42
  diff_repository: DiffRepository,
43
43
  diff_locker: DiffLocker,
44
44
  destination_branch: Branch | None = None,
45
- service: InfrahubServices | None = None,
45
+ workflow: InfrahubWorkflow | None = None,
46
46
  ):
47
47
  self.source_branch = source_branch
48
48
  self.destination_branch: Branch = destination_branch or registry.get_branch_from_registry()
@@ -58,7 +58,7 @@ class BranchMerger:
58
58
  self._destination_schema: SchemaBranch | None = None
59
59
  self._initial_source_schema: SchemaBranch | None = None
60
60
 
61
- self._service = service
61
+ self._workflow = workflow
62
62
 
63
63
  @property
64
64
  def source_schema(self) -> SchemaBranch:
@@ -81,10 +81,10 @@ class BranchMerger:
81
81
  raise ValueError("_initial_source_schema hasn't been initialized")
82
82
 
83
83
  @property
84
- def service(self) -> InfrahubServices:
85
- if not self._service:
86
- raise ValueError("BranchMerger hasn't been initialized with a service object")
87
- return self._service
84
+ def workflow(self) -> InfrahubWorkflow:
85
+ if not self._workflow:
86
+ raise ValueError("BranchMerger hasn't been initialized with a workflow object")
87
+ return self._workflow
88
88
 
89
89
  async def get_initial_source_branch(self) -> SchemaBranch:
90
90
  """Retrieve the schema of the source branch when the branch was created.
@@ -246,6 +246,4 @@ class BranchMerger:
246
246
  destination_branch_id=str(self.destination_branch.get_uuid()),
247
247
  default_branch=repo.default_branch.value,
248
248
  )
249
- await self.service.workflow.submit_workflow(
250
- workflow=GIT_REPOSITORIES_MERGE, parameters={"model": model}
251
- )
249
+ await self.workflow.submit_workflow(workflow=GIT_REPOSITORIES_MERGE, parameters={"model": model})
@@ -1,3 +1,4 @@
1
+ from .schema.attribute_kind_update import AttributeKindUpdateMigration
1
2
  from .schema.attribute_name_update import AttributeNameUpdateMigration
2
3
  from .schema.node_attribute_add import NodeAttributeAddMigration
3
4
  from .schema.node_attribute_remove import NodeAttributeRemoveMigration
@@ -17,6 +18,7 @@ MIGRATION_MAP: dict[str, type[SchemaMigration] | None] = {
17
18
  "node.relationship.remove": PlaceholderDummyMigration,
18
19
  "attribute.name.update": AttributeNameUpdateMigration,
19
20
  "attribute.branch.update": None,
21
+ "attribute.kind.update": AttributeKindUpdateMigration,
20
22
  "relationship.branch.update": None,
21
23
  "relationship.direction.update": None,
22
24
  "relationship.identifier.update": PlaceholderDummyMigration,
@@ -37,6 +37,8 @@ from .m032_cleanup_orphaned_branch_relationships import Migration032
37
37
  from .m033_deduplicate_relationship_vertices import Migration033
38
38
  from .m034_find_orphaned_schema_fields import Migration034
39
39
  from .m035_orphan_relationships import Migration035
40
+ from .m036_drop_attr_value_index import Migration036
41
+ from .m037_index_attr_vals import Migration037
40
42
 
41
43
  if TYPE_CHECKING:
42
44
  from infrahub.core.root import Root
@@ -79,6 +81,8 @@ MIGRATIONS: list[type[GraphMigration | InternalSchemaMigration | ArbitraryMigrat
79
81
  Migration033,
80
82
  Migration034,
81
83
  Migration035,
84
+ Migration036,
85
+ Migration037,
82
86
  ]
83
87
 
84
88
 
@@ -0,0 +1,45 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Sequence
4
+
5
+ from infrahub.constants.database import IndexType
6
+ from infrahub.core.migrations.shared import MigrationResult
7
+ from infrahub.core.query import Query # noqa: TC001
8
+ from infrahub.database import DatabaseType
9
+ from infrahub.database.index import IndexItem
10
+ from infrahub.database.neo4j import IndexManagerNeo4j
11
+
12
+ from ..shared import GraphMigration
13
+
14
+ if TYPE_CHECKING:
15
+ from infrahub.database import InfrahubDatabase
16
+
17
+
18
+ INDEX_TO_DELETE = IndexItem(name="attr_value", label="AttributeValue", properties=["value"], type=IndexType.RANGE)
19
+
20
+
21
+ class Migration036(GraphMigration):
22
+ name: str = "036_drop_attr_value_index"
23
+ queries: Sequence[type[Query]] = []
24
+ minimum_version: int = 35
25
+
26
+ async def execute(self, db: InfrahubDatabase) -> MigrationResult:
27
+ result = MigrationResult()
28
+
29
+ # Only execute this migration for Neo4j
30
+ if db.db_type != DatabaseType.NEO4J:
31
+ return result
32
+
33
+ try:
34
+ index_manager = IndexManagerNeo4j(db=db)
35
+ index_manager.init(nodes=[INDEX_TO_DELETE], rels=[])
36
+ await index_manager.drop()
37
+ except Exception as exc:
38
+ result.errors.append(str(exc))
39
+ return result
40
+
41
+ return result
42
+
43
+ async def validate_migration(self, db: InfrahubDatabase) -> MigrationResult: # noqa: ARG002
44
+ result = MigrationResult()
45
+ return result