devaci 1.0.0__tar.gz → 1.0.1__tar.gz
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.
- {devaci-1.0.0 → devaci-1.0.1}/PKG-INFO +1 -1
- {devaci-1.0.0 → devaci-1.0.1}/pyproject.toml +1 -1
- {devaci-1.0.0 → devaci-1.0.1}/src/devaci/cobra/__init__.py +6 -2
- {devaci-1.0.0 → devaci-1.0.1}/src/devaci/deploy.py +1 -1
- {devaci-1.0.0 → devaci-1.0.1}/src/devaci/jinja.py +2 -4
- {devaci-1.0.0 → devaci-1.0.1}/src/devaci/results.py +7 -3
- {devaci-1.0.0 → devaci-1.0.1}/tests/test_deploy.py +13 -0
- {devaci-1.0.0 → devaci-1.0.1}/tests/test_jinja.py +10 -4
- {devaci-1.0.0 → devaci-1.0.1}/.gitignore +0 -0
- {devaci-1.0.0 → devaci-1.0.1}/LICENSE +0 -0
- {devaci-1.0.0 → devaci-1.0.1}/README.md +0 -0
- {devaci-1.0.0 → devaci-1.0.1}/src/devaci/__init__.py +0 -0
- {devaci-1.0.0 → devaci-1.0.1}/src/devaci/cobra/base.py +0 -0
- {devaci-1.0.0 → devaci-1.0.1}/src/devaci/cobra/builders.py +0 -0
- {devaci-1.0.0 → devaci-1.0.1}/src/devaci/cobra/registry.py +0 -0
- {devaci-1.0.0 → devaci-1.0.1}/src/devaci/console.py +0 -0
- {devaci-1.0.0 → devaci-1.0.1}/src/devaci/data.py +0 -0
- {devaci-1.0.0 → devaci-1.0.1}/src/devaci/exceptions.py +0 -0
- {devaci-1.0.0 → devaci-1.0.1}/src/devaci/filters.py +0 -0
- {devaci-1.0.0 → devaci-1.0.1}/src/devaci/py.typed +0 -0
- {devaci-1.0.0 → devaci-1.0.1}/tests/__init__.py +0 -0
- {devaci-1.0.0 → devaci-1.0.1}/tests/test_cobra.py +0 -0
- {devaci-1.0.0 → devaci-1.0.1}/tests/test_data.py +0 -0
- {devaci-1.0.0 → devaci-1.0.1}/tests/test_devaci.py +0 -0
- {devaci-1.0.0 → devaci-1.0.1}/tests/test_filters.py +0 -0
- {devaci-1.0.0 → devaci-1.0.1}/tests/test_results.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: devaci
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.1
|
|
4
4
|
Summary: Python library that generates Cisco ACI configuration and optionally pushes it to an APIC controller via Cisco's official Cobra SDK.
|
|
5
5
|
Project-URL: Homepage, https://github.com/cocuni80/devaci
|
|
6
6
|
Project-URL: Repository, https://github.com/cocuni80/devaci
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "devaci"
|
|
7
|
-
version = "1.0.
|
|
7
|
+
version = "1.0.1"
|
|
8
8
|
description = "Python library that generates Cisco ACI configuration and optionally pushes it to an APIC controller via Cisco's official Cobra SDK."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -40,12 +40,16 @@ class CobraBuilder:
|
|
|
40
40
|
|
|
41
41
|
@property
|
|
42
42
|
def xml(self) -> str | None:
|
|
43
|
-
"""Return the rendered XML payload."""
|
|
43
|
+
"""Return the rendered XML payload, or None when the config is empty."""
|
|
44
|
+
if not self.config.configMos:
|
|
45
|
+
return None
|
|
44
46
|
return cast(str | None, self.config.xmldata)
|
|
45
47
|
|
|
46
48
|
@property
|
|
47
49
|
def json(self) -> Any:
|
|
48
|
-
"""Return the rendered JSON payload as a dict."""
|
|
50
|
+
"""Return the rendered JSON payload as a dict, or None when the config is empty."""
|
|
51
|
+
if not self.config.configMos:
|
|
52
|
+
return None
|
|
49
53
|
return json.loads(self.config.data)
|
|
50
54
|
|
|
51
55
|
def render(self, output: dict[str, Any]) -> CobraResult:
|
|
@@ -120,7 +120,7 @@ class DeployClass:
|
|
|
120
120
|
|
|
121
121
|
try:
|
|
122
122
|
renderer = JinjaRenderer()
|
|
123
|
-
output = renderer.render(content,
|
|
123
|
+
output = renderer.render(content, **self._variables)
|
|
124
124
|
logs.append(f"[Jinja]: Template {name} was rendered successfully.")
|
|
125
125
|
logger.info(logs[-1])
|
|
126
126
|
|
|
@@ -26,7 +26,7 @@ class JinjaRenderer:
|
|
|
26
26
|
self._env.filters["range"] = range_filter
|
|
27
27
|
self._env.filters["nan"] = nan_filter
|
|
28
28
|
|
|
29
|
-
def render(self, template: str,
|
|
29
|
+
def render(self, template: str, **variables: Any) -> dict[str, Any]:
|
|
30
30
|
"""Render ``template`` with ``variables`` and parse the YAML output."""
|
|
31
31
|
try:
|
|
32
32
|
rendered = self._env.from_string(template).render(**variables)
|
|
@@ -36,7 +36,5 @@ class JinjaRenderer:
|
|
|
36
36
|
|
|
37
37
|
output = load_yaml(rendered)
|
|
38
38
|
if not isinstance(output, dict):
|
|
39
|
-
raise JinjaError(
|
|
40
|
-
f"[Jinja] -> [ConfigError]: Template {name} did not produce a mapping."
|
|
41
|
-
)
|
|
39
|
+
raise JinjaError("[Jinja] -> [ConfigError]: Template did not produce a mapping.")
|
|
42
40
|
return output
|
|
@@ -5,7 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
import json
|
|
6
6
|
from dataclasses import dataclass, field
|
|
7
7
|
from datetime import datetime
|
|
8
|
-
from typing import Any
|
|
8
|
+
from typing import Any, cast
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
def _timestamp() -> str:
|
|
@@ -44,11 +44,15 @@ class CobraResult(Result):
|
|
|
44
44
|
|
|
45
45
|
@property
|
|
46
46
|
def xml(self) -> str | None:
|
|
47
|
-
|
|
47
|
+
if self.config is None or not self.config.configMos:
|
|
48
|
+
return None
|
|
49
|
+
return cast(str | None, self.config.xmldata)
|
|
48
50
|
|
|
49
51
|
@property
|
|
50
52
|
def json(self) -> Any:
|
|
51
|
-
|
|
53
|
+
if self.config is None or not self.config.configMos:
|
|
54
|
+
return None
|
|
55
|
+
return json.loads(self.config.data)
|
|
52
56
|
|
|
53
57
|
def to_dict(self) -> dict[str, Any]:
|
|
54
58
|
data = super().to_dict()
|
|
@@ -83,3 +83,16 @@ def test_deploy_save_output(tmp_path):
|
|
|
83
83
|
output = tmp_path / "scripts" / "config.xml"
|
|
84
84
|
assert output.exists()
|
|
85
85
|
assert "acme" in output.read_text(encoding="utf-8")
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def test_deploy_variable_named_name(tmp_path):
|
|
89
|
+
aci = DeployClass(testing=True, working_folder=tmp_path, logging=False)
|
|
90
|
+
aci.variables = {"name": "test", "descr": "Test tenant"}
|
|
91
|
+
aci.template = (
|
|
92
|
+
"fvTenant:\n - name: {{ name }}\n descr: {{ descr }}\n",
|
|
93
|
+
"tenant.j2",
|
|
94
|
+
)
|
|
95
|
+
aci.deploy()
|
|
96
|
+
|
|
97
|
+
assert aci.results[0]["success"] is True
|
|
98
|
+
assert "test" in aci.config
|
|
@@ -6,23 +6,29 @@ from devaci.jinja import JinjaRenderer
|
|
|
6
6
|
|
|
7
7
|
def test_render_mapping():
|
|
8
8
|
renderer = JinjaRenderer()
|
|
9
|
-
out = renderer.render("fvTenant:\n - name: acme\n"
|
|
9
|
+
out = renderer.render("fvTenant:\n - name: acme\n")
|
|
10
10
|
assert out == {"fvTenant": [{"name": "acme"}]}
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
def test_render_with_variables():
|
|
14
14
|
renderer = JinjaRenderer()
|
|
15
|
-
out = renderer.render("fvTenant:\n - name: {{ tenant }}\n",
|
|
15
|
+
out = renderer.render("fvTenant:\n - name: {{ tenant }}\n", tenant="acme")
|
|
16
|
+
assert out == {"fvTenant": [{"name": "acme"}]}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def test_render_variable_named_name():
|
|
20
|
+
renderer = JinjaRenderer()
|
|
21
|
+
out = renderer.render("fvTenant:\n - name: {{ name }}\n", name="acme")
|
|
16
22
|
assert out == {"fvTenant": [{"name": "acme"}]}
|
|
17
23
|
|
|
18
24
|
|
|
19
25
|
def test_render_syntax_error():
|
|
20
26
|
renderer = JinjaRenderer()
|
|
21
27
|
with pytest.raises(JinjaError):
|
|
22
|
-
renderer.render("{% if %}"
|
|
28
|
+
renderer.render("{% if %}")
|
|
23
29
|
|
|
24
30
|
|
|
25
31
|
def test_render_non_mapping():
|
|
26
32
|
renderer = JinjaRenderer()
|
|
27
33
|
with pytest.raises(JinjaError):
|
|
28
|
-
renderer.render("- a\n- b\n"
|
|
34
|
+
renderer.render("- a\n- b\n")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|