infrahub-server 1.4.10__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 +1 -1
- 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 +23 -2
- infrahub/core/node/create.py +67 -35
- 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.10.dist-info → infrahub_server-1.5.0b0.dist-info}/METADATA +6 -9
- {infrahub_server-1.4.10.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.10.dist-info → infrahub_server-1.5.0b0.dist-info}/LICENSE.txt +0 -0
- {infrahub_server-1.4.10.dist-info → infrahub_server-1.5.0b0.dist-info}/WHEEL +0 -0
- {infrahub_server-1.4.10.dist-info → infrahub_server-1.5.0b0.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, model_validator
|
|
6
|
+
|
|
7
|
+
CONVERT_OBJECT_MUTATION = """
|
|
8
|
+
mutation($node_id: String!, $target_kind: String!, $fields_mapping: GenericScalar!) {
|
|
9
|
+
ConvertObjectType(data: {
|
|
10
|
+
node_id: $node_id,
|
|
11
|
+
target_kind: $target_kind,
|
|
12
|
+
fields_mapping: $fields_mapping
|
|
13
|
+
}) {
|
|
14
|
+
ok
|
|
15
|
+
node
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ConversionFieldValue(BaseModel): # Only one of these fields can be not None
|
|
22
|
+
"""
|
|
23
|
+
Holds the new value of the destination field during an object conversion.
|
|
24
|
+
Use `attribute_value` to specify the new raw value of an attribute.
|
|
25
|
+
Use `peer_id` to specify new peer of a cardinality one relationship.
|
|
26
|
+
Use `peers_ids` to specify new peers of a cardinality many relationship.
|
|
27
|
+
Only one of `attribute_value`, `peer_id` and `peers_ids` can be specified.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
attribute_value: Any | None = None
|
|
31
|
+
peer_id: str | None = None
|
|
32
|
+
peers_ids: list[str] | None = None
|
|
33
|
+
|
|
34
|
+
@model_validator(mode="after")
|
|
35
|
+
def check_only_one_field(self) -> ConversionFieldValue:
|
|
36
|
+
fields = [self.attribute_value, self.peer_id, self.peers_ids]
|
|
37
|
+
set_fields = [f for f in fields if f is not None]
|
|
38
|
+
if len(set_fields) != 1:
|
|
39
|
+
raise ValueError("Exactly one of `attribute_value`, `peer_id`, or `peers_ids` must be set")
|
|
40
|
+
return self
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class ConversionFieldInput(BaseModel):
|
|
44
|
+
"""
|
|
45
|
+
Indicates how to fill in the value of the destination field during an object conversion.
|
|
46
|
+
Use `source_field` to reuse the value of the corresponding field of the object being converted.
|
|
47
|
+
Use `data` to specify the new value for the field.
|
|
48
|
+
Use `use_default_value` to set the destination field to its schema default.
|
|
49
|
+
Only one of `source_field`, `data`, or `use_default_value` can be specified.
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
source_field: str | None = None
|
|
53
|
+
data: ConversionFieldValue | None = None
|
|
54
|
+
use_default_value: bool = False
|
|
55
|
+
|
|
56
|
+
@model_validator(mode="after")
|
|
57
|
+
def check_only_one_field(self) -> ConversionFieldInput:
|
|
58
|
+
fields_set = [self.source_field is not None, self.data is not None, self.use_default_value is True]
|
|
59
|
+
if sum(fields_set) != 1:
|
|
60
|
+
raise ValueError("Exactly one of `source_field`, `data` or `use_default_value` must be set")
|
|
61
|
+
return self
|
infrahub_sdk/ctl/check.py
CHANGED
|
@@ -11,10 +11,9 @@ import typer
|
|
|
11
11
|
from rich.console import Console
|
|
12
12
|
from rich.logging import RichHandler
|
|
13
13
|
|
|
14
|
-
from ..ctl import config
|
|
15
14
|
from ..ctl.client import initialize_client
|
|
16
15
|
from ..ctl.exceptions import QueryNotFoundError
|
|
17
|
-
from ..ctl.repository import get_repository_config
|
|
16
|
+
from ..ctl.repository import find_repository_config_file, get_repository_config
|
|
18
17
|
from ..ctl.utils import catch_exception, execute_graphql_query
|
|
19
18
|
from ..exceptions import ModuleImportError
|
|
20
19
|
|
|
@@ -59,7 +58,7 @@ def run(
|
|
|
59
58
|
FORMAT = "%(message)s"
|
|
60
59
|
logging.basicConfig(level=log_level, format=FORMAT, datefmt="[%X]", handlers=[RichHandler()])
|
|
61
60
|
|
|
62
|
-
repository_config = get_repository_config(
|
|
61
|
+
repository_config = get_repository_config(find_repository_config_file())
|
|
63
62
|
|
|
64
63
|
if list_available:
|
|
65
64
|
list_checks(repository_config=repository_config)
|
infrahub_sdk/ctl/cli_commands.py
CHANGED
|
@@ -20,7 +20,6 @@ from rich.table import Table
|
|
|
20
20
|
|
|
21
21
|
from .. import __version__ as sdk_version
|
|
22
22
|
from ..async_typer import AsyncTyper
|
|
23
|
-
from ..ctl import config
|
|
24
23
|
from ..ctl.branch import app as branch_app
|
|
25
24
|
from ..ctl.check import run as run_check
|
|
26
25
|
from ..ctl.client import initialize_client, initialize_client_sync
|
|
@@ -30,7 +29,7 @@ from ..ctl.menu import app as menu_app
|
|
|
30
29
|
from ..ctl.object import app as object_app
|
|
31
30
|
from ..ctl.render import list_jinja2_transforms, print_template_errors
|
|
32
31
|
from ..ctl.repository import app as repository_app
|
|
33
|
-
from ..ctl.repository import get_repository_config
|
|
32
|
+
from ..ctl.repository import find_repository_config_file, get_repository_config
|
|
34
33
|
from ..ctl.schema import app as schema_app
|
|
35
34
|
from ..ctl.transform import list_transforms
|
|
36
35
|
from ..ctl.utils import (
|
|
@@ -46,7 +45,7 @@ from ..protocols_generator.generator import CodeGenerator
|
|
|
46
45
|
from ..schema import MainSchemaTypesAll, SchemaRoot
|
|
47
46
|
from ..template import Jinja2Template
|
|
48
47
|
from ..template.exceptions import JinjaTemplateError
|
|
49
|
-
from ..utils import
|
|
48
|
+
from ..utils import write_to_file
|
|
50
49
|
from ..yaml import SchemaFile
|
|
51
50
|
from .exporter import dump
|
|
52
51
|
from .importer import load
|
|
@@ -208,7 +207,6 @@ async def _run_transform(
|
|
|
208
207
|
debug: Prints debug info to the command line
|
|
209
208
|
repository_config: Repository config object. This is used to load the graphql query from the repository.
|
|
210
209
|
"""
|
|
211
|
-
branch = get_branch(branch)
|
|
212
210
|
|
|
213
211
|
try:
|
|
214
212
|
response = execute_graphql_query(
|
|
@@ -260,7 +258,7 @@ async def render(
|
|
|
260
258
|
"""Render a local Jinja2 Transform for debugging purpose."""
|
|
261
259
|
|
|
262
260
|
variables_dict = parse_cli_vars(variables)
|
|
263
|
-
repository_config = get_repository_config(
|
|
261
|
+
repository_config = get_repository_config(find_repository_config_file())
|
|
264
262
|
|
|
265
263
|
if list_available or not transform_name:
|
|
266
264
|
list_jinja2_transforms(config=repository_config)
|
|
@@ -270,7 +268,7 @@ async def render(
|
|
|
270
268
|
try:
|
|
271
269
|
transform_config = repository_config.get_jinja2_transform(name=transform_name)
|
|
272
270
|
except KeyError as exc:
|
|
273
|
-
console.print(f'[red]Unable to find "{transform_name}" in
|
|
271
|
+
console.print(f'[red]Unable to find "{transform_name}" in repository config file')
|
|
274
272
|
list_jinja2_transforms(config=repository_config)
|
|
275
273
|
raise typer.Exit(1) from exc
|
|
276
274
|
|
|
@@ -310,7 +308,7 @@ def transform(
|
|
|
310
308
|
"""Render a local transform (TransformPython) for debugging purpose."""
|
|
311
309
|
|
|
312
310
|
variables_dict = parse_cli_vars(variables)
|
|
313
|
-
repository_config = get_repository_config(
|
|
311
|
+
repository_config = get_repository_config(find_repository_config_file())
|
|
314
312
|
|
|
315
313
|
if list_available or not transform_name:
|
|
316
314
|
list_transforms(config=repository_config)
|
|
@@ -409,7 +407,6 @@ def info( # noqa: PLR0915
|
|
|
409
407
|
_: str = CONFIG_PARAM,
|
|
410
408
|
) -> None:
|
|
411
409
|
"""Display the status of the Python SDK."""
|
|
412
|
-
|
|
413
410
|
info: dict[str, Any] = {
|
|
414
411
|
"error": None,
|
|
415
412
|
"status": ":x:",
|
|
@@ -417,12 +414,17 @@ def info( # noqa: PLR0915
|
|
|
417
414
|
"user_info": {},
|
|
418
415
|
"groups": {},
|
|
419
416
|
}
|
|
417
|
+
client = initialize_client_sync()
|
|
418
|
+
fetch_user_details = bool(client.config.username) or bool(client.config.api_token)
|
|
419
|
+
|
|
420
420
|
try:
|
|
421
|
-
client = initialize_client_sync()
|
|
422
421
|
info["infrahub_version"] = client.get_version()
|
|
423
|
-
|
|
422
|
+
|
|
423
|
+
if fetch_user_details:
|
|
424
|
+
info["user_info"] = client.get_user()
|
|
425
|
+
info["groups"] = client.get_user_permissions()
|
|
426
|
+
|
|
424
427
|
info["status"] = ":white_heavy_check_mark:"
|
|
425
|
-
info["groups"] = client.get_user_permissions()
|
|
426
428
|
except Exception as e:
|
|
427
429
|
info["error"] = f"{e!s} ({e.__class__.__name__})"
|
|
428
430
|
|
|
@@ -469,7 +471,7 @@ def info( # noqa: PLR0915
|
|
|
469
471
|
pretty_model = Pretty(client.config.model_dump(), expand_all=True)
|
|
470
472
|
layout["client_info"].update(Panel(pretty_model, title="Client Info"))
|
|
471
473
|
|
|
472
|
-
# Infrahub information
|
|
474
|
+
# Infrahub information panel
|
|
473
475
|
infrahub_info = Table(show_header=False, box=None)
|
|
474
476
|
if info["user_info"]:
|
|
475
477
|
infrahub_info.add_row("User:", info["user_info"]["AccountProfile"]["display_label"])
|
|
@@ -487,6 +489,8 @@ def info( # noqa: PLR0915
|
|
|
487
489
|
infrahub_info.add_row("Groups:", "")
|
|
488
490
|
for group, roles in groups.items():
|
|
489
491
|
infrahub_info.add_row("", group, ", ".join(roles))
|
|
492
|
+
else:
|
|
493
|
+
infrahub_info.add_row("User:", "anonymous")
|
|
490
494
|
|
|
491
495
|
layout["infrahub_info"].update(Panel(infrahub_info, title="Infrahub Info"))
|
|
492
496
|
|
infrahub_sdk/ctl/config.py
CHANGED
|
@@ -2,16 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import sys
|
|
5
6
|
from pathlib import Path
|
|
6
7
|
|
|
7
|
-
import toml
|
|
8
8
|
import typer
|
|
9
9
|
from pydantic import Field, ValidationError, field_validator
|
|
10
10
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
11
11
|
|
|
12
|
+
if sys.version_info >= (3, 11):
|
|
13
|
+
import tomllib
|
|
14
|
+
else:
|
|
15
|
+
import tomli as tomllib
|
|
16
|
+
|
|
12
17
|
DEFAULT_CONFIG_FILE = "infrahubctl.toml"
|
|
13
18
|
ENVVAR_CONFIG_FILE = "INFRAHUBCTL_CONFIG"
|
|
14
19
|
INFRAHUB_REPO_CONFIG_FILE = ".infrahub.yml"
|
|
20
|
+
INFRAHUB_REPO_CONFIG_FILE_ALT = ".infrahub.yaml"
|
|
15
21
|
|
|
16
22
|
|
|
17
23
|
class Settings(BaseSettings):
|
|
@@ -59,7 +65,7 @@ class ConfiguredSettings:
|
|
|
59
65
|
|
|
60
66
|
if config_file.is_file():
|
|
61
67
|
config_string = config_file.read_text(encoding="utf-8")
|
|
62
|
-
config_tmp =
|
|
68
|
+
config_tmp = tomllib.loads(config_string)
|
|
63
69
|
|
|
64
70
|
self._settings = Settings(**config_tmp)
|
|
65
71
|
return
|
infrahub_sdk/ctl/generator.py
CHANGED
|
@@ -6,9 +6,8 @@ from typing import TYPE_CHECKING, Optional
|
|
|
6
6
|
import typer
|
|
7
7
|
from rich.console import Console
|
|
8
8
|
|
|
9
|
-
from ..ctl import config
|
|
10
9
|
from ..ctl.client import initialize_client
|
|
11
|
-
from ..ctl.repository import get_repository_config
|
|
10
|
+
from ..ctl.repository import find_repository_config_file, get_repository_config
|
|
12
11
|
from ..ctl.utils import execute_graphql_query, init_logging, parse_cli_vars
|
|
13
12
|
from ..exceptions import ModuleImportError
|
|
14
13
|
from ..node import InfrahubNode
|
|
@@ -26,7 +25,7 @@ async def run(
|
|
|
26
25
|
variables: Optional[list[str]] = None,
|
|
27
26
|
) -> None:
|
|
28
27
|
init_logging(debug=debug)
|
|
29
|
-
repository_config = get_repository_config(
|
|
28
|
+
repository_config = get_repository_config(find_repository_config_file())
|
|
30
29
|
|
|
31
30
|
if list_available or not generator_name:
|
|
32
31
|
list_generators(repository_config=repository_config)
|
infrahub_sdk/ctl/repository.py
CHANGED
|
@@ -24,11 +24,49 @@ app = AsyncTyper()
|
|
|
24
24
|
console = Console()
|
|
25
25
|
|
|
26
26
|
|
|
27
|
+
def find_repository_config_file(base_path: Path | None = None) -> Path:
|
|
28
|
+
"""Find the repository config file, checking for both .yml and .yaml extensions.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
base_path: Base directory to search in. If None, uses current directory.
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
Path to the config file.
|
|
35
|
+
|
|
36
|
+
Raises:
|
|
37
|
+
FileNotFoundError: If neither .infrahub.yml nor .infrahub.yaml exists.
|
|
38
|
+
"""
|
|
39
|
+
if base_path is None:
|
|
40
|
+
base_path = Path()
|
|
41
|
+
|
|
42
|
+
yml_path = base_path / ".infrahub.yml"
|
|
43
|
+
yaml_path = base_path / ".infrahub.yaml"
|
|
44
|
+
|
|
45
|
+
# Prefer .yml if both exist
|
|
46
|
+
if yml_path.exists():
|
|
47
|
+
return yml_path
|
|
48
|
+
if yaml_path.exists():
|
|
49
|
+
return yaml_path
|
|
50
|
+
# For backward compatibility, return .yml path for error messages
|
|
51
|
+
return yml_path
|
|
52
|
+
|
|
53
|
+
|
|
27
54
|
def get_repository_config(repo_config_file: Path) -> InfrahubRepositoryConfig:
|
|
55
|
+
# If the file doesn't exist, try to find it with alternate extension
|
|
56
|
+
if not repo_config_file.exists():
|
|
57
|
+
if repo_config_file.name == ".infrahub.yml":
|
|
58
|
+
alt_path = repo_config_file.parent / ".infrahub.yaml"
|
|
59
|
+
if alt_path.exists():
|
|
60
|
+
repo_config_file = alt_path
|
|
61
|
+
elif repo_config_file.name == ".infrahub.yaml":
|
|
62
|
+
alt_path = repo_config_file.parent / ".infrahub.yml"
|
|
63
|
+
if alt_path.exists():
|
|
64
|
+
repo_config_file = alt_path
|
|
65
|
+
|
|
28
66
|
try:
|
|
29
67
|
config_file_data = load_repository_config_file(repo_config_file)
|
|
30
68
|
except FileNotFoundError as exc:
|
|
31
|
-
console.print(f"[red]File not found {exc}")
|
|
69
|
+
console.print(f"[red]File not found {exc} (also checked for .infrahub.yml and .infrahub.yaml)")
|
|
32
70
|
raise typer.Exit(1) from exc
|
|
33
71
|
except FileNotValidError as exc:
|
|
34
72
|
console.print(f"[red]{exc.message}")
|
infrahub_sdk/ctl/schema.py
CHANGED
|
@@ -77,7 +77,18 @@ def display_schema_load_errors(response: dict[str, Any], schemas_data: list[Sche
|
|
|
77
77
|
|
|
78
78
|
elif len(loc_path) > 6:
|
|
79
79
|
loc_type = loc_path[5]
|
|
80
|
-
|
|
80
|
+
error_data = node[loc_type]
|
|
81
|
+
attribute = loc_path[6]
|
|
82
|
+
|
|
83
|
+
if isinstance(attribute, str):
|
|
84
|
+
input_label = None
|
|
85
|
+
for data in error_data:
|
|
86
|
+
if data.get(attribute) is not None:
|
|
87
|
+
input_label = data.get("name", None)
|
|
88
|
+
break
|
|
89
|
+
else:
|
|
90
|
+
input_label = error_data[attribute].get("name", None)
|
|
91
|
+
|
|
81
92
|
input_str = error.get("input", None)
|
|
82
93
|
error_message = f"{loc_type[:-1].title()}: {input_label} ({input_str}) | {error['msg']} ({error['type']})"
|
|
83
94
|
console.print(f" Node: {node.get('namespace', None)}{node.get('name', None)} | {error_message}")
|
infrahub_sdk/ctl/utils.py
CHANGED
|
@@ -118,6 +118,10 @@ def execute_graphql_query(
|
|
|
118
118
|
query_str = query_object.load_query()
|
|
119
119
|
|
|
120
120
|
client = initialize_client_sync()
|
|
121
|
+
|
|
122
|
+
if not branch:
|
|
123
|
+
branch = client.config.default_infrahub_branch
|
|
124
|
+
|
|
121
125
|
response = client.execute_graphql(
|
|
122
126
|
query=query_str,
|
|
123
127
|
branch_name=branch,
|
infrahub_sdk/ctl/validate.py
CHANGED
|
@@ -14,7 +14,7 @@ from ..ctl.client import initialize_client, initialize_client_sync
|
|
|
14
14
|
from ..ctl.exceptions import QueryNotFoundError
|
|
15
15
|
from ..ctl.utils import catch_exception, find_graphql_query, parse_cli_vars
|
|
16
16
|
from ..exceptions import GraphQLError
|
|
17
|
-
from ..utils import
|
|
17
|
+
from ..utils import write_to_file
|
|
18
18
|
from ..yaml import SchemaFile
|
|
19
19
|
from .parameters import CONFIG_PARAM
|
|
20
20
|
from .utils import load_yamlfile_from_disk_and_exit
|
|
@@ -68,8 +68,6 @@ def validate_graphql(
|
|
|
68
68
|
) -> None:
|
|
69
69
|
"""Validate the format of a GraphQL Query stored locally by executing it on a remote GraphQL endpoint"""
|
|
70
70
|
|
|
71
|
-
branch = get_branch(branch)
|
|
72
|
-
|
|
73
71
|
try:
|
|
74
72
|
query_str = find_graphql_query(query)
|
|
75
73
|
except QueryNotFoundError:
|
|
@@ -81,6 +79,10 @@ def validate_graphql(
|
|
|
81
79
|
variables_dict = parse_cli_vars(variables)
|
|
82
80
|
|
|
83
81
|
client = initialize_client_sync()
|
|
82
|
+
|
|
83
|
+
if not branch:
|
|
84
|
+
branch = client.config.default_infrahub_branch
|
|
85
|
+
|
|
84
86
|
try:
|
|
85
87
|
response = client.execute_graphql(
|
|
86
88
|
query=query_str,
|
infrahub_sdk/diff.py
CHANGED
|
@@ -37,8 +37,8 @@ class NodeDiffPeer(TypedDict):
|
|
|
37
37
|
|
|
38
38
|
def get_diff_summary_query() -> str:
|
|
39
39
|
return """
|
|
40
|
-
query GetDiffTree($branch_name: String
|
|
41
|
-
DiffTree(branch: $branch_name) {
|
|
40
|
+
query GetDiffTree($branch_name: String!, $name: String, $from_time: DateTime, $to_time: DateTime) {
|
|
41
|
+
DiffTree(branch: $branch_name, name: $name, from_time: $from_time, to_time: $to_time) {
|
|
42
42
|
nodes {
|
|
43
43
|
uuid
|
|
44
44
|
kind
|
|
@@ -117,12 +117,11 @@ def diff_tree_node_to_node_diff(node_dict: dict[str, Any], branch_name: str) ->
|
|
|
117
117
|
)
|
|
118
118
|
relationship_diff["peers"] = peer_diffs
|
|
119
119
|
element_diffs.append(relationship_diff)
|
|
120
|
-
|
|
120
|
+
return NodeDiff(
|
|
121
121
|
branch=branch_name,
|
|
122
122
|
kind=str(node_dict.get("kind")),
|
|
123
123
|
id=str(node_dict.get("uuid")),
|
|
124
|
-
action=str(node_dict.get("
|
|
124
|
+
action=str(node_dict.get("status")),
|
|
125
125
|
display_label=str(node_dict.get("label")),
|
|
126
126
|
elements=element_diffs,
|
|
127
127
|
)
|
|
128
|
-
return node_diff
|
infrahub_sdk/exceptions.py
CHANGED
|
@@ -17,6 +17,8 @@ class JsonDecodeError(Error):
|
|
|
17
17
|
self.url = url
|
|
18
18
|
if not self.message and self.url:
|
|
19
19
|
self.message = f"Unable to decode response as JSON data from {self.url}"
|
|
20
|
+
if self.content:
|
|
21
|
+
self.message += f". Server response: {self.content}"
|
|
20
22
|
super().__init__(self.message)
|
|
21
23
|
|
|
22
24
|
|
infrahub_sdk/graphql.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import json
|
|
3
4
|
from enum import Enum
|
|
4
5
|
from typing import Any
|
|
5
6
|
|
|
@@ -8,7 +9,9 @@ from pydantic import BaseModel
|
|
|
8
9
|
VARIABLE_TYPE_MAPPING = ((str, "String!"), (int, "Int!"), (float, "Float!"), (bool, "Boolean!"))
|
|
9
10
|
|
|
10
11
|
|
|
11
|
-
def convert_to_graphql_as_string(value:
|
|
12
|
+
def convert_to_graphql_as_string(value: Any, convert_enum: bool = False) -> str: # noqa: PLR0911
|
|
13
|
+
if value is None:
|
|
14
|
+
return "null"
|
|
12
15
|
if isinstance(value, str) and value.startswith("$"):
|
|
13
16
|
return value
|
|
14
17
|
if isinstance(value, Enum):
|
|
@@ -16,7 +19,9 @@ def convert_to_graphql_as_string(value: str | bool | list | BaseModel | Enum | A
|
|
|
16
19
|
return convert_to_graphql_as_string(value=value.value, convert_enum=True)
|
|
17
20
|
return value.name
|
|
18
21
|
if isinstance(value, str):
|
|
19
|
-
|
|
22
|
+
# Use json.dumps() to properly escape the string according to JSON rules,
|
|
23
|
+
# which are compatible with GraphQL string escaping
|
|
24
|
+
return json.dumps(value)
|
|
20
25
|
if isinstance(value, bool):
|
|
21
26
|
return repr(value).lower()
|
|
22
27
|
if isinstance(value, list):
|
infrahub_sdk/node/attribute.py
CHANGED
infrahub_sdk/node/node.py
CHANGED
|
@@ -234,15 +234,10 @@ class InfrahubNodeBase:
|
|
|
234
234
|
|
|
235
235
|
rel: RelatedNodeBase | RelationshipManagerBase = getattr(self, item_name)
|
|
236
236
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
# and self._schema.get_relationship(item_name).cardinality == "one"
|
|
242
|
-
# ):
|
|
243
|
-
# data[item_name] = None
|
|
244
|
-
# continue
|
|
245
|
-
# el
|
|
237
|
+
if rel_schema.cardinality == RelationshipCardinality.ONE and rel_schema.optional and not rel.initialized:
|
|
238
|
+
data[item_name] = None
|
|
239
|
+
continue
|
|
240
|
+
|
|
246
241
|
if rel is None or not rel.initialized:
|
|
247
242
|
continue
|
|
248
243
|
|
|
@@ -315,7 +310,16 @@ class InfrahubNodeBase:
|
|
|
315
310
|
variables.pop(variable_key)
|
|
316
311
|
|
|
317
312
|
# TODO: I do not feel _great_ about this
|
|
318
|
-
|
|
313
|
+
# -> I don't even know who you are (but this is not great indeed) -- gmazoyer (quoting Thanos)
|
|
314
|
+
original_data_item = original_data.get(item)
|
|
315
|
+
original_data_item_is_none = original_data_item is None
|
|
316
|
+
if isinstance(original_data_item, dict):
|
|
317
|
+
if "node" in original_data_item:
|
|
318
|
+
original_data_item_is_none = original_data_item["node"] is None
|
|
319
|
+
elif "id" not in original_data_item:
|
|
320
|
+
original_data_item_is_none = True
|
|
321
|
+
|
|
322
|
+
if item in data and (data_item in ({}, []) or (data_item is None and original_data_item_is_none)):
|
|
319
323
|
data.pop(item)
|
|
320
324
|
|
|
321
325
|
def _strip_unmodified(self, data: dict, variables: dict) -> tuple[dict, dict]:
|
|
@@ -324,7 +328,9 @@ class InfrahubNodeBase:
|
|
|
324
328
|
relationship_property = getattr(self, relationship)
|
|
325
329
|
if not relationship_property or relationship not in data:
|
|
326
330
|
continue
|
|
327
|
-
if not relationship_property.initialized
|
|
331
|
+
if not relationship_property.initialized and (
|
|
332
|
+
not isinstance(relationship_property, RelatedNodeBase) or not relationship_property.schema.optional
|
|
333
|
+
):
|
|
328
334
|
data.pop(relationship)
|
|
329
335
|
elif isinstance(relationship_property, RelationshipManagerBase) and not relationship_property.has_update:
|
|
330
336
|
data.pop(relationship)
|
|
@@ -573,8 +579,7 @@ class InfrahubNode(InfrahubNodeBase):
|
|
|
573
579
|
self._validate_artifact_support(ARTIFACT_GENERATE_FEATURE_NOT_SUPPORTED_MESSAGE)
|
|
574
580
|
|
|
575
581
|
artifact = await self._client.get(kind="CoreArtifact", name__value=name, object__ids=[self.id])
|
|
576
|
-
|
|
577
|
-
return content
|
|
582
|
+
return await self._client.object_store.get(identifier=artifact._get_attribute(name="storage_id").value)
|
|
578
583
|
|
|
579
584
|
async def delete(self, timeout: int | None = None, request_context: RequestContext | None = None) -> None:
|
|
580
585
|
input_data = {"data": {"id": self.id}}
|
|
@@ -742,12 +747,11 @@ class InfrahubNode(InfrahubNodeBase):
|
|
|
742
747
|
continue
|
|
743
748
|
|
|
744
749
|
peer_data: dict[str, Any] = {}
|
|
745
|
-
|
|
750
|
+
should_fetch_relationship = prefetch_relationships or (include is not None and rel_name in include)
|
|
751
|
+
if rel_schema and should_fetch_relationship:
|
|
746
752
|
peer_schema = await self._client.schema.get(kind=rel_schema.peer, branch=self._branch)
|
|
747
753
|
peer_node = InfrahubNode(client=self._client, schema=peer_schema, branch=self._branch)
|
|
748
754
|
peer_data = await peer_node.generate_query_data_node(
|
|
749
|
-
include=include,
|
|
750
|
-
exclude=exclude,
|
|
751
755
|
property=property,
|
|
752
756
|
)
|
|
753
757
|
|
|
@@ -886,7 +890,11 @@ class InfrahubNode(InfrahubNodeBase):
|
|
|
886
890
|
await self._process_mutation_result(mutation_name=mutation_name, response=response, timeout=timeout)
|
|
887
891
|
|
|
888
892
|
async def _process_relationships(
|
|
889
|
-
self,
|
|
893
|
+
self,
|
|
894
|
+
node_data: dict[str, Any],
|
|
895
|
+
branch: str,
|
|
896
|
+
related_nodes: list[InfrahubNode],
|
|
897
|
+
timeout: int | None = None,
|
|
890
898
|
) -> None:
|
|
891
899
|
"""Processes the Relationships of a InfrahubNode and add Related Nodes to a list.
|
|
892
900
|
|
|
@@ -1199,8 +1207,7 @@ class InfrahubNodeSync(InfrahubNodeBase):
|
|
|
1199
1207
|
def artifact_fetch(self, name: str) -> str | dict[str, Any]:
|
|
1200
1208
|
self._validate_artifact_support(ARTIFACT_FETCH_FEATURE_NOT_SUPPORTED_MESSAGE)
|
|
1201
1209
|
artifact = self._client.get(kind="CoreArtifact", name__value=name, object__ids=[self.id])
|
|
1202
|
-
|
|
1203
|
-
return content
|
|
1210
|
+
return self._client.object_store.get(identifier=artifact._get_attribute(name="storage_id").value)
|
|
1204
1211
|
|
|
1205
1212
|
def delete(self, timeout: int | None = None, request_context: RequestContext | None = None) -> None:
|
|
1206
1213
|
input_data = {"data": {"id": self.id}}
|
|
@@ -1363,7 +1370,8 @@ class InfrahubNodeSync(InfrahubNodeBase):
|
|
|
1363
1370
|
continue
|
|
1364
1371
|
|
|
1365
1372
|
peer_data: dict[str, Any] = {}
|
|
1366
|
-
|
|
1373
|
+
should_fetch_relationship = prefetch_relationships or (include is not None and rel_name in include)
|
|
1374
|
+
if rel_schema and should_fetch_relationship:
|
|
1367
1375
|
peer_schema = self._client.schema.get(kind=rel_schema.peer, branch=self._branch)
|
|
1368
1376
|
peer_node = InfrahubNodeSync(client=self._client, schema=peer_schema, branch=self._branch)
|
|
1369
1377
|
peer_data = peer_node.generate_query_data_node(include=include, exclude=exclude, property=property)
|
infrahub_sdk/playback.py
CHANGED
|
@@ -56,5 +56,4 @@ class JSONPlayback(BaseSettings):
|
|
|
56
56
|
with Path(f"{self.directory}/{filename}.json").open(encoding="utf-8") as fobj:
|
|
57
57
|
data = ujson.load(fobj)
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
return response
|
|
59
|
+
return httpx.Response(status_code=data["status_code"], content=data["response_content"], request=request)
|