infrahub-server 1.2.7__py3-none-any.whl → 1.2.8__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/transformation.py +1 -0
- infrahub/artifacts/models.py +4 -0
- infrahub/cli/db.py +1 -1
- infrahub/computed_attribute/tasks.py +1 -0
- infrahub/config.py +2 -1
- infrahub/constants/__init__.py +0 -0
- infrahub/core/constants/__init__.py +1 -0
- infrahub/core/graph/index.py +3 -1
- infrahub/core/manager.py +16 -5
- infrahub/core/migrations/graph/m014_remove_index_attr_value.py +7 -8
- infrahub/core/protocols.py +1 -0
- infrahub/core/query/node.py +96 -29
- infrahub/core/schema/definitions/core/builtin.py +2 -4
- infrahub/core/schema/definitions/core/transform.py +1 -0
- infrahub/core/validators/aggregated_checker.py +2 -2
- infrahub/core/validators/uniqueness/query.py +8 -3
- infrahub/database/__init__.py +2 -10
- infrahub/database/index.py +1 -1
- infrahub/database/memgraph.py +2 -1
- infrahub/database/neo4j.py +1 -1
- infrahub/git/integrator.py +27 -3
- infrahub/git/models.py +4 -0
- infrahub/git/tasks.py +3 -0
- infrahub/git_credential/helper.py +2 -2
- infrahub/message_bus/operations/requests/proposed_change.py +6 -0
- infrahub/message_bus/types.py +3 -0
- infrahub/patch/queries/consolidate_duplicated_nodes.py +109 -0
- infrahub/patch/queries/delete_duplicated_edges.py +138 -0
- infrahub/proposed_change/tasks.py +1 -0
- infrahub/server.py +3 -1
- infrahub/transformations/models.py +3 -0
- infrahub/transformations/tasks.py +1 -0
- infrahub/webhook/models.py +3 -0
- infrahub_sdk/client.py +4 -4
- infrahub_sdk/config.py +17 -0
- infrahub_sdk/ctl/cli_commands.py +7 -1
- infrahub_sdk/ctl/generator.py +2 -2
- infrahub_sdk/generator.py +12 -66
- infrahub_sdk/operation.py +80 -0
- infrahub_sdk/protocols.py +12 -0
- infrahub_sdk/recorder.py +3 -0
- infrahub_sdk/schema/repository.py +4 -0
- infrahub_sdk/transforms.py +15 -27
- {infrahub_server-1.2.7.dist-info → infrahub_server-1.2.8.dist-info}/METADATA +2 -2
- {infrahub_server-1.2.7.dist-info → infrahub_server-1.2.8.dist-info}/RECORD +50 -46
- infrahub_testcontainers/docker-compose.test.yml +2 -0
- /infrahub/{database/constants.py → constants/database.py} +0 -0
- {infrahub_server-1.2.7.dist-info → infrahub_server-1.2.8.dist-info}/LICENSE.txt +0 -0
- {infrahub_server-1.2.7.dist-info → infrahub_server-1.2.8.dist-info}/WHEEL +0 -0
- {infrahub_server-1.2.7.dist-info → infrahub_server-1.2.8.dist-info}/entry_points.txt +0 -0
infrahub/api/transformation.py
CHANGED
|
@@ -88,6 +88,7 @@ async def transform_python(
|
|
|
88
88
|
branch=branch_params.branch.name,
|
|
89
89
|
transform_location=f"{transform.file_path.value}::{transform.class_name.value}",
|
|
90
90
|
timeout=transform.timeout.value,
|
|
91
|
+
convert_query_response=transform.convert_query_response.value or False,
|
|
91
92
|
data=data,
|
|
92
93
|
)
|
|
93
94
|
|
infrahub/artifacts/models.py
CHANGED
|
@@ -12,6 +12,10 @@ class CheckArtifactCreate(BaseModel):
|
|
|
12
12
|
content_type: str = Field(..., description="Content type of the artifact")
|
|
13
13
|
transform_type: str = Field(..., description="The type of transform associated with this artifact")
|
|
14
14
|
transform_location: str = Field(..., description="The transforms location within the repository")
|
|
15
|
+
convert_query_response: bool = Field(
|
|
16
|
+
default=False,
|
|
17
|
+
description="Indicate if the query response should be converted to InfrahubNode objects for Python transforms",
|
|
18
|
+
)
|
|
15
19
|
repository_id: str = Field(..., description="The unique ID of the Repository")
|
|
16
20
|
repository_name: str = Field(..., description="The name of the Repository")
|
|
17
21
|
repository_kind: str = Field(..., description="The kind of the Repository")
|
infrahub/cli/db.py
CHANGED
|
@@ -412,7 +412,7 @@ async def update_core_schema(
|
|
|
412
412
|
update_db=True,
|
|
413
413
|
)
|
|
414
414
|
default_branch.update_schema_hash()
|
|
415
|
-
rprint("The Core Schema has been updated")
|
|
415
|
+
rprint("The Core Schema has been updated, make sure to rebase any open branches after the upgrade")
|
|
416
416
|
if debug:
|
|
417
417
|
rprint(f"New schema hash: {default_branch.active_schema_hash.main}")
|
|
418
418
|
await default_branch.save(db=dbt)
|
|
@@ -113,6 +113,7 @@ async def process_transform(
|
|
|
113
113
|
location=f"{transform.file_path.value}::{transform.class_name.value}",
|
|
114
114
|
data=data,
|
|
115
115
|
client=service.client,
|
|
116
|
+
convert_query_response=transform.convert_query_response.value,
|
|
116
117
|
) # type: ignore[misc]
|
|
117
118
|
|
|
118
119
|
await service.client.execute_graphql(
|
infrahub/config.py
CHANGED
|
@@ -23,7 +23,7 @@ from pydantic import (
|
|
|
23
23
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
24
24
|
from typing_extensions import Self
|
|
25
25
|
|
|
26
|
-
from infrahub.database
|
|
26
|
+
from infrahub.constants.database import DatabaseType
|
|
27
27
|
from infrahub.exceptions import InitializationError, ProcessingError
|
|
28
28
|
|
|
29
29
|
if TYPE_CHECKING:
|
|
@@ -629,6 +629,7 @@ class AnalyticsSettings(BaseSettings):
|
|
|
629
629
|
class ExperimentalFeaturesSettings(BaseSettings):
|
|
630
630
|
model_config = SettingsConfigDict(env_prefix="INFRAHUB_EXPERIMENTAL_")
|
|
631
631
|
graphql_enums: bool = False
|
|
632
|
+
value_db_index: bool = False
|
|
632
633
|
|
|
633
634
|
|
|
634
635
|
class SecuritySettings(BaseSettings):
|
|
File without changes
|
|
@@ -150,6 +150,7 @@ class ContentType(InfrahubStringEnum):
|
|
|
150
150
|
APPLICATION_JSON = "application/json"
|
|
151
151
|
APPLICATION_YAML = "application/yaml"
|
|
152
152
|
APPLICATION_XML = "application/xml"
|
|
153
|
+
APPLICATION_HCL = "application/hcl"
|
|
153
154
|
TEXT_PLAIN = "text/plain"
|
|
154
155
|
TEXT_MARKDOWN = "text/markdown"
|
|
155
156
|
TEXT_CSV = "text/csv"
|
infrahub/core/graph/index.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from infrahub.database
|
|
3
|
+
from infrahub.constants.database import IndexType
|
|
4
4
|
from infrahub.database.index import IndexItem
|
|
5
5
|
|
|
6
6
|
node_indexes: list[IndexItem] = [
|
|
@@ -17,6 +17,8 @@ node_indexes: list[IndexItem] = [
|
|
|
17
17
|
IndexItem(name="diff_node_uuid", label="DiffNode", properties=["uuid"], type=IndexType.TEXT),
|
|
18
18
|
]
|
|
19
19
|
|
|
20
|
+
attr_value_index = IndexItem(name="attr_value", label="AttributeValue", properties=["value"], type=IndexType.RANGE)
|
|
21
|
+
|
|
20
22
|
rel_indexes: list[IndexItem] = [
|
|
21
23
|
IndexItem(name="attr_from", label="HAS_ATTRIBUTE", properties=["from"], type=IndexType.RANGE),
|
|
22
24
|
IndexItem(name="attr_branch", label="HAS_ATTRIBUTE", properties=["branch"], type=IndexType.RANGE),
|
infrahub/core/manager.py
CHANGED
|
@@ -1229,20 +1229,31 @@ class NodeManager:
|
|
|
1229
1229
|
if not prefetch_relationships and not fields:
|
|
1230
1230
|
return
|
|
1231
1231
|
cardinality_one_identifiers_by_kind: dict[str, dict[str, RelationshipDirection]] | None = None
|
|
1232
|
-
|
|
1232
|
+
outbound_identifiers: set[str] | None = None
|
|
1233
|
+
inbound_identifiers: set[str] | None = None
|
|
1234
|
+
bidirectional_identifiers: set[str] | None = None
|
|
1233
1235
|
if not prefetch_relationships:
|
|
1234
1236
|
cardinality_one_identifiers_by_kind = _get_cardinality_one_identifiers_by_kind(
|
|
1235
1237
|
nodes=nodes_by_id.values(), fields=fields or {}
|
|
1236
1238
|
)
|
|
1237
|
-
|
|
1239
|
+
outbound_identifiers = set()
|
|
1240
|
+
inbound_identifiers = set()
|
|
1241
|
+
bidirectional_identifiers = set()
|
|
1238
1242
|
for identifier_direction_map in cardinality_one_identifiers_by_kind.values():
|
|
1239
|
-
|
|
1240
|
-
|
|
1243
|
+
for identifier, direction in identifier_direction_map.items():
|
|
1244
|
+
if direction is RelationshipDirection.OUTBOUND:
|
|
1245
|
+
outbound_identifiers.add(identifier)
|
|
1246
|
+
elif direction is RelationshipDirection.INBOUND:
|
|
1247
|
+
inbound_identifiers.add(identifier)
|
|
1248
|
+
elif direction is RelationshipDirection.BIDIR:
|
|
1249
|
+
bidirectional_identifiers.add(identifier)
|
|
1241
1250
|
|
|
1242
1251
|
query = await NodeListGetRelationshipsQuery.init(
|
|
1243
1252
|
db=db,
|
|
1244
1253
|
ids=list(nodes_by_id.keys()),
|
|
1245
|
-
|
|
1254
|
+
outbound_identifiers=None if outbound_identifiers is None else list(outbound_identifiers),
|
|
1255
|
+
inbound_identifiers=None if inbound_identifiers is None else list(inbound_identifiers),
|
|
1256
|
+
bidirectional_identifiers=None if bidirectional_identifiers is None else list(bidirectional_identifiers),
|
|
1246
1257
|
branch=branch,
|
|
1247
1258
|
at=at,
|
|
1248
1259
|
branch_agnostic=branch_agnostic,
|
|
@@ -2,10 +2,10 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from typing import TYPE_CHECKING, Sequence
|
|
4
4
|
|
|
5
|
+
from infrahub.constants.database import IndexType
|
|
5
6
|
from infrahub.core.migrations.shared import MigrationResult
|
|
6
7
|
from infrahub.core.query import Query # noqa: TC001
|
|
7
8
|
from infrahub.database import DatabaseType
|
|
8
|
-
from infrahub.database.constants import IndexType
|
|
9
9
|
from infrahub.database.index import IndexItem
|
|
10
10
|
|
|
11
11
|
from ..shared import GraphMigration
|
|
@@ -29,13 +29,12 @@ class Migration014(GraphMigration):
|
|
|
29
29
|
if db.db_type != DatabaseType.NEO4J:
|
|
30
30
|
return result
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
return result
|
|
32
|
+
try:
|
|
33
|
+
db.manager.index.init(nodes=[INDEX_TO_DELETE], rels=[])
|
|
34
|
+
await db.manager.index.drop()
|
|
35
|
+
except Exception as exc:
|
|
36
|
+
result.errors.append(str(exc))
|
|
37
|
+
return result
|
|
39
38
|
|
|
40
39
|
return result
|
|
41
40
|
|
infrahub/core/protocols.py
CHANGED
infrahub/core/query/node.py
CHANGED
|
@@ -649,51 +649,118 @@ class NodeListGetRelationshipsQuery(Query):
|
|
|
649
649
|
type: QueryType = QueryType.READ
|
|
650
650
|
insert_return: bool = False
|
|
651
651
|
|
|
652
|
-
def __init__(
|
|
652
|
+
def __init__(
|
|
653
|
+
self,
|
|
654
|
+
ids: list[str],
|
|
655
|
+
outbound_identifiers: list[str] | None = None,
|
|
656
|
+
inbound_identifiers: list[str] | None = None,
|
|
657
|
+
bidirectional_identifiers: list[str] | None = None,
|
|
658
|
+
**kwargs,
|
|
659
|
+
):
|
|
653
660
|
self.ids = ids
|
|
654
|
-
self.
|
|
661
|
+
self.outbound_identifiers = outbound_identifiers
|
|
662
|
+
self.inbound_identifiers = inbound_identifiers
|
|
663
|
+
self.bidirectional_identifiers = bidirectional_identifiers
|
|
655
664
|
super().__init__(**kwargs)
|
|
656
665
|
|
|
657
666
|
async def query_init(self, db: InfrahubDatabase, **kwargs) -> None: # noqa: ARG002
|
|
658
667
|
self.params["ids"] = self.ids
|
|
659
|
-
self.params["
|
|
668
|
+
self.params["outbound_identifiers"] = self.outbound_identifiers
|
|
669
|
+
self.params["inbound_identifiers"] = self.inbound_identifiers
|
|
670
|
+
self.params["bidirectional_identifiers"] = self.bidirectional_identifiers
|
|
660
671
|
|
|
661
672
|
rels_filter, rels_params = self.branch.get_query_filter_path(at=self.at, branch_agnostic=self.branch_agnostic)
|
|
662
673
|
self.params.update(rels_params)
|
|
663
674
|
|
|
664
675
|
query = """
|
|
665
676
|
MATCH (n:Node) WHERE n.uuid IN $ids
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
677
|
+
CALL {
|
|
678
|
+
WITH n
|
|
679
|
+
MATCH (n)<-[:IS_RELATED]-(rel:Relationship)<-[:IS_RELATED]-(peer)
|
|
680
|
+
WHERE ($inbound_identifiers IS NULL OR rel.name in $inbound_identifiers)
|
|
681
|
+
AND n.uuid <> peer.uuid
|
|
682
|
+
WITH DISTINCT n, rel, peer
|
|
683
|
+
CALL {
|
|
684
|
+
WITH n, rel, peer
|
|
685
|
+
MATCH (n)<-[r:IS_RELATED]-(rel)
|
|
686
|
+
WHERE (%(filters)s)
|
|
687
|
+
WITH n, rel, peer, r
|
|
688
|
+
ORDER BY r.from DESC
|
|
689
|
+
LIMIT 1
|
|
690
|
+
WITH n, rel, peer, r AS r1
|
|
691
|
+
WHERE r1.status = "active"
|
|
692
|
+
MATCH (rel)<-[r:IS_RELATED]-(peer)
|
|
693
|
+
WHERE (%(filters)s)
|
|
694
|
+
WITH r1, r
|
|
695
|
+
ORDER BY r.from DESC
|
|
696
|
+
LIMIT 1
|
|
697
|
+
WITH r1, r AS r2
|
|
698
|
+
WHERE r2.status = "active"
|
|
699
|
+
RETURN 1 AS is_active
|
|
700
|
+
}
|
|
701
|
+
RETURN n.uuid AS n_uuid, rel.name AS rel_name, peer.uuid AS peer_uuid, "inbound" as direction
|
|
702
|
+
UNION
|
|
703
|
+
WITH n
|
|
704
|
+
MATCH (n)-[:IS_RELATED]->(rel:Relationship)-[:IS_RELATED]->(peer)
|
|
705
|
+
WHERE ($outbound_identifiers IS NULL OR rel.name in $outbound_identifiers)
|
|
706
|
+
AND n.uuid <> peer.uuid
|
|
707
|
+
WITH DISTINCT n, rel, peer
|
|
708
|
+
CALL {
|
|
709
|
+
WITH n, rel, peer
|
|
710
|
+
MATCH (n)-[r:IS_RELATED]->(rel)
|
|
711
|
+
WHERE (%(filters)s)
|
|
712
|
+
WITH n, rel, peer, r
|
|
713
|
+
ORDER BY r.from DESC
|
|
714
|
+
LIMIT 1
|
|
715
|
+
WITH n, rel, peer, r AS r1
|
|
716
|
+
WHERE r1.status = "active"
|
|
717
|
+
MATCH (rel)-[r:IS_RELATED]->(peer)
|
|
718
|
+
WHERE (%(filters)s)
|
|
719
|
+
WITH r1, r
|
|
720
|
+
ORDER BY r.from DESC
|
|
721
|
+
LIMIT 1
|
|
722
|
+
WITH r1, r AS r2
|
|
723
|
+
WHERE r2.status = "active"
|
|
724
|
+
RETURN 1 AS is_active
|
|
725
|
+
}
|
|
726
|
+
RETURN n.uuid AS n_uuid, rel.name AS rel_name, peer.uuid AS peer_uuid, "outbound" as direction
|
|
727
|
+
UNION
|
|
728
|
+
WITH n
|
|
729
|
+
MATCH (n)-[:IS_RELATED]->(rel:Relationship)<-[:IS_RELATED]-(peer)
|
|
730
|
+
WHERE ($bidirectional_identifiers IS NULL OR rel.name in $bidirectional_identifiers)
|
|
731
|
+
AND n.uuid <> peer.uuid
|
|
732
|
+
WITH DISTINCT n, rel, peer
|
|
733
|
+
CALL {
|
|
734
|
+
WITH n, rel, peer
|
|
735
|
+
MATCH (n)-[r:IS_RELATED]->(rel)
|
|
736
|
+
WHERE (%(filters)s)
|
|
737
|
+
WITH n, rel, peer, r
|
|
738
|
+
ORDER BY r.from DESC
|
|
739
|
+
LIMIT 1
|
|
740
|
+
WITH n, rel, peer, r AS r1
|
|
741
|
+
WHERE r1.status = "active"
|
|
742
|
+
MATCH (rel)<-[r:IS_RELATED]-(peer)
|
|
743
|
+
WHERE (%(filters)s)
|
|
744
|
+
WITH r1, r
|
|
745
|
+
ORDER BY r.from DESC
|
|
746
|
+
LIMIT 1
|
|
747
|
+
WITH r1, r AS r2
|
|
748
|
+
WHERE r2.status = "active"
|
|
749
|
+
RETURN 1 AS is_active
|
|
750
|
+
}
|
|
751
|
+
RETURN n.uuid AS n_uuid, rel.name AS rel_name, peer.uuid AS peer_uuid, "bidirectional" as direction
|
|
752
|
+
}
|
|
753
|
+
RETURN DISTINCT n_uuid, rel_name, peer_uuid, direction
|
|
685
754
|
""" % {"filters": rels_filter}
|
|
686
|
-
|
|
687
755
|
self.add_to_query(query)
|
|
688
|
-
|
|
689
|
-
self.return_labels = ["n", "rel", "peer", "r1", "r2", "direction"]
|
|
756
|
+
self.return_labels = ["n_uuid", "rel_name", "peer_uuid", "direction"]
|
|
690
757
|
|
|
691
758
|
def get_peers_group_by_node(self) -> GroupedPeerNodes:
|
|
692
759
|
gpn = GroupedPeerNodes()
|
|
693
|
-
for result in self.
|
|
694
|
-
node_id = result.get("
|
|
695
|
-
rel_name = result.get("
|
|
696
|
-
peer_id = result.get("
|
|
760
|
+
for result in self.get_results():
|
|
761
|
+
node_id = result.get("n_uuid")
|
|
762
|
+
rel_name = result.get("rel_name")
|
|
763
|
+
peer_id = result.get("peer_uuid")
|
|
697
764
|
direction = str(result.get("direction"))
|
|
698
765
|
direction_enum = {
|
|
699
766
|
"inbound": RelationshipDirection.INBOUND,
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
from infrahub.core.constants import
|
|
2
|
-
BranchSupportType,
|
|
3
|
-
)
|
|
1
|
+
from infrahub.core.constants import BranchSupportType
|
|
4
2
|
|
|
5
3
|
from ...attribute_schema import AttributeSchema as Attr
|
|
6
4
|
from ...node_schema import NodeSchema
|
|
@@ -8,7 +6,7 @@ from ...node_schema import NodeSchema
|
|
|
8
6
|
builtin_tag = NodeSchema(
|
|
9
7
|
name="Tag",
|
|
10
8
|
namespace="Builtin",
|
|
11
|
-
description="Standard Tag object to
|
|
9
|
+
description="Standard Tag object to attach to other objects to provide some context.",
|
|
12
10
|
include_in_menu=True,
|
|
13
11
|
icon="mdi:tag-multiple",
|
|
14
12
|
label="Tag",
|
|
@@ -97,9 +97,9 @@ class AggregatedConstraintChecker:
|
|
|
97
97
|
error_detail_str += f"={data_path.value!r}"
|
|
98
98
|
error_detail_str_list.append(error_detail_str)
|
|
99
99
|
if data_path.peer_id:
|
|
100
|
-
error_detail_str
|
|
100
|
+
error_detail_str = f"{data_path.field_name}.id={data_path.peer_id}"
|
|
101
101
|
error_detail_str_list.append(error_detail_str)
|
|
102
|
-
if
|
|
102
|
+
if error_detail_str_list:
|
|
103
103
|
error_str += " The error relates to field "
|
|
104
104
|
error_str += ",".join(error_detail_str_list)
|
|
105
105
|
error_str += "."
|
|
@@ -39,7 +39,7 @@ class NodeUniqueAttributeConstraintQuery(Query):
|
|
|
39
39
|
)
|
|
40
40
|
|
|
41
41
|
attribute_names = set()
|
|
42
|
-
attr_paths, attr_paths_with_value = [], []
|
|
42
|
+
attr_paths, attr_paths_with_value, attr_values = [], [], []
|
|
43
43
|
for attr_path in self.query_request.unique_attribute_paths:
|
|
44
44
|
try:
|
|
45
45
|
property_rel_name = self.attribute_property_map[attr_path.property_name or "value"]
|
|
@@ -50,6 +50,7 @@ class NodeUniqueAttributeConstraintQuery(Query):
|
|
|
50
50
|
attribute_names.add(attr_path.attribute_name)
|
|
51
51
|
if attr_path.value:
|
|
52
52
|
attr_paths_with_value.append((attr_path.attribute_name, property_rel_name, attr_path.value))
|
|
53
|
+
attr_values.append(attr_path.value)
|
|
53
54
|
else:
|
|
54
55
|
attr_paths.append((attr_path.attribute_name, property_rel_name))
|
|
55
56
|
|
|
@@ -57,6 +58,7 @@ class NodeUniqueAttributeConstraintQuery(Query):
|
|
|
57
58
|
relationship_attr_paths = []
|
|
58
59
|
relationship_only_attr_paths = []
|
|
59
60
|
relationship_only_attr_values = []
|
|
61
|
+
relationship_attr_values = []
|
|
60
62
|
relationship_attr_paths_with_value = []
|
|
61
63
|
for rel_path in self.query_request.relationship_attribute_paths:
|
|
62
64
|
relationship_names.add(rel_path.identifier)
|
|
@@ -64,6 +66,7 @@ class NodeUniqueAttributeConstraintQuery(Query):
|
|
|
64
66
|
relationship_attr_paths_with_value.append(
|
|
65
67
|
(rel_path.identifier, rel_path.attribute_name, rel_path.value)
|
|
66
68
|
)
|
|
69
|
+
relationship_attr_values.append(rel_path.value)
|
|
67
70
|
elif rel_path.attribute_name:
|
|
68
71
|
relationship_attr_paths.append((rel_path.identifier, rel_path.attribute_name))
|
|
69
72
|
else:
|
|
@@ -87,12 +90,14 @@ class NodeUniqueAttributeConstraintQuery(Query):
|
|
|
87
90
|
"node_kind": self.query_request.kind,
|
|
88
91
|
"attr_paths": attr_paths,
|
|
89
92
|
"attr_paths_with_value": attr_paths_with_value,
|
|
93
|
+
"attr_values": attr_values,
|
|
90
94
|
"attribute_names": list(attribute_names),
|
|
91
95
|
"relationship_names": list(relationship_names),
|
|
92
96
|
"relationship_attr_paths": relationship_attr_paths,
|
|
93
97
|
"relationship_attr_paths_with_value": relationship_attr_paths_with_value,
|
|
94
98
|
"relationship_only_attr_paths": relationship_only_attr_paths,
|
|
95
99
|
"relationship_only_attr_values": relationship_only_attr_values,
|
|
100
|
+
"relationship_attr_values": relationship_attr_values,
|
|
96
101
|
"min_count_required": self.min_count_required,
|
|
97
102
|
}
|
|
98
103
|
)
|
|
@@ -101,7 +106,7 @@ class NodeUniqueAttributeConstraintQuery(Query):
|
|
|
101
106
|
MATCH attr_path = (start_node:%(node_kind)s)-[:HAS_ATTRIBUTE]->(attr:Attribute)-[r:HAS_VALUE]->(attr_value:AttributeValue)
|
|
102
107
|
WHERE attr.name in $attribute_names
|
|
103
108
|
AND ([attr.name, type(r)] in $attr_paths
|
|
104
|
-
OR [attr.name, type(r), attr_value.value] in $attr_paths_with_value)
|
|
109
|
+
OR (attr_value.value in $attr_values AND [attr.name, type(r), attr_value.value] in $attr_paths_with_value))
|
|
105
110
|
RETURN start_node, attr_path as potential_path, NULL as rel_identifier, attr.name as potential_attr, attr_value.value as potential_attr_value
|
|
106
111
|
""" % {"node_kind": self.query_request.kind}
|
|
107
112
|
|
|
@@ -109,7 +114,7 @@ class NodeUniqueAttributeConstraintQuery(Query):
|
|
|
109
114
|
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)
|
|
110
115
|
WHERE relationship_node.name in $relationship_names
|
|
111
116
|
AND ([relationship_node.name, rel_attr.name] in $relationship_attr_paths
|
|
112
|
-
OR [relationship_node.name, rel_attr.name, rel_attr_value.value] in $relationship_attr_paths_with_value)
|
|
117
|
+
OR (rel_attr_value.value in $relationship_attr_values AND [relationship_node.name, rel_attr.name, rel_attr_value.value] in $relationship_attr_paths_with_value))
|
|
113
118
|
RETURN start_node, rel_path as potential_path, relationship_node.name as rel_identifier, rel_attr.name as potential_attr, rel_attr_value.value as potential_attr_value
|
|
114
119
|
""" % {"node_kind": self.query_request.kind}
|
|
115
120
|
|
infrahub/database/__init__.py
CHANGED
|
@@ -26,13 +26,13 @@ from opentelemetry import trace
|
|
|
26
26
|
from typing_extensions import Self
|
|
27
27
|
|
|
28
28
|
from infrahub import config, lock
|
|
29
|
+
from infrahub.constants.database import DatabaseType, Neo4jRuntime
|
|
29
30
|
from infrahub.core import registry
|
|
30
31
|
from infrahub.core.query import QueryType
|
|
31
32
|
from infrahub.exceptions import DatabaseError
|
|
32
33
|
from infrahub.log import get_logger
|
|
33
34
|
from infrahub.utils import InfrahubStringEnum
|
|
34
35
|
|
|
35
|
-
from .constants import DatabaseType, Neo4jRuntime
|
|
36
36
|
from .memgraph import DatabaseManagerMemgraph
|
|
37
37
|
from .metrics import CONNECTION_POOL_USAGE, QUERY_EXECUTION_METRICS, TRANSACTION_RETRIES
|
|
38
38
|
from .neo4j import DatabaseManagerNeo4j
|
|
@@ -44,8 +44,6 @@ if TYPE_CHECKING:
|
|
|
44
44
|
from infrahub.core.schema import MainSchemaTypes, NodeSchema
|
|
45
45
|
from infrahub.core.schema.schema_branch import SchemaBranch
|
|
46
46
|
|
|
47
|
-
from .manager import DatabaseManager
|
|
48
|
-
|
|
49
47
|
validated_database = {}
|
|
50
48
|
R = TypeVar("R")
|
|
51
49
|
|
|
@@ -134,7 +132,6 @@ class InfrahubDatabase:
|
|
|
134
132
|
mode: InfrahubDatabaseMode = InfrahubDatabaseMode.DRIVER,
|
|
135
133
|
db_type: DatabaseType | None = None,
|
|
136
134
|
default_neo4j_runtime: Neo4jRuntime = Neo4jRuntime.DEFAULT,
|
|
137
|
-
db_manager: DatabaseManager | None = None,
|
|
138
135
|
schemas: list[SchemaBranch] | None = None,
|
|
139
136
|
session: AsyncSession | None = None,
|
|
140
137
|
session_mode: InfrahubDatabaseSessionMode = InfrahubDatabaseSessionMode.WRITE,
|
|
@@ -161,10 +158,7 @@ class InfrahubDatabase:
|
|
|
161
158
|
else:
|
|
162
159
|
self.db_type = config.SETTINGS.database.db_type
|
|
163
160
|
|
|
164
|
-
if
|
|
165
|
-
self.manager = db_manager
|
|
166
|
-
self.manager.db = self
|
|
167
|
-
elif self.db_type == DatabaseType.NEO4J:
|
|
161
|
+
if self.db_type == DatabaseType.NEO4J:
|
|
168
162
|
self.manager = DatabaseManagerNeo4j(db=self)
|
|
169
163
|
elif self.db_type == DatabaseType.MEMGRAPH:
|
|
170
164
|
self.manager = DatabaseManagerMemgraph(db=self)
|
|
@@ -228,7 +222,6 @@ class InfrahubDatabase:
|
|
|
228
222
|
db_type=self.db_type,
|
|
229
223
|
default_neo4j_runtime=self.default_neo4j_runtime,
|
|
230
224
|
schemas=schemas or self._schemas.values(),
|
|
231
|
-
db_manager=self.manager,
|
|
232
225
|
driver=self._driver,
|
|
233
226
|
session_mode=session_mode,
|
|
234
227
|
queries_names_to_config=self.queries_names_to_config,
|
|
@@ -243,7 +236,6 @@ class InfrahubDatabase:
|
|
|
243
236
|
db_type=self.db_type,
|
|
244
237
|
default_neo4j_runtime=self.default_neo4j_runtime,
|
|
245
238
|
schemas=schemas or self._schemas.values(),
|
|
246
|
-
db_manager=self.manager,
|
|
247
239
|
driver=self._driver,
|
|
248
240
|
session=self._session,
|
|
249
241
|
session_mode=self._session_mode,
|
infrahub/database/index.py
CHANGED
|
@@ -5,7 +5,7 @@ from typing import TYPE_CHECKING
|
|
|
5
5
|
|
|
6
6
|
from pydantic import BaseModel
|
|
7
7
|
|
|
8
|
-
from .constants import EntityType, IndexType # noqa: TC001
|
|
8
|
+
from infrahub.constants.database import EntityType, IndexType # noqa: TC001
|
|
9
9
|
|
|
10
10
|
if TYPE_CHECKING:
|
|
11
11
|
from infrahub.database import InfrahubDatabase
|
infrahub/database/memgraph.py
CHANGED
|
@@ -2,7 +2,8 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from typing import TYPE_CHECKING
|
|
4
4
|
|
|
5
|
-
from .constants import EntityType, IndexType
|
|
5
|
+
from infrahub.constants.database import EntityType, IndexType
|
|
6
|
+
|
|
6
7
|
from .index import IndexInfo, IndexItem, IndexManagerBase
|
|
7
8
|
from .manager import DatabaseManager
|
|
8
9
|
|
infrahub/database/neo4j.py
CHANGED
|
@@ -2,9 +2,9 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from typing import TYPE_CHECKING
|
|
4
4
|
|
|
5
|
+
from infrahub.constants.database import EntityType, IndexType
|
|
5
6
|
from infrahub.core.query import QueryType
|
|
6
7
|
|
|
7
|
-
from .constants import EntityType, IndexType
|
|
8
8
|
from .index import IndexInfo, IndexItem, IndexManagerBase
|
|
9
9
|
from .manager import DatabaseManager
|
|
10
10
|
|
infrahub/git/integrator.py
CHANGED
|
@@ -10,6 +10,7 @@ import ujson
|
|
|
10
10
|
import yaml
|
|
11
11
|
from infrahub_sdk import InfrahubClient # noqa: TC002
|
|
12
12
|
from infrahub_sdk.exceptions import ValidationError
|
|
13
|
+
from infrahub_sdk.node import InfrahubNode
|
|
13
14
|
from infrahub_sdk.protocols import (
|
|
14
15
|
CoreArtifact,
|
|
15
16
|
CoreArtifactDefinition,
|
|
@@ -53,7 +54,6 @@ if TYPE_CHECKING:
|
|
|
53
54
|
import types
|
|
54
55
|
|
|
55
56
|
from infrahub_sdk.checks import InfrahubCheck
|
|
56
|
-
from infrahub_sdk.node import InfrahubNode
|
|
57
57
|
from infrahub_sdk.schema.repository import InfrahubRepositoryArtifactDefinitionConfig
|
|
58
58
|
from infrahub_sdk.transforms import InfrahubTransform
|
|
59
59
|
|
|
@@ -123,6 +123,10 @@ class TransformPythonInformation(BaseModel):
|
|
|
123
123
|
timeout: int
|
|
124
124
|
"""Timeout for the function."""
|
|
125
125
|
|
|
126
|
+
convert_query_response: bool = Field(
|
|
127
|
+
..., description="Indicate if the transform should convert the query response to InfrahubNode objects"
|
|
128
|
+
)
|
|
129
|
+
|
|
126
130
|
|
|
127
131
|
class InfrahubRepositoryIntegrator(InfrahubRepositoryBase):
|
|
128
132
|
"""
|
|
@@ -874,6 +878,7 @@ class InfrahubRepositoryIntegrator(InfrahubRepositoryBase):
|
|
|
874
878
|
file_path=file_path,
|
|
875
879
|
query=str(graphql_query.id),
|
|
876
880
|
timeout=transform_class.timeout,
|
|
881
|
+
convert_query_response=transform.convert_query_response,
|
|
877
882
|
)
|
|
878
883
|
)
|
|
879
884
|
|
|
@@ -1005,6 +1010,7 @@ class InfrahubRepositoryIntegrator(InfrahubRepositoryBase):
|
|
|
1005
1010
|
"file_path": transform.file_path,
|
|
1006
1011
|
"class_name": transform.class_name,
|
|
1007
1012
|
"timeout": transform.timeout,
|
|
1013
|
+
"convert_query_response": transform.convert_query_response,
|
|
1008
1014
|
}
|
|
1009
1015
|
create_payload = self.sdk.schema.generate_payload_create(
|
|
1010
1016
|
schema=schema,
|
|
@@ -1028,6 +1034,9 @@ class InfrahubRepositoryIntegrator(InfrahubRepositoryBase):
|
|
|
1028
1034
|
if existing_transform.timeout.value != local_transform.timeout:
|
|
1029
1035
|
existing_transform.timeout.value = local_transform.timeout
|
|
1030
1036
|
|
|
1037
|
+
if existing_transform.convert_query_response.value != local_transform.convert_query_response:
|
|
1038
|
+
existing_transform.convert_query_response.value = local_transform.convert_query_response
|
|
1039
|
+
|
|
1031
1040
|
await existing_transform.save()
|
|
1032
1041
|
|
|
1033
1042
|
@classmethod
|
|
@@ -1038,6 +1047,7 @@ class InfrahubRepositoryIntegrator(InfrahubRepositoryBase):
|
|
|
1038
1047
|
existing_transform.query.id != local_transform.query
|
|
1039
1048
|
or existing_transform.file_path.value != local_transform.file_path
|
|
1040
1049
|
or existing_transform.timeout.value != local_transform.timeout
|
|
1050
|
+
or existing_transform.convert_query_response.value != local_transform.convert_query_response
|
|
1041
1051
|
):
|
|
1042
1052
|
return False
|
|
1043
1053
|
return True
|
|
@@ -1129,7 +1139,13 @@ class InfrahubRepositoryIntegrator(InfrahubRepositoryBase):
|
|
|
1129
1139
|
|
|
1130
1140
|
@task(name="python-transform-execute", task_run_name="Execute Python Transform", cache_policy=NONE) # type: ignore[arg-type]
|
|
1131
1141
|
async def execute_python_transform(
|
|
1132
|
-
self,
|
|
1142
|
+
self,
|
|
1143
|
+
branch_name: str,
|
|
1144
|
+
commit: str,
|
|
1145
|
+
location: str,
|
|
1146
|
+
client: InfrahubClient,
|
|
1147
|
+
convert_query_response: bool,
|
|
1148
|
+
data: dict | None = None,
|
|
1133
1149
|
) -> Any:
|
|
1134
1150
|
"""Execute A Python Transform stored in the repository."""
|
|
1135
1151
|
log = get_run_logger()
|
|
@@ -1159,7 +1175,13 @@ class InfrahubRepositoryIntegrator(InfrahubRepositoryBase):
|
|
|
1159
1175
|
|
|
1160
1176
|
transform_class: type[InfrahubTransform] = getattr(module, class_name)
|
|
1161
1177
|
|
|
1162
|
-
transform = transform_class(
|
|
1178
|
+
transform = transform_class(
|
|
1179
|
+
root_directory=commit_worktree.directory,
|
|
1180
|
+
branch=branch_name,
|
|
1181
|
+
client=client,
|
|
1182
|
+
convert_query_response=convert_query_response,
|
|
1183
|
+
infrahub_node=InfrahubNode,
|
|
1184
|
+
)
|
|
1163
1185
|
return await transform.run(data=data)
|
|
1164
1186
|
|
|
1165
1187
|
except ModuleNotFoundError as exc:
|
|
@@ -1216,6 +1238,7 @@ class InfrahubRepositoryIntegrator(InfrahubRepositoryBase):
|
|
|
1216
1238
|
location=transformation_location,
|
|
1217
1239
|
data=response,
|
|
1218
1240
|
client=self.sdk,
|
|
1241
|
+
convert_query_response=transformation.convert_query_response.value,
|
|
1219
1242
|
) # type: ignore[misc]
|
|
1220
1243
|
|
|
1221
1244
|
if definition.content_type.value == ContentType.APPLICATION_JSON.value and isinstance(artifact_content, dict):
|
|
@@ -1275,6 +1298,7 @@ class InfrahubRepositoryIntegrator(InfrahubRepositoryBase):
|
|
|
1275
1298
|
location=message.transform_location,
|
|
1276
1299
|
data=response,
|
|
1277
1300
|
client=self.sdk,
|
|
1301
|
+
convert_query_response=message.convert_query_response,
|
|
1278
1302
|
) # type: ignore[misc]
|
|
1279
1303
|
|
|
1280
1304
|
if message.content_type == ContentType.APPLICATION_JSON.value and isinstance(artifact_content, dict):
|
infrahub/git/models.py
CHANGED
|
@@ -29,6 +29,10 @@ class RequestArtifactGenerate(BaseModel):
|
|
|
29
29
|
repository_name: str = Field(..., description="The name of the Repository")
|
|
30
30
|
repository_kind: str = Field(..., description="The kind of the Repository")
|
|
31
31
|
branch_name: str = Field(..., description="The branch where the check is run")
|
|
32
|
+
convert_query_response: bool = Field(
|
|
33
|
+
default=False,
|
|
34
|
+
description="Indicate if the query response should be converted to InfrahubNode objects for Python transforms",
|
|
35
|
+
)
|
|
32
36
|
target_id: str = Field(..., description="The ID of the target object for this artifact")
|
|
33
37
|
target_kind: str = Field(..., description="The kind of the target object for this artifact")
|
|
34
38
|
target_name: str = Field(..., description="Name of the artifact target")
|
infrahub/git/tasks.py
CHANGED
|
@@ -339,10 +339,12 @@ async def generate_request_artifact_definition(
|
|
|
339
339
|
)
|
|
340
340
|
transform_location = ""
|
|
341
341
|
|
|
342
|
+
convert_query_response = False
|
|
342
343
|
if transform.typename == InfrahubKind.TRANSFORMJINJA2:
|
|
343
344
|
transform_location = transform.template_path.value
|
|
344
345
|
elif transform.typename == InfrahubKind.TRANSFORMPYTHON:
|
|
345
346
|
transform_location = f"{transform.file_path.value}::{transform.class_name.value}"
|
|
347
|
+
convert_query_response = transform.convert_query_response.value
|
|
346
348
|
|
|
347
349
|
for relationship in group.members.peers:
|
|
348
350
|
member = relationship.peer
|
|
@@ -368,6 +370,7 @@ async def generate_request_artifact_definition(
|
|
|
368
370
|
target_name=member.display_label,
|
|
369
371
|
target_kind=member.get_kind(),
|
|
370
372
|
timeout=transform.timeout.value,
|
|
373
|
+
convert_query_response=convert_query_response,
|
|
371
374
|
context=context,
|
|
372
375
|
)
|
|
373
376
|
|