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
|
@@ -119,7 +119,7 @@ class SchemaBranch:
|
|
|
119
119
|
def template_names(self) -> list[str]:
|
|
120
120
|
return list(self.templates.keys())
|
|
121
121
|
|
|
122
|
-
def get_all_kind_id_map(self, nodes_and_generics_only: bool = False) -> dict[str, str]:
|
|
122
|
+
def get_all_kind_id_map(self, nodes_and_generics_only: bool = False) -> dict[str, str | None]:
|
|
123
123
|
kind_id_map = {}
|
|
124
124
|
if nodes_and_generics_only:
|
|
125
125
|
names = self.node_names + self.generic_names_without_templates
|
|
@@ -441,7 +441,7 @@ class SchemaBranch:
|
|
|
441
441
|
return list(all_schemas.values())
|
|
442
442
|
|
|
443
443
|
def get_schemas_by_rel_identifier(self, identifier: str) -> list[MainSchemaTypes]:
|
|
444
|
-
nodes: list[
|
|
444
|
+
nodes: list[MainSchemaTypes] = []
|
|
445
445
|
for node_name in list(self.nodes.keys()) + list(self.generics.keys()):
|
|
446
446
|
node = self.get(name=node_name, duplicate=False)
|
|
447
447
|
rel = node.get_relationship_by_identifier(id=identifier, raise_on_error=False)
|
|
@@ -660,7 +660,7 @@ class SchemaBranch:
|
|
|
660
660
|
and not (
|
|
661
661
|
schema_attribute_path.relationship_schema.name == "ip_namespace"
|
|
662
662
|
and isinstance(node_schema, NodeSchema)
|
|
663
|
-
and (node_schema.is_ip_address
|
|
663
|
+
and (node_schema.is_ip_address or node_schema.is_ip_prefix)
|
|
664
664
|
)
|
|
665
665
|
):
|
|
666
666
|
raise ValueError(
|
|
@@ -1447,7 +1447,8 @@ class SchemaBranch:
|
|
|
1447
1447
|
node.validate_inheritance(interface=generic_kind_schema)
|
|
1448
1448
|
|
|
1449
1449
|
# Store the list of node referencing a specific generics
|
|
1450
|
-
|
|
1450
|
+
if node.namespace != "Internal":
|
|
1451
|
+
generics_used_by[generic_kind].append(node.kind)
|
|
1451
1452
|
node.inherit_from_interface(interface=generic_kind_schema)
|
|
1452
1453
|
|
|
1453
1454
|
if len(generic_with_hierarchical_support) > 1:
|
|
@@ -1925,7 +1926,10 @@ class SchemaBranch:
|
|
|
1925
1926
|
for node_name in self.node_names + self.generic_names:
|
|
1926
1927
|
node = self.get(name=node_name, duplicate=False)
|
|
1927
1928
|
|
|
1928
|
-
if node.namespace in RESTRICTED_NAMESPACES
|
|
1929
|
+
if node.namespace in RESTRICTED_NAMESPACES and node.kind not in (
|
|
1930
|
+
InfrahubKind.IPRANGEAVAILABLE,
|
|
1931
|
+
InfrahubKind.IPPREFIXAVAILABLE,
|
|
1932
|
+
):
|
|
1929
1933
|
continue
|
|
1930
1934
|
|
|
1931
1935
|
profiles_rel_settings: dict[str, Any] = {
|
|
@@ -27,6 +27,14 @@ class TemplateSchema(BaseNodeSchema):
|
|
|
27
27
|
def is_template_schema(self) -> bool:
|
|
28
28
|
return True
|
|
29
29
|
|
|
30
|
+
@property
|
|
31
|
+
def is_ip_prefix(self) -> bool:
|
|
32
|
+
return False
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def is_ip_address(self) -> bool:
|
|
36
|
+
return False
|
|
37
|
+
|
|
30
38
|
def get_labels(self) -> list[str]:
|
|
31
39
|
"""Return the labels for this object, composed of the kind and the list of Generic this object is inheriting from."""
|
|
32
40
|
|
|
@@ -6,7 +6,7 @@ from infrahub_sdk.protocols import CoreValidator
|
|
|
6
6
|
from infrahub.context import InfrahubContext
|
|
7
7
|
from infrahub.core.constants import ValidatorConclusion, ValidatorState
|
|
8
8
|
from infrahub.core.timestamp import Timestamp
|
|
9
|
-
from infrahub.services import
|
|
9
|
+
from infrahub.services.adapters.event import InfrahubEventService
|
|
10
10
|
from infrahub.validators.events import send_failed_validator, send_passed_validator
|
|
11
11
|
|
|
12
12
|
|
|
@@ -14,7 +14,7 @@ async def run_checks_and_update_validator(
|
|
|
14
14
|
checks: list[Coroutine[Any, None, ValidatorConclusion]],
|
|
15
15
|
validator: CoreValidator,
|
|
16
16
|
context: InfrahubContext,
|
|
17
|
-
|
|
17
|
+
event_service: InfrahubEventService,
|
|
18
18
|
proposed_change_id: str,
|
|
19
19
|
) -> None:
|
|
20
20
|
"""
|
|
@@ -38,7 +38,7 @@ async def run_checks_and_update_validator(
|
|
|
38
38
|
failed_early = True
|
|
39
39
|
await validator.save()
|
|
40
40
|
await send_failed_validator(
|
|
41
|
-
|
|
41
|
+
event_service=event_service, validator=validator, proposed_change_id=proposed_change_id, context=context
|
|
42
42
|
)
|
|
43
43
|
# Continue to iterate to wait for the end of all checks
|
|
44
44
|
|
|
@@ -52,9 +52,9 @@ async def run_checks_and_update_validator(
|
|
|
52
52
|
if not failed_early:
|
|
53
53
|
if validator.conclusion.value == ValidatorConclusion.SUCCESS.value:
|
|
54
54
|
await send_passed_validator(
|
|
55
|
-
|
|
55
|
+
event_service=event_service, validator=validator, proposed_change_id=proposed_change_id, context=context
|
|
56
56
|
)
|
|
57
57
|
else:
|
|
58
58
|
await send_failed_validator(
|
|
59
|
-
|
|
59
|
+
event_service=event_service, validator=validator, proposed_change_id=proposed_change_id, context=context
|
|
60
60
|
)
|
|
@@ -13,19 +13,18 @@ from infrahub.core.schema import GenericSchema, NodeSchema
|
|
|
13
13
|
from infrahub.core.validators.aggregated_checker import AggregatedConstraintChecker
|
|
14
14
|
from infrahub.core.validators.model import SchemaConstraintValidatorRequest, SchemaViolation
|
|
15
15
|
from infrahub.dependencies.registry import get_component_registry
|
|
16
|
-
from infrahub.
|
|
16
|
+
from infrahub.workers.dependencies import get_database
|
|
17
17
|
from infrahub.workflows.utils import add_tags
|
|
18
18
|
|
|
19
19
|
from .models.validate_migration import SchemaValidateMigrationData, SchemaValidatorPathResponseData
|
|
20
20
|
|
|
21
21
|
if TYPE_CHECKING:
|
|
22
22
|
from infrahub.core.schema.schema_branch import SchemaBranch
|
|
23
|
+
from infrahub.database import InfrahubDatabase
|
|
23
24
|
|
|
24
25
|
|
|
25
26
|
@flow(name="schema_validate_migrations", flow_run_name="Validate schema migrations", persist_result=True)
|
|
26
|
-
async def schema_validate_migrations(
|
|
27
|
-
message: SchemaValidateMigrationData, service: InfrahubServices
|
|
28
|
-
) -> list[SchemaValidatorPathResponseData]:
|
|
27
|
+
async def schema_validate_migrations(message: SchemaValidateMigrationData) -> list[SchemaValidatorPathResponseData]:
|
|
29
28
|
batch = InfrahubBatch(return_exceptions=True)
|
|
30
29
|
log = get_run_logger()
|
|
31
30
|
await add_tags(branches=[message.branch.name])
|
|
@@ -47,7 +46,7 @@ async def schema_validate_migrations(
|
|
|
47
46
|
node_schema=schema,
|
|
48
47
|
schema_path=constraint.path,
|
|
49
48
|
schema_branch=message.schema_branch,
|
|
50
|
-
|
|
49
|
+
database=await get_database(),
|
|
51
50
|
)
|
|
52
51
|
|
|
53
52
|
results = [result async for _, result in batch.execute()]
|
|
@@ -67,9 +66,9 @@ async def schema_path_validate(
|
|
|
67
66
|
node_schema: NodeSchema | GenericSchema,
|
|
68
67
|
schema_path: SchemaPath,
|
|
69
68
|
schema_branch: SchemaBranch,
|
|
70
|
-
|
|
69
|
+
database: InfrahubDatabase,
|
|
71
70
|
) -> SchemaValidatorPathResponseData:
|
|
72
|
-
async with
|
|
71
|
+
async with database.start_session(read_only=True) as db:
|
|
73
72
|
constraint_request = SchemaConstraintValidatorRequest(
|
|
74
73
|
branch=branch,
|
|
75
74
|
constraint_name=constraint_name,
|
|
@@ -75,7 +75,7 @@ class UniquenessChecker(ConstraintCheckerInterface):
|
|
|
75
75
|
|
|
76
76
|
async def build_query_request(self, schema: MainSchemaTypes) -> NodeUniquenessQueryRequest:
|
|
77
77
|
unique_attr_paths = {
|
|
78
|
-
QueryAttributePath(attribute_name=attr_schema.name, property_name="value")
|
|
78
|
+
QueryAttributePath(attribute_name=attr_schema.name, attribute_kind=attr_schema.kind, property_name="value")
|
|
79
79
|
for attr_schema in schema.unique_attributes
|
|
80
80
|
}
|
|
81
81
|
relationship_attr_paths = set()
|
|
@@ -92,7 +92,9 @@ class UniquenessChecker(ConstraintCheckerInterface):
|
|
|
92
92
|
sub_schema, property_name = get_attribute_path_from_string(path, schema)
|
|
93
93
|
if isinstance(sub_schema, AttributeSchema):
|
|
94
94
|
unique_attr_paths.add(
|
|
95
|
-
QueryAttributePath(
|
|
95
|
+
QueryAttributePath(
|
|
96
|
+
attribute_name=sub_schema.name, attribute_kind=sub_schema.kind, property_name=property_name
|
|
97
|
+
)
|
|
96
98
|
)
|
|
97
99
|
elif isinstance(sub_schema, RelationshipSchema):
|
|
98
100
|
relationship_attr_paths.add(
|
|
@@ -3,7 +3,9 @@ from __future__ import annotations
|
|
|
3
3
|
from typing import TYPE_CHECKING, Any
|
|
4
4
|
|
|
5
5
|
from infrahub.core.constants.relationship_label import RELATIONSHIP_TO_VALUE_LABEL
|
|
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
|
from .model import QueryAttributePathValued, QueryRelationshipPathValued
|
|
9
11
|
|
|
@@ -40,6 +42,7 @@ class NodeUniqueAttributeConstraintQuery(Query):
|
|
|
40
42
|
items="relationships(active_path)", item_names=["branch", "branch_level"]
|
|
41
43
|
)
|
|
42
44
|
|
|
45
|
+
attrs_include_large_type = False
|
|
43
46
|
attribute_names = set()
|
|
44
47
|
attr_paths, attr_paths_with_value, attr_values = [], [], []
|
|
45
48
|
for attr_path in self.query_request.unique_attribute_paths:
|
|
@@ -49,12 +52,19 @@ class NodeUniqueAttributeConstraintQuery(Query):
|
|
|
49
52
|
raise ValueError(
|
|
50
53
|
f"{attr_path.property_name} is not a valid property for a uniqueness constraint"
|
|
51
54
|
) from exc
|
|
55
|
+
if is_large_attribute_type(attr_path.attribute_kind):
|
|
56
|
+
attrs_include_large_type = True
|
|
52
57
|
attribute_names.add(attr_path.attribute_name)
|
|
53
58
|
if attr_path.value:
|
|
54
59
|
attr_paths_with_value.append((attr_path.attribute_name, property_rel_name, attr_path.value))
|
|
55
60
|
attr_values.append(attr_path.value)
|
|
56
61
|
else:
|
|
57
62
|
attr_paths.append((attr_path.attribute_name, property_rel_name))
|
|
63
|
+
attr_value_label = (
|
|
64
|
+
GraphAttributeValueNode.get_default_label()
|
|
65
|
+
if attrs_include_large_type
|
|
66
|
+
else GraphAttributeValueIndexedNode.get_default_label()
|
|
67
|
+
)
|
|
58
68
|
|
|
59
69
|
relationship_names = set()
|
|
60
70
|
relationship_attr_paths = []
|
|
@@ -112,11 +122,11 @@ class NodeUniqueAttributeConstraintQuery(Query):
|
|
|
112
122
|
""" % {"node_kind": self.query_request.kind}
|
|
113
123
|
|
|
114
124
|
attr_paths_with_value_subquery = """
|
|
115
|
-
MATCH attr_path = (start_node:%(node_kind)s)-[:HAS_ATTRIBUTE]->(attr:Attribute)-[r:HAS_VALUE]->(attr_value
|
|
125
|
+
MATCH attr_path = (start_node:%(node_kind)s)-[:HAS_ATTRIBUTE]->(attr:Attribute)-[r:HAS_VALUE]->(attr_value:%(attr_value_label)s)
|
|
116
126
|
WHERE attr.name in $attribute_names AND attr_value.value in $attr_values
|
|
117
127
|
AND [attr.name, type(r), attr_value.value] in $attr_paths_with_value
|
|
118
128
|
RETURN start_node, attr_path as potential_path, NULL as rel_identifier, attr.name as potential_attr, attr_value.value as potential_attr_value
|
|
119
|
-
""" % {"node_kind": self.query_request.kind}
|
|
129
|
+
""" % {"node_kind": self.query_request.kind, "attr_value_label": attr_value_label}
|
|
120
130
|
|
|
121
131
|
relationship_attr_paths_subquery = """
|
|
122
132
|
MATCH rel_path = (start_node:%(node_kind)s)-[:IS_RELATED]-(relationship_node:Relationship)-[:IS_RELATED]-(related_n:Node)-[:HAS_ATTRIBUTE]->(rel_attr:Attribute)-[:HAS_VALUE]->(rel_attr_value:AttributeValue)
|
|
@@ -262,8 +272,20 @@ class UniquenessValidationQuery(Query):
|
|
|
262
272
|
self.node_ids_to_exclude = node_ids_to_exclude
|
|
263
273
|
super().__init__(**kwargs)
|
|
264
274
|
|
|
275
|
+
def _is_attribute_large_type(self, db: InfrahubDatabase, node_kind: str, attribute_name: str) -> bool:
|
|
276
|
+
"""Determine if an attribute is a large type that should use AttributeValue instead of AttributeValueIndexed."""
|
|
277
|
+
node_schema = db.schema.get(node_kind, branch=self.branch, duplicate=False)
|
|
278
|
+
attr_schema = node_schema.get_attribute(attribute_name)
|
|
279
|
+
return is_large_attribute_type(attr_schema.kind)
|
|
280
|
+
|
|
265
281
|
def _build_attr_subquery(
|
|
266
|
-
self,
|
|
282
|
+
self,
|
|
283
|
+
node_kind: str,
|
|
284
|
+
attr_path: QueryAttributePathValued,
|
|
285
|
+
index: int,
|
|
286
|
+
branch_filter: str,
|
|
287
|
+
is_first_query: bool,
|
|
288
|
+
is_large_type: bool,
|
|
267
289
|
) -> tuple[str, dict[str, str | int | float | bool]]:
|
|
268
290
|
attr_name_var = f"attr_name_{index}"
|
|
269
291
|
attr_value_var = f"attr_value_{index}"
|
|
@@ -271,8 +293,16 @@ class UniquenessValidationQuery(Query):
|
|
|
271
293
|
first_query_filter = "WHERE $node_ids_to_exclude IS NULL OR NOT node.uuid IN $node_ids_to_exclude"
|
|
272
294
|
else:
|
|
273
295
|
first_query_filter = ""
|
|
296
|
+
|
|
297
|
+
# Determine the appropriate label based on attribute type
|
|
298
|
+
attr_value_label = (
|
|
299
|
+
GraphAttributeValueNode.get_default_label()
|
|
300
|
+
if is_large_type
|
|
301
|
+
else GraphAttributeValueIndexedNode.get_default_label()
|
|
302
|
+
)
|
|
303
|
+
|
|
274
304
|
attribute_query = """
|
|
275
|
-
MATCH (node:%(node_kind)s)-[:HAS_ATTRIBUTE]->(attr:Attribute {name: $%(attr_name_var)s})-[:HAS_VALUE]->(
|
|
305
|
+
MATCH (node:%(node_kind)s)-[:HAS_ATTRIBUTE]->(attr:Attribute {name: $%(attr_name_var)s})-[:HAS_VALUE]->(:%(attr_value_label)s {value: $%(attr_value_var)s})
|
|
276
306
|
%(first_query_filter)s
|
|
277
307
|
WITH DISTINCT node
|
|
278
308
|
CALL (node) {
|
|
@@ -300,6 +330,7 @@ CALL (node) {
|
|
|
300
330
|
"attr_value_var": attr_value_var,
|
|
301
331
|
"branch_filter": branch_filter,
|
|
302
332
|
"index": index,
|
|
333
|
+
"attr_value_label": attr_value_label,
|
|
303
334
|
}
|
|
304
335
|
params: dict[str, str | int | float | bool] = {
|
|
305
336
|
attr_name_var: attr_path.attribute_name,
|
|
@@ -314,6 +345,7 @@ CALL (node) {
|
|
|
314
345
|
index: int,
|
|
315
346
|
branch_filter: str,
|
|
316
347
|
is_first_query: bool,
|
|
348
|
+
is_large_type: bool = False,
|
|
317
349
|
) -> tuple[str, dict[str, str | int | float | bool]]:
|
|
318
350
|
params: dict[str, str | int | float | bool] = {}
|
|
319
351
|
rel_attr_query = ""
|
|
@@ -321,6 +353,14 @@ CALL (node) {
|
|
|
321
353
|
if rel_path.attribute_name and rel_path.attribute_value:
|
|
322
354
|
attr_name_var = f"attr_name_{index}"
|
|
323
355
|
attr_value_var = f"attr_value_{index}"
|
|
356
|
+
|
|
357
|
+
# Determine the appropriate label based on relationship attribute type
|
|
358
|
+
rel_attr_value_label = (
|
|
359
|
+
GraphAttributeValueNode.get_default_label()
|
|
360
|
+
if is_large_type
|
|
361
|
+
else GraphAttributeValueIndexedNode.get_default_label()
|
|
362
|
+
)
|
|
363
|
+
|
|
324
364
|
rel_attr_query = """
|
|
325
365
|
MATCH (peer)-[r:HAS_ATTRIBUTE]->(attr:Attribute {name: $%(attr_name_var)s})
|
|
326
366
|
WHERE %(branch_filter)s
|
|
@@ -330,19 +370,25 @@ CALL (node) {
|
|
|
330
370
|
LIMIT 1
|
|
331
371
|
WITH attr, is_active
|
|
332
372
|
WHERE is_active = TRUE
|
|
333
|
-
MATCH (attr)-[r:HAS_VALUE]->(
|
|
373
|
+
MATCH (attr)-[r:HAS_VALUE]->(:%(rel_attr_value_label)s {value: $%(attr_value_var)s})
|
|
334
374
|
WHERE %(branch_filter)s
|
|
335
375
|
WITH r
|
|
336
376
|
ORDER BY r.branch_level DESC, r.from DESC, r.status ASC
|
|
337
377
|
LIMIT 1
|
|
338
378
|
WITH r
|
|
339
379
|
WHERE r.status = "active"
|
|
340
|
-
""" % {
|
|
380
|
+
""" % {
|
|
381
|
+
"attr_name_var": attr_name_var,
|
|
382
|
+
"attr_value_var": attr_value_var,
|
|
383
|
+
"branch_filter": branch_filter,
|
|
384
|
+
"rel_attr_value_label": rel_attr_value_label,
|
|
385
|
+
}
|
|
341
386
|
rel_attr_match = (
|
|
342
|
-
"-[r:HAS_ATTRIBUTE]->(attr:Attribute {name: $%(attr_name_var)s})-[:HAS_VALUE]->(
|
|
387
|
+
"-[r:HAS_ATTRIBUTE]->(attr:Attribute {name: $%(attr_name_var)s})-[:HAS_VALUE]->(:%(rel_attr_value_label)s {value: $%(attr_value_var)s})"
|
|
343
388
|
% {
|
|
344
389
|
"attr_name_var": attr_name_var,
|
|
345
390
|
"attr_value_var": attr_value_var,
|
|
391
|
+
"rel_attr_value_label": rel_attr_value_label,
|
|
346
392
|
}
|
|
347
393
|
)
|
|
348
394
|
params[attr_name_var] = rel_path.attribute_name
|
|
@@ -426,12 +472,16 @@ CALL (node) {
|
|
|
426
472
|
for index, schema_path in enumerate(self.query_request.unique_valued_paths):
|
|
427
473
|
is_first_query = index == 0
|
|
428
474
|
if isinstance(schema_path, QueryAttributePathValued):
|
|
475
|
+
is_large_type = self._is_attribute_large_type(
|
|
476
|
+
db=db, node_kind=self.query_request.kind, attribute_name=schema_path.attribute_name
|
|
477
|
+
)
|
|
429
478
|
subquery, params = self._build_attr_subquery(
|
|
430
479
|
node_kind=self.query_request.kind,
|
|
431
480
|
attr_path=schema_path,
|
|
432
481
|
index=index,
|
|
433
482
|
branch_filter=branch_filter,
|
|
434
483
|
is_first_query=is_first_query,
|
|
484
|
+
is_large_type=is_large_type,
|
|
435
485
|
)
|
|
436
486
|
else:
|
|
437
487
|
subquery, params = self._build_rel_subquery(
|
infrahub/database/__init__.py
CHANGED
|
@@ -295,7 +295,8 @@ class InfrahubDatabase:
|
|
|
295
295
|
traceback: TracebackType | None,
|
|
296
296
|
) -> None:
|
|
297
297
|
if self._mode == InfrahubDatabaseMode.SESSION:
|
|
298
|
-
|
|
298
|
+
await self._session.close()
|
|
299
|
+
return
|
|
299
300
|
|
|
300
301
|
if self._mode == InfrahubDatabaseMode.TRANSACTION:
|
|
301
302
|
if exc_type is not None:
|
infrahub/events/__init__.py
CHANGED
|
@@ -3,6 +3,16 @@ from .branch_action import BranchCreatedEvent, BranchDeletedEvent, BranchMergedE
|
|
|
3
3
|
from .group_action import GroupMemberAddedEvent, GroupMemberRemovedEvent
|
|
4
4
|
from .models import EventMeta, InfrahubEvent
|
|
5
5
|
from .node_action import NodeCreatedEvent, NodeDeletedEvent, NodeUpdatedEvent
|
|
6
|
+
from .proposed_change_action import (
|
|
7
|
+
ProposedChangeApprovalRevokedEvent,
|
|
8
|
+
ProposedChangeApprovedEvent,
|
|
9
|
+
ProposedChangeMergedEvent,
|
|
10
|
+
ProposedChangeRejectedEvent,
|
|
11
|
+
ProposedChangeRejectionRevokedEvent,
|
|
12
|
+
ProposedChangeReviewRequestedEvent,
|
|
13
|
+
ProposedChangeThreadCreatedEvent,
|
|
14
|
+
ProposedChangeThreadUpdatedEvent,
|
|
15
|
+
)
|
|
6
16
|
from .repository_action import CommitUpdatedEvent
|
|
7
17
|
from .validator_action import ValidatorFailedEvent, ValidatorPassedEvent, ValidatorStartedEvent
|
|
8
18
|
|
|
@@ -21,6 +31,14 @@ __all__ = [
|
|
|
21
31
|
"NodeCreatedEvent",
|
|
22
32
|
"NodeDeletedEvent",
|
|
23
33
|
"NodeUpdatedEvent",
|
|
34
|
+
"ProposedChangeApprovalRevokedEvent",
|
|
35
|
+
"ProposedChangeApprovedEvent",
|
|
36
|
+
"ProposedChangeMergedEvent",
|
|
37
|
+
"ProposedChangeRejectedEvent",
|
|
38
|
+
"ProposedChangeRejectionRevokedEvent",
|
|
39
|
+
"ProposedChangeReviewRequestedEvent",
|
|
40
|
+
"ProposedChangeThreadCreatedEvent",
|
|
41
|
+
"ProposedChangeThreadUpdatedEvent",
|
|
24
42
|
"ValidatorFailedEvent",
|
|
25
43
|
"ValidatorPassedEvent",
|
|
26
44
|
"ValidatorStartedEvent",
|
infrahub/events/constants.py
CHANGED
infrahub/events/generator.py
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
from infrahub.context import InfrahubContext
|
|
2
2
|
from infrahub.core.branch import Branch
|
|
3
3
|
from infrahub.core.changelog.models import RelationshipChangelogGetter
|
|
4
|
-
from infrahub.core.constants import MutationAction
|
|
4
|
+
from infrahub.core.constants import InfrahubKind, MutationAction
|
|
5
5
|
from infrahub.core.node import Node
|
|
6
|
+
from infrahub.core.protocols import CoreProposedChange
|
|
6
7
|
from infrahub.database import InfrahubDatabase
|
|
7
8
|
from infrahub.events.node_action import NodeDeletedEvent, NodeMutatedEvent, NodeUpdatedEvent, get_node_event
|
|
8
9
|
from infrahub.groups.parsers import GroupNodeMutationParser
|
|
9
10
|
from infrahub.worker import WORKER_IDENTITY
|
|
10
11
|
|
|
11
12
|
from .models import EventMeta, InfrahubEvent
|
|
13
|
+
from .proposed_change_action import ProposedChangeThreadCreatedEvent, ProposedChangeThreadUpdatedEvent
|
|
12
14
|
|
|
13
15
|
|
|
14
16
|
async def generate_node_mutation_events(
|
|
@@ -68,4 +70,29 @@ async def generate_node_mutation_events(
|
|
|
68
70
|
|
|
69
71
|
group_parser = GroupNodeMutationParser(db=db, branch=branch)
|
|
70
72
|
group_events = await group_parser.group_events_from_node_actions(events=events)
|
|
71
|
-
|
|
73
|
+
|
|
74
|
+
specific_events: list[InfrahubEvent] = []
|
|
75
|
+
if (kind := node.get_kind()) in [
|
|
76
|
+
InfrahubKind.CHANGETHREAD,
|
|
77
|
+
InfrahubKind.OBJECTTHREAD,
|
|
78
|
+
InfrahubKind.ARTIFACTTHREAD,
|
|
79
|
+
InfrahubKind.FILETHREAD,
|
|
80
|
+
]:
|
|
81
|
+
proposed_change: CoreProposedChange = await node.change.get_peer(db=db, peer_type=CoreProposedChange) # type: ignore[attr-defined]
|
|
82
|
+
action_to_event_map = {
|
|
83
|
+
MutationAction.CREATED: ProposedChangeThreadCreatedEvent,
|
|
84
|
+
MutationAction.UPDATED: ProposedChangeThreadUpdatedEvent,
|
|
85
|
+
}
|
|
86
|
+
if action in action_to_event_map:
|
|
87
|
+
specific_events.append(
|
|
88
|
+
action_to_event_map[action](
|
|
89
|
+
proposed_change_id=proposed_change.id,
|
|
90
|
+
proposed_change_name=proposed_change.name.value,
|
|
91
|
+
proposed_change_state=proposed_change.state.value,
|
|
92
|
+
thread_id=node.id,
|
|
93
|
+
thread_kind=kind,
|
|
94
|
+
meta=EventMeta.from_context(context=context),
|
|
95
|
+
)
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
return events + group_events + specific_events
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
from typing import ClassVar
|
|
2
|
+
|
|
3
|
+
from pydantic import Field
|
|
4
|
+
|
|
5
|
+
from infrahub.core.constants import InfrahubKind, MutationAction
|
|
6
|
+
|
|
7
|
+
from .constants import EVENT_NAMESPACE
|
|
8
|
+
from .models import InfrahubEvent
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ProposedChangeEvent(InfrahubEvent):
|
|
12
|
+
proposed_change_id: str = Field(..., description="The ID of the proposed change")
|
|
13
|
+
proposed_change_name: str = Field(..., description="The name of the proposed change")
|
|
14
|
+
proposed_change_state: str = Field(..., description="The state of the proposed change")
|
|
15
|
+
|
|
16
|
+
def get_resource(self) -> dict[str, str]:
|
|
17
|
+
return {
|
|
18
|
+
"prefect.resource.id": f"infrahub.proposed_change.{self.proposed_change_id}",
|
|
19
|
+
"infrahub.node.kind": InfrahubKind.PROPOSEDCHANGE,
|
|
20
|
+
"infrahub.node.id": self.proposed_change_id,
|
|
21
|
+
"infrahub.proposed_change.name": self.proposed_change_name,
|
|
22
|
+
"infrahub.proposed_change.state": self.proposed_change_state,
|
|
23
|
+
"infrahub.branch.name": self.meta.context.branch.name,
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ProposedChangeReviewEvent(ProposedChangeEvent):
|
|
28
|
+
reviewer_account_id: str = Field(..., description="The ID of the user who reviewed the proposed change")
|
|
29
|
+
reviewer_account_name: str = Field(..., description="The name of the user who reviewed the proposed change")
|
|
30
|
+
reviewer_decision: str = Field(..., description="The decision made by the reviewer")
|
|
31
|
+
|
|
32
|
+
def get_related(self) -> list[dict[str, str]]:
|
|
33
|
+
related = super().get_related()
|
|
34
|
+
related.append(
|
|
35
|
+
{
|
|
36
|
+
"prefect.resource.id": self.reviewer_account_id,
|
|
37
|
+
"prefect.resource.role": "infrahub.related.node",
|
|
38
|
+
"infrahub.node.kind": InfrahubKind.GENERICACCOUNT,
|
|
39
|
+
"infrahub.node.id": self.reviewer_account_id,
|
|
40
|
+
"infrahub.reviewer.account.name": self.reviewer_account_name,
|
|
41
|
+
}
|
|
42
|
+
)
|
|
43
|
+
return related
|
|
44
|
+
|
|
45
|
+
def get_resource(self) -> dict[str, str]:
|
|
46
|
+
return {**super().get_resource(), "infrahub.proposed_change.reviewer_decision": self.reviewer_decision}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class ProposedChangeReviewRevokedEvent(ProposedChangeEvent):
|
|
50
|
+
reviewer_account_id: str = Field(..., description="The ID of the user who reviewed the proposed change")
|
|
51
|
+
reviewer_account_name: str = Field(..., description="The name of the user who reviewed the proposed change")
|
|
52
|
+
reviewer_former_decision: str = Field(..., description="The former decision made by the reviewer")
|
|
53
|
+
|
|
54
|
+
def get_related(self) -> list[dict[str, str]]:
|
|
55
|
+
related = super().get_related()
|
|
56
|
+
related.append(
|
|
57
|
+
{
|
|
58
|
+
"prefect.resource.id": self.reviewer_account_id,
|
|
59
|
+
"prefect.resource.role": "infrahub.related.node",
|
|
60
|
+
"infrahub.node.kind": InfrahubKind.GENERICACCOUNT,
|
|
61
|
+
"infrahub.node.id": self.reviewer_account_id,
|
|
62
|
+
"infrahub.reviewer.account.name": self.reviewer_account_name,
|
|
63
|
+
}
|
|
64
|
+
)
|
|
65
|
+
return related
|
|
66
|
+
|
|
67
|
+
def get_resource(self) -> dict[str, str]:
|
|
68
|
+
return {
|
|
69
|
+
**super().get_resource(),
|
|
70
|
+
"infrahub.proposed_change.reviewer_former_decision": self.reviewer_former_decision,
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class ProposedChangeMergedEvent(ProposedChangeEvent):
|
|
75
|
+
"""Event generated when a proposed change has been merged"""
|
|
76
|
+
|
|
77
|
+
event_name: ClassVar[str] = f"{EVENT_NAMESPACE}.proposed_change.merged"
|
|
78
|
+
|
|
79
|
+
merged_by_account_id: str = Field(..., description="The ID of the user who merged the proposed change")
|
|
80
|
+
merged_by_account_name: str = Field(..., description="The name of the user who merged the proposed change")
|
|
81
|
+
|
|
82
|
+
def get_related(self) -> list[dict[str, str]]:
|
|
83
|
+
related = super().get_related()
|
|
84
|
+
related.append(
|
|
85
|
+
{
|
|
86
|
+
"prefect.resource.id": self.merged_by_account_id,
|
|
87
|
+
"prefect.resource.role": "infrahub.related.node",
|
|
88
|
+
"infrahub.node.kind": InfrahubKind.GENERICACCOUNT,
|
|
89
|
+
"infrahub.node.id": self.merged_by_account_id,
|
|
90
|
+
"infrahub.merged_by.account.name": self.merged_by_account_name,
|
|
91
|
+
}
|
|
92
|
+
)
|
|
93
|
+
return related
|
|
94
|
+
|
|
95
|
+
def get_resource(self) -> dict[str, str]:
|
|
96
|
+
return {
|
|
97
|
+
**super().get_resource(),
|
|
98
|
+
"infrahub.proposed_change.merged_by_account_id": self.merged_by_account_id,
|
|
99
|
+
"infrahub.proposed_change.merged_by_account_name": self.merged_by_account_name,
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class ProposedChangeReviewRequestedEvent(ProposedChangeEvent):
|
|
104
|
+
"""Event generated when a proposed change has been flagged for review"""
|
|
105
|
+
|
|
106
|
+
event_name: ClassVar[str] = f"{EVENT_NAMESPACE}.proposed_change.review_requested"
|
|
107
|
+
|
|
108
|
+
requested_by_account_id: str = Field(
|
|
109
|
+
..., description="The ID of the user who requested the proposed change to be reviewed"
|
|
110
|
+
)
|
|
111
|
+
requested_by_account_name: str = Field(
|
|
112
|
+
..., description="The name of the user who requested the proposed change to be reviewed"
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
def get_related(self) -> list[dict[str, str]]:
|
|
116
|
+
related = super().get_related()
|
|
117
|
+
related.append(
|
|
118
|
+
{
|
|
119
|
+
"prefect.resource.id": self.requested_by_account_id,
|
|
120
|
+
"prefect.resource.role": "infrahub.related.node",
|
|
121
|
+
"infrahub.node.kind": InfrahubKind.GENERICACCOUNT,
|
|
122
|
+
"infrahub.node.id": self.requested_by_account_id,
|
|
123
|
+
"infrahub.requested_by.account.name": self.requested_by_account_name,
|
|
124
|
+
}
|
|
125
|
+
)
|
|
126
|
+
return related
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class ProposedChangeApprovedEvent(ProposedChangeReviewEvent):
|
|
130
|
+
"""Event generated when a proposed change has been approved"""
|
|
131
|
+
|
|
132
|
+
event_name: ClassVar[str] = f"{EVENT_NAMESPACE}.proposed_change.approved"
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class ProposedChangeRejectedEvent(ProposedChangeReviewEvent):
|
|
136
|
+
"""Event generated when a proposed change has been rejected"""
|
|
137
|
+
|
|
138
|
+
event_name: ClassVar[str] = f"{EVENT_NAMESPACE}.proposed_change.rejected"
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
class ProposedChangeApprovalRevokedEvent(ProposedChangeReviewRevokedEvent):
|
|
142
|
+
"""Event generated when a proposed change approval has been revoked"""
|
|
143
|
+
|
|
144
|
+
event_name: ClassVar[str] = f"{EVENT_NAMESPACE}.proposed_change.approval_revoked"
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class ProposedChangeRejectionRevokedEvent(ProposedChangeReviewRevokedEvent):
|
|
148
|
+
"""Event generated when a proposed change rejection has been revoked"""
|
|
149
|
+
|
|
150
|
+
event_name: ClassVar[str] = f"{EVENT_NAMESPACE}.proposed_change.rejection_revoked"
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
class ProposedChangeThreadEvent(ProposedChangeEvent):
|
|
154
|
+
thread_id: str = Field(..., description="The ID of the thread that was created or updated")
|
|
155
|
+
thread_kind: str = Field(..., description="The name of the thread that was created or updated")
|
|
156
|
+
|
|
157
|
+
def get_related(self) -> list[dict[str, str]]:
|
|
158
|
+
related = super().get_related()
|
|
159
|
+
related.append(
|
|
160
|
+
{
|
|
161
|
+
"prefect.resource.id": self.thread_id,
|
|
162
|
+
"prefect.resource.role": "infrahub.related.node",
|
|
163
|
+
"infrahub.node.kind": self.thread_kind,
|
|
164
|
+
"infrahub.node.id": self.thread_id,
|
|
165
|
+
}
|
|
166
|
+
)
|
|
167
|
+
return related
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
class ProposedChangeThreadCreatedEvent(ProposedChangeThreadEvent):
|
|
171
|
+
"""Event generated when a thread has been created in a proposed change"""
|
|
172
|
+
|
|
173
|
+
event_name: ClassVar[str] = f"{EVENT_NAMESPACE}.proposed_change_thread.created"
|
|
174
|
+
action: MutationAction = MutationAction.CREATED
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
class ProposedChangeThreadUpdatedEvent(ProposedChangeThreadEvent):
|
|
178
|
+
"""Event generated when a thread has been updated in a proposed change"""
|
|
179
|
+
|
|
180
|
+
event_name: ClassVar[str] = f"{EVENT_NAMESPACE}.proposed_change_thread.updated"
|
|
181
|
+
action: MutationAction = MutationAction.UPDATED
|