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.
- infrahub/api/internal.py +5 -0
- infrahub/artifacts/tasks.py +17 -22
- infrahub/branch/merge_mutation_checker.py +38 -0
- infrahub/cli/__init__.py +2 -2
- infrahub/cli/context.py +7 -3
- infrahub/cli/db.py +5 -16
- infrahub/cli/upgrade.py +7 -29
- infrahub/computed_attribute/tasks.py +36 -46
- infrahub/config.py +53 -2
- infrahub/constants/environment.py +1 -0
- infrahub/core/attribute.py +9 -7
- infrahub/core/branch/tasks.py +43 -41
- infrahub/core/constants/__init__.py +20 -6
- infrahub/core/constants/infrahubkind.py +2 -0
- infrahub/core/diff/coordinator.py +3 -1
- infrahub/core/diff/repository/repository.py +0 -8
- infrahub/core/diff/tasks.py +11 -8
- infrahub/core/graph/__init__.py +1 -1
- infrahub/core/graph/index.py +1 -2
- infrahub/core/graph/schema.py +50 -29
- infrahub/core/initialization.py +62 -33
- infrahub/core/ipam/tasks.py +4 -3
- infrahub/core/merge.py +8 -10
- infrahub/core/migrations/graph/__init__.py +2 -0
- infrahub/core/migrations/graph/m035_drop_attr_value_index.py +45 -0
- infrahub/core/migrations/query/attribute_add.py +27 -2
- infrahub/core/migrations/schema/tasks.py +6 -5
- infrahub/core/node/proposed_change.py +43 -0
- infrahub/core/protocols.py +12 -0
- infrahub/core/query/attribute.py +32 -14
- infrahub/core/query/diff.py +11 -0
- infrahub/core/query/ipam.py +13 -7
- infrahub/core/query/node.py +51 -10
- infrahub/core/query/resource_manager.py +3 -3
- infrahub/core/schema/basenode_schema.py +8 -0
- infrahub/core/schema/definitions/core/__init__.py +10 -1
- infrahub/core/schema/definitions/core/ipam.py +28 -2
- infrahub/core/schema/definitions/core/propose_change.py +15 -0
- infrahub/core/schema/definitions/core/webhook.py +3 -0
- infrahub/core/schema/generic_schema.py +10 -0
- infrahub/core/schema/manager.py +10 -1
- infrahub/core/schema/node_schema.py +22 -17
- infrahub/core/schema/profile_schema.py +8 -0
- infrahub/core/schema/schema_branch.py +9 -5
- infrahub/core/schema/template_schema.py +8 -0
- infrahub/core/validators/checks_runner.py +5 -5
- infrahub/core/validators/tasks.py +6 -7
- infrahub/core/validators/uniqueness/checker.py +4 -2
- infrahub/core/validators/uniqueness/model.py +1 -0
- infrahub/core/validators/uniqueness/query.py +57 -7
- infrahub/database/__init__.py +2 -1
- infrahub/events/__init__.py +18 -0
- infrahub/events/constants.py +7 -0
- infrahub/events/generator.py +29 -2
- infrahub/events/proposed_change_action.py +181 -0
- infrahub/generators/tasks.py +24 -20
- infrahub/git/base.py +4 -7
- infrahub/git/integrator.py +21 -12
- infrahub/git/repository.py +15 -30
- infrahub/git/tasks.py +121 -106
- infrahub/graphql/field_extractor.py +69 -0
- infrahub/graphql/manager.py +15 -11
- infrahub/graphql/mutations/account.py +2 -2
- infrahub/graphql/mutations/action.py +8 -2
- infrahub/graphql/mutations/artifact_definition.py +4 -1
- infrahub/graphql/mutations/branch.py +10 -5
- infrahub/graphql/mutations/graphql_query.py +2 -1
- infrahub/graphql/mutations/main.py +14 -8
- infrahub/graphql/mutations/menu.py +2 -1
- infrahub/graphql/mutations/proposed_change.py +225 -8
- infrahub/graphql/mutations/relationship.py +5 -0
- infrahub/graphql/mutations/repository.py +2 -1
- infrahub/graphql/mutations/tasks.py +7 -9
- infrahub/graphql/mutations/webhook.py +4 -1
- infrahub/graphql/parser.py +15 -6
- infrahub/graphql/queries/__init__.py +10 -1
- infrahub/graphql/queries/account.py +3 -3
- infrahub/graphql/queries/branch.py +2 -2
- infrahub/graphql/queries/diff/tree.py +3 -3
- infrahub/graphql/queries/event.py +13 -3
- infrahub/graphql/queries/ipam.py +23 -1
- infrahub/graphql/queries/proposed_change.py +84 -0
- infrahub/graphql/queries/relationship.py +2 -2
- infrahub/graphql/queries/resource_manager.py +3 -3
- infrahub/graphql/queries/search.py +3 -2
- infrahub/graphql/queries/status.py +3 -2
- infrahub/graphql/queries/task.py +2 -2
- infrahub/graphql/resolvers/ipam.py +440 -0
- infrahub/graphql/resolvers/many_relationship.py +4 -3
- infrahub/graphql/resolvers/resolver.py +5 -5
- infrahub/graphql/resolvers/single_relationship.py +3 -2
- infrahub/graphql/schema.py +25 -5
- infrahub/graphql/types/__init__.py +2 -2
- infrahub/graphql/types/attribute.py +3 -3
- infrahub/graphql/types/event.py +60 -0
- infrahub/groups/tasks.py +6 -6
- infrahub/lock.py +3 -2
- infrahub/menu/generator.py +8 -0
- infrahub/message_bus/operations/__init__.py +9 -12
- infrahub/message_bus/operations/git/file.py +6 -5
- infrahub/message_bus/operations/git/repository.py +12 -20
- infrahub/message_bus/operations/refresh/registry.py +15 -9
- infrahub/message_bus/operations/send/echo.py +7 -4
- infrahub/message_bus/types.py +1 -0
- infrahub/permissions/globals.py +1 -4
- infrahub/permissions/manager.py +8 -5
- infrahub/pools/prefix.py +7 -5
- infrahub/prefect_server/app.py +31 -0
- infrahub/prefect_server/bootstrap.py +18 -0
- infrahub/proposed_change/action_checker.py +206 -0
- infrahub/proposed_change/approval_revoker.py +40 -0
- infrahub/proposed_change/branch_diff.py +3 -1
- infrahub/proposed_change/checker.py +45 -0
- infrahub/proposed_change/constants.py +32 -2
- infrahub/proposed_change/tasks.py +182 -150
- infrahub/py.typed +0 -0
- infrahub/server.py +29 -17
- infrahub/services/__init__.py +13 -28
- infrahub/services/adapters/cache/__init__.py +4 -0
- infrahub/services/adapters/cache/nats.py +2 -0
- infrahub/services/adapters/cache/redis.py +3 -0
- infrahub/services/adapters/message_bus/__init__.py +0 -2
- infrahub/services/adapters/message_bus/local.py +1 -2
- infrahub/services/adapters/message_bus/nats.py +6 -8
- infrahub/services/adapters/message_bus/rabbitmq.py +7 -9
- infrahub/services/adapters/workflow/__init__.py +1 -0
- infrahub/services/adapters/workflow/local.py +1 -8
- infrahub/services/component.py +2 -1
- infrahub/task_manager/event.py +52 -0
- infrahub/task_manager/models.py +9 -0
- infrahub/tasks/artifact.py +6 -7
- infrahub/tasks/check.py +4 -7
- infrahub/telemetry/tasks.py +15 -18
- infrahub/transformations/tasks.py +10 -6
- infrahub/trigger/tasks.py +4 -3
- infrahub/types.py +4 -0
- infrahub/validators/events.py +7 -7
- infrahub/validators/tasks.py +6 -7
- infrahub/webhook/models.py +45 -45
- infrahub/webhook/tasks.py +25 -24
- infrahub/workers/dependencies.py +143 -0
- infrahub/workers/infrahub_async.py +19 -43
- infrahub/workflows/catalogue.py +16 -2
- infrahub/workflows/initialization.py +5 -4
- infrahub/workflows/models.py +2 -0
- infrahub_sdk/client.py +6 -6
- infrahub_sdk/ctl/repository.py +51 -0
- infrahub_sdk/ctl/schema.py +9 -9
- infrahub_sdk/protocols.py +40 -6
- {infrahub_server-1.3.5.dist-info → infrahub_server-1.4.0b0.dist-info}/METADATA +5 -4
- {infrahub_server-1.3.5.dist-info → infrahub_server-1.4.0b0.dist-info}/RECORD +158 -144
- infrahub_testcontainers/container.py +17 -0
- infrahub_testcontainers/docker-compose-cluster.test.yml +56 -1
- infrahub_testcontainers/docker-compose.test.yml +56 -1
- infrahub_testcontainers/helpers.py +4 -1
- {infrahub_server-1.3.5.dist-info → infrahub_server-1.4.0b0.dist-info}/LICENSE.txt +0 -0
- {infrahub_server-1.3.5.dist-info → infrahub_server-1.4.0b0.dist-info}/WHEEL +0 -0
- {infrahub_server-1.3.5.dist-info → infrahub_server-1.4.0b0.dist-info}/entry_points.txt +0 -0
infrahub/core/graph/schema.py
CHANGED
|
@@ -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(
|
|
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(
|
|
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(
|
|
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(
|
|
179
|
+
class GraphAttributeValueNode(GraphVertex):
|
|
172
180
|
default_label: str = "AttributeValue"
|
|
173
181
|
properties: GraphAttributeValueProperties
|
|
174
182
|
relationships: GraphAttributeValueRelationships
|
|
175
183
|
|
|
176
184
|
|
|
177
|
-
class
|
|
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(
|
|
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(
|
|
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
|
-
|
|
233
|
-
"nodes"
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
"
|
|
246
|
-
"
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
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()
|
infrahub/core/initialization.py
CHANGED
|
@@ -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
|
|
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) ->
|
|
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
|
-
|
|
337
|
-
await
|
|
338
|
-
await
|
|
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
|
|
342
|
+
return role
|
|
342
343
|
|
|
343
344
|
|
|
344
|
-
async def
|
|
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=
|
|
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
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
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) ->
|
|
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=
|
|
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
|
|
459
|
-
db: InfrahubDatabase,
|
|
460
|
-
) ->
|
|
461
|
-
|
|
462
|
-
|
|
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: {
|
|
478
|
+
log.info(f"Created account group: {name}")
|
|
466
479
|
|
|
467
|
-
for
|
|
468
|
-
await group.members.add(db=db, data=
|
|
469
|
-
await group.members.save(db=db)
|
|
470
|
-
log.info(f"Assigned account group: {
|
|
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
|
|
556
|
+
# Create default account roles, groups and permissions
|
|
526
557
|
# --------------------------------------------------
|
|
527
|
-
|
|
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
|
|
infrahub/core/ipam/tasks.py
CHANGED
|
@@ -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.
|
|
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]
|
|
24
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
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
|
|
85
|
-
if not self.
|
|
86
|
-
raise ValueError("BranchMerger hasn't been initialized with a
|
|
87
|
-
return self.
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
infrahub/core/protocols.py
CHANGED
|
@@ -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
|