devaci 0.1.0__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-0.1.0/.gitignore +236 -0
- devaci-0.1.0/LICENSE +21 -0
- devaci-0.1.0/PKG-INFO +110 -0
- devaci-0.1.0/README.md +81 -0
- devaci-0.1.0/pyproject.toml +86 -0
- devaci-0.1.0/src/devaci/__init__.py +20 -0
- devaci-0.1.0/src/devaci/cobra/__init__.py +93 -0
- devaci-0.1.0/src/devaci/cobra/base.py +28 -0
- devaci-0.1.0/src/devaci/cobra/builders.py +1886 -0
- devaci-0.1.0/src/devaci/cobra/registry.py +24 -0
- devaci-0.1.0/src/devaci/console.py +34 -0
- devaci-0.1.0/src/devaci/data.py +81 -0
- devaci-0.1.0/src/devaci/deploy.py +320 -0
- devaci-0.1.0/src/devaci/exceptions.py +23 -0
- devaci-0.1.0/src/devaci/filters.py +103 -0
- devaci-0.1.0/src/devaci/jinja.py +42 -0
- devaci-0.1.0/src/devaci/py.typed +0 -0
- devaci-0.1.0/src/devaci/results.py +71 -0
- devaci-0.1.0/tests/__init__.py +0 -0
- devaci-0.1.0/tests/test.py +22 -0
- devaci-0.1.0/tests/test_cobra.py +10 -0
- devaci-0.1.0/tests/test_deploy.py +30 -0
- devaci-0.1.0/tests/test_devaci.py +5 -0
- devaci-0.1.0/tests/test_outputs.py +48 -0
devaci-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[codz]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
share/python-wheels/
|
|
24
|
+
*.egg-info/
|
|
25
|
+
.installed.cfg
|
|
26
|
+
*.egg
|
|
27
|
+
MANIFEST
|
|
28
|
+
|
|
29
|
+
# PyInstaller
|
|
30
|
+
# Usually these files are written by a python script from a template
|
|
31
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
32
|
+
*.manifest
|
|
33
|
+
*.spec
|
|
34
|
+
|
|
35
|
+
# Installer logs
|
|
36
|
+
pip-log.txt
|
|
37
|
+
pip-delete-this-directory.txt
|
|
38
|
+
|
|
39
|
+
# Unit test / coverage reports
|
|
40
|
+
htmlcov/
|
|
41
|
+
.tox/
|
|
42
|
+
.nox/
|
|
43
|
+
.coverage
|
|
44
|
+
.coverage.*
|
|
45
|
+
.cache
|
|
46
|
+
nosetests.xml
|
|
47
|
+
coverage.xml
|
|
48
|
+
*.cover
|
|
49
|
+
*.py.cover
|
|
50
|
+
*.lcov
|
|
51
|
+
.hypothesis/
|
|
52
|
+
.pytest_cache/
|
|
53
|
+
cover/
|
|
54
|
+
|
|
55
|
+
# Translations
|
|
56
|
+
*.mo
|
|
57
|
+
*.pot
|
|
58
|
+
|
|
59
|
+
# Django stuff:
|
|
60
|
+
*.log
|
|
61
|
+
local_settings.py
|
|
62
|
+
db.sqlite3
|
|
63
|
+
db.sqlite3-journal
|
|
64
|
+
|
|
65
|
+
# Flask stuff:
|
|
66
|
+
instance/
|
|
67
|
+
.webassets-cache
|
|
68
|
+
|
|
69
|
+
# Scrapy stuff:
|
|
70
|
+
.scrapy
|
|
71
|
+
|
|
72
|
+
# Sphinx documentation
|
|
73
|
+
docs/_build/
|
|
74
|
+
|
|
75
|
+
# PyBuilder
|
|
76
|
+
.pybuilder/
|
|
77
|
+
target/
|
|
78
|
+
|
|
79
|
+
# Jupyter Notebook
|
|
80
|
+
.ipynb_checkpoints
|
|
81
|
+
|
|
82
|
+
# IPython
|
|
83
|
+
profile_default/
|
|
84
|
+
ipython_config.py
|
|
85
|
+
|
|
86
|
+
# pyenv
|
|
87
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
88
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
89
|
+
# .python-version
|
|
90
|
+
|
|
91
|
+
# pipenv
|
|
92
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
93
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
94
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
95
|
+
# install all needed dependencies.
|
|
96
|
+
# Pipfile.lock
|
|
97
|
+
|
|
98
|
+
# uv
|
|
99
|
+
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
|
100
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
101
|
+
# commonly ignored for libraries.
|
|
102
|
+
# uv.lock
|
|
103
|
+
|
|
104
|
+
# poetry
|
|
105
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
106
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
107
|
+
# commonly ignored for libraries.
|
|
108
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
109
|
+
# poetry.lock
|
|
110
|
+
# poetry.toml
|
|
111
|
+
|
|
112
|
+
# pdm
|
|
113
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
114
|
+
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
|
|
115
|
+
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
|
|
116
|
+
# pdm.lock
|
|
117
|
+
# pdm.toml
|
|
118
|
+
.pdm-python
|
|
119
|
+
.pdm-build/
|
|
120
|
+
|
|
121
|
+
# pixi
|
|
122
|
+
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
|
|
123
|
+
# pixi.lock
|
|
124
|
+
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
|
|
125
|
+
# in the .venv directory. It is recommended not to include this directory in version control.
|
|
126
|
+
.pixi/*
|
|
127
|
+
!.pixi/config.toml
|
|
128
|
+
|
|
129
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
130
|
+
__pypackages__/
|
|
131
|
+
|
|
132
|
+
# Celery stuff
|
|
133
|
+
celerybeat-schedule*
|
|
134
|
+
celerybeat.pid
|
|
135
|
+
|
|
136
|
+
# Redis
|
|
137
|
+
*.rdb
|
|
138
|
+
*.aof
|
|
139
|
+
*.pid
|
|
140
|
+
|
|
141
|
+
# RabbitMQ
|
|
142
|
+
mnesia/
|
|
143
|
+
rabbitmq/
|
|
144
|
+
rabbitmq-data/
|
|
145
|
+
|
|
146
|
+
# ActiveMQ
|
|
147
|
+
activemq-data/
|
|
148
|
+
|
|
149
|
+
# SageMath parsed files
|
|
150
|
+
*.sage.py
|
|
151
|
+
|
|
152
|
+
# Environments
|
|
153
|
+
.env
|
|
154
|
+
.envrc
|
|
155
|
+
.venv
|
|
156
|
+
env/
|
|
157
|
+
venv/
|
|
158
|
+
ENV/
|
|
159
|
+
env.bak/
|
|
160
|
+
venv.bak/
|
|
161
|
+
|
|
162
|
+
# Spyder project settings
|
|
163
|
+
.spyderproject
|
|
164
|
+
.spyproject
|
|
165
|
+
|
|
166
|
+
# Rope project settings
|
|
167
|
+
.ropeproject
|
|
168
|
+
|
|
169
|
+
# mkdocs/Zensical documentation
|
|
170
|
+
/site
|
|
171
|
+
|
|
172
|
+
# mypy
|
|
173
|
+
.mypy_cache/
|
|
174
|
+
.dmypy.json
|
|
175
|
+
dmypy.json
|
|
176
|
+
|
|
177
|
+
# Pyre type checker
|
|
178
|
+
.pyre/
|
|
179
|
+
|
|
180
|
+
# pytype static type analyzer
|
|
181
|
+
.pytype/
|
|
182
|
+
|
|
183
|
+
# Cython debug symbols
|
|
184
|
+
cython_debug/
|
|
185
|
+
|
|
186
|
+
# PyCharm
|
|
187
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
188
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
189
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
190
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
191
|
+
# .idea/
|
|
192
|
+
|
|
193
|
+
# Abstra
|
|
194
|
+
# Abstra is an AI-powered process automation framework.
|
|
195
|
+
# Ignore directories containing user credentials, local state, and settings.
|
|
196
|
+
# Learn more at https://abstra.io/docs
|
|
197
|
+
.abstra/
|
|
198
|
+
|
|
199
|
+
# Visual Studio Code
|
|
200
|
+
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore that
|
|
201
|
+
# can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
|
202
|
+
# and can be added to the global gitignore or merged into this file. However, if you prefer, you
|
|
203
|
+
# could uncomment the following to ignore the entire vscode folder
|
|
204
|
+
# .vscode/
|
|
205
|
+
# Temporary file for partial code execution
|
|
206
|
+
tempCodeRunnerFile.py
|
|
207
|
+
|
|
208
|
+
# Ruff stuff:
|
|
209
|
+
.ruff_cache/
|
|
210
|
+
|
|
211
|
+
# PyPI configuration file
|
|
212
|
+
.pypirc
|
|
213
|
+
|
|
214
|
+
# Vendor binaries (Cisco ACI Cobra SDK wheels)
|
|
215
|
+
vendor/
|
|
216
|
+
|
|
217
|
+
# Local reminder: do not publish yet
|
|
218
|
+
DO_NOT_PUBLISH.md
|
|
219
|
+
|
|
220
|
+
# Sensitive test data (e.g. xlsx, csv) - not committed
|
|
221
|
+
data/
|
|
222
|
+
|
|
223
|
+
# Generated outputs (private ACI configuration) - not committed
|
|
224
|
+
outputs/
|
|
225
|
+
scripts/
|
|
226
|
+
logs/
|
|
227
|
+
logging.json
|
|
228
|
+
tests/testing/
|
|
229
|
+
|
|
230
|
+
# Marimo
|
|
231
|
+
marimo/_static/
|
|
232
|
+
marimo/_lsp/
|
|
233
|
+
__marimo__/
|
|
234
|
+
|
|
235
|
+
# Streamlit
|
|
236
|
+
.streamlit/secrets.toml
|
devaci-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jorge Riveros
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
devaci-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: devaci
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python library that generates Cisco ACI configuration and optionally pushes it to an APIC controller via Cisco's official Cobra SDK.
|
|
5
|
+
Project-URL: Homepage, https://github.com/cocuni80/devaci
|
|
6
|
+
Project-URL: Repository, https://github.com/cocuni80/devaci
|
|
7
|
+
Project-URL: Issues, https://github.com/cocuni80/devaci/issues
|
|
8
|
+
Author: Jorge Riveros
|
|
9
|
+
License: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: aci,apic,cisco,cobra,networking,sdn
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: System Administrators
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Topic :: System :: Networking
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Requires-Dist: jinja2>=3.1.6
|
|
24
|
+
Requires-Dist: openpyxl>=3.1.5
|
|
25
|
+
Requires-Dist: pandas>=2.3.3
|
|
26
|
+
Requires-Dist: pyyaml>=6.0.3
|
|
27
|
+
Requires-Dist: rich>=15.0.0
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
|
|
30
|
+
# devaci
|
|
31
|
+
|
|
32
|
+
Python library that generates Cisco ACI configuration and optionally pushes it to an APIC controller via Cisco's official Cobra SDK.
|
|
33
|
+
|
|
34
|
+
## Structure
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
devaci/
|
|
38
|
+
├── LICENSE
|
|
39
|
+
├── README.md
|
|
40
|
+
├── pyproject.toml
|
|
41
|
+
├── pyrightconfig.json
|
|
42
|
+
├── uv.lock
|
|
43
|
+
├── src/
|
|
44
|
+
│ └── devaci/
|
|
45
|
+
│ ├── __init__.py # public API
|
|
46
|
+
│ ├── console.py # logging + rich console
|
|
47
|
+
│ ├── data.py # xlsx/csv loading and filtering
|
|
48
|
+
│ ├── deploy.py # DeployClass orchestrator
|
|
49
|
+
│ ├── exceptions.py
|
|
50
|
+
│ ├── filters.py # Jinja filters + YAML loader
|
|
51
|
+
│ ├── jinja.py # JinjaRenderer
|
|
52
|
+
│ ├── results.py # result dataclasses
|
|
53
|
+
│ └── cobra/
|
|
54
|
+
│ ├── __init__.py # CobraBuilder
|
|
55
|
+
│ ├── base.py
|
|
56
|
+
│ ├── builders.py # registered ACI object handlers
|
|
57
|
+
│ └── registry.py
|
|
58
|
+
└── tests/
|
|
59
|
+
├── test_cobra.py
|
|
60
|
+
├── test_deploy.py
|
|
61
|
+
├── test_devaci.py
|
|
62
|
+
└── test_outputs.py
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Installation (development)
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
pip install uv # if you don't have it yet
|
|
69
|
+
uv sync # creates .venv and installs devaci + dev dependencies
|
|
70
|
+
source .venv/bin/activate
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
To add/remove dependencies:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
uv add requests # runtime dependency
|
|
77
|
+
uv add --dev httpx # development dependency
|
|
78
|
+
uv remove requests
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## External dependency: Cobra SDK
|
|
82
|
+
|
|
83
|
+
The Cisco ACI Cobra SDK (`acicobra`, `acimodel`, `aciparser`, `acirpc`) is **not on PyPI**.
|
|
84
|
+
Download the `.whl` files from Cisco and place them in the `vendor/` folder (not tracked by git), then:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
uv pip install vendor/*.whl
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Tests that depend on the Cobra SDK fail if it is not installed.
|
|
91
|
+
|
|
92
|
+
## Tests
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
uv run pytest
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Lint and type checking
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
uv run ruff check .
|
|
102
|
+
uv run mypy src
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Publishing
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
uv build
|
|
109
|
+
uv publish
|
|
110
|
+
```
|
devaci-0.1.0/README.md
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# devaci
|
|
2
|
+
|
|
3
|
+
Python library that generates Cisco ACI configuration and optionally pushes it to an APIC controller via Cisco's official Cobra SDK.
|
|
4
|
+
|
|
5
|
+
## Structure
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
devaci/
|
|
9
|
+
├── LICENSE
|
|
10
|
+
├── README.md
|
|
11
|
+
├── pyproject.toml
|
|
12
|
+
├── pyrightconfig.json
|
|
13
|
+
├── uv.lock
|
|
14
|
+
├── src/
|
|
15
|
+
│ └── devaci/
|
|
16
|
+
│ ├── __init__.py # public API
|
|
17
|
+
│ ├── console.py # logging + rich console
|
|
18
|
+
│ ├── data.py # xlsx/csv loading and filtering
|
|
19
|
+
│ ├── deploy.py # DeployClass orchestrator
|
|
20
|
+
│ ├── exceptions.py
|
|
21
|
+
│ ├── filters.py # Jinja filters + YAML loader
|
|
22
|
+
│ ├── jinja.py # JinjaRenderer
|
|
23
|
+
│ ├── results.py # result dataclasses
|
|
24
|
+
│ └── cobra/
|
|
25
|
+
│ ├── __init__.py # CobraBuilder
|
|
26
|
+
│ ├── base.py
|
|
27
|
+
│ ├── builders.py # registered ACI object handlers
|
|
28
|
+
│ └── registry.py
|
|
29
|
+
└── tests/
|
|
30
|
+
├── test_cobra.py
|
|
31
|
+
├── test_deploy.py
|
|
32
|
+
├── test_devaci.py
|
|
33
|
+
└── test_outputs.py
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Installation (development)
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install uv # if you don't have it yet
|
|
40
|
+
uv sync # creates .venv and installs devaci + dev dependencies
|
|
41
|
+
source .venv/bin/activate
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
To add/remove dependencies:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
uv add requests # runtime dependency
|
|
48
|
+
uv add --dev httpx # development dependency
|
|
49
|
+
uv remove requests
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## External dependency: Cobra SDK
|
|
53
|
+
|
|
54
|
+
The Cisco ACI Cobra SDK (`acicobra`, `acimodel`, `aciparser`, `acirpc`) is **not on PyPI**.
|
|
55
|
+
Download the `.whl` files from Cisco and place them in the `vendor/` folder (not tracked by git), then:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
uv pip install vendor/*.whl
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Tests that depend on the Cobra SDK fail if it is not installed.
|
|
62
|
+
|
|
63
|
+
## Tests
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
uv run pytest
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Lint and type checking
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
uv run ruff check .
|
|
73
|
+
uv run mypy src
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Publishing
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
uv build
|
|
80
|
+
uv publish
|
|
81
|
+
```
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "devaci"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Python library that generates Cisco ACI configuration and optionally pushes it to an APIC controller via Cisco's official Cobra SDK."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Jorge Riveros" },
|
|
14
|
+
]
|
|
15
|
+
keywords = ["cisco", "aci", "apic", "cobra", "networking", "sdn"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 3 - Alpha",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"Intended Audience :: System Administrators",
|
|
20
|
+
"License :: OSI Approved :: MIT License",
|
|
21
|
+
"Operating System :: OS Independent",
|
|
22
|
+
"Programming Language :: Python :: 3",
|
|
23
|
+
"Programming Language :: Python :: 3.10",
|
|
24
|
+
"Programming Language :: Python :: 3.11",
|
|
25
|
+
"Programming Language :: Python :: 3.12",
|
|
26
|
+
"Topic :: System :: Networking",
|
|
27
|
+
]
|
|
28
|
+
dependencies = [
|
|
29
|
+
# Note: the Cisco ACI Cobra SDK (acicobra, acimodel, aciparser, acirpc)
|
|
30
|
+
# is distributed as .whl/.egg by Cisco and is NOT on PyPI.
|
|
31
|
+
# Install it separately before using devaci.
|
|
32
|
+
"jinja2>=3.1.6",
|
|
33
|
+
"openpyxl>=3.1.5",
|
|
34
|
+
"pandas>=2.3.3",
|
|
35
|
+
"pyyaml>=6.0.3",
|
|
36
|
+
"rich>=15.0.0",
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
[dependency-groups]
|
|
40
|
+
dev = [
|
|
41
|
+
"pytest>=8.0",
|
|
42
|
+
"pytest-cov>=5.0",
|
|
43
|
+
"ruff>=0.5",
|
|
44
|
+
"mypy>=1.10",
|
|
45
|
+
"types-pyyaml>=6.0.12.20260906",
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
[project.urls]
|
|
49
|
+
Homepage = "https://github.com/cocuni80/devaci"
|
|
50
|
+
Repository = "https://github.com/cocuni80/devaci"
|
|
51
|
+
Issues = "https://github.com/cocuni80/devaci/issues"
|
|
52
|
+
|
|
53
|
+
[tool.hatch.build.targets.wheel]
|
|
54
|
+
packages = ["src/devaci"]
|
|
55
|
+
exclude = ["src/devaci/_legacy"]
|
|
56
|
+
|
|
57
|
+
[tool.hatch.build.targets.sdist]
|
|
58
|
+
include = ["src/devaci", "tests", "README.md", "LICENSE"]
|
|
59
|
+
exclude = ["src/devaci/_legacy"]
|
|
60
|
+
|
|
61
|
+
[tool.pytest.ini_options]
|
|
62
|
+
testpaths = ["tests"]
|
|
63
|
+
addopts = "-ra"
|
|
64
|
+
|
|
65
|
+
[tool.ruff]
|
|
66
|
+
target-version = "py310"
|
|
67
|
+
line-length = 100
|
|
68
|
+
exclude = ["src/devaci/_legacy"]
|
|
69
|
+
|
|
70
|
+
[tool.ruff.lint]
|
|
71
|
+
select = ["E", "F", "I", "N", "UP", "B", "SIM"]
|
|
72
|
+
|
|
73
|
+
[tool.ruff.lint.per-file-ignores]
|
|
74
|
+
# Cobra SDK convention uses PascalCase locals mirroring Mo class names,
|
|
75
|
+
# and the legacy handlers use nested `if` guards (kept verbatim).
|
|
76
|
+
"src/devaci/cobra/builders.py" = ["N806", "SIM102"]
|
|
77
|
+
|
|
78
|
+
[tool.mypy]
|
|
79
|
+
python_version = "3.10"
|
|
80
|
+
strict = true
|
|
81
|
+
files = ["src"]
|
|
82
|
+
exclude = ["_legacy"]
|
|
83
|
+
|
|
84
|
+
[[tool.mypy.overrides]]
|
|
85
|
+
module = ["cobra", "cobra.*", "jinja2", "pandas"]
|
|
86
|
+
ignore_missing_imports = true
|
|
@@ -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
|
+
]
|
|
@@ -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
|
|
@@ -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)
|