infrahub-server 1.2.2__py3-none-any.whl → 1.2.4__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/cli/git_agent.py +4 -10
- infrahub/computed_attribute/tasks.py +8 -8
- infrahub/config.py +35 -0
- infrahub/core/constants/__init__.py +1 -0
- infrahub/core/constraint/node/runner.py +6 -5
- infrahub/core/graph/__init__.py +1 -1
- infrahub/core/migrations/graph/__init__.py +6 -1
- infrahub/core/migrations/graph/m018_uniqueness_nulls.py +68 -70
- infrahub/core/migrations/graph/m024_missing_hierarchy_backfill.py +69 -0
- infrahub/core/migrations/graph/m025_uniqueness_nulls.py +26 -0
- infrahub/core/migrations/schema/node_attribute_remove.py +16 -2
- infrahub/core/models.py +7 -1
- infrahub/core/node/__init__.py +4 -4
- infrahub/core/node/constraints/grouped_uniqueness.py +30 -10
- infrahub/core/query/ipam.py +1 -1
- infrahub/core/registry.py +18 -0
- infrahub/core/schema/basenode_schema.py +21 -1
- infrahub/core/schema/definitions/internal.py +2 -1
- infrahub/core/schema/generated/base_node_schema.py +1 -1
- infrahub/core/schema/manager.py +21 -0
- infrahub/core/schema/schema_branch.py +17 -9
- infrahub/database/__init__.py +10 -0
- infrahub/events/group_action.py +6 -1
- infrahub/events/node_action.py +5 -1
- infrahub/git/integrator.py +9 -7
- infrahub/graphql/mutations/main.py +10 -12
- infrahub/menu/repository.py +6 -6
- infrahub/message_bus/messages/__init__.py +0 -2
- infrahub/message_bus/operations/__init__.py +0 -1
- infrahub/message_bus/operations/event/__init__.py +2 -2
- infrahub/server.py +6 -11
- infrahub/services/adapters/cache/__init__.py +17 -0
- infrahub/services/adapters/cache/redis.py +11 -1
- infrahub/services/adapters/message_bus/__init__.py +20 -0
- infrahub/services/component.py +1 -2
- infrahub/tasks/registry.py +3 -7
- infrahub/workers/infrahub_async.py +4 -10
- infrahub_sdk/client.py +6 -6
- infrahub_sdk/ctl/cli_commands.py +32 -37
- infrahub_sdk/ctl/render.py +39 -0
- infrahub_sdk/exceptions.py +6 -2
- infrahub_sdk/generator.py +1 -1
- infrahub_sdk/node.py +38 -11
- infrahub_sdk/protocols_base.py +8 -1
- infrahub_sdk/pytest_plugin/items/jinja2_transform.py +22 -26
- infrahub_sdk/schema/__init__.py +10 -1
- infrahub_sdk/store.py +351 -75
- infrahub_sdk/template/__init__.py +209 -0
- infrahub_sdk/template/exceptions.py +38 -0
- infrahub_sdk/template/filters.py +151 -0
- infrahub_sdk/template/models.py +10 -0
- infrahub_sdk/utils.py +7 -0
- {infrahub_server-1.2.2.dist-info → infrahub_server-1.2.4.dist-info}/METADATA +2 -1
- {infrahub_server-1.2.2.dist-info → infrahub_server-1.2.4.dist-info}/RECORD +61 -59
- infrahub_testcontainers/container.py +6 -0
- infrahub_testcontainers/docker-compose.test.yml +1 -0
- infrahub_testcontainers/haproxy.cfg +3 -3
- infrahub_testcontainers/helpers.py +1 -1
- infrahub/message_bus/messages/event_worker_newprimaryapi.py +0 -9
- infrahub/message_bus/operations/event/worker.py +0 -9
- infrahub/support/__init__.py +0 -0
- infrahub/support/macro.py +0 -69
- {infrahub_server-1.2.2.dist-info → infrahub_server-1.2.4.dist-info}/LICENSE.txt +0 -0
- {infrahub_server-1.2.2.dist-info → infrahub_server-1.2.4.dist-info}/WHEEL +0 -0
- {infrahub_server-1.2.2.dist-info → infrahub_server-1.2.4.dist-info}/entry_points.txt +0 -0
infrahub_sdk/ctl/cli_commands.py
CHANGED
|
@@ -9,7 +9,6 @@ import sys
|
|
|
9
9
|
from pathlib import Path
|
|
10
10
|
from typing import TYPE_CHECKING, Any, Callable, Optional
|
|
11
11
|
|
|
12
|
-
import jinja2
|
|
13
12
|
import typer
|
|
14
13
|
import ujson
|
|
15
14
|
from rich.console import Console
|
|
@@ -18,7 +17,6 @@ from rich.logging import RichHandler
|
|
|
18
17
|
from rich.panel import Panel
|
|
19
18
|
from rich.pretty import Pretty
|
|
20
19
|
from rich.table import Table
|
|
21
|
-
from rich.traceback import Traceback
|
|
22
20
|
|
|
23
21
|
from .. import __version__ as sdk_version
|
|
24
22
|
from ..async_typer import AsyncTyper
|
|
@@ -31,7 +29,7 @@ from ..ctl.exceptions import QueryNotFoundError
|
|
|
31
29
|
from ..ctl.generator import run as run_generator
|
|
32
30
|
from ..ctl.menu import app as menu_app
|
|
33
31
|
from ..ctl.object import app as object_app
|
|
34
|
-
from ..ctl.render import list_jinja2_transforms
|
|
32
|
+
from ..ctl.render import list_jinja2_transforms, print_template_errors
|
|
35
33
|
from ..ctl.repository import app as repository_app
|
|
36
34
|
from ..ctl.repository import get_repository_config
|
|
37
35
|
from ..ctl.schema import app as schema_app
|
|
@@ -44,8 +42,9 @@ from ..ctl.utils import (
|
|
|
44
42
|
)
|
|
45
43
|
from ..ctl.validate import app as validate_app
|
|
46
44
|
from ..exceptions import GraphQLError, ModuleImportError
|
|
47
|
-
from ..jinja2 import identify_faulty_jinja_code
|
|
48
45
|
from ..schema import MainSchemaTypesAll, SchemaRoot
|
|
46
|
+
from ..template import Jinja2Template
|
|
47
|
+
from ..template.exceptions import JinjaTemplateError
|
|
49
48
|
from ..utils import get_branch, write_to_file
|
|
50
49
|
from ..yaml import SchemaFile
|
|
51
50
|
from .exporter import dump
|
|
@@ -168,43 +167,28 @@ async def run(
|
|
|
168
167
|
raise typer.Abort(f"Unable to Load the method {method} in the Python script at {script}")
|
|
169
168
|
|
|
170
169
|
client = initialize_client(
|
|
171
|
-
branch=branch,
|
|
170
|
+
branch=branch,
|
|
171
|
+
timeout=timeout,
|
|
172
|
+
max_concurrent_execution=concurrent,
|
|
173
|
+
identifier=module_name,
|
|
172
174
|
)
|
|
173
175
|
func = getattr(module, method)
|
|
174
176
|
await func(client=client, log=log, branch=branch, **variables_dict)
|
|
175
177
|
|
|
176
178
|
|
|
177
|
-
def render_jinja2_template(template_path: Path, variables: dict[str,
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
raise typer.Exit(1)
|
|
181
|
-
|
|
182
|
-
templateLoader = jinja2.FileSystemLoader(searchpath=".")
|
|
183
|
-
templateEnv = jinja2.Environment(loader=templateLoader, trim_blocks=True, lstrip_blocks=True)
|
|
184
|
-
template = templateEnv.get_template(str(template_path))
|
|
185
|
-
|
|
179
|
+
async def render_jinja2_template(template_path: Path, variables: dict[str, Any], data: dict[str, Any]) -> str:
|
|
180
|
+
variables["data"] = data
|
|
181
|
+
jinja_template = Jinja2Template(template=Path(template_path), template_directory=Path())
|
|
186
182
|
try:
|
|
187
|
-
rendered_tpl =
|
|
188
|
-
except
|
|
189
|
-
|
|
190
|
-
console.print(f"[yellow] {exc}")
|
|
191
|
-
raise typer.Exit(1) from exc
|
|
192
|
-
|
|
193
|
-
except jinja2.UndefinedError as exc:
|
|
194
|
-
console.print("[red]An error occurred while rendering the jinja template")
|
|
195
|
-
traceback = Traceback(show_locals=False)
|
|
196
|
-
errors = identify_faulty_jinja_code(traceback=traceback)
|
|
197
|
-
for frame, syntax in errors:
|
|
198
|
-
console.print(f"[yellow]{frame.filename} on line {frame.lineno}\n")
|
|
199
|
-
console.print(syntax)
|
|
200
|
-
console.print("")
|
|
201
|
-
console.print(traceback.trace.stacks[0].exc_value)
|
|
183
|
+
rendered_tpl = await jinja_template.render(variables=variables)
|
|
184
|
+
except JinjaTemplateError as exc:
|
|
185
|
+
print_template_errors(error=exc, console=console)
|
|
202
186
|
raise typer.Exit(1) from exc
|
|
203
187
|
|
|
204
188
|
return rendered_tpl
|
|
205
189
|
|
|
206
190
|
|
|
207
|
-
def _run_transform(
|
|
191
|
+
async def _run_transform(
|
|
208
192
|
query_name: str,
|
|
209
193
|
variables: dict[str, Any],
|
|
210
194
|
transform_func: Callable,
|
|
@@ -227,7 +211,11 @@ def _run_transform(
|
|
|
227
211
|
|
|
228
212
|
try:
|
|
229
213
|
response = execute_graphql_query(
|
|
230
|
-
query=query_name,
|
|
214
|
+
query=query_name,
|
|
215
|
+
variables_dict=variables,
|
|
216
|
+
branch=branch,
|
|
217
|
+
debug=debug,
|
|
218
|
+
repository_config=repository_config,
|
|
231
219
|
)
|
|
232
220
|
|
|
233
221
|
# TODO: response is a dict and can't be printed to the console in this way.
|
|
@@ -249,7 +237,7 @@ def _run_transform(
|
|
|
249
237
|
raise typer.Abort()
|
|
250
238
|
|
|
251
239
|
if asyncio.iscoroutinefunction(transform_func):
|
|
252
|
-
output =
|
|
240
|
+
output = await transform_func(response)
|
|
253
241
|
else:
|
|
254
242
|
output = transform_func(response)
|
|
255
243
|
return output
|
|
@@ -257,7 +245,7 @@ def _run_transform(
|
|
|
257
245
|
|
|
258
246
|
@app.command(name="render")
|
|
259
247
|
@catch_exception(console=console)
|
|
260
|
-
def render(
|
|
248
|
+
async def render(
|
|
261
249
|
transform_name: str = typer.Argument(default="", help="Name of the Python transformation", show_default=False),
|
|
262
250
|
variables: Optional[list[str]] = typer.Argument(
|
|
263
251
|
None, help="Variables to pass along with the query. Format key=value key=value."
|
|
@@ -289,7 +277,7 @@ def render(
|
|
|
289
277
|
transform_func = functools.partial(render_jinja2_template, transform_config.template_path, variables_dict)
|
|
290
278
|
|
|
291
279
|
# Query GQL and run the transform
|
|
292
|
-
result = _run_transform(
|
|
280
|
+
result = await _run_transform(
|
|
293
281
|
query_name=transform_config.query,
|
|
294
282
|
variables=variables_dict,
|
|
295
283
|
transform_func=transform_func,
|
|
@@ -410,7 +398,10 @@ def version() -> None:
|
|
|
410
398
|
|
|
411
399
|
@app.command(name="info")
|
|
412
400
|
@catch_exception(console=console)
|
|
413
|
-
def info(
|
|
401
|
+
def info( # noqa: PLR0915
|
|
402
|
+
detail: bool = typer.Option(False, help="Display detailed information."),
|
|
403
|
+
_: str = CONFIG_PARAM,
|
|
404
|
+
) -> None:
|
|
414
405
|
"""Display the status of the Python SDK."""
|
|
415
406
|
|
|
416
407
|
info: dict[str, Any] = {
|
|
@@ -476,10 +467,14 @@ def info(detail: bool = typer.Option(False, help="Display detailed information."
|
|
|
476
467
|
infrahub_info = Table(show_header=False, box=None)
|
|
477
468
|
if info["user_info"]:
|
|
478
469
|
infrahub_info.add_row("User:", info["user_info"]["AccountProfile"]["display_label"])
|
|
479
|
-
infrahub_info.add_row(
|
|
470
|
+
infrahub_info.add_row(
|
|
471
|
+
"Description:",
|
|
472
|
+
info["user_info"]["AccountProfile"]["description"]["value"],
|
|
473
|
+
)
|
|
480
474
|
infrahub_info.add_row("Status:", info["user_info"]["AccountProfile"]["status"]["label"])
|
|
481
475
|
infrahub_info.add_row(
|
|
482
|
-
"Number of Groups:",
|
|
476
|
+
"Number of Groups:",
|
|
477
|
+
str(info["user_info"]["AccountProfile"]["member_of_groups"]["count"]),
|
|
483
478
|
)
|
|
484
479
|
|
|
485
480
|
if groups := info["groups"]:
|
infrahub_sdk/ctl/render.py
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
from rich.console import Console
|
|
2
2
|
|
|
3
3
|
from ..schema.repository import InfrahubRepositoryConfig
|
|
4
|
+
from ..template.exceptions import (
|
|
5
|
+
JinjaTemplateError,
|
|
6
|
+
JinjaTemplateNotFoundError,
|
|
7
|
+
JinjaTemplateSyntaxError,
|
|
8
|
+
JinjaTemplateUndefinedError,
|
|
9
|
+
)
|
|
4
10
|
|
|
5
11
|
|
|
6
12
|
def list_jinja2_transforms(config: InfrahubRepositoryConfig) -> None:
|
|
@@ -9,3 +15,36 @@ def list_jinja2_transforms(config: InfrahubRepositoryConfig) -> None:
|
|
|
9
15
|
|
|
10
16
|
for transform in config.jinja2_transforms:
|
|
11
17
|
console.print(f"{transform.name} ({transform.template_path})")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def print_template_errors(error: JinjaTemplateError, console: Console) -> None:
|
|
21
|
+
if isinstance(error, JinjaTemplateNotFoundError):
|
|
22
|
+
console.print("[red]An error occurred while rendering the jinja template")
|
|
23
|
+
console.print("")
|
|
24
|
+
if error.base_template:
|
|
25
|
+
console.print(f"Base template: [yellow]{error.base_template}")
|
|
26
|
+
console.print(f"Missing template: [yellow]{error.filename}")
|
|
27
|
+
return
|
|
28
|
+
|
|
29
|
+
if isinstance(error, JinjaTemplateUndefinedError):
|
|
30
|
+
console.print("[red]An error occurred while rendering the jinja template")
|
|
31
|
+
for current_error in error.errors:
|
|
32
|
+
console.print(f"[yellow]{current_error.frame.filename} on line {current_error.frame.lineno}\n")
|
|
33
|
+
console.print(current_error.syntax)
|
|
34
|
+
console.print("")
|
|
35
|
+
console.print(error.message)
|
|
36
|
+
return
|
|
37
|
+
|
|
38
|
+
if isinstance(error, JinjaTemplateSyntaxError):
|
|
39
|
+
console.print("[red]A syntax error was encountered within the template")
|
|
40
|
+
console.print("")
|
|
41
|
+
if error.filename:
|
|
42
|
+
console.print(f"Filename: [yellow]{error.filename}")
|
|
43
|
+
console.print(f"Line number: [yellow]{error.lineno}")
|
|
44
|
+
console.print()
|
|
45
|
+
console.print(error.message)
|
|
46
|
+
return
|
|
47
|
+
|
|
48
|
+
console.print("[red]An error occurred while rendering the jinja template")
|
|
49
|
+
console.print("")
|
|
50
|
+
console.print(f"[yellow]{error.message}")
|
infrahub_sdk/exceptions.py
CHANGED
|
@@ -69,12 +69,12 @@ class ModuleImportError(Error):
|
|
|
69
69
|
class NodeNotFoundError(Error):
|
|
70
70
|
def __init__(
|
|
71
71
|
self,
|
|
72
|
-
node_type: str,
|
|
73
72
|
identifier: Mapping[str, list[str]],
|
|
74
73
|
message: str = "Unable to find the node in the database.",
|
|
75
74
|
branch_name: str | None = None,
|
|
75
|
+
node_type: str | None = None,
|
|
76
76
|
):
|
|
77
|
-
self.node_type = node_type
|
|
77
|
+
self.node_type = node_type or "unknown"
|
|
78
78
|
self.identifier = identifier
|
|
79
79
|
self.branch_name = branch_name
|
|
80
80
|
|
|
@@ -88,6 +88,10 @@ class NodeNotFoundError(Error):
|
|
|
88
88
|
"""
|
|
89
89
|
|
|
90
90
|
|
|
91
|
+
class NodeInvalidError(NodeNotFoundError):
|
|
92
|
+
pass
|
|
93
|
+
|
|
94
|
+
|
|
91
95
|
class ResourceNotDefinedError(Error):
|
|
92
96
|
"""Raised when trying to access a resource that hasn't been defined."""
|
|
93
97
|
|
infrahub_sdk/generator.py
CHANGED
|
@@ -137,7 +137,7 @@ class InfrahubGenerator:
|
|
|
137
137
|
|
|
138
138
|
for node in self._nodes + self._related_nodes:
|
|
139
139
|
if node.id:
|
|
140
|
-
self._init_client.store.set(
|
|
140
|
+
self._init_client.store.set(node=node)
|
|
141
141
|
|
|
142
142
|
@abstractmethod
|
|
143
143
|
async def generate(self, data: dict) -> None:
|
infrahub_sdk/node.py
CHANGED
|
@@ -15,7 +15,7 @@ from .exceptions import (
|
|
|
15
15
|
)
|
|
16
16
|
from .graphql import Mutation, Query
|
|
17
17
|
from .schema import GenericSchemaAPI, RelationshipCardinality, RelationshipKind
|
|
18
|
-
from .utils import compare_lists, get_flat_value
|
|
18
|
+
from .utils import compare_lists, generate_short_id, get_flat_value
|
|
19
19
|
from .uuidt import UUIDT
|
|
20
20
|
|
|
21
21
|
if TYPE_CHECKING:
|
|
@@ -43,6 +43,20 @@ ARTIFACT_DEFINITION_GENERATE_FEATURE_NOT_SUPPORTED_MESSAGE = (
|
|
|
43
43
|
"calling generate is only supported for CoreArtifactDefinition nodes"
|
|
44
44
|
)
|
|
45
45
|
|
|
46
|
+
HFID_STR_SEPARATOR = "__"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def parse_human_friendly_id(hfid: str | list[str]) -> tuple[str | None, list[str]]:
|
|
50
|
+
"""Parse a human friendly ID into a kind and an identifier."""
|
|
51
|
+
if isinstance(hfid, str):
|
|
52
|
+
hfid_parts = hfid.split(HFID_STR_SEPARATOR)
|
|
53
|
+
if len(hfid_parts) == 1:
|
|
54
|
+
return None, hfid_parts
|
|
55
|
+
return hfid_parts[0], hfid_parts[1:]
|
|
56
|
+
if isinstance(hfid, list):
|
|
57
|
+
return None, hfid
|
|
58
|
+
raise ValueError(f"Invalid human friendly ID: {hfid}")
|
|
59
|
+
|
|
46
60
|
|
|
47
61
|
class Attribute:
|
|
48
62
|
"""Represents an attribute of a Node, including its schema, value, and properties."""
|
|
@@ -340,10 +354,10 @@ class RelatedNode(RelatedNodeBase):
|
|
|
340
354
|
return self._peer # type: ignore[return-value]
|
|
341
355
|
|
|
342
356
|
if self.id and self.typename:
|
|
343
|
-
return self._client.store.get(key=self.id, kind=self.typename) # type: ignore[return-value]
|
|
357
|
+
return self._client.store.get(key=self.id, kind=self.typename, branch=self._branch) # type: ignore[return-value]
|
|
344
358
|
|
|
345
359
|
if self.hfid_str:
|
|
346
|
-
return self._client.store.
|
|
360
|
+
return self._client.store.get(key=self.hfid_str, branch=self._branch) # type: ignore[return-value]
|
|
347
361
|
|
|
348
362
|
raise ValueError("Node must have at least one identifier (ID or HFID) to query it.")
|
|
349
363
|
|
|
@@ -387,10 +401,10 @@ class RelatedNodeSync(RelatedNodeBase):
|
|
|
387
401
|
return self._peer # type: ignore[return-value]
|
|
388
402
|
|
|
389
403
|
if self.id and self.typename:
|
|
390
|
-
return self._client.store.get(key=self.id, kind=self.typename) # type: ignore[return-value]
|
|
404
|
+
return self._client.store.get(key=self.id, kind=self.typename, branch=self._branch) # type: ignore[return-value]
|
|
391
405
|
|
|
392
406
|
if self.hfid_str:
|
|
393
|
-
return self._client.store.
|
|
407
|
+
return self._client.store.get(key=self.hfid_str, branch=self._branch) # type: ignore[return-value]
|
|
394
408
|
|
|
395
409
|
raise ValueError("Node must have at least one identifier (ID or HFID) to query it.")
|
|
396
410
|
|
|
@@ -678,6 +692,11 @@ class InfrahubNodeBase:
|
|
|
678
692
|
self._branch = branch
|
|
679
693
|
self._existing: bool = True
|
|
680
694
|
|
|
695
|
+
# Generate a unique ID only to be used inside the SDK
|
|
696
|
+
# The format if this ID is purposely different from the ID used by the API
|
|
697
|
+
# This is done to avoid confusion and potential conflicts between the IDs
|
|
698
|
+
self._internal_id = generate_short_id()
|
|
699
|
+
|
|
681
700
|
self.id = data.get("id", None) if isinstance(data, dict) else None
|
|
682
701
|
self.display_label: str | None = data.get("display_label", None) if isinstance(data, dict) else None
|
|
683
702
|
self.typename: str | None = data.get("__typename", schema.kind) if isinstance(data, dict) else schema.kind
|
|
@@ -694,6 +713,9 @@ class InfrahubNodeBase:
|
|
|
694
713
|
self._init_attributes(data)
|
|
695
714
|
self._init_relationships(data)
|
|
696
715
|
|
|
716
|
+
def get_branch(self) -> str:
|
|
717
|
+
return self._branch
|
|
718
|
+
|
|
697
719
|
def get_path_value(self, path: str) -> Any:
|
|
698
720
|
path_parts = path.split("__")
|
|
699
721
|
return_value = None
|
|
@@ -794,6 +816,11 @@ class InfrahubNodeBase:
|
|
|
794
816
|
def get_kind(self) -> str:
|
|
795
817
|
return self._schema.kind
|
|
796
818
|
|
|
819
|
+
def get_all_kinds(self) -> list[str]:
|
|
820
|
+
if hasattr(self._schema, "inherit_from"):
|
|
821
|
+
return [self._schema.kind] + self._schema.inherit_from
|
|
822
|
+
return [self._schema.kind]
|
|
823
|
+
|
|
797
824
|
def is_ip_prefix(self) -> bool:
|
|
798
825
|
builtin_ipprefix_kind = "BuiltinIPPrefix"
|
|
799
826
|
return self.get_kind() == builtin_ipprefix_kind or builtin_ipprefix_kind in self._schema.inherit_from # type: ignore[union-attr]
|
|
@@ -1201,7 +1228,7 @@ class InfrahubNode(InfrahubNodeBase):
|
|
|
1201
1228
|
else:
|
|
1202
1229
|
await self._client.group_context.add_related_nodes(ids=[self.id], update_group_context=update_group_context)
|
|
1203
1230
|
|
|
1204
|
-
self._client.store.set(
|
|
1231
|
+
self._client.store.set(node=self)
|
|
1205
1232
|
|
|
1206
1233
|
async def generate_query_data(
|
|
1207
1234
|
self,
|
|
@@ -1479,15 +1506,15 @@ class InfrahubNode(InfrahubNodeBase):
|
|
|
1479
1506
|
for rel_name in self._relationships:
|
|
1480
1507
|
rel = getattr(self, rel_name)
|
|
1481
1508
|
if rel and isinstance(rel, RelatedNode):
|
|
1482
|
-
relation = node_data["node"].get(rel_name)
|
|
1483
|
-
if relation.get("node", None):
|
|
1509
|
+
relation = node_data["node"].get(rel_name, None)
|
|
1510
|
+
if relation and relation.get("node", None):
|
|
1484
1511
|
related_node = await InfrahubNode.from_graphql(
|
|
1485
1512
|
client=self._client, branch=branch, data=relation, timeout=timeout
|
|
1486
1513
|
)
|
|
1487
1514
|
related_nodes.append(related_node)
|
|
1488
1515
|
elif rel and isinstance(rel, RelationshipManager):
|
|
1489
|
-
peers = node_data["node"].get(rel_name)
|
|
1490
|
-
if peers:
|
|
1516
|
+
peers = node_data["node"].get(rel_name, None)
|
|
1517
|
+
if peers and peers["edges"]:
|
|
1491
1518
|
for peer in peers["edges"]:
|
|
1492
1519
|
related_node = await InfrahubNode.from_graphql(
|
|
1493
1520
|
client=self._client, branch=branch, data=peer, timeout=timeout
|
|
@@ -1726,7 +1753,7 @@ class InfrahubNodeSync(InfrahubNodeBase):
|
|
|
1726
1753
|
else:
|
|
1727
1754
|
self._client.group_context.add_related_nodes(ids=[self.id], update_group_context=update_group_context)
|
|
1728
1755
|
|
|
1729
|
-
self._client.store.set(
|
|
1756
|
+
self._client.store.set(node=self)
|
|
1730
1757
|
|
|
1731
1758
|
def generate_query_data(
|
|
1732
1759
|
self,
|
infrahub_sdk/protocols_base.py
CHANGED
|
@@ -144,7 +144,8 @@ class AnyAttributeOptional(Attribute):
|
|
|
144
144
|
@runtime_checkable
|
|
145
145
|
class CoreNodeBase(Protocol):
|
|
146
146
|
_schema: MainSchemaTypes
|
|
147
|
-
|
|
147
|
+
_internal_id: str
|
|
148
|
+
id: str # NOTE this is incorrect, should be str | None
|
|
148
149
|
display_label: str | None
|
|
149
150
|
|
|
150
151
|
@property
|
|
@@ -153,10 +154,16 @@ class CoreNodeBase(Protocol):
|
|
|
153
154
|
@property
|
|
154
155
|
def hfid_str(self) -> str | None: ...
|
|
155
156
|
|
|
157
|
+
def get_human_friendly_id(self) -> list[str] | None: ...
|
|
158
|
+
|
|
156
159
|
def get_human_friendly_id_as_string(self, include_kind: bool = False) -> str | None: ...
|
|
157
160
|
|
|
158
161
|
def get_kind(self) -> str: ...
|
|
159
162
|
|
|
163
|
+
def get_all_kinds(self) -> list[str]: ...
|
|
164
|
+
|
|
165
|
+
def get_branch(self) -> str: ...
|
|
166
|
+
|
|
160
167
|
def is_ip_prefix(self) -> bool: ...
|
|
161
168
|
|
|
162
169
|
def is_ip_address(self) -> bool: ...
|
|
@@ -1,51 +1,47 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import asyncio
|
|
3
4
|
import difflib
|
|
5
|
+
from pathlib import Path
|
|
4
6
|
from typing import TYPE_CHECKING, Any
|
|
5
7
|
|
|
6
8
|
import jinja2
|
|
7
9
|
import ujson
|
|
8
10
|
from httpx import HTTPStatusError
|
|
9
|
-
from rich.console import Console
|
|
10
|
-
from rich.traceback import Traceback
|
|
11
11
|
|
|
12
|
-
from ...
|
|
13
|
-
from
|
|
12
|
+
from ...template import Jinja2Template
|
|
13
|
+
from ...template.exceptions import JinjaTemplateError
|
|
14
|
+
from ..exceptions import OutputMatchError
|
|
14
15
|
from ..models import InfrahubInputOutputTest, InfrahubTestExpectedResult
|
|
15
16
|
from .base import InfrahubItem
|
|
16
17
|
|
|
17
18
|
if TYPE_CHECKING:
|
|
18
|
-
from pathlib import Path
|
|
19
|
-
|
|
20
19
|
from pytest import ExceptionInfo
|
|
21
20
|
|
|
22
21
|
|
|
23
22
|
class InfrahubJinja2Item(InfrahubItem):
|
|
23
|
+
def _get_jinja2(self) -> Jinja2Template:
|
|
24
|
+
return Jinja2Template(
|
|
25
|
+
template=Path(self.resource_config.template_path), # type: ignore[attr-defined]
|
|
26
|
+
template_directory=Path(self.session.infrahub_config_path.parent), # type: ignore[attr-defined]
|
|
27
|
+
)
|
|
28
|
+
|
|
24
29
|
def get_jinja2_environment(self) -> jinja2.Environment:
|
|
25
|
-
|
|
26
|
-
return
|
|
30
|
+
jinja2_template = self._get_jinja2()
|
|
31
|
+
return jinja2_template.get_environment()
|
|
27
32
|
|
|
28
33
|
def get_jinja2_template(self) -> jinja2.Template:
|
|
29
|
-
|
|
34
|
+
jinja2_template = self._get_jinja2()
|
|
35
|
+
return jinja2_template.get_template()
|
|
30
36
|
|
|
31
37
|
def render_jinja2_template(self, variables: dict[str, Any]) -> str | None:
|
|
38
|
+
jinja2_template = self._get_jinja2()
|
|
39
|
+
|
|
32
40
|
try:
|
|
33
|
-
return
|
|
34
|
-
except
|
|
35
|
-
traceback = Traceback(show_locals=False)
|
|
36
|
-
errors = identify_faulty_jinja_code(traceback=traceback)
|
|
37
|
-
console = Console()
|
|
38
|
-
with console.capture() as capture:
|
|
39
|
-
console.print(f"An error occurred while rendering Jinja2 transform:{self.name!r}\n", soft_wrap=True)
|
|
40
|
-
console.print(f"{exc.message}\n", soft_wrap=True)
|
|
41
|
-
for frame, syntax in errors:
|
|
42
|
-
console.print(f"{frame.filename} on line {frame.lineno}\n", soft_wrap=True)
|
|
43
|
-
console.print(syntax, soft_wrap=True)
|
|
44
|
-
str_output = capture.get()
|
|
41
|
+
return asyncio.run(jinja2_template.render(variables=variables))
|
|
42
|
+
except JinjaTemplateError as exc:
|
|
45
43
|
if self.test.expect == InfrahubTestExpectedResult.PASS:
|
|
46
|
-
raise
|
|
47
|
-
name=self.name, message=str_output, rtb=traceback, errors=errors
|
|
48
|
-
) from exc
|
|
44
|
+
raise exc
|
|
49
45
|
return None
|
|
50
46
|
|
|
51
47
|
def get_result_differences(self, computed: Any) -> str | None:
|
|
@@ -99,8 +95,8 @@ class InfrahubJinja2TransformUnitRenderItem(InfrahubJinja2Item):
|
|
|
99
95
|
raise OutputMatchError(name=self.name, differences=differences)
|
|
100
96
|
|
|
101
97
|
def repr_failure(self, excinfo: ExceptionInfo, style: str | None = None) -> str:
|
|
102
|
-
if isinstance(excinfo.value, (
|
|
103
|
-
return excinfo.value.message
|
|
98
|
+
if isinstance(excinfo.value, (JinjaTemplateError)):
|
|
99
|
+
return str(excinfo.value.message)
|
|
104
100
|
|
|
105
101
|
return super().repr_failure(excinfo, style=style)
|
|
106
102
|
|
infrahub_sdk/schema/__init__.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
|
+
import json
|
|
4
5
|
from collections.abc import MutableMapping
|
|
5
6
|
from enum import Enum
|
|
6
7
|
from time import sleep
|
|
@@ -13,6 +14,7 @@ from typing_extensions import TypeAlias
|
|
|
13
14
|
|
|
14
15
|
from ..exceptions import (
|
|
15
16
|
InvalidResponseError,
|
|
17
|
+
JsonDecodeError,
|
|
16
18
|
SchemaNotFoundError,
|
|
17
19
|
ValidationError,
|
|
18
20
|
)
|
|
@@ -420,7 +422,14 @@ class InfrahubSchema(InfrahubSchemaBase):
|
|
|
420
422
|
response = await self.client._get(url=url, timeout=timeout)
|
|
421
423
|
response.raise_for_status()
|
|
422
424
|
|
|
423
|
-
|
|
425
|
+
try:
|
|
426
|
+
data: MutableMapping[str, Any] = response.json()
|
|
427
|
+
except json.decoder.JSONDecodeError as exc:
|
|
428
|
+
raise JsonDecodeError(
|
|
429
|
+
message=f"Invalid Schema response received from the server at {response.url}: {response.text} [{response.status_code}] ",
|
|
430
|
+
content=response.text,
|
|
431
|
+
url=response.url,
|
|
432
|
+
) from exc
|
|
424
433
|
|
|
425
434
|
nodes: MutableMapping[str, MainSchemaTypesAPI] = {}
|
|
426
435
|
for node_schema in data.get("nodes", []):
|