infrahub-server 1.1.6__py3-none-any.whl → 1.1.8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- infrahub/core/attribute.py +4 -1
- infrahub/core/branch/tasks.py +7 -4
- infrahub/core/diff/combiner.py +11 -7
- infrahub/core/diff/coordinator.py +49 -70
- infrahub/core/diff/data_check_synchronizer.py +86 -7
- infrahub/core/diff/enricher/aggregated.py +3 -3
- infrahub/core/diff/enricher/cardinality_one.py +6 -6
- infrahub/core/diff/enricher/hierarchy.py +17 -4
- infrahub/core/diff/enricher/labels.py +18 -3
- infrahub/core/diff/enricher/path_identifier.py +7 -8
- infrahub/core/diff/merger/merger.py +5 -3
- infrahub/core/diff/model/path.py +66 -25
- infrahub/core/diff/parent_node_adder.py +78 -0
- infrahub/core/diff/payload_builder.py +13 -2
- infrahub/core/diff/query/all_conflicts.py +5 -2
- infrahub/core/diff/query/diff_get.py +2 -1
- infrahub/core/diff/query/field_specifiers.py +2 -0
- infrahub/core/diff/query/field_summary.py +2 -1
- infrahub/core/diff/query/filters.py +12 -1
- infrahub/core/diff/query/has_conflicts_query.py +5 -2
- infrahub/core/diff/query/{drop_tracking_id.py → merge_tracking_id.py} +3 -3
- infrahub/core/diff/query/roots_metadata.py +8 -1
- infrahub/core/diff/query/save.py +230 -139
- infrahub/core/diff/query/summary_counts_enricher.py +267 -0
- infrahub/core/diff/query/time_range_query.py +2 -1
- infrahub/core/diff/query_parser.py +49 -24
- infrahub/core/diff/repository/deserializer.py +31 -27
- infrahub/core/diff/repository/repository.py +215 -41
- infrahub/core/diff/tasks.py +4 -4
- infrahub/core/graph/__init__.py +1 -1
- infrahub/core/graph/index.py +3 -0
- infrahub/core/migrations/graph/__init__.py +4 -0
- infrahub/core/migrations/graph/m019_restore_rels_to_time.py +256 -0
- infrahub/core/migrations/graph/m020_duplicate_edges.py +160 -0
- infrahub/core/migrations/query/node_duplicate.py +38 -18
- infrahub/core/migrations/schema/node_remove.py +26 -12
- infrahub/core/migrations/shared.py +10 -8
- infrahub/core/node/__init__.py +19 -9
- infrahub/core/node/constraints/grouped_uniqueness.py +25 -5
- infrahub/core/node/ipam.py +6 -1
- infrahub/core/node/permissions.py +4 -0
- infrahub/core/query/attribute.py +2 -0
- infrahub/core/query/diff.py +41 -3
- infrahub/core/query/node.py +74 -21
- infrahub/core/query/relationship.py +107 -17
- infrahub/core/query/resource_manager.py +5 -1
- infrahub/core/relationship/model.py +8 -12
- infrahub/core/schema/definitions/core.py +1 -0
- infrahub/core/utils.py +1 -0
- infrahub/core/validators/uniqueness/query.py +20 -17
- infrahub/database/__init__.py +14 -0
- infrahub/dependencies/builder/constraint/grouped/node_runner.py +0 -2
- infrahub/dependencies/builder/diff/coordinator.py +0 -2
- infrahub/dependencies/builder/diff/deserializer.py +3 -1
- infrahub/dependencies/builder/diff/enricher/hierarchy.py +3 -1
- infrahub/dependencies/builder/diff/parent_node_adder.py +8 -0
- infrahub/graphql/mutations/computed_attribute.py +3 -1
- infrahub/graphql/mutations/diff.py +41 -10
- infrahub/graphql/mutations/main.py +11 -6
- infrahub/graphql/mutations/relationship.py +29 -1
- infrahub/graphql/mutations/resource_manager.py +3 -3
- infrahub/graphql/mutations/tasks.py +6 -3
- infrahub/graphql/queries/resource_manager.py +7 -3
- infrahub/permissions/__init__.py +2 -1
- infrahub/permissions/types.py +26 -0
- infrahub_sdk/client.py +10 -2
- infrahub_sdk/config.py +3 -0
- infrahub_sdk/ctl/check.py +3 -3
- infrahub_sdk/ctl/cli_commands.py +16 -11
- infrahub_sdk/ctl/exceptions.py +0 -6
- infrahub_sdk/ctl/exporter.py +1 -1
- infrahub_sdk/ctl/generator.py +5 -5
- infrahub_sdk/ctl/importer.py +3 -2
- infrahub_sdk/ctl/menu.py +1 -1
- infrahub_sdk/ctl/object.py +1 -1
- infrahub_sdk/ctl/repository.py +23 -15
- infrahub_sdk/ctl/schema.py +2 -2
- infrahub_sdk/ctl/utils.py +4 -3
- infrahub_sdk/ctl/validate.py +2 -1
- infrahub_sdk/exceptions.py +12 -0
- infrahub_sdk/generator.py +3 -0
- infrahub_sdk/node.py +7 -4
- infrahub_sdk/testing/schemas/animal.py +9 -0
- infrahub_sdk/utils.py +11 -1
- infrahub_sdk/yaml.py +2 -3
- {infrahub_server-1.1.6.dist-info → infrahub_server-1.1.8.dist-info}/METADATA +41 -7
- {infrahub_server-1.1.6.dist-info → infrahub_server-1.1.8.dist-info}/RECORD +94 -91
- infrahub_testcontainers/container.py +12 -3
- infrahub_testcontainers/docker-compose.test.yml +22 -3
- infrahub_testcontainers/haproxy.cfg +43 -0
- infrahub_testcontainers/helpers.py +85 -1
- infrahub/core/diff/enricher/summary_counts.py +0 -105
- infrahub/dependencies/builder/diff/enricher/summary_counts.py +0 -8
- infrahub_sdk/ctl/_file.py +0 -13
- {infrahub_server-1.1.6.dist-info → infrahub_server-1.1.8.dist-info}/LICENSE.txt +0 -0
- {infrahub_server-1.1.6.dist-info → infrahub_server-1.1.8.dist-info}/WHEEL +0 -0
- {infrahub_server-1.1.6.dist-info → infrahub_server-1.1.8.dist-info}/entry_points.txt +0 -0
infrahub_sdk/client.py
CHANGED
|
@@ -3,6 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import copy
|
|
5
5
|
import logging
|
|
6
|
+
import time
|
|
6
7
|
from collections.abc import Coroutine, MutableMapping
|
|
7
8
|
from functools import wraps
|
|
8
9
|
from time import sleep
|
|
@@ -38,6 +39,7 @@ from .exceptions import (
|
|
|
38
39
|
NodeNotFoundError,
|
|
39
40
|
ServerNotReachableError,
|
|
40
41
|
ServerNotResponsiveError,
|
|
42
|
+
URLNotFoundError,
|
|
41
43
|
)
|
|
42
44
|
from .graphql import Mutation, Query
|
|
43
45
|
from .node import (
|
|
@@ -878,7 +880,8 @@ class InfrahubClient(BaseClient):
|
|
|
878
880
|
|
|
879
881
|
retry = True
|
|
880
882
|
resp = None
|
|
881
|
-
|
|
883
|
+
start_time = time.time()
|
|
884
|
+
while retry and time.time() - start_time < self.config.max_retry_duration:
|
|
882
885
|
retry = self.retry_on_failure
|
|
883
886
|
try:
|
|
884
887
|
resp = await self._post(url=url, payload=payload, headers=headers, timeout=timeout)
|
|
@@ -902,6 +905,8 @@ class InfrahubClient(BaseClient):
|
|
|
902
905
|
errors = response.get("errors", [])
|
|
903
906
|
messages = [error.get("message") for error in errors]
|
|
904
907
|
raise AuthenticationError(" | ".join(messages)) from exc
|
|
908
|
+
if exc.response.status_code == 404:
|
|
909
|
+
raise URLNotFoundError(url=url)
|
|
905
910
|
|
|
906
911
|
if not resp:
|
|
907
912
|
raise Error("Unexpected situation, resp hasn't been initialized.")
|
|
@@ -1613,7 +1618,8 @@ class InfrahubClientSync(BaseClient):
|
|
|
1613
1618
|
|
|
1614
1619
|
retry = True
|
|
1615
1620
|
resp = None
|
|
1616
|
-
|
|
1621
|
+
start_time = time.time()
|
|
1622
|
+
while retry and time.time() - start_time < self.config.max_retry_duration:
|
|
1617
1623
|
retry = self.retry_on_failure
|
|
1618
1624
|
try:
|
|
1619
1625
|
resp = self._post(url=url, payload=payload, headers=headers, timeout=timeout)
|
|
@@ -1637,6 +1643,8 @@ class InfrahubClientSync(BaseClient):
|
|
|
1637
1643
|
errors = response.get("errors", [])
|
|
1638
1644
|
messages = [error.get("message") for error in errors]
|
|
1639
1645
|
raise AuthenticationError(" | ".join(messages)) from exc
|
|
1646
|
+
if exc.response.status_code == 404:
|
|
1647
|
+
raise URLNotFoundError(url=url)
|
|
1640
1648
|
|
|
1641
1649
|
if not resp:
|
|
1642
1650
|
raise Error("Unexpected situation, resp hasn't been initialized.")
|
infrahub_sdk/config.py
CHANGED
|
@@ -56,6 +56,9 @@ class ConfigBase(BaseSettings):
|
|
|
56
56
|
pagination_size: int = Field(default=50, description="Page size for queries to the server")
|
|
57
57
|
retry_delay: int = Field(default=5, description="Number of seconds to wait until attempting a retry.")
|
|
58
58
|
retry_on_failure: bool = Field(default=False, description="Retry operation in case of failure")
|
|
59
|
+
max_retry_duration: int = Field(
|
|
60
|
+
default=300, description="Maximum duration until we stop attempting to retry if enabled."
|
|
61
|
+
)
|
|
59
62
|
schema_converge_timeout: int = Field(
|
|
60
63
|
default=60, description="Number of seconds to wait for schema to have converged"
|
|
61
64
|
)
|
infrahub_sdk/ctl/check.py
CHANGED
|
@@ -5,7 +5,7 @@ import sys
|
|
|
5
5
|
from asyncio import run as aiorun
|
|
6
6
|
from dataclasses import dataclass
|
|
7
7
|
from pathlib import Path
|
|
8
|
-
from typing import TYPE_CHECKING
|
|
8
|
+
from typing import TYPE_CHECKING, Optional
|
|
9
9
|
|
|
10
10
|
import typer
|
|
11
11
|
from rich.console import Console
|
|
@@ -50,8 +50,8 @@ def run(
|
|
|
50
50
|
format_json: bool,
|
|
51
51
|
list_available: bool,
|
|
52
52
|
variables: dict[str, str],
|
|
53
|
-
name: str
|
|
54
|
-
branch: str
|
|
53
|
+
name: Optional[str] = None,
|
|
54
|
+
branch: Optional[str] = None,
|
|
55
55
|
) -> None:
|
|
56
56
|
"""Locate and execute all checks under the defined path."""
|
|
57
57
|
|
infrahub_sdk/ctl/cli_commands.py
CHANGED
|
@@ -7,7 +7,7 @@ import logging
|
|
|
7
7
|
import platform
|
|
8
8
|
import sys
|
|
9
9
|
from pathlib import Path
|
|
10
|
-
from typing import TYPE_CHECKING, Any, Callable
|
|
10
|
+
from typing import TYPE_CHECKING, Any, Callable, Optional
|
|
11
11
|
|
|
12
12
|
import jinja2
|
|
13
13
|
import typer
|
|
@@ -74,13 +74,13 @@ console = Console()
|
|
|
74
74
|
@catch_exception(console=console)
|
|
75
75
|
def check(
|
|
76
76
|
check_name: str = typer.Argument(default="", help="Name of the Python check"),
|
|
77
|
-
branch: str
|
|
77
|
+
branch: Optional[str] = None,
|
|
78
78
|
path: str = typer.Option(".", help="Root directory"),
|
|
79
79
|
debug: bool = False,
|
|
80
80
|
format_json: bool = False,
|
|
81
81
|
_: str = CONFIG_PARAM,
|
|
82
82
|
list_available: bool = typer.Option(False, "--list", help="Show available Python checks"),
|
|
83
|
-
variables: list[str]
|
|
83
|
+
variables: Optional[list[str]] = typer.Argument(
|
|
84
84
|
None, help="Variables to pass along with the query. Format key=value key=value."
|
|
85
85
|
),
|
|
86
86
|
) -> None:
|
|
@@ -102,12 +102,12 @@ def check(
|
|
|
102
102
|
@catch_exception(console=console)
|
|
103
103
|
async def generator(
|
|
104
104
|
generator_name: str = typer.Argument(default="", help="Name of the Generator"),
|
|
105
|
-
branch: str
|
|
105
|
+
branch: Optional[str] = None,
|
|
106
106
|
path: str = typer.Option(".", help="Root directory"),
|
|
107
107
|
debug: bool = False,
|
|
108
108
|
_: str = CONFIG_PARAM,
|
|
109
109
|
list_available: bool = typer.Option(False, "--list", help="Show available Generators"),
|
|
110
|
-
variables: list[str]
|
|
110
|
+
variables: Optional[list[str]] = typer.Argument(
|
|
111
111
|
None, help="Variables to pass along with the query. Format key=value key=value."
|
|
112
112
|
),
|
|
113
113
|
) -> None:
|
|
@@ -129,14 +129,14 @@ async def run(
|
|
|
129
129
|
method: str = "run",
|
|
130
130
|
debug: bool = False,
|
|
131
131
|
_: str = CONFIG_PARAM,
|
|
132
|
-
branch: str = typer.Option(
|
|
133
|
-
concurrent: int
|
|
132
|
+
branch: str = typer.Option(None, help="Branch on which to run the script."),
|
|
133
|
+
concurrent: Optional[int] = typer.Option(
|
|
134
134
|
None,
|
|
135
135
|
help="Maximum number of requests to execute at the same time.",
|
|
136
136
|
envvar="INFRAHUB_MAX_CONCURRENT_EXECUTION",
|
|
137
137
|
),
|
|
138
138
|
timeout: int = typer.Option(60, help="Timeout in sec", envvar="INFRAHUB_TIMEOUT"),
|
|
139
|
-
variables: list[str]
|
|
139
|
+
variables: Optional[list[str]] = typer.Argument(
|
|
140
140
|
None, help="Variables to pass along with the query. Format key=value key=value."
|
|
141
141
|
),
|
|
142
142
|
) -> None:
|
|
@@ -259,7 +259,7 @@ def _run_transform(
|
|
|
259
259
|
@catch_exception(console=console)
|
|
260
260
|
def render(
|
|
261
261
|
transform_name: str = typer.Argument(default="", help="Name of the Python transformation", show_default=False),
|
|
262
|
-
variables: list[str]
|
|
262
|
+
variables: Optional[list[str]] = typer.Argument(
|
|
263
263
|
None, help="Variables to pass along with the query. Format key=value key=value."
|
|
264
264
|
),
|
|
265
265
|
branch: str = typer.Option(None, help="Branch on which to render the transform."),
|
|
@@ -309,7 +309,7 @@ def render(
|
|
|
309
309
|
@catch_exception(console=console)
|
|
310
310
|
def transform(
|
|
311
311
|
transform_name: str = typer.Argument(default="", help="Name of the Python transformation", show_default=False),
|
|
312
|
-
variables: list[str]
|
|
312
|
+
variables: Optional[list[str]] = typer.Argument(
|
|
313
313
|
None, help="Variables to pass along with the query. Format key=value key=value."
|
|
314
314
|
),
|
|
315
315
|
branch: str = typer.Option(None, help="Branch on which to run the transformation"),
|
|
@@ -352,7 +352,11 @@ def transform(
|
|
|
352
352
|
# Run Transform
|
|
353
353
|
result = asyncio.run(transform.run(data=data))
|
|
354
354
|
|
|
355
|
-
|
|
355
|
+
if isinstance(result, str):
|
|
356
|
+
json_string = result
|
|
357
|
+
else:
|
|
358
|
+
json_string = ujson.dumps(result, indent=2, sort_keys=True)
|
|
359
|
+
|
|
356
360
|
if out:
|
|
357
361
|
write_to_file(Path(out), json_string)
|
|
358
362
|
else:
|
|
@@ -383,6 +387,7 @@ def protocols(
|
|
|
383
387
|
|
|
384
388
|
else:
|
|
385
389
|
client = initialize_client_sync()
|
|
390
|
+
branch = branch or client.default_branch
|
|
386
391
|
schema.update(client.schema.fetch(branch=branch))
|
|
387
392
|
|
|
388
393
|
code_generator = CodeGenerator(schema=schema)
|
infrahub_sdk/ctl/exceptions.py
CHANGED
|
@@ -6,9 +6,3 @@ class QueryNotFoundError(Error):
|
|
|
6
6
|
def __init__(self, name: str, message: str = ""):
|
|
7
7
|
self.message = message or f"The requested query '{name}' was not found."
|
|
8
8
|
super().__init__(self.message)
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class FileNotValidError(Error):
|
|
12
|
-
def __init__(self, name: str, message: str = ""):
|
|
13
|
-
self.message = message or f"Cannot parse '{name}' content."
|
|
14
|
-
super().__init__(self.message)
|
infrahub_sdk/ctl/exporter.py
CHANGED
|
@@ -22,7 +22,7 @@ def dump(
|
|
|
22
22
|
directory: Path = typer.Option(directory_name_with_timestamp, help="Directory path to store export"),
|
|
23
23
|
quiet: bool = typer.Option(False, help="No console output"),
|
|
24
24
|
_: str = CONFIG_PARAM,
|
|
25
|
-
branch: str = typer.Option(
|
|
25
|
+
branch: str = typer.Option(None, help="Branch from which to export"),
|
|
26
26
|
concurrent: int = typer.Option(
|
|
27
27
|
4,
|
|
28
28
|
help="Maximum number of requests to execute at the same time.",
|
infrahub_sdk/ctl/generator.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from pathlib import Path
|
|
4
|
-
from typing import TYPE_CHECKING
|
|
4
|
+
from typing import TYPE_CHECKING, Optional
|
|
5
5
|
|
|
6
6
|
import typer
|
|
7
7
|
from rich.console import Console
|
|
@@ -9,7 +9,7 @@ from rich.console import Console
|
|
|
9
9
|
from ..ctl import config
|
|
10
10
|
from ..ctl.client import initialize_client
|
|
11
11
|
from ..ctl.repository import get_repository_config
|
|
12
|
-
from ..ctl.utils import execute_graphql_query, parse_cli_vars
|
|
12
|
+
from ..ctl.utils import execute_graphql_query, init_logging, parse_cli_vars
|
|
13
13
|
from ..exceptions import ModuleImportError
|
|
14
14
|
from ..node import InfrahubNode
|
|
15
15
|
|
|
@@ -20,11 +20,12 @@ if TYPE_CHECKING:
|
|
|
20
20
|
async def run(
|
|
21
21
|
generator_name: str,
|
|
22
22
|
path: str, # noqa: ARG001
|
|
23
|
-
debug: bool,
|
|
23
|
+
debug: bool,
|
|
24
24
|
list_available: bool,
|
|
25
25
|
branch: str | None = None,
|
|
26
|
-
variables: list[str]
|
|
26
|
+
variables: Optional[list[str]] = None,
|
|
27
27
|
) -> None:
|
|
28
|
+
init_logging(debug=debug)
|
|
28
29
|
repository_config = get_repository_config(Path(config.INFRAHUB_REPO_CONFIG_FILE))
|
|
29
30
|
|
|
30
31
|
if list_available or not generator_name:
|
|
@@ -34,7 +35,6 @@ async def run(
|
|
|
34
35
|
generator_config = repository_config.get_generator_definition(name=generator_name)
|
|
35
36
|
|
|
36
37
|
console = Console()
|
|
37
|
-
|
|
38
38
|
relative_path = str(generator_config.file_path.parent) if generator_config.file_path.parent != Path() else None
|
|
39
39
|
|
|
40
40
|
try:
|
infrahub_sdk/ctl/importer.py
CHANGED
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from asyncio import run as aiorun
|
|
4
4
|
from pathlib import Path
|
|
5
|
+
from typing import Optional
|
|
5
6
|
|
|
6
7
|
import typer
|
|
7
8
|
from rich.console import Console
|
|
@@ -25,8 +26,8 @@ def load(
|
|
|
25
26
|
),
|
|
26
27
|
quiet: bool = typer.Option(False, help="No console output"),
|
|
27
28
|
_: str = CONFIG_PARAM,
|
|
28
|
-
branch: str = typer.Option(
|
|
29
|
-
concurrent: int
|
|
29
|
+
branch: str = typer.Option(None, help="Branch from which to export"),
|
|
30
|
+
concurrent: Optional[int] = typer.Option(
|
|
30
31
|
None,
|
|
31
32
|
help="Maximum number of requests to execute at the same time.",
|
|
32
33
|
envvar="INFRAHUB_MAX_CONCURRENT_EXECUTION",
|
infrahub_sdk/ctl/menu.py
CHANGED
|
@@ -27,7 +27,7 @@ def callback() -> None:
|
|
|
27
27
|
async def load(
|
|
28
28
|
menus: list[Path],
|
|
29
29
|
debug: bool = False,
|
|
30
|
-
branch: str = typer.Option(
|
|
30
|
+
branch: str = typer.Option(None, help="Branch on which to load the menu."),
|
|
31
31
|
_: str = CONFIG_PARAM,
|
|
32
32
|
) -> None:
|
|
33
33
|
"""Load one or multiple menu files into Infrahub."""
|
infrahub_sdk/ctl/object.py
CHANGED
|
@@ -27,7 +27,7 @@ def callback() -> None:
|
|
|
27
27
|
async def load(
|
|
28
28
|
paths: list[Path],
|
|
29
29
|
debug: bool = False,
|
|
30
|
-
branch: str = typer.Option(
|
|
30
|
+
branch: str = typer.Option(None, help="Branch on which to load the objects."),
|
|
31
31
|
_: str = CONFIG_PARAM,
|
|
32
32
|
) -> None:
|
|
33
33
|
"""Load one or multiple objects files into Infrahub."""
|
infrahub_sdk/ctl/repository.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from pathlib import Path
|
|
4
|
+
from typing import Optional
|
|
4
5
|
|
|
5
6
|
import typer
|
|
6
7
|
import yaml
|
|
@@ -8,15 +9,14 @@ from pydantic import ValidationError
|
|
|
8
9
|
from rich.console import Console
|
|
9
10
|
from rich.table import Table
|
|
10
11
|
|
|
11
|
-
from infrahub_sdk.ctl.client import initialize_client
|
|
12
|
-
|
|
13
12
|
from ..async_typer import AsyncTyper
|
|
14
|
-
from ..
|
|
15
|
-
from ..ctl.utils import init_logging
|
|
13
|
+
from ..exceptions import FileNotValidError
|
|
16
14
|
from ..graphql import Mutation, Query
|
|
17
15
|
from ..schema.repository import InfrahubRepositoryConfig
|
|
18
|
-
from
|
|
16
|
+
from ..utils import read_file
|
|
17
|
+
from .client import initialize_client
|
|
19
18
|
from .parameters import CONFIG_PARAM
|
|
19
|
+
from .utils import init_logging
|
|
20
20
|
|
|
21
21
|
app = AsyncTyper()
|
|
22
22
|
console = Console()
|
|
@@ -69,12 +69,11 @@ async def add(
|
|
|
69
69
|
name: str,
|
|
70
70
|
location: str,
|
|
71
71
|
description: str = "",
|
|
72
|
-
username: str
|
|
72
|
+
username: Optional[str] = None,
|
|
73
73
|
password: str = "",
|
|
74
|
-
|
|
74
|
+
ref: str = "",
|
|
75
75
|
read_only: bool = False,
|
|
76
76
|
debug: bool = False,
|
|
77
|
-
branch: str = typer.Option("main", help="Branch on which to add the repository."),
|
|
78
77
|
_: str = CONFIG_PARAM,
|
|
79
78
|
) -> None:
|
|
80
79
|
"""Add a new repository."""
|
|
@@ -86,15 +85,24 @@ async def add(
|
|
|
86
85
|
"name": {"value": name},
|
|
87
86
|
"location": {"value": location},
|
|
88
87
|
"description": {"value": description},
|
|
89
|
-
"commit": {"value": commit},
|
|
90
88
|
},
|
|
91
89
|
}
|
|
90
|
+
if read_only:
|
|
91
|
+
input_data["data"]["ref"] = {"value": ref}
|
|
92
|
+
else:
|
|
93
|
+
input_data["data"]["default_branch"] = {"value": ref}
|
|
92
94
|
|
|
93
95
|
client = initialize_client()
|
|
94
96
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
97
|
+
if username or password:
|
|
98
|
+
credential = await client.create(
|
|
99
|
+
kind="CorePasswordCredential",
|
|
100
|
+
name=name,
|
|
101
|
+
username=username,
|
|
102
|
+
password=password,
|
|
103
|
+
)
|
|
104
|
+
await credential.save(allow_upsert=True)
|
|
105
|
+
input_data["data"]["credential"] = {"id": credential.id}
|
|
98
106
|
|
|
99
107
|
query = Mutation(
|
|
100
108
|
mutation="CoreReadOnlyRepositoryCreate" if read_only else "CoreRepositoryCreate",
|
|
@@ -102,18 +110,18 @@ async def add(
|
|
|
102
110
|
query={"ok": None},
|
|
103
111
|
)
|
|
104
112
|
|
|
105
|
-
await client.execute_graphql(query=query.render(),
|
|
113
|
+
await client.execute_graphql(query=query.render(), tracker="mutation-repository-create")
|
|
106
114
|
|
|
107
115
|
|
|
108
116
|
@app.command()
|
|
109
117
|
async def list(
|
|
110
|
-
branch: str
|
|
118
|
+
branch: Optional[str] = typer.Option(None, help="Branch on which to list repositories."),
|
|
111
119
|
debug: bool = False,
|
|
112
120
|
_: str = CONFIG_PARAM,
|
|
113
121
|
) -> None:
|
|
114
122
|
init_logging(debug=debug)
|
|
115
123
|
|
|
116
|
-
client = initialize_client(
|
|
124
|
+
client = initialize_client()
|
|
117
125
|
|
|
118
126
|
repo_status_query = {
|
|
119
127
|
"CoreGenericRepository": {
|
infrahub_sdk/ctl/schema.py
CHANGED
|
@@ -108,7 +108,7 @@ def get_node(schemas_data: list[dict], schema_index: int, node_index: int) -> di
|
|
|
108
108
|
async def load(
|
|
109
109
|
schemas: list[Path],
|
|
110
110
|
debug: bool = False,
|
|
111
|
-
branch: str = typer.Option(
|
|
111
|
+
branch: str = typer.Option(None, help="Branch on which to load the schema."),
|
|
112
112
|
wait: int = typer.Option(0, help="Time in seconds to wait until the schema has converged across all workers"),
|
|
113
113
|
_: str = CONFIG_PARAM,
|
|
114
114
|
) -> None:
|
|
@@ -159,7 +159,7 @@ async def load(
|
|
|
159
159
|
async def check(
|
|
160
160
|
schemas: list[Path],
|
|
161
161
|
debug: bool = False,
|
|
162
|
-
branch: str = typer.Option(
|
|
162
|
+
branch: str = typer.Option(None, help="Branch on which to check the schema."),
|
|
163
163
|
_: str = CONFIG_PARAM,
|
|
164
164
|
) -> None:
|
|
165
165
|
"""Check if schema files are valid and what would be the impact of loading them with Infrahub."""
|
infrahub_sdk/ctl/utils.py
CHANGED
|
@@ -6,7 +6,7 @@ import traceback
|
|
|
6
6
|
from collections.abc import Coroutine
|
|
7
7
|
from functools import wraps
|
|
8
8
|
from pathlib import Path
|
|
9
|
-
from typing import TYPE_CHECKING, Any, Callable, NoReturn, TypeVar
|
|
9
|
+
from typing import TYPE_CHECKING, Any, Callable, NoReturn, Optional, TypeVar
|
|
10
10
|
|
|
11
11
|
import pendulum
|
|
12
12
|
import typer
|
|
@@ -17,10 +17,10 @@ from rich.console import Console
|
|
|
17
17
|
from rich.logging import RichHandler
|
|
18
18
|
from rich.markup import escape
|
|
19
19
|
|
|
20
|
-
from ..ctl.exceptions import FileNotValidError, QueryNotFoundError
|
|
21
20
|
from ..exceptions import (
|
|
22
21
|
AuthenticationError,
|
|
23
22
|
Error,
|
|
23
|
+
FileNotValidError,
|
|
24
24
|
GraphQLError,
|
|
25
25
|
NodeNotFoundError,
|
|
26
26
|
ResourceNotDefinedError,
|
|
@@ -30,6 +30,7 @@ from ..exceptions import (
|
|
|
30
30
|
)
|
|
31
31
|
from ..yaml import YamlFile
|
|
32
32
|
from .client import initialize_client_sync
|
|
33
|
+
from .exceptions import QueryNotFoundError
|
|
33
34
|
|
|
34
35
|
if TYPE_CHECKING:
|
|
35
36
|
from ..schema.repository import InfrahubRepositoryConfig
|
|
@@ -144,7 +145,7 @@ def print_graphql_errors(console: Console, errors: list) -> None:
|
|
|
144
145
|
console.print(f"[red]{escape(str(error))}")
|
|
145
146
|
|
|
146
147
|
|
|
147
|
-
def parse_cli_vars(variables: list[str]
|
|
148
|
+
def parse_cli_vars(variables: Optional[list[str]]) -> dict[str, str]:
|
|
148
149
|
if not variables:
|
|
149
150
|
return {}
|
|
150
151
|
|
infrahub_sdk/ctl/validate.py
CHANGED
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import sys
|
|
4
4
|
from pathlib import Path
|
|
5
|
+
from typing import Optional
|
|
5
6
|
|
|
6
7
|
import typer
|
|
7
8
|
import ujson
|
|
@@ -57,7 +58,7 @@ async def validate_schema(schema: Path, _: str = CONFIG_PARAM) -> None:
|
|
|
57
58
|
@catch_exception(console=console)
|
|
58
59
|
def validate_graphql(
|
|
59
60
|
query: str,
|
|
60
|
-
variables: list[str]
|
|
61
|
+
variables: Optional[list[str]] = typer.Argument(
|
|
61
62
|
None, help="Variables to pass along with the query. Format key=value key=value."
|
|
62
63
|
),
|
|
63
64
|
debug: bool = typer.Option(False, help="Display more troubleshooting information."),
|
infrahub_sdk/exceptions.py
CHANGED
|
@@ -121,6 +121,12 @@ class AuthenticationError(Error):
|
|
|
121
121
|
super().__init__(self.message)
|
|
122
122
|
|
|
123
123
|
|
|
124
|
+
class URLNotFoundError(Error):
|
|
125
|
+
def __init__(self, url: str):
|
|
126
|
+
self.message = f"`{url}` not found."
|
|
127
|
+
super().__init__(self.message)
|
|
128
|
+
|
|
129
|
+
|
|
124
130
|
class FeatureNotSupportedError(Error):
|
|
125
131
|
"""Raised when trying to use a method on a node that doesn't support it."""
|
|
126
132
|
|
|
@@ -131,3 +137,9 @@ class UninitializedError(Error):
|
|
|
131
137
|
|
|
132
138
|
class InvalidResponseError(Error):
|
|
133
139
|
"""Raised when an object requires an initialization step before use"""
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
class FileNotValidError(Error):
|
|
143
|
+
def __init__(self, name: str, message: str = ""):
|
|
144
|
+
self.message = message or f"Cannot parse '{name}' content."
|
|
145
|
+
super().__init__(self.message)
|
infrahub_sdk/generator.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import logging
|
|
3
4
|
import os
|
|
4
5
|
from abc import abstractmethod
|
|
5
6
|
from typing import TYPE_CHECKING
|
|
@@ -27,6 +28,7 @@ class InfrahubGenerator:
|
|
|
27
28
|
generator_instance: str = "",
|
|
28
29
|
params: dict | None = None,
|
|
29
30
|
convert_query_response: bool = False,
|
|
31
|
+
logger: logging.Logger | None = None,
|
|
30
32
|
) -> None:
|
|
31
33
|
self.query = query
|
|
32
34
|
self.branch = branch
|
|
@@ -41,6 +43,7 @@ class InfrahubGenerator:
|
|
|
41
43
|
self._related_nodes: list[InfrahubNode] = []
|
|
42
44
|
self.infrahub_node = infrahub_node
|
|
43
45
|
self.convert_query_response = convert_query_response
|
|
46
|
+
self.logger = logger if logger else logging.getLogger("infrahub.tasks")
|
|
44
47
|
|
|
45
48
|
@property
|
|
46
49
|
def store(self) -> NodeStore:
|
infrahub_sdk/node.py
CHANGED
|
@@ -187,6 +187,7 @@ class RelatedNodeBase:
|
|
|
187
187
|
if node_data:
|
|
188
188
|
self._id = node_data.get("id", None)
|
|
189
189
|
self._hfid = node_data.get("hfid", None)
|
|
190
|
+
self._kind = node_data.get("kind", None)
|
|
190
191
|
self._display_label = node_data.get("display_label", None)
|
|
191
192
|
self._typename = node_data.get("__typename", None)
|
|
192
193
|
|
|
@@ -255,6 +256,8 @@ class RelatedNodeBase:
|
|
|
255
256
|
data["id"] = self.id
|
|
256
257
|
elif self.hfid is not None:
|
|
257
258
|
data["hfid"] = self.hfid
|
|
259
|
+
if self._kind is not None:
|
|
260
|
+
data["kind"] = self._kind
|
|
258
261
|
|
|
259
262
|
for prop_name in self._properties:
|
|
260
263
|
if getattr(self, prop_name) is not None:
|
|
@@ -1118,14 +1121,14 @@ class InfrahubNode(InfrahubNodeBase):
|
|
|
1118
1121
|
async def artifact_generate(self, name: str) -> None:
|
|
1119
1122
|
self._validate_artifact_support(ARTIFACT_GENERATE_FEATURE_NOT_SUPPORTED_MESSAGE)
|
|
1120
1123
|
|
|
1121
|
-
artifact = await self._client.get(kind="CoreArtifact",
|
|
1124
|
+
artifact = await self._client.get(kind="CoreArtifact", name__value=name, object__ids=[self.id])
|
|
1122
1125
|
await artifact.definition.fetch() # type: ignore[attr-defined]
|
|
1123
1126
|
await artifact.definition.peer.generate([artifact.id]) # type: ignore[attr-defined]
|
|
1124
1127
|
|
|
1125
1128
|
async def artifact_fetch(self, name: str) -> str | dict[str, Any]:
|
|
1126
1129
|
self._validate_artifact_support(ARTIFACT_GENERATE_FEATURE_NOT_SUPPORTED_MESSAGE)
|
|
1127
1130
|
|
|
1128
|
-
artifact = await self._client.get(kind="CoreArtifact",
|
|
1131
|
+
artifact = await self._client.get(kind="CoreArtifact", name__value=name, object__ids=[self.id])
|
|
1129
1132
|
content = await self._client.object_store.get(identifier=artifact.storage_id.value) # type: ignore[attr-defined]
|
|
1130
1133
|
return content
|
|
1131
1134
|
|
|
@@ -1635,13 +1638,13 @@ class InfrahubNodeSync(InfrahubNodeBase):
|
|
|
1635
1638
|
|
|
1636
1639
|
def artifact_generate(self, name: str) -> None:
|
|
1637
1640
|
self._validate_artifact_support(ARTIFACT_GENERATE_FEATURE_NOT_SUPPORTED_MESSAGE)
|
|
1638
|
-
artifact = self._client.get(kind="CoreArtifact",
|
|
1641
|
+
artifact = self._client.get(kind="CoreArtifact", name__value=name, object__ids=[self.id])
|
|
1639
1642
|
artifact.definition.fetch() # type: ignore[attr-defined]
|
|
1640
1643
|
artifact.definition.peer.generate([artifact.id]) # type: ignore[attr-defined]
|
|
1641
1644
|
|
|
1642
1645
|
def artifact_fetch(self, name: str) -> str | dict[str, Any]:
|
|
1643
1646
|
self._validate_artifact_support(ARTIFACT_FETCH_FEATURE_NOT_SUPPORTED_MESSAGE)
|
|
1644
|
-
artifact = self._client.get(kind="CoreArtifact",
|
|
1647
|
+
artifact = self._client.get(kind="CoreArtifact", name__value=name, object__ids=[self.id])
|
|
1645
1648
|
content = self._client.object_store.get(identifier=artifact.storage_id.value) # type: ignore[attr-defined]
|
|
1646
1649
|
return content
|
|
1647
1650
|
|
|
@@ -80,6 +80,7 @@ class SchemaAnimal:
|
|
|
80
80
|
namespace=NAMESPACE,
|
|
81
81
|
include_in_menu=True,
|
|
82
82
|
inherit_from=[TESTING_ANIMAL],
|
|
83
|
+
human_friendly_id=["owner__name__value", "name__value", "color__value"],
|
|
83
84
|
display_labels=["name__value", "breed__value", "color__value"],
|
|
84
85
|
order_by=["name__value"],
|
|
85
86
|
attributes=[
|
|
@@ -108,6 +109,14 @@ class SchemaAnimal:
|
|
|
108
109
|
identifier="person__animal",
|
|
109
110
|
cardinality="many",
|
|
110
111
|
direction=RelationshipDirection.INBOUND,
|
|
112
|
+
max_count=10,
|
|
113
|
+
),
|
|
114
|
+
Rel(
|
|
115
|
+
name="favorite_animal",
|
|
116
|
+
peer=TESTING_ANIMAL,
|
|
117
|
+
identifier="favorite_animal",
|
|
118
|
+
cardinality="one",
|
|
119
|
+
direction=RelationshipDirection.INBOUND,
|
|
111
120
|
),
|
|
112
121
|
Rel(
|
|
113
122
|
name="best_friends",
|
infrahub_sdk/utils.py
CHANGED
|
@@ -17,7 +17,7 @@ from graphql import (
|
|
|
17
17
|
|
|
18
18
|
from infrahub_sdk.repository import GitRepoManager
|
|
19
19
|
|
|
20
|
-
from .exceptions import JsonDecodeError
|
|
20
|
+
from .exceptions import FileNotValidError, JsonDecodeError
|
|
21
21
|
|
|
22
22
|
if TYPE_CHECKING:
|
|
23
23
|
from graphql import GraphQLResolveInfo
|
|
@@ -342,6 +342,16 @@ def write_to_file(path: Path, value: Any) -> bool:
|
|
|
342
342
|
return written is not None
|
|
343
343
|
|
|
344
344
|
|
|
345
|
+
def read_file(file_name: Path) -> str:
|
|
346
|
+
if not file_name.is_file():
|
|
347
|
+
raise FileNotValidError(name=str(file_name), message=f"{file_name} is not a valid file")
|
|
348
|
+
try:
|
|
349
|
+
with Path.open(file_name, encoding="utf-8") as fobj:
|
|
350
|
+
return fobj.read()
|
|
351
|
+
except UnicodeDecodeError as exc:
|
|
352
|
+
raise FileNotValidError(name=str(file_name), message=f"Unable to read {file_name} with utf-8 encoding") from exc
|
|
353
|
+
|
|
354
|
+
|
|
345
355
|
def get_user_permissions(data: list[dict]) -> dict:
|
|
346
356
|
groups = {}
|
|
347
357
|
for group in data:
|
infrahub_sdk/yaml.py
CHANGED
|
@@ -8,9 +8,8 @@ import yaml
|
|
|
8
8
|
from pydantic import BaseModel, Field
|
|
9
9
|
from typing_extensions import Self
|
|
10
10
|
|
|
11
|
-
from .
|
|
12
|
-
from .
|
|
13
|
-
from .utils import find_files
|
|
11
|
+
from .exceptions import FileNotValidError
|
|
12
|
+
from .utils import find_files, read_file
|
|
14
13
|
|
|
15
14
|
|
|
16
15
|
class InfrahubFileApiVersion(str, Enum):
|