infrahub-server 1.4.10__py3-none-any.whl → 1.5.0b1__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 +208 -16
- infrahub/api/artifact.py +3 -0
- infrahub/api/diff/diff.py +1 -1
- infrahub/api/query.py +2 -0
- infrahub/api/schema.py +3 -0
- infrahub/auth.py +5 -5
- infrahub/cli/db.py +26 -2
- infrahub/cli/db_commands/clean_duplicate_schema_fields.py +212 -0
- infrahub/config.py +7 -2
- infrahub/core/attribute.py +25 -22
- infrahub/core/branch/models.py +2 -2
- infrahub/core/branch/needs_rebase_status.py +11 -0
- infrahub/core/branch/tasks.py +4 -3
- infrahub/core/changelog/models.py +4 -12
- infrahub/core/constants/__init__.py +1 -0
- infrahub/core/constants/infrahubkind.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/model/path.py +4 -0
- infrahub/core/diff/payload_builder.py +1 -1
- infrahub/core/diff/query/artifact.py +1 -1
- infrahub/core/graph/__init__.py +1 -1
- infrahub/core/initialization.py +2 -2
- infrahub/core/ipam/utilization.py +1 -1
- infrahub/core/manager.py +9 -84
- infrahub/core/migrations/graph/__init__.py +6 -0
- infrahub/core/migrations/graph/m040_profile_attrs_in_db.py +166 -0
- infrahub/core/migrations/graph/m041_create_hfid_display_label_in_db.py +97 -0
- infrahub/core/migrations/graph/m042_backfill_hfid_display_label_in_db.py +86 -0
- infrahub/core/migrations/schema/node_attribute_add.py +5 -2
- infrahub/core/migrations/shared.py +5 -6
- infrahub/core/node/__init__.py +165 -42
- infrahub/core/node/constraints/attribute_uniqueness.py +3 -1
- infrahub/core/node/create.py +67 -35
- infrahub/core/node/lock_utils.py +98 -0
- infrahub/core/node/node_property_attribute.py +230 -0
- infrahub/core/node/standard.py +1 -1
- infrahub/core/property.py +11 -0
- infrahub/core/protocols.py +8 -1
- infrahub/core/query/attribute.py +27 -15
- infrahub/core/query/node.py +61 -185
- infrahub/core/query/relationship.py +43 -26
- infrahub/core/query/subquery.py +0 -8
- infrahub/core/registry.py +2 -2
- infrahub/core/relationship/constraints/count.py +1 -1
- infrahub/core/relationship/model.py +60 -20
- infrahub/core/schema/attribute_schema.py +0 -2
- infrahub/core/schema/basenode_schema.py +42 -2
- infrahub/core/schema/definitions/core/__init__.py +2 -0
- infrahub/core/schema/definitions/core/generator.py +2 -0
- infrahub/core/schema/definitions/core/group.py +16 -2
- infrahub/core/schema/definitions/core/repository.py +7 -0
- infrahub/core/schema/definitions/internal.py +14 -1
- infrahub/core/schema/generated/base_node_schema.py +6 -1
- infrahub/core/schema/node_schema.py +5 -2
- infrahub/core/schema/relationship_schema.py +0 -1
- infrahub/core/schema/schema_branch.py +137 -2
- infrahub/core/schema/schema_branch_display.py +123 -0
- infrahub/core/schema/schema_branch_hfid.py +114 -0
- infrahub/core/validators/aggregated_checker.py +1 -1
- infrahub/core/validators/determiner.py +12 -1
- infrahub/core/validators/relationship/peer.py +1 -1
- infrahub/core/validators/tasks.py +1 -1
- infrahub/display_labels/__init__.py +0 -0
- infrahub/display_labels/gather.py +48 -0
- infrahub/display_labels/models.py +240 -0
- infrahub/display_labels/tasks.py +186 -0
- infrahub/display_labels/triggers.py +22 -0
- infrahub/events/group_action.py +1 -1
- infrahub/events/node_action.py +1 -1
- infrahub/generators/constants.py +7 -0
- infrahub/generators/models.py +38 -12
- infrahub/generators/tasks.py +34 -16
- infrahub/git/base.py +38 -1
- infrahub/git/integrator.py +22 -14
- infrahub/graphql/analyzer.py +1 -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/display_label.py +111 -0
- infrahub/graphql/mutations/generator.py +25 -7
- infrahub/graphql/mutations/hfid.py +118 -0
- 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/relationship.py +2 -2
- infrahub/graphql/mutations/repository.py +22 -83
- infrahub/graphql/mutations/resource_manager.py +2 -2
- infrahub/graphql/mutations/schema.py +5 -5
- infrahub/graphql/mutations/webhook.py +1 -1
- infrahub/graphql/queries/resource_manager.py +1 -1
- infrahub/graphql/registry.py +173 -0
- infrahub/graphql/resolvers/resolver.py +2 -0
- infrahub/graphql/schema.py +8 -1
- infrahub/groups/tasks.py +1 -1
- infrahub/hfid/__init__.py +0 -0
- infrahub/hfid/gather.py +48 -0
- infrahub/hfid/models.py +240 -0
- infrahub/hfid/tasks.py +185 -0
- infrahub/hfid/triggers.py +22 -0
- infrahub/lock.py +67 -30
- infrahub/locks/__init__.py +0 -0
- infrahub/locks/tasks.py +37 -0
- infrahub/middleware.py +26 -1
- 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/proposed_change/tasks.py +10 -1
- infrahub/repositories/__init__.py +0 -0
- infrahub/repositories/create_repository.py +113 -0
- infrahub/server.py +16 -3
- infrahub/services/__init__.py +8 -5
- infrahub/tasks/registry.py +6 -4
- infrahub/trigger/catalogue.py +4 -0
- infrahub/trigger/models.py +2 -0
- infrahub/trigger/tasks.py +3 -0
- infrahub/webhook/models.py +1 -1
- infrahub/workflows/catalogue.py +110 -3
- infrahub/workflows/initialization.py +16 -0
- infrahub/workflows/models.py +17 -2
- infrahub_sdk/branch.py +5 -8
- infrahub_sdk/checks.py +1 -1
- 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 +18 -12
- infrahub_sdk/ctl/config.py +8 -2
- infrahub_sdk/ctl/generator.py +6 -3
- infrahub_sdk/ctl/graphql.py +184 -0
- infrahub_sdk/ctl/repository.py +39 -1
- infrahub_sdk/ctl/schema.py +18 -3
- 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/generator.py +7 -1
- infrahub_sdk/graphql/__init__.py +12 -0
- infrahub_sdk/graphql/constants.py +1 -0
- infrahub_sdk/graphql/plugin.py +85 -0
- infrahub_sdk/graphql/query.py +77 -0
- infrahub_sdk/{graphql.py → graphql/renderers.py} +88 -75
- infrahub_sdk/graphql/utils.py +40 -0
- infrahub_sdk/node/attribute.py +2 -0
- infrahub_sdk/node/node.py +28 -20
- infrahub_sdk/playback.py +1 -2
- infrahub_sdk/protocols.py +54 -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/__init__.py +38 -0
- infrahub_sdk/schema/main.py +1 -0
- infrahub_sdk/schema/repository.py +8 -0
- infrahub_sdk/spec/object.py +120 -7
- infrahub_sdk/spec/range_expansion.py +118 -0
- infrahub_sdk/timestamp.py +18 -6
- infrahub_sdk/transforms.py +1 -1
- {infrahub_server-1.4.10.dist-info → infrahub_server-1.5.0b1.dist-info}/METADATA +9 -11
- {infrahub_server-1.4.10.dist-info → infrahub_server-1.5.0b1.dist-info}/RECORD +177 -134
- infrahub_testcontainers/container.py +1 -1
- infrahub_testcontainers/docker-compose-cluster.test.yml +1 -1
- infrahub_testcontainers/docker-compose.test.yml +1 -1
- 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.10.dist-info → infrahub_server-1.5.0b1.dist-info}/LICENSE.txt +0 -0
- {infrahub_server-1.4.10.dist-info → infrahub_server-1.5.0b1.dist-info}/WHEEL +0 -0
- {infrahub_server-1.4.10.dist-info → infrahub_server-1.5.0b1.dist-info}/entry_points.txt +0 -0
infrahub_sdk/protocols.py
CHANGED
|
@@ -131,6 +131,7 @@ class CoreGenericRepository(CoreNode):
|
|
|
131
131
|
queries: RelationshipManager
|
|
132
132
|
checks: RelationshipManager
|
|
133
133
|
generators: RelationshipManager
|
|
134
|
+
groups_objects: RelationshipManager
|
|
134
135
|
|
|
135
136
|
|
|
136
137
|
class CoreGroup(CoreNode):
|
|
@@ -233,6 +234,10 @@ class CoreWebhook(CoreNode):
|
|
|
233
234
|
validate_certificates: BooleanOptional
|
|
234
235
|
|
|
235
236
|
|
|
237
|
+
class CoreWeightedPoolResource(CoreNode):
|
|
238
|
+
allocation_weight: IntegerOptional
|
|
239
|
+
|
|
240
|
+
|
|
236
241
|
class LineageOwner(CoreNode):
|
|
237
242
|
pass
|
|
238
243
|
|
|
@@ -321,6 +326,7 @@ class CoreCheckDefinition(CoreTaskTarget):
|
|
|
321
326
|
|
|
322
327
|
|
|
323
328
|
class CoreCustomWebhook(CoreWebhook, CoreTaskTarget):
|
|
329
|
+
shared_key: StringOptional
|
|
324
330
|
transformation: RelatedNode
|
|
325
331
|
|
|
326
332
|
|
|
@@ -350,6 +356,10 @@ class CoreGeneratorAction(CoreAction):
|
|
|
350
356
|
generator: RelatedNode
|
|
351
357
|
|
|
352
358
|
|
|
359
|
+
class CoreGeneratorAwareGroup(CoreGroup):
|
|
360
|
+
pass
|
|
361
|
+
|
|
362
|
+
|
|
353
363
|
class CoreGeneratorCheck(CoreCheck):
|
|
354
364
|
instance: String
|
|
355
365
|
|
|
@@ -361,6 +371,8 @@ class CoreGeneratorDefinition(CoreTaskTarget):
|
|
|
361
371
|
file_path: String
|
|
362
372
|
class_name: String
|
|
363
373
|
convert_query_response: BooleanOptional
|
|
374
|
+
execute_in_proposed_change: BooleanOptional
|
|
375
|
+
execute_after_merge: BooleanOptional
|
|
364
376
|
query: RelatedNode
|
|
365
377
|
repository: RelatedNode
|
|
366
378
|
targets: RelatedNode
|
|
@@ -405,12 +417,12 @@ class CoreGraphQLQueryGroup(CoreGroup):
|
|
|
405
417
|
|
|
406
418
|
|
|
407
419
|
class CoreGroupAction(CoreAction):
|
|
408
|
-
|
|
420
|
+
member_action: Dropdown
|
|
409
421
|
group: RelatedNode
|
|
410
422
|
|
|
411
423
|
|
|
412
424
|
class CoreGroupTriggerRule(CoreTriggerRule):
|
|
413
|
-
|
|
425
|
+
member_update: Dropdown
|
|
414
426
|
group: RelatedNode
|
|
415
427
|
|
|
416
428
|
|
|
@@ -442,7 +454,7 @@ class CoreNodeTriggerAttributeMatch(CoreNodeTriggerMatch):
|
|
|
442
454
|
|
|
443
455
|
class CoreNodeTriggerRelationshipMatch(CoreNodeTriggerMatch):
|
|
444
456
|
relationship_name: String
|
|
445
|
-
|
|
457
|
+
modification_type: Dropdown
|
|
446
458
|
peer: StringOptional
|
|
447
459
|
|
|
448
460
|
|
|
@@ -457,6 +469,7 @@ class CoreNumberPool(CoreResourcePool, LineageSource):
|
|
|
457
469
|
node_attribute: String
|
|
458
470
|
start_range: Integer
|
|
459
471
|
end_range: Integer
|
|
472
|
+
pool_type: Enum
|
|
460
473
|
|
|
461
474
|
|
|
462
475
|
class CoreObjectPermission(CoreBasePermission):
|
|
@@ -481,7 +494,10 @@ class CoreProposedChange(CoreTaskTarget):
|
|
|
481
494
|
source_branch: String
|
|
482
495
|
destination_branch: String
|
|
483
496
|
state: Enum
|
|
497
|
+
is_draft: Boolean
|
|
498
|
+
total_comments: IntegerOptional
|
|
484
499
|
approved_by: RelationshipManager
|
|
500
|
+
rejected_by: RelationshipManager
|
|
485
501
|
reviewers: RelationshipManager
|
|
486
502
|
created_by: RelatedNode
|
|
487
503
|
comments: RelationshipManager
|
|
@@ -555,6 +571,14 @@ class InternalAccountToken(CoreNode):
|
|
|
555
571
|
account: RelatedNode
|
|
556
572
|
|
|
557
573
|
|
|
574
|
+
class InternalIPPrefixAvailable(BuiltinIPPrefix):
|
|
575
|
+
pass
|
|
576
|
+
|
|
577
|
+
|
|
578
|
+
class InternalIPRangeAvailable(BuiltinIPAddress):
|
|
579
|
+
last_address: IPHost
|
|
580
|
+
|
|
581
|
+
|
|
558
582
|
class InternalRefreshToken(CoreNode):
|
|
559
583
|
expiration: DateTime
|
|
560
584
|
account: RelatedNode
|
|
@@ -664,6 +688,7 @@ class CoreGenericRepositorySync(CoreNodeSync):
|
|
|
664
688
|
queries: RelationshipManagerSync
|
|
665
689
|
checks: RelationshipManagerSync
|
|
666
690
|
generators: RelationshipManagerSync
|
|
691
|
+
groups_objects: RelationshipManagerSync
|
|
667
692
|
|
|
668
693
|
|
|
669
694
|
class CoreGroupSync(CoreNodeSync):
|
|
@@ -766,6 +791,10 @@ class CoreWebhookSync(CoreNodeSync):
|
|
|
766
791
|
validate_certificates: BooleanOptional
|
|
767
792
|
|
|
768
793
|
|
|
794
|
+
class CoreWeightedPoolResourceSync(CoreNodeSync):
|
|
795
|
+
allocation_weight: IntegerOptional
|
|
796
|
+
|
|
797
|
+
|
|
769
798
|
class LineageOwnerSync(CoreNodeSync):
|
|
770
799
|
pass
|
|
771
800
|
|
|
@@ -854,6 +883,7 @@ class CoreCheckDefinitionSync(CoreTaskTargetSync):
|
|
|
854
883
|
|
|
855
884
|
|
|
856
885
|
class CoreCustomWebhookSync(CoreWebhookSync, CoreTaskTargetSync):
|
|
886
|
+
shared_key: StringOptional
|
|
857
887
|
transformation: RelatedNodeSync
|
|
858
888
|
|
|
859
889
|
|
|
@@ -883,6 +913,10 @@ class CoreGeneratorActionSync(CoreActionSync):
|
|
|
883
913
|
generator: RelatedNodeSync
|
|
884
914
|
|
|
885
915
|
|
|
916
|
+
class CoreGeneratorAwareGroupSync(CoreGroupSync):
|
|
917
|
+
pass
|
|
918
|
+
|
|
919
|
+
|
|
886
920
|
class CoreGeneratorCheckSync(CoreCheckSync):
|
|
887
921
|
instance: String
|
|
888
922
|
|
|
@@ -894,6 +928,8 @@ class CoreGeneratorDefinitionSync(CoreTaskTargetSync):
|
|
|
894
928
|
file_path: String
|
|
895
929
|
class_name: String
|
|
896
930
|
convert_query_response: BooleanOptional
|
|
931
|
+
execute_in_proposed_change: BooleanOptional
|
|
932
|
+
execute_after_merge: BooleanOptional
|
|
897
933
|
query: RelatedNodeSync
|
|
898
934
|
repository: RelatedNodeSync
|
|
899
935
|
targets: RelatedNodeSync
|
|
@@ -938,12 +974,12 @@ class CoreGraphQLQueryGroupSync(CoreGroupSync):
|
|
|
938
974
|
|
|
939
975
|
|
|
940
976
|
class CoreGroupActionSync(CoreActionSync):
|
|
941
|
-
|
|
977
|
+
member_action: Dropdown
|
|
942
978
|
group: RelatedNodeSync
|
|
943
979
|
|
|
944
980
|
|
|
945
981
|
class CoreGroupTriggerRuleSync(CoreTriggerRuleSync):
|
|
946
|
-
|
|
982
|
+
member_update: Dropdown
|
|
947
983
|
group: RelatedNodeSync
|
|
948
984
|
|
|
949
985
|
|
|
@@ -975,7 +1011,7 @@ class CoreNodeTriggerAttributeMatchSync(CoreNodeTriggerMatchSync):
|
|
|
975
1011
|
|
|
976
1012
|
class CoreNodeTriggerRelationshipMatchSync(CoreNodeTriggerMatchSync):
|
|
977
1013
|
relationship_name: String
|
|
978
|
-
|
|
1014
|
+
modification_type: Dropdown
|
|
979
1015
|
peer: StringOptional
|
|
980
1016
|
|
|
981
1017
|
|
|
@@ -990,6 +1026,7 @@ class CoreNumberPoolSync(CoreResourcePoolSync, LineageSourceSync):
|
|
|
990
1026
|
node_attribute: String
|
|
991
1027
|
start_range: Integer
|
|
992
1028
|
end_range: Integer
|
|
1029
|
+
pool_type: Enum
|
|
993
1030
|
|
|
994
1031
|
|
|
995
1032
|
class CoreObjectPermissionSync(CoreBasePermissionSync):
|
|
@@ -1014,7 +1051,10 @@ class CoreProposedChangeSync(CoreTaskTargetSync):
|
|
|
1014
1051
|
source_branch: String
|
|
1015
1052
|
destination_branch: String
|
|
1016
1053
|
state: Enum
|
|
1054
|
+
is_draft: Boolean
|
|
1055
|
+
total_comments: IntegerOptional
|
|
1017
1056
|
approved_by: RelationshipManagerSync
|
|
1057
|
+
rejected_by: RelationshipManagerSync
|
|
1018
1058
|
reviewers: RelationshipManagerSync
|
|
1019
1059
|
created_by: RelatedNodeSync
|
|
1020
1060
|
comments: RelationshipManagerSync
|
|
@@ -1088,6 +1128,14 @@ class InternalAccountTokenSync(CoreNodeSync):
|
|
|
1088
1128
|
account: RelatedNodeSync
|
|
1089
1129
|
|
|
1090
1130
|
|
|
1131
|
+
class InternalIPPrefixAvailableSync(BuiltinIPPrefixSync):
|
|
1132
|
+
pass
|
|
1133
|
+
|
|
1134
|
+
|
|
1135
|
+
class InternalIPRangeAvailableSync(BuiltinIPAddressSync):
|
|
1136
|
+
last_address: IPHost
|
|
1137
|
+
|
|
1138
|
+
|
|
1091
1139
|
class InternalRefreshTokenSync(CoreNodeSync):
|
|
1092
1140
|
expiration: DateTime
|
|
1093
1141
|
account: RelatedNodeSync
|
|
@@ -9,7 +9,7 @@ from pytest import exit as exit_test
|
|
|
9
9
|
from .. import InfrahubClientSync
|
|
10
10
|
from ..utils import is_valid_url
|
|
11
11
|
from .loader import InfrahubYamlFile
|
|
12
|
-
from .utils import load_repository_config
|
|
12
|
+
from .utils import find_repository_config_file, load_repository_config
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
def pytest_addoption(parser: Parser) -> None:
|
|
@@ -18,9 +18,9 @@ def pytest_addoption(parser: Parser) -> None:
|
|
|
18
18
|
"--infrahub-repo-config",
|
|
19
19
|
action="store",
|
|
20
20
|
dest="infrahub_repo_config",
|
|
21
|
-
default=
|
|
21
|
+
default=None,
|
|
22
22
|
metavar="INFRAHUB_REPO_CONFIG_FILE",
|
|
23
|
-
help="Infrahub configuration file for the repository (
|
|
23
|
+
help="Infrahub configuration file for the repository (.infrahub.yml or .infrahub.yaml)",
|
|
24
24
|
)
|
|
25
25
|
group.addoption(
|
|
26
26
|
"--infrahub-address",
|
|
@@ -63,7 +63,10 @@ def pytest_addoption(parser: Parser) -> None:
|
|
|
63
63
|
|
|
64
64
|
|
|
65
65
|
def pytest_sessionstart(session: Session) -> None:
|
|
66
|
-
|
|
66
|
+
if session.config.option.infrahub_repo_config:
|
|
67
|
+
session.infrahub_config_path = Path(session.config.option.infrahub_repo_config) # type: ignore[attr-defined]
|
|
68
|
+
else:
|
|
69
|
+
session.infrahub_config_path = find_repository_config_file() # type: ignore[attr-defined]
|
|
67
70
|
|
|
68
71
|
if session.infrahub_config_path.is_file(): # type: ignore[attr-defined]
|
|
69
72
|
session.infrahub_repo_config = load_repository_config(repo_config_file=session.infrahub_config_path) # type: ignore[attr-defined]
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
from pathlib import Path
|
|
2
4
|
|
|
3
5
|
import yaml
|
|
@@ -6,7 +8,45 @@ from ..schema.repository import InfrahubRepositoryConfig
|
|
|
6
8
|
from .exceptions import FileNotValidError
|
|
7
9
|
|
|
8
10
|
|
|
11
|
+
def find_repository_config_file(base_path: Path | None = None) -> Path:
|
|
12
|
+
"""Find the repository config file, checking for both .yml and .yaml extensions.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
base_path: Base directory to search in. If None, uses current directory.
|
|
16
|
+
|
|
17
|
+
Returns:
|
|
18
|
+
Path to the config file.
|
|
19
|
+
|
|
20
|
+
Raises:
|
|
21
|
+
FileNotFoundError: If neither .infrahub.yml nor .infrahub.yaml exists.
|
|
22
|
+
"""
|
|
23
|
+
if base_path is None:
|
|
24
|
+
base_path = Path()
|
|
25
|
+
|
|
26
|
+
yml_path = base_path / ".infrahub.yml"
|
|
27
|
+
yaml_path = base_path / ".infrahub.yaml"
|
|
28
|
+
|
|
29
|
+
# Prefer .yml if both exist
|
|
30
|
+
if yml_path.exists():
|
|
31
|
+
return yml_path
|
|
32
|
+
if yaml_path.exists():
|
|
33
|
+
return yaml_path
|
|
34
|
+
# For backward compatibility, return .yml path for error messages
|
|
35
|
+
return yml_path
|
|
36
|
+
|
|
37
|
+
|
|
9
38
|
def load_repository_config(repo_config_file: Path) -> InfrahubRepositoryConfig:
|
|
39
|
+
# If the file doesn't exist, try to find it with alternate extension
|
|
40
|
+
if not repo_config_file.exists():
|
|
41
|
+
if repo_config_file.name == ".infrahub.yml":
|
|
42
|
+
alt_path = repo_config_file.parent / ".infrahub.yaml"
|
|
43
|
+
if alt_path.exists():
|
|
44
|
+
repo_config_file = alt_path
|
|
45
|
+
elif repo_config_file.name == ".infrahub.yaml":
|
|
46
|
+
alt_path = repo_config_file.parent / ".infrahub.yml"
|
|
47
|
+
if alt_path.exists():
|
|
48
|
+
repo_config_file = alt_path
|
|
49
|
+
|
|
10
50
|
if not repo_config_file.is_file():
|
|
11
51
|
raise FileNotFoundError(repo_config_file)
|
|
12
52
|
|
infrahub_sdk/repository.py
CHANGED
infrahub_sdk/schema/__init__.py
CHANGED
|
@@ -474,6 +474,25 @@ class InfrahubSchema(InfrahubSchemaBase):
|
|
|
474
474
|
|
|
475
475
|
return branch_schema.nodes
|
|
476
476
|
|
|
477
|
+
async def get_graphql_schema(self, branch: str | None = None) -> str:
|
|
478
|
+
"""Get the GraphQL schema as a string.
|
|
479
|
+
|
|
480
|
+
Args:
|
|
481
|
+
branch: The branch to get the schema for. Defaults to default_branch.
|
|
482
|
+
|
|
483
|
+
Returns:
|
|
484
|
+
The GraphQL schema as a string.
|
|
485
|
+
"""
|
|
486
|
+
branch = branch or self.client.default_branch
|
|
487
|
+
url = f"{self.client.address}/schema.graphql?branch={branch}"
|
|
488
|
+
|
|
489
|
+
response = await self.client._get(url=url)
|
|
490
|
+
|
|
491
|
+
if response.status_code != 200:
|
|
492
|
+
raise ValueError(f"Failed to fetch GraphQL schema: HTTP {response.status_code} - {response.text}")
|
|
493
|
+
|
|
494
|
+
return response.text
|
|
495
|
+
|
|
477
496
|
async def _fetch(self, branch: str, namespaces: list[str] | None = None) -> BranchSchema:
|
|
478
497
|
url_parts = [("branch", branch)]
|
|
479
498
|
if namespaces:
|
|
@@ -697,6 +716,25 @@ class InfrahubSchemaSync(InfrahubSchemaBase):
|
|
|
697
716
|
|
|
698
717
|
return branch_schema.nodes
|
|
699
718
|
|
|
719
|
+
def get_graphql_schema(self, branch: str | None = None) -> str:
|
|
720
|
+
"""Get the GraphQL schema as a string.
|
|
721
|
+
|
|
722
|
+
Args:
|
|
723
|
+
branch: The branch to get the schema for. Defaults to default_branch.
|
|
724
|
+
|
|
725
|
+
Returns:
|
|
726
|
+
The GraphQL schema as a string.
|
|
727
|
+
"""
|
|
728
|
+
branch = branch or self.client.default_branch
|
|
729
|
+
url = f"{self.client.address}/schema.graphql?branch={branch}"
|
|
730
|
+
|
|
731
|
+
response = self.client._get(url=url)
|
|
732
|
+
|
|
733
|
+
if response.status_code != 200:
|
|
734
|
+
raise ValueError(f"Failed to fetch GraphQL schema: HTTP {response.status_code} - {response.text}")
|
|
735
|
+
|
|
736
|
+
return response.text
|
|
737
|
+
|
|
700
738
|
def _fetch(self, branch: str, namespaces: list[str] | None = None) -> BranchSchema:
|
|
701
739
|
url_parts = [("branch", branch)]
|
|
702
740
|
if namespaces:
|
infrahub_sdk/schema/main.py
CHANGED
|
@@ -267,6 +267,7 @@ class BaseSchema(BaseModel):
|
|
|
267
267
|
description: str | None = None
|
|
268
268
|
include_in_menu: bool | None = None
|
|
269
269
|
menu_placement: str | None = None
|
|
270
|
+
display_label: str | None = None
|
|
270
271
|
display_labels: list[str] | None = None
|
|
271
272
|
human_friendly_id: list[str] | None = None
|
|
272
273
|
icon: str | None = None
|
|
@@ -96,6 +96,14 @@ class InfrahubGeneratorDefinitionConfig(InfrahubRepositoryConfigElement):
|
|
|
96
96
|
default=False,
|
|
97
97
|
description="Decide if the generator should convert the result of the GraphQL query to SDK InfrahubNode objects.",
|
|
98
98
|
)
|
|
99
|
+
execute_in_proposed_change: bool = Field(
|
|
100
|
+
default=True,
|
|
101
|
+
description="Decide if the generator should execute in a proposed change.",
|
|
102
|
+
)
|
|
103
|
+
execute_after_merge: bool = Field(
|
|
104
|
+
default=True,
|
|
105
|
+
description="Decide if the generator should execute after a merge.",
|
|
106
|
+
)
|
|
99
107
|
|
|
100
108
|
def load_class(self, import_root: str | None = None, relative_path: str | None = None) -> type[InfrahubGenerator]:
|
|
101
109
|
module = import_module(module_path=self.file_path, import_root=import_root, relative_path=relative_path)
|
infrahub_sdk/spec/object.py
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import copy
|
|
4
|
+
import re
|
|
5
|
+
from abc import ABC, abstractmethod
|
|
3
6
|
from enum import Enum
|
|
4
|
-
from typing import TYPE_CHECKING, Any
|
|
7
|
+
from typing import TYPE_CHECKING, Any, ClassVar
|
|
5
8
|
|
|
6
9
|
from pydantic import BaseModel, Field
|
|
7
10
|
|
|
8
11
|
from ..exceptions import ObjectValidationError, ValidationError
|
|
9
12
|
from ..schema import GenericSchemaAPI, RelationshipKind, RelationshipSchema
|
|
10
13
|
from ..yaml import InfrahubFile, InfrahubFileKind
|
|
14
|
+
from .range_expansion import MATCH_PATTERN, range_expansion
|
|
11
15
|
|
|
12
16
|
if TYPE_CHECKING:
|
|
13
17
|
from ..client import InfrahubClient
|
|
@@ -42,6 +46,11 @@ class RelationshipDataFormat(str, Enum):
|
|
|
42
46
|
MANY_REF = "many_ref_list"
|
|
43
47
|
|
|
44
48
|
|
|
49
|
+
class ObjectStrategy(str, Enum):
|
|
50
|
+
NORMAL = "normal"
|
|
51
|
+
RANGE_EXPAND = "range_expand"
|
|
52
|
+
|
|
53
|
+
|
|
45
54
|
class RelationshipInfo(BaseModel):
|
|
46
55
|
name: str
|
|
47
56
|
rel_schema: RelationshipSchema
|
|
@@ -164,14 +173,100 @@ async def get_relationship_info(
|
|
|
164
173
|
return info
|
|
165
174
|
|
|
166
175
|
|
|
176
|
+
def expand_data_with_ranges(data: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
|
177
|
+
"""Expand any item in data with range pattern in any value. Supports multiple fields, requires equal expansion length."""
|
|
178
|
+
range_pattern = re.compile(MATCH_PATTERN)
|
|
179
|
+
expanded = []
|
|
180
|
+
for item in data:
|
|
181
|
+
# Find all fields to expand
|
|
182
|
+
expand_fields = {}
|
|
183
|
+
for key, value in item.items():
|
|
184
|
+
if isinstance(value, str) and range_pattern.search(value):
|
|
185
|
+
try:
|
|
186
|
+
expand_fields[key] = range_expansion(value)
|
|
187
|
+
except Exception:
|
|
188
|
+
# If expansion fails, treat as no expansion
|
|
189
|
+
expand_fields[key] = [value]
|
|
190
|
+
if not expand_fields:
|
|
191
|
+
expanded.append(item)
|
|
192
|
+
continue
|
|
193
|
+
# Check all expanded lists have the same length
|
|
194
|
+
lengths = [len(v) for v in expand_fields.values()]
|
|
195
|
+
if len(set(lengths)) > 1:
|
|
196
|
+
raise ValidationError(f"Range expansion mismatch: fields expanded to different lengths: {lengths}")
|
|
197
|
+
n = lengths[0]
|
|
198
|
+
# Zip expanded values and produce new items
|
|
199
|
+
for i in range(n):
|
|
200
|
+
new_item = copy.deepcopy(item)
|
|
201
|
+
for key, values in expand_fields.items():
|
|
202
|
+
new_item[key] = values[i]
|
|
203
|
+
expanded.append(new_item)
|
|
204
|
+
return expanded
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
class DataProcessor(ABC):
|
|
208
|
+
"""Abstract base class for data processing strategies"""
|
|
209
|
+
|
|
210
|
+
@abstractmethod
|
|
211
|
+
def process_data(self, data: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
|
212
|
+
"""Process the data according to the strategy"""
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
class SingleDataProcessor(DataProcessor):
|
|
216
|
+
"""Process data without any expansion"""
|
|
217
|
+
|
|
218
|
+
def process_data(self, data: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
|
219
|
+
return data
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
class RangeExpandDataProcessor(DataProcessor):
|
|
223
|
+
"""Process data with range expansion"""
|
|
224
|
+
|
|
225
|
+
def process_data(self, data: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
|
226
|
+
return expand_data_with_ranges(data)
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
class DataProcessorFactory:
|
|
230
|
+
"""Factory to create appropriate data processor based on strategy"""
|
|
231
|
+
|
|
232
|
+
_processors: ClassVar[dict[ObjectStrategy, type[DataProcessor]]] = {
|
|
233
|
+
ObjectStrategy.NORMAL: SingleDataProcessor,
|
|
234
|
+
ObjectStrategy.RANGE_EXPAND: RangeExpandDataProcessor,
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
@classmethod
|
|
238
|
+
def get_processor(cls, strategy: ObjectStrategy) -> DataProcessor:
|
|
239
|
+
processor_class = cls._processors.get(strategy)
|
|
240
|
+
if not processor_class:
|
|
241
|
+
raise ValueError(
|
|
242
|
+
f"Unknown strategy: {strategy} - no processor found. Valid strategies are: {list(cls._processors.keys())}"
|
|
243
|
+
)
|
|
244
|
+
return processor_class()
|
|
245
|
+
|
|
246
|
+
@classmethod
|
|
247
|
+
def register_processor(cls, strategy: ObjectStrategy, processor_class: type[DataProcessor]) -> None:
|
|
248
|
+
"""Register a new processor for a strategy - useful for future extensions"""
|
|
249
|
+
cls._processors[strategy] = processor_class
|
|
250
|
+
|
|
251
|
+
|
|
167
252
|
class InfrahubObjectFileData(BaseModel):
|
|
168
253
|
kind: str
|
|
254
|
+
strategy: ObjectStrategy = ObjectStrategy.NORMAL
|
|
169
255
|
data: list[dict[str, Any]] = Field(default_factory=list)
|
|
170
256
|
|
|
257
|
+
def _get_processed_data(self, data: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
|
258
|
+
"""Get data processed according to the strategy"""
|
|
259
|
+
processor = DataProcessorFactory.get_processor(self.strategy)
|
|
260
|
+
return processor.process_data(data)
|
|
261
|
+
|
|
171
262
|
async def validate_format(self, client: InfrahubClient, branch: str | None = None) -> list[ObjectValidationError]:
|
|
172
263
|
errors: list[ObjectValidationError] = []
|
|
173
264
|
schema = await client.schema.get(kind=self.kind, branch=branch)
|
|
174
|
-
|
|
265
|
+
|
|
266
|
+
processed_data = self._get_processed_data(data=self.data)
|
|
267
|
+
self.data = processed_data
|
|
268
|
+
|
|
269
|
+
for idx, item in enumerate(processed_data):
|
|
175
270
|
errors.extend(
|
|
176
271
|
await self.validate_object(
|
|
177
272
|
client=client,
|
|
@@ -180,13 +275,16 @@ class InfrahubObjectFileData(BaseModel):
|
|
|
180
275
|
data=item,
|
|
181
276
|
branch=branch,
|
|
182
277
|
default_schema_kind=self.kind,
|
|
278
|
+
strategy=self.strategy, # Pass strategy down
|
|
183
279
|
)
|
|
184
280
|
)
|
|
185
281
|
return errors
|
|
186
282
|
|
|
187
283
|
async def process(self, client: InfrahubClient, branch: str | None = None) -> None:
|
|
188
284
|
schema = await client.schema.get(kind=self.kind, branch=branch)
|
|
189
|
-
|
|
285
|
+
processed_data = self._get_processed_data(data=self.data)
|
|
286
|
+
|
|
287
|
+
for idx, item in enumerate(processed_data):
|
|
190
288
|
await self.create_node(
|
|
191
289
|
client=client,
|
|
192
290
|
schema=schema,
|
|
@@ -206,6 +304,7 @@ class InfrahubObjectFileData(BaseModel):
|
|
|
206
304
|
context: dict | None = None,
|
|
207
305
|
branch: str | None = None,
|
|
208
306
|
default_schema_kind: str | None = None,
|
|
307
|
+
strategy: ObjectStrategy = ObjectStrategy.NORMAL,
|
|
209
308
|
) -> list[ObjectValidationError]:
|
|
210
309
|
errors: list[ObjectValidationError] = []
|
|
211
310
|
context = context.copy() if context else {}
|
|
@@ -255,6 +354,7 @@ class InfrahubObjectFileData(BaseModel):
|
|
|
255
354
|
context=context,
|
|
256
355
|
branch=branch,
|
|
257
356
|
default_schema_kind=default_schema_kind,
|
|
357
|
+
strategy=strategy,
|
|
258
358
|
)
|
|
259
359
|
)
|
|
260
360
|
|
|
@@ -270,6 +370,7 @@ class InfrahubObjectFileData(BaseModel):
|
|
|
270
370
|
context: dict | None = None,
|
|
271
371
|
branch: str | None = None,
|
|
272
372
|
default_schema_kind: str | None = None,
|
|
373
|
+
strategy: ObjectStrategy = ObjectStrategy.NORMAL,
|
|
273
374
|
) -> list[ObjectValidationError]:
|
|
274
375
|
context = context.copy() if context else {}
|
|
275
376
|
errors: list[ObjectValidationError] = []
|
|
@@ -311,7 +412,11 @@ class InfrahubObjectFileData(BaseModel):
|
|
|
311
412
|
rel_info.find_matching_relationship(peer_schema=peer_schema)
|
|
312
413
|
context.update(rel_info.get_context(value="placeholder"))
|
|
313
414
|
|
|
314
|
-
|
|
415
|
+
# Use strategy-aware data processing
|
|
416
|
+
processor = DataProcessorFactory.get_processor(strategy)
|
|
417
|
+
expanded_data = processor.process_data(data["data"])
|
|
418
|
+
|
|
419
|
+
for idx, peer_data in enumerate(expanded_data):
|
|
315
420
|
context["list_index"] = idx
|
|
316
421
|
errors.extend(
|
|
317
422
|
await cls.validate_object(
|
|
@@ -322,6 +427,7 @@ class InfrahubObjectFileData(BaseModel):
|
|
|
322
427
|
context=context,
|
|
323
428
|
branch=branch,
|
|
324
429
|
default_schema_kind=default_schema_kind,
|
|
430
|
+
strategy=strategy,
|
|
325
431
|
)
|
|
326
432
|
)
|
|
327
433
|
return errors
|
|
@@ -525,7 +631,8 @@ class InfrahubObjectFileData(BaseModel):
|
|
|
525
631
|
rel_info.find_matching_relationship(peer_schema=peer_schema)
|
|
526
632
|
context.update(rel_info.get_context(value=parent_node.id))
|
|
527
633
|
|
|
528
|
-
|
|
634
|
+
expanded_data = expand_data_with_ranges(data=data["data"])
|
|
635
|
+
for idx, peer_data in enumerate(expanded_data):
|
|
529
636
|
context["list_index"] = idx
|
|
530
637
|
if isinstance(peer_data, dict):
|
|
531
638
|
node = await cls.create_node(
|
|
@@ -594,14 +701,20 @@ class ObjectFile(InfrahubFile):
|
|
|
594
701
|
@property
|
|
595
702
|
def spec(self) -> InfrahubObjectFileData:
|
|
596
703
|
if not self._spec:
|
|
597
|
-
|
|
704
|
+
try:
|
|
705
|
+
self._spec = InfrahubObjectFileData(**self.data.spec)
|
|
706
|
+
except Exception as exc:
|
|
707
|
+
raise ValidationError(identifier=str(self.location), message=str(exc))
|
|
598
708
|
return self._spec
|
|
599
709
|
|
|
600
710
|
def validate_content(self) -> None:
|
|
601
711
|
super().validate_content()
|
|
602
712
|
if self.kind != InfrahubFileKind.OBJECT:
|
|
603
713
|
raise ValueError("File is not an Infrahub Object file")
|
|
604
|
-
|
|
714
|
+
try:
|
|
715
|
+
self._spec = InfrahubObjectFileData(**self.data.spec)
|
|
716
|
+
except Exception as exc:
|
|
717
|
+
raise ValidationError(identifier=str(self.location), message=str(exc))
|
|
605
718
|
|
|
606
719
|
async def validate_format(self, client: InfrahubClient, branch: str | None = None) -> None:
|
|
607
720
|
self.validate_content()
|