devaci 0.1.0__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.
- devaci/__init__.py +20 -0
- devaci/cobra/__init__.py +93 -0
- devaci/cobra/base.py +28 -0
- devaci/cobra/builders.py +1886 -0
- devaci/cobra/registry.py +24 -0
- devaci/console.py +34 -0
- devaci/data.py +81 -0
- devaci/deploy.py +320 -0
- devaci/exceptions.py +23 -0
- devaci/filters.py +103 -0
- devaci/jinja.py +42 -0
- devaci/py.typed +0 -0
- devaci/results.py +71 -0
- devaci-0.1.0.dist-info/METADATA +110 -0
- devaci-0.1.0.dist-info/RECORD +17 -0
- devaci-0.1.0.dist-info/WHEEL +4 -0
- devaci-0.1.0.dist-info/licenses/LICENSE +21 -0
devaci/__init__.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""devaci - Generate and push Cisco ACI configuration via the Cobra SDK."""
|
|
2
|
+
|
|
3
|
+
from devaci.cobra import CobraBuilder
|
|
4
|
+
from devaci.deploy import DeployClass
|
|
5
|
+
from devaci.exceptions import CobraError, DataError, DeployError, DevaciError, JinjaError
|
|
6
|
+
from devaci.jinja import JinjaRenderer
|
|
7
|
+
|
|
8
|
+
__version__ = "0.1.0"
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"CobraBuilder",
|
|
12
|
+
"DeployClass",
|
|
13
|
+
"JinjaRenderer",
|
|
14
|
+
"DevaciError",
|
|
15
|
+
"CobraError",
|
|
16
|
+
"JinjaError",
|
|
17
|
+
"DataError",
|
|
18
|
+
"DeployError",
|
|
19
|
+
"__version__",
|
|
20
|
+
]
|
devaci/cobra/__init__.py
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""Cobra model builder for devaci."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from typing import Any, cast
|
|
7
|
+
|
|
8
|
+
import cobra.mit.request
|
|
9
|
+
import cobra.model.pol
|
|
10
|
+
|
|
11
|
+
from devaci.cobra.registry import REGISTRY
|
|
12
|
+
from devaci.console import logger
|
|
13
|
+
from devaci.results import CobraResult
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class CobraBuilder:
|
|
17
|
+
"""Build an ACI configuration (:class:`ConfigRequest`) from a rendered dict.
|
|
18
|
+
|
|
19
|
+
The dict's top-level keys map to registered handlers (see
|
|
20
|
+
:mod:`devaci.cobra.builders`). Each handler receives the builder and the
|
|
21
|
+
list of objects and is responsible for adding Mo instances to
|
|
22
|
+
:attr:`config`.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(self) -> None:
|
|
26
|
+
self._root = ""
|
|
27
|
+
self._uni = cobra.model.pol.Uni(self._root)
|
|
28
|
+
self.config = cobra.mit.request.ConfigRequest()
|
|
29
|
+
self.result: CobraResult | None = None
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
def root(self) -> str:
|
|
33
|
+
"""Return the ACI root (policy universe DN)."""
|
|
34
|
+
return self._root
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def uni(self) -> cobra.model.pol.Uni:
|
|
38
|
+
"""Return the root ``pol.Uni`` managed object."""
|
|
39
|
+
return self._uni
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def xml(self) -> str | None:
|
|
43
|
+
"""Return the rendered XML payload."""
|
|
44
|
+
return cast(str | None, self.config.xmldata)
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def json(self) -> Any:
|
|
48
|
+
"""Return the rendered JSON payload as a dict."""
|
|
49
|
+
return json.loads(self.config.data)
|
|
50
|
+
|
|
51
|
+
def render(self, output: dict[str, Any]) -> CobraResult:
|
|
52
|
+
"""Render ``output`` through the registered handlers.
|
|
53
|
+
|
|
54
|
+
Keys with empty values are skipped; unknown keys and handler errors
|
|
55
|
+
are logged and reported via the returned :class:`CobraResult`.
|
|
56
|
+
"""
|
|
57
|
+
logs: list[str] = []
|
|
58
|
+
success = True
|
|
59
|
+
|
|
60
|
+
for key, value in output.items():
|
|
61
|
+
if value in (None, [], {}, ""):
|
|
62
|
+
continue
|
|
63
|
+
|
|
64
|
+
handler = REGISTRY.get(key)
|
|
65
|
+
if handler is None:
|
|
66
|
+
success = False
|
|
67
|
+
msg = f"[Cobra] -> [ConfigError]: Class {key} does not exist."
|
|
68
|
+
logger.warning(msg)
|
|
69
|
+
logs.append(msg)
|
|
70
|
+
continue
|
|
71
|
+
|
|
72
|
+
try:
|
|
73
|
+
handler(self, value)
|
|
74
|
+
msg = f"[Cobra]: Class {key} was rendered successfully."
|
|
75
|
+
logger.info(msg)
|
|
76
|
+
logs.append(msg)
|
|
77
|
+
except Exception as exc:
|
|
78
|
+
success = False
|
|
79
|
+
msg = f"[Cobra] -> [{type(exc).__name__}]: Class {key} failed: {exc}"
|
|
80
|
+
logger.error(msg)
|
|
81
|
+
logs.append(msg)
|
|
82
|
+
|
|
83
|
+
if not self.config.configMos:
|
|
84
|
+
success = False
|
|
85
|
+
msg = "[Cobra] -> [ConfigError]: No object was found in configuration."
|
|
86
|
+
logger.warning(msg)
|
|
87
|
+
logs.append(msg)
|
|
88
|
+
|
|
89
|
+
self.result = CobraResult(success=success, log=logs, config=self.config)
|
|
90
|
+
return self.result
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
from devaci.cobra import builders # noqa: E402, F401
|
devaci/cobra/base.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""Shared helpers for Cobra object builders."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Iterable, Mapping
|
|
6
|
+
from math import isnan
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def not_nan_str(value: Mapping[str, Any], keys: Iterable[str]) -> bool:
|
|
11
|
+
"""Return True when none of the given keys hold an invalid value.
|
|
12
|
+
|
|
13
|
+
A value is considered invalid when it is ``None``, an empty/whitespace
|
|
14
|
+
string, the string ``nan`` (case-insensitive), or a float NaN. Missing
|
|
15
|
+
keys are ignored.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def is_invalid(v: Any) -> bool:
|
|
19
|
+
if v is None:
|
|
20
|
+
return True
|
|
21
|
+
if isinstance(v, str):
|
|
22
|
+
v = v.strip()
|
|
23
|
+
return not v or v.lower() == "nan"
|
|
24
|
+
if isinstance(v, float):
|
|
25
|
+
return isnan(v)
|
|
26
|
+
return False
|
|
27
|
+
|
|
28
|
+
return not any(is_invalid(value[k]) for k in keys if k in value)
|