infrahub-server 1.3.5__py3-none-any.whl → 1.4.0b0__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 (158) 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 +7 -29
  8. infrahub/computed_attribute/tasks.py +36 -46
  9. infrahub/config.py +53 -2
  10. infrahub/constants/environment.py +1 -0
  11. infrahub/core/attribute.py +9 -7
  12. infrahub/core/branch/tasks.py +43 -41
  13. infrahub/core/constants/__init__.py +20 -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 +62 -33
  22. infrahub/core/ipam/tasks.py +4 -3
  23. infrahub/core/merge.py +8 -10
  24. infrahub/core/migrations/graph/__init__.py +2 -0
  25. infrahub/core/migrations/graph/m035_drop_attr_value_index.py +45 -0
  26. infrahub/core/migrations/query/attribute_add.py +27 -2
  27. infrahub/core/migrations/schema/tasks.py +6 -5
  28. infrahub/core/node/proposed_change.py +43 -0
  29. infrahub/core/protocols.py +12 -0
  30. infrahub/core/query/attribute.py +32 -14
  31. infrahub/core/query/diff.py +11 -0
  32. infrahub/core/query/ipam.py +13 -7
  33. infrahub/core/query/node.py +51 -10
  34. infrahub/core/query/resource_manager.py +3 -3
  35. infrahub/core/schema/basenode_schema.py +8 -0
  36. infrahub/core/schema/definitions/core/__init__.py +10 -1
  37. infrahub/core/schema/definitions/core/ipam.py +28 -2
  38. infrahub/core/schema/definitions/core/propose_change.py +15 -0
  39. infrahub/core/schema/definitions/core/webhook.py +3 -0
  40. infrahub/core/schema/generic_schema.py +10 -0
  41. infrahub/core/schema/manager.py +10 -1
  42. infrahub/core/schema/node_schema.py +22 -17
  43. infrahub/core/schema/profile_schema.py +8 -0
  44. infrahub/core/schema/schema_branch.py +9 -5
  45. infrahub/core/schema/template_schema.py +8 -0
  46. infrahub/core/validators/checks_runner.py +5 -5
  47. infrahub/core/validators/tasks.py +6 -7
  48. infrahub/core/validators/uniqueness/checker.py +4 -2
  49. infrahub/core/validators/uniqueness/model.py +1 -0
  50. infrahub/core/validators/uniqueness/query.py +57 -7
  51. infrahub/database/__init__.py +2 -1
  52. infrahub/events/__init__.py +18 -0
  53. infrahub/events/constants.py +7 -0
  54. infrahub/events/generator.py +29 -2
  55. infrahub/events/proposed_change_action.py +181 -0
  56. infrahub/generators/tasks.py +24 -20
  57. infrahub/git/base.py +4 -7
  58. infrahub/git/integrator.py +21 -12
  59. infrahub/git/repository.py +15 -30
  60. infrahub/git/tasks.py +121 -106
  61. infrahub/graphql/field_extractor.py +69 -0
  62. infrahub/graphql/manager.py +15 -11
  63. infrahub/graphql/mutations/account.py +2 -2
  64. infrahub/graphql/mutations/action.py +8 -2
  65. infrahub/graphql/mutations/artifact_definition.py +4 -1
  66. infrahub/graphql/mutations/branch.py +10 -5
  67. infrahub/graphql/mutations/graphql_query.py +2 -1
  68. infrahub/graphql/mutations/main.py +14 -8
  69. infrahub/graphql/mutations/menu.py +2 -1
  70. infrahub/graphql/mutations/proposed_change.py +225 -8
  71. infrahub/graphql/mutations/relationship.py +5 -0
  72. infrahub/graphql/mutations/repository.py +2 -1
  73. infrahub/graphql/mutations/tasks.py +7 -9
  74. infrahub/graphql/mutations/webhook.py +4 -1
  75. infrahub/graphql/parser.py +15 -6
  76. infrahub/graphql/queries/__init__.py +10 -1
  77. infrahub/graphql/queries/account.py +3 -3
  78. infrahub/graphql/queries/branch.py +2 -2
  79. infrahub/graphql/queries/diff/tree.py +3 -3
  80. infrahub/graphql/queries/event.py +13 -3
  81. infrahub/graphql/queries/ipam.py +23 -1
  82. infrahub/graphql/queries/proposed_change.py +84 -0
  83. infrahub/graphql/queries/relationship.py +2 -2
  84. infrahub/graphql/queries/resource_manager.py +3 -3
  85. infrahub/graphql/queries/search.py +3 -2
  86. infrahub/graphql/queries/status.py +3 -2
  87. infrahub/graphql/queries/task.py +2 -2
  88. infrahub/graphql/resolvers/ipam.py +440 -0
  89. infrahub/graphql/resolvers/many_relationship.py +4 -3
  90. infrahub/graphql/resolvers/resolver.py +5 -5
  91. infrahub/graphql/resolvers/single_relationship.py +3 -2
  92. infrahub/graphql/schema.py +25 -5
  93. infrahub/graphql/types/__init__.py +2 -2
  94. infrahub/graphql/types/attribute.py +3 -3
  95. infrahub/graphql/types/event.py +60 -0
  96. infrahub/groups/tasks.py +6 -6
  97. infrahub/lock.py +3 -2
  98. infrahub/menu/generator.py +8 -0
  99. infrahub/message_bus/operations/__init__.py +9 -12
  100. infrahub/message_bus/operations/git/file.py +6 -5
  101. infrahub/message_bus/operations/git/repository.py +12 -20
  102. infrahub/message_bus/operations/refresh/registry.py +15 -9
  103. infrahub/message_bus/operations/send/echo.py +7 -4
  104. infrahub/message_bus/types.py +1 -0
  105. infrahub/permissions/globals.py +1 -4
  106. infrahub/permissions/manager.py +8 -5
  107. infrahub/pools/prefix.py +7 -5
  108. infrahub/prefect_server/app.py +31 -0
  109. infrahub/prefect_server/bootstrap.py +18 -0
  110. infrahub/proposed_change/action_checker.py +206 -0
  111. infrahub/proposed_change/approval_revoker.py +40 -0
  112. infrahub/proposed_change/branch_diff.py +3 -1
  113. infrahub/proposed_change/checker.py +45 -0
  114. infrahub/proposed_change/constants.py +32 -2
  115. infrahub/proposed_change/tasks.py +182 -150
  116. infrahub/py.typed +0 -0
  117. infrahub/server.py +29 -17
  118. infrahub/services/__init__.py +13 -28
  119. infrahub/services/adapters/cache/__init__.py +4 -0
  120. infrahub/services/adapters/cache/nats.py +2 -0
  121. infrahub/services/adapters/cache/redis.py +3 -0
  122. infrahub/services/adapters/message_bus/__init__.py +0 -2
  123. infrahub/services/adapters/message_bus/local.py +1 -2
  124. infrahub/services/adapters/message_bus/nats.py +6 -8
  125. infrahub/services/adapters/message_bus/rabbitmq.py +7 -9
  126. infrahub/services/adapters/workflow/__init__.py +1 -0
  127. infrahub/services/adapters/workflow/local.py +1 -8
  128. infrahub/services/component.py +2 -1
  129. infrahub/task_manager/event.py +52 -0
  130. infrahub/task_manager/models.py +9 -0
  131. infrahub/tasks/artifact.py +6 -7
  132. infrahub/tasks/check.py +4 -7
  133. infrahub/telemetry/tasks.py +15 -18
  134. infrahub/transformations/tasks.py +10 -6
  135. infrahub/trigger/tasks.py +4 -3
  136. infrahub/types.py +4 -0
  137. infrahub/validators/events.py +7 -7
  138. infrahub/validators/tasks.py +6 -7
  139. infrahub/webhook/models.py +45 -45
  140. infrahub/webhook/tasks.py +25 -24
  141. infrahub/workers/dependencies.py +143 -0
  142. infrahub/workers/infrahub_async.py +19 -43
  143. infrahub/workflows/catalogue.py +16 -2
  144. infrahub/workflows/initialization.py +5 -4
  145. infrahub/workflows/models.py +2 -0
  146. infrahub_sdk/client.py +6 -6
  147. infrahub_sdk/ctl/repository.py +51 -0
  148. infrahub_sdk/ctl/schema.py +9 -9
  149. infrahub_sdk/protocols.py +40 -6
  150. {infrahub_server-1.3.5.dist-info → infrahub_server-1.4.0b0.dist-info}/METADATA +5 -4
  151. {infrahub_server-1.3.5.dist-info → infrahub_server-1.4.0b0.dist-info}/RECORD +158 -144
  152. infrahub_testcontainers/container.py +17 -0
  153. infrahub_testcontainers/docker-compose-cluster.test.yml +56 -1
  154. infrahub_testcontainers/docker-compose.test.yml +56 -1
  155. infrahub_testcontainers/helpers.py +4 -1
  156. {infrahub_server-1.3.5.dist-info → infrahub_server-1.4.0b0.dist-info}/LICENSE.txt +0 -0
  157. {infrahub_server-1.3.5.dist-info → infrahub_server-1.4.0b0.dist-info}/WHEEL +0 -0
  158. {infrahub_server-1.3.5.dist-info → infrahub_server-1.4.0b0.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
@@ -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,
@@ -408,7 +409,7 @@ async def create_default_roles(db: InfrahubDatabase) -> Node:
408
409
  await modify_permission.save(db=db)
409
410
 
410
411
  role_name = "General Access"
411
- role = await Node.init(db=db, schema=InfrahubKind.ACCOUNTROLE)
412
+ role = await Node.init(db=db, schema=CoreAccountRole)
412
413
  await role.new(
413
414
  db=db,
414
415
  name=role_name,
@@ -423,16 +424,29 @@ async def create_default_roles(db: InfrahubDatabase) -> Node:
423
424
  await role.save(db=db)
424
425
  log.info(f"Created account role: {role_name}")
425
426
 
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}")
427
+ return role
428
+
429
+
430
+ async def create_proposed_change_reviewer_role(db: InfrahubDatabase) -> CoreAccountRole:
431
+ reviewer_permission = await Node.init(db=db, schema=InfrahubKind.GLOBALPERMISSION)
432
+ await reviewer_permission.new(
433
+ db=db,
434
+ action=GlobalPermissions.REVIEW_PROPOSED_CHANGE.value,
435
+ decision=PermissionDecision.ALLOW_ALL.value,
436
+ description="Allow a user to approve or revoke proposed changes",
437
+ )
438
+ await reviewer_permission.save(db=db)
439
+
440
+ role_name = "Proposed Change Reviewer"
441
+ role = await Node.init(db=db, schema=CoreAccountRole)
442
+ await role.new(db=db, name=role_name, permissions=[reviewer_permission])
443
+ await role.save(db=db)
444
+ log.info(f"Created account role: {role_name}")
431
445
 
432
446
  return role
433
447
 
434
448
 
435
- async def create_anonymous_role(db: InfrahubDatabase) -> Node:
449
+ async def create_anonymous_role(db: InfrahubDatabase) -> CoreAccountRole:
436
450
  deny_permission = await Node.init(db=db, schema=InfrahubKind.OBJECTPERMISSION)
437
451
  await deny_permission.new(
438
452
  db=db, name="*", namespace="*", action=PermissionAction.ANY.value, decision=PermissionDecision.DENY.value
@@ -445,7 +459,7 @@ async def create_anonymous_role(db: InfrahubDatabase) -> Node:
445
459
  hfid=["*", "*", PermissionAction.VIEW.value, str(PermissionDecision.ALLOW_ALL.value)],
446
460
  )
447
461
 
448
- role = await Node.init(db=db, schema=InfrahubKind.ACCOUNTROLE)
462
+ role = await Node.init(db=db, schema=CoreAccountRole)
449
463
  await role.new(
450
464
  db=db, name=config.SETTINGS.main.anonymous_access_role, permissions=[deny_permission, view_permission]
451
465
  )
@@ -455,23 +469,40 @@ async def create_anonymous_role(db: InfrahubDatabase) -> Node:
455
469
  return role
456
470
 
457
471
 
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])
472
+ async def create_accounts_group(
473
+ db: InfrahubDatabase, name: str, roles: Sequence[CoreAccountRole], accounts: Sequence[CoreAccount]
474
+ ) -> CoreAccountGroup:
475
+ group = await Node.init(db=db, schema=CoreAccountGroup)
476
+ await group.new(db=db, name=name, roles=list(roles))
464
477
  await group.save(db=db)
465
- log.info(f"Created account group: {group_name}")
478
+ log.info(f"Created account group: {name}")
466
479
 
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}")
480
+ for account in accounts:
481
+ await group.members.add(db=db, data=account) # type: ignore[arg-type]
482
+ await group.members.save(db=db)
483
+ log.info(f"Assigned account group: {name} to {account.name.value}")
471
484
 
472
485
  return group
473
486
 
474
487
 
488
+ async def create_default_account_groups(
489
+ db: InfrahubDatabase,
490
+ admin_accounts: Sequence[CoreAccount] | None = None,
491
+ accounts: Sequence[CoreAccount] | None = None,
492
+ ) -> None:
493
+ administrator_role = await create_super_administrator_role(db=db)
494
+ await create_accounts_group(
495
+ db=db, name="Super Administrators", roles=[administrator_role], accounts=admin_accounts or []
496
+ )
497
+
498
+ default_role = await create_default_role(db=db)
499
+ proposed_change_reviewer_role = await create_proposed_change_reviewer_role(db=db)
500
+
501
+ await create_accounts_group(
502
+ db=db, name="Infrahub Users", roles=[default_role, proposed_change_reviewer_role], accounts=accounts or []
503
+ )
504
+
505
+
475
506
  async def first_time_initialization(db: InfrahubDatabase) -> None:
476
507
  # --------------------------------------------------
477
508
  # Create the default Branch
@@ -522,12 +553,10 @@ async def first_time_initialization(db: InfrahubDatabase) -> None:
522
553
  )
523
554
 
524
555
  # --------------------------------------------------
525
- # Create Global Permissions and assign them
556
+ # Create default account roles, groups and permissions
526
557
  # --------------------------------------------------
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)
558
+ await create_default_account_groups(db=db, admin_accounts=admin_accounts)
529
559
 
530
- await create_default_roles(db=db)
531
560
  if config.SETTINGS.main.allow_anonymous_access:
532
561
  await create_anonymous_role(db=db)
533
562
 
@@ -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})
@@ -36,6 +36,7 @@ from .m031_check_number_attributes import Migration031
36
36
  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
+ from .m035_drop_attr_value_index import Migration035
39
40
 
40
41
  if TYPE_CHECKING:
41
42
  from infrahub.core.root import Root
@@ -77,6 +78,7 @@ MIGRATIONS: list[type[GraphMigration | InternalSchemaMigration | ArbitraryMigrat
77
78
  Migration032,
78
79
  Migration033,
79
80
  Migration034,
81
+ Migration035,
80
82
  ]
81
83
 
82
84
 
@@ -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 Migration035(GraphMigration):
22
+ name: str = "035_drop_attr_value_index"
23
+ queries: Sequence[type[Query]] = []
24
+ minimum_version: int = 34
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
@@ -3,7 +3,9 @@ from __future__ import annotations
3
3
  from typing import TYPE_CHECKING, Any
4
4
 
5
5
  from infrahub.core.constants import NULL_VALUE, RelationshipStatus
6
+ from infrahub.core.graph.schema import GraphAttributeValueIndexedNode, GraphAttributeValueNode
6
7
  from infrahub.core.query import Query, QueryType
8
+ from infrahub.types import is_large_attribute_type
7
9
 
8
10
  if TYPE_CHECKING:
9
11
  from infrahub.database import InfrahubDatabase
@@ -36,7 +38,6 @@ class AttributeAddQuery(Query):
36
38
 
37
39
  self.params["node_kind"] = self.node_kind
38
40
  self.params["attr_name"] = self.attribute_name
39
- self.params["attr_type"] = self.attribute_kind
40
41
  self.params["branch_support"] = self.branch_support
41
42
  self.params["current_time"] = self.at.to_string()
42
43
 
@@ -55,8 +56,31 @@ class AttributeAddQuery(Query):
55
56
  self.params["is_protected_default"] = False
56
57
  self.params["is_visible_default"] = True
57
58
 
59
+ attr_value_label = GraphAttributeValueNode.get_default_label()
60
+ if not is_large_attribute_type(self.attribute_kind):
61
+ # should be indexed
62
+ attr_value_label += f":{GraphAttributeValueIndexedNode.get_default_label()}"
63
+ match_query = """
64
+ MERGE (av:%(attr_value_label)s { value: $attr_value, is_default: true })
65
+ LIMIT 1
66
+ """ % {"attr_value_label": attr_value_label}
67
+ else:
68
+ # cannot be indexed
69
+ match_query = """
70
+ OPTIONAL MATCH (existing_av:%(attr_value_label)s { value: $attr_value, is_default: true })
71
+ WHERE NOT existing_av:AttributeValueIndexed
72
+ CALL (existing_av) {
73
+ WITH existing_av
74
+ WHERE existing_av IS NULL
75
+ CREATE (:%(attr_value_label)s { value: $attr_value, is_default: true })
76
+ }
77
+ MATCH (av:%(attr_value_label)s { value: $attr_value, is_default: true })
78
+ WHERE NOT av:AttributeValueIndexed
79
+ LIMIT 1
80
+ """ % {"attr_value_label": attr_value_label}
81
+
58
82
  query = """
59
- MERGE (av:AttributeValue { value: $attr_value, is_default: true })
83
+ %(match_query)s
60
84
  MERGE (is_protected_value:Boolean { value: $is_protected_default })
61
85
  MERGE (is_visible_value:Boolean { value: $is_visible_default })
62
86
  WITH av, is_protected_value, is_visible_value
@@ -84,6 +108,7 @@ class AttributeAddQuery(Query):
84
108
  SET has_attr_e.to = $current_time
85
109
  )
86
110
  """ % {
111
+ "match_query": match_query,
87
112
  "branch_filter": branch_filter,
88
113
  "node_kind": self.node_kind,
89
114
  "uuid_generation": db.render_uuid_generation(node_label="a", node_attr="uuid"),
@@ -10,17 +10,18 @@ from prefect.logging import get_run_logger
10
10
  from infrahub.core.branch import Branch # noqa: TC001
11
11
  from infrahub.core.migrations import MIGRATION_MAP
12
12
  from infrahub.core.path import SchemaPath # noqa: TC001
13
- from infrahub.services import InfrahubServices # noqa: TC001 needed for prefect flow
13
+ from infrahub.workers.dependencies import get_database
14
14
  from infrahub.workflows.utils import add_branch_tag
15
15
 
16
16
  from .models import SchemaApplyMigrationData, SchemaMigrationPathResponseData
17
17
 
18
18
  if TYPE_CHECKING:
19
19
  from infrahub.core.schema import MainSchemaTypes
20
+ from infrahub.database import InfrahubDatabase
20
21
 
21
22
 
22
23
  @flow(name="schema_apply_migrations", flow_run_name="Apply schema migrations", persist_result=True)
23
- async def schema_apply_migrations(message: SchemaApplyMigrationData, service: InfrahubServices) -> list[str]:
24
+ async def schema_apply_migrations(message: SchemaApplyMigrationData) -> list[str]:
24
25
  await add_branch_tag(branch_name=message.branch.name)
25
26
  log = get_run_logger()
26
27
 
@@ -55,7 +56,7 @@ async def schema_apply_migrations(message: SchemaApplyMigrationData, service: In
55
56
  new_node_schema=new_node_schema,
56
57
  previous_node_schema=previous_node_schema,
57
58
  schema_path=migration.path,
58
- service=service,
59
+ database=await get_database(),
59
60
  )
60
61
 
61
62
  async for _, result in batch.execute():
@@ -75,13 +76,13 @@ async def schema_path_migrate(
75
76
  branch: Branch,
76
77
  migration_name: str,
77
78
  schema_path: SchemaPath,
78
- service: InfrahubServices,
79
+ database: InfrahubDatabase,
79
80
  new_node_schema: MainSchemaTypes | None = None,
80
81
  previous_node_schema: MainSchemaTypes | None = None,
81
82
  ) -> SchemaMigrationPathResponseData:
82
83
  log = get_run_logger()
83
84
 
84
- async with service.database.start_session() as db:
85
+ async with database.start_session() as db:
85
86
  node_kind = None
86
87
  if new_node_schema:
87
88
  node_kind = new_node_schema.kind
@@ -0,0 +1,43 @@
1
+ from typing import cast
2
+
3
+ from infrahub.core.constants.infrahubkind import THREADCOMMENT
4
+ from infrahub.core.manager import NodeManager
5
+ from infrahub.core.node import Node
6
+ from infrahub.core.protocols import CoreProposedChange as CoreProposedChangeProtocol
7
+ from infrahub.database import InfrahubDatabase
8
+
9
+
10
+ class CoreProposedChange(Node):
11
+ async def to_graphql(
12
+ self,
13
+ db: InfrahubDatabase,
14
+ fields: dict | None = None,
15
+ related_node_ids: set | None = None,
16
+ filter_sensitive: bool = False,
17
+ permissions: dict | None = None,
18
+ include_properties: bool = True,
19
+ ) -> dict:
20
+ response = await super().to_graphql(
21
+ db,
22
+ fields=fields,
23
+ related_node_ids=related_node_ids,
24
+ filter_sensitive=filter_sensitive,
25
+ permissions=permissions,
26
+ include_properties=include_properties,
27
+ )
28
+
29
+ if fields:
30
+ if "total_comments" in fields:
31
+ total_comments = 0
32
+ proposed_change = cast(CoreProposedChangeProtocol, self)
33
+ change_comments = await proposed_change.comments.get_relationships(db=db)
34
+ total_comments += len(change_comments)
35
+
36
+ threads = await proposed_change.threads.get_peers(db=db)
37
+ thread_comments = await NodeManager.query(
38
+ db=db, schema=THREADCOMMENT, filters={"thread__ids": list(threads.keys())}
39
+ )
40
+ total_comments += len(thread_comments)
41
+ response["total_comments"] = {"value": total_comments}
42
+
43
+ return response
@@ -319,6 +319,7 @@ class CoreCheckDefinition(CoreTaskTarget):
319
319
 
320
320
 
321
321
  class CoreCustomWebhook(CoreWebhook, CoreTaskTarget):
322
+ shared_key: StringOptional
322
323
  transformation: RelationshipManager
323
324
 
324
325
 
@@ -480,7 +481,10 @@ class CoreProposedChange(CoreTaskTarget):
480
481
  source_branch: String
481
482
  destination_branch: String
482
483
  state: Enum
484
+ is_draft: Boolean
485
+ total_comments: IntegerOptional
483
486
  approved_by: RelationshipManager
487
+ rejected_by: RelationshipManager
484
488
  reviewers: RelationshipManager
485
489
  created_by: RelationshipManager
486
490
  comments: RelationshipManager
@@ -554,6 +558,14 @@ class InternalAccountToken(CoreNode):
554
558
  account: RelationshipManager
555
559
 
556
560
 
561
+ class InternalIPPrefixAvailable(BuiltinIPPrefix):
562
+ pass
563
+
564
+
565
+ class InternalIPRangeAvailable(BuiltinIPAddress):
566
+ last_address: IPHost
567
+
568
+
557
569
  class InternalRefreshToken(CoreNode):
558
570
  expiration: DateTime
559
571
  account: RelationshipManager