infrahub-server 1.4.9__py3-none-any.whl → 1.5.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/actions/tasks.py +200 -16
- infrahub/api/artifact.py +3 -0
- infrahub/api/query.py +2 -0
- infrahub/api/schema.py +3 -0
- infrahub/auth.py +5 -5
- infrahub/cli/db.py +2 -2
- infrahub/config.py +7 -2
- infrahub/core/attribute.py +22 -19
- infrahub/core/branch/models.py +2 -2
- infrahub/core/branch/needs_rebase_status.py +11 -0
- infrahub/core/branch/tasks.py +2 -2
- infrahub/core/constants/__init__.py +1 -0
- infrahub/core/convert_object_type/object_conversion.py +201 -0
- infrahub/core/convert_object_type/repository_conversion.py +89 -0
- infrahub/core/convert_object_type/schema_mapping.py +27 -3
- infrahub/core/diff/query/artifact.py +12 -9
- infrahub/core/graph/__init__.py +1 -1
- infrahub/core/initialization.py +2 -2
- infrahub/core/manager.py +3 -81
- infrahub/core/migrations/graph/__init__.py +2 -0
- infrahub/core/migrations/graph/m040_profile_attrs_in_db.py +166 -0
- infrahub/core/node/__init__.py +26 -3
- infrahub/core/node/create.py +79 -38
- infrahub/core/node/lock_utils.py +98 -0
- infrahub/core/property.py +11 -0
- infrahub/core/protocols.py +1 -0
- infrahub/core/query/attribute.py +27 -15
- infrahub/core/query/node.py +47 -184
- infrahub/core/query/relationship.py +43 -26
- infrahub/core/query/subquery.py +0 -8
- infrahub/core/relationship/model.py +59 -19
- infrahub/core/schema/attribute_schema.py +0 -2
- infrahub/core/schema/definitions/core/repository.py +7 -0
- infrahub/core/schema/relationship_schema.py +0 -1
- infrahub/core/schema/schema_branch.py +3 -2
- infrahub/generators/models.py +31 -12
- infrahub/generators/tasks.py +3 -1
- infrahub/git/base.py +38 -1
- infrahub/graphql/api/dependencies.py +2 -4
- infrahub/graphql/api/endpoints.py +2 -2
- infrahub/graphql/app.py +2 -4
- infrahub/graphql/initialization.py +2 -3
- infrahub/graphql/manager.py +212 -137
- infrahub/graphql/middleware.py +12 -0
- infrahub/graphql/mutations/branch.py +11 -0
- infrahub/graphql/mutations/computed_attribute.py +110 -3
- infrahub/graphql/mutations/convert_object_type.py +34 -13
- infrahub/graphql/mutations/ipam.py +21 -8
- infrahub/graphql/mutations/main.py +37 -153
- infrahub/graphql/mutations/profile.py +195 -0
- infrahub/graphql/mutations/proposed_change.py +2 -1
- infrahub/graphql/mutations/repository.py +22 -83
- infrahub/graphql/mutations/webhook.py +1 -1
- infrahub/graphql/registry.py +173 -0
- infrahub/graphql/schema.py +4 -1
- infrahub/lock.py +52 -26
- infrahub/locks/__init__.py +0 -0
- infrahub/locks/tasks.py +37 -0
- infrahub/patch/plan_writer.py +2 -2
- infrahub/profiles/__init__.py +0 -0
- infrahub/profiles/node_applier.py +101 -0
- infrahub/profiles/queries/__init__.py +0 -0
- infrahub/profiles/queries/get_profile_data.py +99 -0
- infrahub/profiles/tasks.py +63 -0
- infrahub/repositories/__init__.py +0 -0
- infrahub/repositories/create_repository.py +113 -0
- infrahub/tasks/registry.py +6 -4
- infrahub/webhook/models.py +1 -1
- infrahub/workflows/catalogue.py +38 -3
- infrahub/workflows/models.py +17 -2
- infrahub_sdk/branch.py +5 -8
- infrahub_sdk/client.py +364 -84
- infrahub_sdk/convert_object_type.py +61 -0
- infrahub_sdk/ctl/check.py +2 -3
- infrahub_sdk/ctl/cli_commands.py +16 -12
- infrahub_sdk/ctl/config.py +8 -2
- infrahub_sdk/ctl/generator.py +2 -3
- infrahub_sdk/ctl/repository.py +39 -1
- infrahub_sdk/ctl/schema.py +12 -1
- infrahub_sdk/ctl/utils.py +4 -0
- infrahub_sdk/ctl/validate.py +5 -3
- infrahub_sdk/diff.py +4 -5
- infrahub_sdk/exceptions.py +2 -0
- infrahub_sdk/graphql.py +7 -2
- infrahub_sdk/node/attribute.py +2 -0
- infrahub_sdk/node/node.py +28 -20
- infrahub_sdk/playback.py +1 -2
- infrahub_sdk/protocols.py +40 -6
- infrahub_sdk/pytest_plugin/plugin.py +7 -4
- infrahub_sdk/pytest_plugin/utils.py +40 -0
- infrahub_sdk/repository.py +1 -2
- infrahub_sdk/schema/main.py +1 -0
- infrahub_sdk/spec/object.py +43 -4
- infrahub_sdk/spec/range_expansion.py +118 -0
- infrahub_sdk/timestamp.py +18 -6
- {infrahub_server-1.4.9.dist-info → infrahub_server-1.5.0b0.dist-info}/METADATA +20 -24
- {infrahub_server-1.4.9.dist-info → infrahub_server-1.5.0b0.dist-info}/RECORD +102 -84
- infrahub_testcontainers/models.py +2 -2
- infrahub_testcontainers/performance_test.py +4 -4
- infrahub/core/convert_object_type/conversion.py +0 -134
- {infrahub_server-1.4.9.dist-info → infrahub_server-1.5.0b0.dist-info}/LICENSE.txt +0 -0
- {infrahub_server-1.4.9.dist-info → infrahub_server-1.5.0b0.dist-info}/WHEEL +0 -0
- {infrahub_server-1.4.9.dist-info → infrahub_server-1.5.0b0.dist-info}/entry_points.txt +0 -0
infrahub/graphql/manager.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import hashlib
|
|
3
4
|
from dataclasses import dataclass
|
|
4
5
|
from typing import TYPE_CHECKING, Any, Iterable
|
|
5
6
|
|
|
@@ -17,9 +18,9 @@ from infrahub.core.schema import (
|
|
|
17
18
|
RelationshipSchema,
|
|
18
19
|
TemplateSchema,
|
|
19
20
|
)
|
|
20
|
-
from infrahub.core.timestamp import Timestamp
|
|
21
21
|
from infrahub.graphql.mutations.attribute import BaseAttributeCreate, BaseAttributeUpdate
|
|
22
22
|
from infrahub.graphql.mutations.graphql_query import InfrahubGraphQLQueryMutation
|
|
23
|
+
from infrahub.graphql.mutations.profile import InfrahubProfileMutation
|
|
23
24
|
from infrahub.types import ATTRIBUTE_TYPES, InfrahubDataType, get_attribute_type
|
|
24
25
|
|
|
25
26
|
from .directives import DIRECTIVES
|
|
@@ -40,6 +41,7 @@ from .mutations.resource_manager import (
|
|
|
40
41
|
InfrahubNumberPoolMutation,
|
|
41
42
|
)
|
|
42
43
|
from .mutations.webhook import InfrahubWebhookMutation
|
|
44
|
+
from .registry import registry
|
|
43
45
|
from .resolvers.ipam import ipam_paginated_list_resolver
|
|
44
46
|
from .resolvers.resolver import (
|
|
45
47
|
account_resolver,
|
|
@@ -69,7 +71,6 @@ from .types.event import EVENT_TYPES
|
|
|
69
71
|
if TYPE_CHECKING:
|
|
70
72
|
from graphql import GraphQLSchema
|
|
71
73
|
|
|
72
|
-
from infrahub.core.branch import Branch
|
|
73
74
|
from infrahub.core.schema.schema_branch import SchemaBranch
|
|
74
75
|
|
|
75
76
|
|
|
@@ -93,81 +94,34 @@ class GraphqlMutations:
|
|
|
93
94
|
delete: type[InfrahubMutation]
|
|
94
95
|
|
|
95
96
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
97
|
+
@dataclass
|
|
98
|
+
class InterfaceReference:
|
|
99
|
+
reference: type[graphene.Interface]
|
|
100
|
+
reference_hash: str
|
|
100
101
|
|
|
101
102
|
|
|
102
103
|
@dataclass
|
|
103
|
-
class
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
schema_hash: str
|
|
107
|
-
gql_manager: GraphQLSchemaManager
|
|
104
|
+
class InfrahubObjectReference:
|
|
105
|
+
reference: type[InfrahubObject]
|
|
106
|
+
reference_hash: str
|
|
108
107
|
|
|
109
108
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
"""Return inactive branches that were purged"""
|
|
121
|
-
inactive_branches: set[str] = set()
|
|
122
|
-
for branch_name in list(cls._branch_details_by_name):
|
|
123
|
-
if branch_name not in active_branches:
|
|
124
|
-
inactive_branches.add(branch_name)
|
|
125
|
-
del cls._branch_details_by_name[branch_name]
|
|
126
|
-
return inactive_branches
|
|
127
|
-
|
|
128
|
-
@classmethod
|
|
129
|
-
def _cache_branch(
|
|
130
|
-
cls, branch: Branch, schema_branch: SchemaBranch, schema_hash: str | None = None
|
|
131
|
-
) -> BranchDetails:
|
|
132
|
-
if not schema_hash:
|
|
133
|
-
if branch.schema_hash:
|
|
134
|
-
schema_hash = branch.schema_hash.main
|
|
135
|
-
else:
|
|
136
|
-
schema_hash = schema_branch.get_hash()
|
|
137
|
-
branch_details = BranchDetails(
|
|
138
|
-
branch_name=branch.name,
|
|
139
|
-
schema_changed_at=Timestamp(branch.schema_changed_at) if branch.schema_changed_at else Timestamp(),
|
|
140
|
-
schema_hash=schema_hash,
|
|
141
|
-
gql_manager=cls(schema=schema_branch),
|
|
142
|
-
)
|
|
143
|
-
cls._branch_details_by_name[branch.name] = branch_details
|
|
144
|
-
return branch_details
|
|
145
|
-
|
|
146
|
-
@classmethod
|
|
147
|
-
def get_manager_for_branch(cls, branch: Branch, schema_branch: SchemaBranch) -> GraphQLSchemaManager:
|
|
148
|
-
if branch.name not in cls._branch_details_by_name:
|
|
149
|
-
branch_details = cls._cache_branch(branch=branch, schema_branch=schema_branch)
|
|
150
|
-
return branch_details.gql_manager
|
|
151
|
-
cached_branch_details = cls._branch_details_by_name[branch.name]
|
|
152
|
-
# try to use the schema_changed_at time b/c it is faster than checking the hash
|
|
153
|
-
if branch.schema_changed_at:
|
|
154
|
-
changed_at_time = Timestamp(branch.schema_changed_at)
|
|
155
|
-
if changed_at_time > cached_branch_details.schema_changed_at:
|
|
156
|
-
cached_branch_details = cls._cache_branch(branch=branch, schema_branch=schema_branch)
|
|
157
|
-
return cached_branch_details.gql_manager
|
|
158
|
-
if branch.schema_hash:
|
|
159
|
-
current_hash = branch.active_schema_hash.main
|
|
160
|
-
else:
|
|
161
|
-
current_hash = schema_branch.get_hash()
|
|
162
|
-
if cached_branch_details.schema_hash != current_hash:
|
|
163
|
-
cached_branch_details = cls._cache_branch(
|
|
164
|
-
branch=branch, schema_branch=schema_branch, schema_hash=current_hash
|
|
165
|
-
)
|
|
109
|
+
@dataclass
|
|
110
|
+
class InfrahubEdgedReference:
|
|
111
|
+
reference: type[InfrahubObject]
|
|
112
|
+
reference_hash: str
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def get_attr_kind(node_schema: MainSchemaTypes, attr_schema: AttributeSchema) -> str:
|
|
116
|
+
if not config.SETTINGS.experimental_features.graphql_enums or not attr_schema.enum:
|
|
117
|
+
return attr_schema.kind
|
|
118
|
+
return get_enum_attribute_type_name(node_schema=node_schema, attr_schema=attr_schema)
|
|
166
119
|
|
|
167
|
-
return cached_branch_details.gql_manager
|
|
168
120
|
|
|
121
|
+
class GraphQLSchemaManager:
|
|
169
122
|
def __init__(self, schema: SchemaBranch) -> None:
|
|
170
123
|
self.schema = schema
|
|
124
|
+
self.schema_hash = schema.get_hash()
|
|
171
125
|
|
|
172
126
|
self._full_graphql_schema: GraphQLSchema | None = None
|
|
173
127
|
self._graphql_types: dict[str, GraphQLTypes] = {}
|
|
@@ -275,9 +229,7 @@ class GraphQLSchemaManager:
|
|
|
275
229
|
raise ValueError(f"Unable to find {name!r}")
|
|
276
230
|
|
|
277
231
|
def get_all(self) -> dict[str, GraphQLTypes]:
|
|
278
|
-
|
|
279
|
-
infrahub_types.update(self._extra_types)
|
|
280
|
-
return infrahub_types
|
|
232
|
+
return self._graphql_types
|
|
281
233
|
|
|
282
234
|
def set_type(self, name: str, graphql_type: GraphQLTypes) -> None:
|
|
283
235
|
self._graphql_types[name] = graphql_type
|
|
@@ -342,7 +294,9 @@ class GraphQLSchemaManager:
|
|
|
342
294
|
)
|
|
343
295
|
ATTRIBUTE_TYPES[base_enum_name] = data_type_class
|
|
344
296
|
|
|
345
|
-
def _get_related_input_type(
|
|
297
|
+
def _get_related_input_type(
|
|
298
|
+
self, relationship: RelationshipSchema
|
|
299
|
+
) -> type[RelatedNodeInput | RelatedIPPrefixNodeInput | RelatedIPAddressNodeInput]:
|
|
346
300
|
peer_schema = self.schema.get(name=relationship.peer, duplicate=False)
|
|
347
301
|
if peer_schema.is_ip_prefix:
|
|
348
302
|
return RelatedIPPrefixNodeInput
|
|
@@ -400,12 +354,15 @@ class GraphQLSchemaManager:
|
|
|
400
354
|
# Generate all GraphQL ObjectType, Nested, Paginated & NestedPaginated and store them in the registry
|
|
401
355
|
for node_schema in full_schema.values():
|
|
402
356
|
if isinstance(node_schema, NodeSchema | ProfileSchema | TemplateSchema):
|
|
403
|
-
|
|
357
|
+
node_object_type = self.generate_graphql_object(schema=node_schema, populate_cache=True)
|
|
404
358
|
node_type_edged = self.generate_graphql_edged_object(
|
|
405
|
-
schema=node_schema, node=
|
|
359
|
+
schema=node_schema, node=node_object_type, populate_cache=True
|
|
406
360
|
)
|
|
407
361
|
nested_node_type_edged = self.generate_graphql_edged_object(
|
|
408
|
-
schema=node_schema,
|
|
362
|
+
schema=node_schema,
|
|
363
|
+
node=node_object_type,
|
|
364
|
+
relation_property=relationship_property,
|
|
365
|
+
populate_cache=True,
|
|
409
366
|
)
|
|
410
367
|
|
|
411
368
|
self.generate_graphql_paginated_object(schema=node_schema, edge=node_type_edged, populate_cache=True)
|
|
@@ -533,6 +490,8 @@ class GraphQLSchemaManager:
|
|
|
533
490
|
base_class = InfrahubIPPrefixMutation
|
|
534
491
|
elif isinstance(node_schema, NodeSchema) and node_schema.is_ip_address:
|
|
535
492
|
base_class = InfrahubIPAddressMutation
|
|
493
|
+
elif isinstance(node_schema, ProfileSchema):
|
|
494
|
+
base_class = InfrahubProfileMutation
|
|
536
495
|
else:
|
|
537
496
|
base_class = mutation_map.get(node_schema.kind, InfrahubMutation)
|
|
538
497
|
|
|
@@ -557,13 +516,15 @@ class GraphQLSchemaManager:
|
|
|
557
516
|
|
|
558
517
|
return type("MutationMixin", (object,), class_attrs)
|
|
559
518
|
|
|
560
|
-
def generate_graphql_object(self, schema: MainSchemaTypes, populate_cache: bool = False) ->
|
|
519
|
+
def generate_graphql_object(self, schema: MainSchemaTypes, populate_cache: bool = False) -> InfrahubObjectReference:
|
|
561
520
|
"""Generate a GraphQL object Type from a Infrahub NodeSchema."""
|
|
562
521
|
|
|
563
522
|
interfaces: set[type[InfrahubObject]] = set()
|
|
523
|
+
md5hash = hashlib.md5(usedforsecurity=False)
|
|
524
|
+
md5hash.update(f"{schema.kind}{schema.get_hash()}".encode())
|
|
564
525
|
|
|
565
526
|
if isinstance(schema, NodeSchema | ProfileSchema | TemplateSchema) and schema.inherit_from:
|
|
566
|
-
for generic_name in schema.inherit_from:
|
|
527
|
+
for generic_name in sorted(schema.inherit_from):
|
|
567
528
|
generic = self.get_type(name=generic_name)
|
|
568
529
|
interfaces.add(generic)
|
|
569
530
|
|
|
@@ -597,21 +558,26 @@ class GraphQLSchemaManager:
|
|
|
597
558
|
req = "" if attr.optional else " (required)"
|
|
598
559
|
main_attrs[attr.name] = graphene.Field(attr_type, description=f"{attr.description}{req}")
|
|
599
560
|
|
|
561
|
+
object_hash = md5hash.hexdigest()
|
|
562
|
+
|
|
600
563
|
graphql_object = type(schema.kind, (InfrahubObject,), main_attrs)
|
|
564
|
+
registry.set_object_type(reference=graphql_object, reference_hash=object_hash, schema_hash=self.schema_hash)
|
|
601
565
|
|
|
602
566
|
if populate_cache:
|
|
603
567
|
self.set_type(name=schema.kind, graphql_type=graphql_object)
|
|
604
568
|
|
|
605
|
-
return graphql_object
|
|
569
|
+
return InfrahubObjectReference(reference=graphql_object, reference_hash=object_hash)
|
|
606
570
|
|
|
607
|
-
def generate_interface_object(
|
|
608
|
-
self, schema: GenericSchema, populate_cache: bool = False
|
|
609
|
-
) -> type[graphene.Interface]:
|
|
571
|
+
def generate_interface_object(self, schema: GenericSchema, populate_cache: bool = False) -> InterfaceReference:
|
|
610
572
|
meta_attrs = {
|
|
611
573
|
"name": schema.kind,
|
|
612
574
|
"description": schema.description,
|
|
613
575
|
}
|
|
614
576
|
|
|
577
|
+
md5hash = hashlib.md5(usedforsecurity=False)
|
|
578
|
+
md5hash.update(f"interface-{schema.kind}{schema.get_hash()}".encode())
|
|
579
|
+
interface_hash = md5hash.hexdigest()
|
|
580
|
+
|
|
615
581
|
main_attrs = {
|
|
616
582
|
"id": graphene.Field(graphene.String, required=False, description="Unique identifier"),
|
|
617
583
|
"hfid": graphene.Field(
|
|
@@ -620,7 +586,6 @@ class GraphQLSchemaManager:
|
|
|
620
586
|
description="Human friendly identifier",
|
|
621
587
|
),
|
|
622
588
|
"display_label": graphene.String(required=False),
|
|
623
|
-
"Meta": type("Meta", (object,), meta_attrs),
|
|
624
589
|
}
|
|
625
590
|
|
|
626
591
|
for attr in schema.attributes:
|
|
@@ -628,12 +593,18 @@ class GraphQLSchemaManager:
|
|
|
628
593
|
attr_type = self.get_type(name=get_attribute_type(kind=attr_kind).get_graphql_type_name())
|
|
629
594
|
main_attrs[attr.name] = graphene.Field(attr_type, description=attr.description)
|
|
630
595
|
|
|
631
|
-
interface_object =
|
|
596
|
+
interface_object = registry.get_interface_type(reference_hash=interface_hash, schema_hash=self.schema_hash)
|
|
597
|
+
if not interface_object:
|
|
598
|
+
main_attrs["Meta"] = type("Meta", (object,), meta_attrs)
|
|
599
|
+
interface_object = type(schema.kind, (InfrahubInterface,), main_attrs)
|
|
600
|
+
|
|
601
|
+
interface_reference = InterfaceReference(reference=interface_object, reference_hash=interface_hash)
|
|
602
|
+
registry.set_interface_type(reference=interface_reference, schema_hash=self.schema_hash)
|
|
632
603
|
|
|
633
604
|
if populate_cache:
|
|
634
605
|
self.set_type(name=schema.kind, graphql_type=interface_object)
|
|
635
606
|
|
|
636
|
-
return
|
|
607
|
+
return interface_reference
|
|
637
608
|
|
|
638
609
|
def define_relationship_property(self, data_source: type[InfrahubObject], data_owner: type[InfrahubObject]) -> None:
|
|
639
610
|
type_name = "RelationshipProperty"
|
|
@@ -721,7 +692,18 @@ class GraphQLSchemaManager:
|
|
|
721
692
|
elif rel.cardinality == RelationshipCardinality.MANY:
|
|
722
693
|
attrs[rel.name] = graphene.InputField(graphene.List(input_type), description=rel.description)
|
|
723
694
|
|
|
724
|
-
|
|
695
|
+
input_name = f"{schema.kind}CreateInput"
|
|
696
|
+
md5hash = hashlib.md5(usedforsecurity=False)
|
|
697
|
+
md5hash.update(f"{input_name}{schema.get_hash()}".encode())
|
|
698
|
+
input_hash = md5hash.hexdigest()
|
|
699
|
+
mutation_input_type = registry.get_input_type(reference_hash=input_hash, schema_hash=self.schema_hash)
|
|
700
|
+
if not mutation_input_type:
|
|
701
|
+
mutation_input_type = type(input_name, (graphene.InputObjectType,), attrs)
|
|
702
|
+
registry.set_input_type(
|
|
703
|
+
reference=mutation_input_type, reference_hash=input_hash, schema_hash=self.schema_hash
|
|
704
|
+
)
|
|
705
|
+
|
|
706
|
+
return mutation_input_type
|
|
725
707
|
|
|
726
708
|
def generate_graphql_mutation_update_input(self, schema: MainSchemaTypes) -> type[graphene.InputObjectType]:
|
|
727
709
|
"""Generate an InputObjectType Object from a Infrahub NodeSchema
|
|
@@ -734,7 +716,7 @@ class GraphQLSchemaManager:
|
|
|
734
716
|
slug = InputField(StringAttributeUpdate, required=False)
|
|
735
717
|
description = InputField(StringAttributeUpdate, required=False)
|
|
736
718
|
"""
|
|
737
|
-
attrs: dict[str, graphene.String | graphene.InputField] = {
|
|
719
|
+
attrs: dict[str, graphene.String | graphene.InputField | graphene.List] = {
|
|
738
720
|
"id": graphene.String(required=False),
|
|
739
721
|
"hfid": graphene.List(of_type=graphene.String, required=False),
|
|
740
722
|
}
|
|
@@ -760,7 +742,18 @@ class GraphQLSchemaManager:
|
|
|
760
742
|
graphene.List(input_type), required=False, description=rel.description
|
|
761
743
|
)
|
|
762
744
|
|
|
763
|
-
|
|
745
|
+
input_name = f"{schema.kind}UpdateInput"
|
|
746
|
+
md5hash = hashlib.md5(usedforsecurity=False)
|
|
747
|
+
md5hash.update(f"{input_name}{schema.get_hash()}".encode())
|
|
748
|
+
input_hash = md5hash.hexdigest()
|
|
749
|
+
mutation_input_type = registry.get_input_type(reference_hash=input_hash, schema_hash=self.schema_hash)
|
|
750
|
+
if not mutation_input_type:
|
|
751
|
+
mutation_input_type = type(input_name, (graphene.InputObjectType,), attrs)
|
|
752
|
+
registry.set_input_type(
|
|
753
|
+
reference=mutation_input_type, reference_hash=input_hash, schema_hash=self.schema_hash
|
|
754
|
+
)
|
|
755
|
+
|
|
756
|
+
return mutation_input_type
|
|
764
757
|
|
|
765
758
|
def generate_graphql_mutation_upsert_input(
|
|
766
759
|
self, schema: NodeSchema | ProfileSchema | TemplateSchema
|
|
@@ -775,7 +768,7 @@ class GraphQLSchemaManager:
|
|
|
775
768
|
slug = InputField(StringAttributeUpdate, required=True)
|
|
776
769
|
description = InputField(StringAttributeUpdate, required=False)
|
|
777
770
|
"""
|
|
778
|
-
attrs: dict[str, graphene.String | graphene.InputField] = {
|
|
771
|
+
attrs: dict[str, graphene.String | graphene.InputField | graphene.List] = {
|
|
779
772
|
"id": graphene.String(required=False),
|
|
780
773
|
"hfid": graphene.List(of_type=graphene.String, required=False),
|
|
781
774
|
}
|
|
@@ -806,8 +799,18 @@ class GraphQLSchemaManager:
|
|
|
806
799
|
attrs[rel.name] = graphene.InputField(
|
|
807
800
|
graphene.List(input_type), required=required, description=rel.description
|
|
808
801
|
)
|
|
802
|
+
input_name = f"{schema.kind}UpsertInput"
|
|
803
|
+
md5hash = hashlib.md5(usedforsecurity=False)
|
|
804
|
+
md5hash.update(f"{input_name}{schema.get_hash()}".encode())
|
|
805
|
+
input_hash = md5hash.hexdigest()
|
|
806
|
+
mutation_input_type = registry.get_input_type(reference_hash=input_hash, schema_hash=self.schema_hash)
|
|
807
|
+
if not mutation_input_type:
|
|
808
|
+
mutation_input_type = type(input_name, (graphene.InputObjectType,), attrs)
|
|
809
|
+
registry.set_input_type(
|
|
810
|
+
reference=mutation_input_type, reference_hash=input_hash, schema_hash=self.schema_hash
|
|
811
|
+
)
|
|
809
812
|
|
|
810
|
-
return
|
|
813
|
+
return mutation_input_type
|
|
811
814
|
|
|
812
815
|
def generate_graphql_mutation_create(
|
|
813
816
|
self,
|
|
@@ -819,17 +822,27 @@ class GraphQLSchemaManager:
|
|
|
819
822
|
"""Generate a GraphQL Mutation to CREATE an object based on the specified NodeSchema."""
|
|
820
823
|
name = f"{schema.kind}{mutation_type}"
|
|
821
824
|
|
|
822
|
-
|
|
825
|
+
md5hash = hashlib.md5(usedforsecurity=False)
|
|
826
|
+
md5hash.update(f"{name}{schema.get_hash()}".encode())
|
|
827
|
+
mutation_hash = md5hash.hexdigest()
|
|
823
828
|
|
|
824
|
-
|
|
829
|
+
mutation_object = registry.get_mutation_type(reference_hash=mutation_hash, schema_hash=self.schema_hash)
|
|
830
|
+
if not mutation_object:
|
|
831
|
+
object_type = self.generate_graphql_object(schema=schema)
|
|
825
832
|
|
|
826
|
-
|
|
827
|
-
main_attrs["Meta"] = type("Meta", (object,), meta_attrs)
|
|
833
|
+
main_attrs: dict[str, Any] = {"ok": graphene.Boolean(), "object": graphene.Field(object_type.reference)}
|
|
828
834
|
|
|
829
|
-
|
|
830
|
-
|
|
835
|
+
meta_attrs: dict[str, Any] = {"schema": schema, "name": name, "description": schema.description}
|
|
836
|
+
main_attrs["Meta"] = type("Meta", (object,), meta_attrs)
|
|
831
837
|
|
|
832
|
-
|
|
838
|
+
args_attrs = {"data": input_type(required=True), "context": ContextInput(required=False)}
|
|
839
|
+
main_attrs["Arguments"] = type("Arguments", (object,), args_attrs)
|
|
840
|
+
mutation_object = type(name, (base_class,), main_attrs)
|
|
841
|
+
registry.set_mutation_type(
|
|
842
|
+
reference=mutation_object, reference_hash=mutation_hash, schema_hash=self.schema_hash
|
|
843
|
+
)
|
|
844
|
+
|
|
845
|
+
return mutation_object
|
|
833
846
|
|
|
834
847
|
def generate_graphql_mutation_update(
|
|
835
848
|
self,
|
|
@@ -840,34 +853,50 @@ class GraphQLSchemaManager:
|
|
|
840
853
|
"""Generate a GraphQL Mutation to UPDATE an object based on the specified NodeSchema."""
|
|
841
854
|
name = f"{schema.kind}Update"
|
|
842
855
|
|
|
843
|
-
|
|
856
|
+
md5hash = hashlib.md5(usedforsecurity=False)
|
|
857
|
+
md5hash.update(f"{name}{schema.get_hash()}".encode())
|
|
858
|
+
mutation_hash = md5hash.hexdigest()
|
|
859
|
+
|
|
860
|
+
mutation_object = registry.get_mutation_type(reference_hash=mutation_hash, schema_hash=self.schema_hash)
|
|
861
|
+
if not mutation_object:
|
|
862
|
+
object_type = self.generate_graphql_object(schema=schema)
|
|
863
|
+
|
|
864
|
+
main_attrs: dict[str, Any] = {"ok": graphene.Boolean(), "object": graphene.Field(object_type.reference)}
|
|
844
865
|
|
|
845
|
-
|
|
866
|
+
meta_attrs: dict[str, Any] = {"schema": schema, "name": name, "description": schema.description}
|
|
867
|
+
main_attrs["Meta"] = type("Meta", (object,), meta_attrs)
|
|
846
868
|
|
|
847
|
-
|
|
848
|
-
|
|
869
|
+
args_attrs = {"data": input_type(required=True), "context": ContextInput(required=False)}
|
|
870
|
+
main_attrs["Arguments"] = type("Arguments", (object,), args_attrs)
|
|
849
871
|
|
|
850
|
-
|
|
851
|
-
|
|
872
|
+
mutation_object = type(name, (base_class,), main_attrs)
|
|
873
|
+
registry.set_mutation_type(
|
|
874
|
+
reference=mutation_object, reference_hash=mutation_hash, schema_hash=self.schema_hash
|
|
875
|
+
)
|
|
852
876
|
|
|
853
|
-
return
|
|
877
|
+
return mutation_object
|
|
854
878
|
|
|
855
|
-
@staticmethod
|
|
856
879
|
def generate_graphql_mutation_delete(
|
|
857
|
-
schema: NodeSchema | ProfileSchema | TemplateSchema, base_class: type[InfrahubMutation] = InfrahubMutation
|
|
880
|
+
self, schema: NodeSchema | ProfileSchema | TemplateSchema, base_class: type[InfrahubMutation] = InfrahubMutation
|
|
858
881
|
) -> type[InfrahubMutation]:
|
|
859
882
|
"""Generate a GraphQL Mutation to DELETE an object based on the specified NodeSchema."""
|
|
860
883
|
name = f"{schema.kind}Delete"
|
|
884
|
+
md5hash = hashlib.md5(usedforsecurity=False)
|
|
885
|
+
md5hash.update(f"{name}{schema.get_hash()}".encode())
|
|
886
|
+
mutation_hash = md5hash.hexdigest()
|
|
887
|
+
mutation_object = registry.get_mutation_type(reference_hash=mutation_hash, schema_hash=self.schema_hash)
|
|
888
|
+
if not mutation_object:
|
|
889
|
+
main_attrs: dict[str, Any] = {"ok": graphene.Boolean()}
|
|
890
|
+
meta_attrs = {"schema": schema, "name": name, "description": schema.description}
|
|
891
|
+
main_attrs["Meta"] = type("Meta", (object,), meta_attrs)
|
|
892
|
+
args_attrs: dict[str, Any] = {"data": DeleteInput(required=True), "context": ContextInput(required=False)}
|
|
893
|
+
main_attrs["Arguments"] = type("Arguments", (object,), args_attrs)
|
|
894
|
+
mutation_object = type(name, (base_class,), main_attrs)
|
|
895
|
+
registry.set_mutation_type(
|
|
896
|
+
reference=mutation_object, reference_hash=mutation_hash, schema_hash=self.schema_hash
|
|
897
|
+
)
|
|
861
898
|
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
meta_attrs = {"schema": schema, "name": name, "description": schema.description}
|
|
865
|
-
main_attrs["Meta"] = type("Meta", (object,), meta_attrs)
|
|
866
|
-
|
|
867
|
-
args_attrs: dict[str, Any] = {"data": DeleteInput(required=True), "context": ContextInput(required=False)}
|
|
868
|
-
main_attrs["Arguments"] = type("Arguments", (object,), args_attrs)
|
|
869
|
-
|
|
870
|
-
return type(name, (base_class,), main_attrs)
|
|
899
|
+
return mutation_object
|
|
871
900
|
|
|
872
901
|
def generate_filters(
|
|
873
902
|
self, schema: MainSchemaTypes, top_level: bool = False, include_properties: bool = True
|
|
@@ -940,16 +969,20 @@ class GraphQLSchemaManager:
|
|
|
940
969
|
def generate_graphql_edged_object(
|
|
941
970
|
self,
|
|
942
971
|
schema: MainSchemaTypes,
|
|
943
|
-
node:
|
|
972
|
+
node: InterfaceReference | InfrahubObjectReference,
|
|
944
973
|
relation_property: type[InfrahubObject] | None = None,
|
|
945
974
|
populate_cache: bool = False,
|
|
946
|
-
) ->
|
|
975
|
+
) -> InfrahubEdgedReference:
|
|
947
976
|
"""Generate a edged GraphQL object Type from a Infrahub NodeSchema for pagination."""
|
|
948
977
|
|
|
949
978
|
object_name = f"Edged{schema.kind}"
|
|
950
979
|
if relation_property:
|
|
951
980
|
object_name = f"NestedEdged{schema.kind}"
|
|
952
981
|
|
|
982
|
+
md5hash = hashlib.md5(usedforsecurity=False)
|
|
983
|
+
md5hash.update(f"{object_name}{schema.get_hash()}{node.reference_hash}".encode())
|
|
984
|
+
edge_hash = md5hash.hexdigest()
|
|
985
|
+
|
|
953
986
|
meta_attrs: dict[str, Any] = {
|
|
954
987
|
"schema": schema,
|
|
955
988
|
"name": object_name,
|
|
@@ -958,22 +991,27 @@ class GraphQLSchemaManager:
|
|
|
958
991
|
}
|
|
959
992
|
|
|
960
993
|
main_attrs: dict[str, Any] = {
|
|
961
|
-
"node": graphene.Field(node, required=False),
|
|
994
|
+
"node": graphene.Field(node.reference, required=False),
|
|
962
995
|
"Meta": type("Meta", (object,), meta_attrs),
|
|
963
996
|
}
|
|
964
997
|
|
|
965
998
|
if relation_property:
|
|
966
999
|
main_attrs["properties"] = graphene.Field(relation_property, required=False)
|
|
967
1000
|
|
|
968
|
-
graphql_edged_object =
|
|
1001
|
+
graphql_edged_object = registry.get_edge_type(reference_hash=edge_hash, schema_hash=self.schema_hash)
|
|
1002
|
+
if not graphql_edged_object:
|
|
1003
|
+
graphql_edged_object = type(object_name, (InfrahubObject,), main_attrs)
|
|
1004
|
+
registry.set_edge_type(
|
|
1005
|
+
reference=graphql_edged_object, reference_hash=edge_hash, schema_hash=self.schema_hash
|
|
1006
|
+
)
|
|
969
1007
|
|
|
970
1008
|
if populate_cache:
|
|
971
1009
|
self.set_type(name=object_name, graphql_type=graphql_edged_object)
|
|
972
1010
|
|
|
973
|
-
return graphql_edged_object
|
|
1011
|
+
return InfrahubEdgedReference(reference=graphql_edged_object, reference_hash=edge_hash)
|
|
974
1012
|
|
|
975
1013
|
def generate_graphql_paginated_object(
|
|
976
|
-
self, schema: MainSchemaTypes, edge:
|
|
1014
|
+
self, schema: MainSchemaTypes, edge: InfrahubEdgedReference, nested: bool = False, populate_cache: bool = False
|
|
977
1015
|
) -> type[InfrahubObject]:
|
|
978
1016
|
"""Generate a paginated GraphQL object Type from a Infrahub NodeSchema."""
|
|
979
1017
|
|
|
@@ -981,6 +1019,10 @@ class GraphQLSchemaManager:
|
|
|
981
1019
|
if nested:
|
|
982
1020
|
object_name = f"NestedPaginated{schema.kind}"
|
|
983
1021
|
|
|
1022
|
+
md5hash = hashlib.md5(usedforsecurity=False)
|
|
1023
|
+
md5hash.update(f"{object_name}{schema.get_hash()}{edge.reference_hash}".encode())
|
|
1024
|
+
paginated_hash = md5hash.hexdigest()
|
|
1025
|
+
|
|
984
1026
|
meta_attrs: dict[str, Any] = {
|
|
985
1027
|
"schema": schema,
|
|
986
1028
|
"name": object_name,
|
|
@@ -991,14 +1033,21 @@ class GraphQLSchemaManager:
|
|
|
991
1033
|
|
|
992
1034
|
main_attrs: dict[str, Any] = {
|
|
993
1035
|
"count": graphene.Int(required=True),
|
|
994
|
-
"edges": graphene.List(of_type=graphene.NonNull(edge), required=True),
|
|
1036
|
+
"edges": graphene.List(of_type=graphene.NonNull(edge.reference), required=True),
|
|
995
1037
|
"permissions": graphene.Field(
|
|
996
1038
|
PaginatedObjectPermission, required=True, resolver=parent_field_name_resolver
|
|
997
1039
|
),
|
|
998
|
-
"Meta": type("Meta", (object,), meta_attrs),
|
|
999
1040
|
}
|
|
1000
1041
|
|
|
1001
|
-
graphql_paginated_object =
|
|
1042
|
+
graphql_paginated_object = registry.get_paginated_type(
|
|
1043
|
+
reference_hash=paginated_hash, schema_hash=self.schema_hash
|
|
1044
|
+
)
|
|
1045
|
+
if not graphql_paginated_object:
|
|
1046
|
+
main_attrs["Meta"] = type("Meta", (object,), meta_attrs)
|
|
1047
|
+
graphql_paginated_object = type(object_name, (InfrahubObject,), main_attrs)
|
|
1048
|
+
registry.set_paginated_type(
|
|
1049
|
+
reference=graphql_paginated_object, reference_hash=paginated_hash, schema_hash=self.schema_hash
|
|
1050
|
+
)
|
|
1002
1051
|
|
|
1003
1052
|
if populate_cache:
|
|
1004
1053
|
self.set_type(name=object_name, graphql_type=graphql_paginated_object)
|
|
@@ -1028,27 +1077,53 @@ class GraphQLSchemaManager:
|
|
|
1028
1077
|
main_attrs["properties"] = graphene.Field(relation_property, required=False)
|
|
1029
1078
|
|
|
1030
1079
|
object_name = f"NestedEdged{schema.kind}"
|
|
1031
|
-
|
|
1080
|
+
md5hash = hashlib.md5(usedforsecurity=False)
|
|
1081
|
+
md5hash.update(f"{object_name}{schema.get_hash()}".encode())
|
|
1082
|
+
paginated_hash = md5hash.hexdigest()
|
|
1083
|
+
nested_interface_object = registry.get_paginated_type(
|
|
1084
|
+
reference_hash=paginated_hash, schema_hash=self.schema_hash
|
|
1085
|
+
)
|
|
1086
|
+
if not nested_interface_object:
|
|
1087
|
+
nested_interface_object = type(object_name, (InfrahubObject,), main_attrs)
|
|
1088
|
+
registry.set_paginated_type(
|
|
1089
|
+
reference=nested_interface_object, reference_hash=paginated_hash, schema_hash=self.schema_hash
|
|
1090
|
+
)
|
|
1032
1091
|
|
|
1033
1092
|
if populate_cache:
|
|
1034
1093
|
self.set_type(name=object_name, graphql_type=nested_interface_object)
|
|
1035
1094
|
|
|
1036
1095
|
return nested_interface_object
|
|
1037
1096
|
|
|
1038
|
-
@staticmethod
|
|
1039
1097
|
def generate_paginated_interface_object(
|
|
1040
|
-
schema: GenericSchema, base_interface: type[graphene.ObjectType]
|
|
1098
|
+
self, schema: GenericSchema, base_interface: type[graphene.ObjectType]
|
|
1041
1099
|
) -> type[InfrahubObject]:
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1100
|
+
object_name = f"NestedPaginated{schema.kind}"
|
|
1101
|
+
md5hash = hashlib.md5(usedforsecurity=False)
|
|
1102
|
+
md5hash.update(f"{object_name}{schema.get_hash()}".encode())
|
|
1103
|
+
paginated_hash = md5hash.hexdigest()
|
|
1104
|
+
|
|
1105
|
+
nested_interface_object = registry.get_paginated_type(
|
|
1106
|
+
reference_hash=paginated_hash, schema_hash=self.schema_hash
|
|
1107
|
+
)
|
|
1108
|
+
if not nested_interface_object:
|
|
1109
|
+
meta_attrs: dict[str, Any] = {
|
|
1110
|
+
"name": object_name,
|
|
1111
|
+
"schema": schema,
|
|
1112
|
+
"description": schema.description,
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
main_attrs: dict[str, Any] = {
|
|
1116
|
+
"count": graphene.Int(required=True),
|
|
1117
|
+
"edges": graphene.List(of_type=graphene.NonNull(base_interface)),
|
|
1118
|
+
"Meta": type("Meta", (object,), meta_attrs),
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
nested_interface_object = type(object_name, (InfrahubObject,), main_attrs)
|
|
1122
|
+
registry.set_paginated_type(
|
|
1123
|
+
reference=nested_interface_object, reference_hash=paginated_hash, schema_hash=self.schema_hash
|
|
1124
|
+
)
|
|
1125
|
+
|
|
1126
|
+
return nested_interface_object
|
|
1047
1127
|
|
|
1048
|
-
main_attrs: dict[str, Any] = {
|
|
1049
|
-
"count": graphene.Int(required=True),
|
|
1050
|
-
"edges": graphene.List(of_type=graphene.NonNull(base_interface)),
|
|
1051
|
-
"Meta": type("Meta", (object,), meta_attrs),
|
|
1052
|
-
}
|
|
1053
1128
|
|
|
1054
|
-
|
|
1129
|
+
registry._register_manager(manager=GraphQLSchemaManager)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from infrahub.core.branch.needs_rebase_status import check_need_rebase_status
|
|
2
|
+
|
|
3
|
+
ALLOWED_MUTATIONS_ON_NEED_REBASE_BRANCH = ["BranchRebase", "BranchDelete", "BranchCreate", "ProposedChangeCreate"]
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def raise_on_mutation_on_branch_needing_rebase(next, root, info, **kwargs): # type: ignore # noqa
|
|
7
|
+
if info.operation.operation.value == "mutation":
|
|
8
|
+
mutation_name = info.operation.selection_set.selections[0].name.value
|
|
9
|
+
if mutation_name not in ALLOWED_MUTATIONS_ON_NEED_REBASE_BRANCH:
|
|
10
|
+
check_need_rebase_status(branch=info.context.branch)
|
|
11
|
+
|
|
12
|
+
return next(root, info, **kwargs)
|
|
@@ -7,8 +7,10 @@ from opentelemetry import trace
|
|
|
7
7
|
from typing_extensions import Self
|
|
8
8
|
|
|
9
9
|
from infrahub.branch.merge_mutation_checker import verify_branch_merge_mutation_allowed
|
|
10
|
+
from infrahub.core import registry
|
|
10
11
|
from infrahub.core.branch import Branch
|
|
11
12
|
from infrahub.database import retry_db_transaction
|
|
13
|
+
from infrahub.exceptions import BranchNotFoundError, ValidationError
|
|
12
14
|
from infrahub.graphql.context import apply_external_context
|
|
13
15
|
from infrahub.graphql.field_extractor import extract_graphql_fields
|
|
14
16
|
from infrahub.graphql.types.context import ContextInput
|
|
@@ -66,12 +68,21 @@ class BranchCreate(Mutation):
|
|
|
66
68
|
background_execution: bool = False,
|
|
67
69
|
wait_until_completion: bool = True,
|
|
68
70
|
) -> Self:
|
|
71
|
+
if data.origin_branch and data.origin_branch != registry.default_branch:
|
|
72
|
+
raise ValueError(f"origin_branch must be '{registry.default_branch}'")
|
|
73
|
+
|
|
69
74
|
graphql_context: GraphqlContext = info.context
|
|
70
75
|
task: dict | None = None
|
|
71
76
|
|
|
72
77
|
model = BranchCreateModel(**data)
|
|
73
78
|
await apply_external_context(graphql_context=graphql_context, context_input=context)
|
|
74
79
|
|
|
80
|
+
try:
|
|
81
|
+
await Branch.get_by_name(db=graphql_context.db, name=model.name)
|
|
82
|
+
raise ValidationError(f"The branch {model.name} already exists")
|
|
83
|
+
except BranchNotFoundError:
|
|
84
|
+
pass
|
|
85
|
+
|
|
75
86
|
if background_execution or not wait_until_completion:
|
|
76
87
|
workflow = await graphql_context.active_service.workflow.submit_workflow(
|
|
77
88
|
workflow=BRANCH_CREATE, context=graphql_context.get_context(), parameters={"model": model}
|