nac-cli 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.
Files changed (68) hide show
  1. nac_cli-0.1.0/.github/dependabot.yml +20 -0
  2. nac_cli-0.1.0/.github/workflows/release.yml +148 -0
  3. nac_cli-0.1.0/.github/workflows/test.yml +194 -0
  4. nac_cli-0.1.0/.gitignore +85 -0
  5. nac_cli-0.1.0/.pre-commit-config.yaml +34 -0
  6. nac_cli-0.1.0/CHANGELOG.md +3 -0
  7. nac_cli-0.1.0/LICENSE +385 -0
  8. nac_cli-0.1.0/PKG-INFO +386 -0
  9. nac_cli-0.1.0/README.md +346 -0
  10. nac_cli-0.1.0/pyproject.toml +105 -0
  11. nac_cli-0.1.0/release-notes.md +2 -0
  12. nac_cli-0.1.0/scripts/license_headers.py +136 -0
  13. nac_cli-0.1.0/src/nac/__init__.py +6 -0
  14. nac_cli-0.1.0/src/nac/__main__.py +7 -0
  15. nac_cli-0.1.0/src/nac/bootstrap.py +181 -0
  16. nac_cli-0.1.0/src/nac/cli/__init__.py +24 -0
  17. nac_cli-0.1.0/src/nac/cli/apply.py +69 -0
  18. nac_cli-0.1.0/src/nac/cli/destroy.py +60 -0
  19. nac_cli-0.1.0/src/nac/cli/init.py +52 -0
  20. nac_cli-0.1.0/src/nac/cli/main.py +104 -0
  21. nac_cli-0.1.0/src/nac/cli/options.py +114 -0
  22. nac_cli-0.1.0/src/nac/cli/passthrough.py +117 -0
  23. nac_cli-0.1.0/src/nac/cli/plan.py +76 -0
  24. nac_cli-0.1.0/src/nac/cli/setup.py +179 -0
  25. nac_cli-0.1.0/src/nac/cli/test.py +63 -0
  26. nac_cli-0.1.0/src/nac/cli/validate.py +90 -0
  27. nac_cli-0.1.0/src/nac/cli/versions.py +92 -0
  28. nac_cli-0.1.0/src/nac/config.py +181 -0
  29. nac_cli-0.1.0/src/nac/exceptions.py +34 -0
  30. nac_cli-0.1.0/src/nac/manifest.py +155 -0
  31. nac_cli-0.1.0/src/nac/output.py +113 -0
  32. nac_cli-0.1.0/src/nac/runner.py +135 -0
  33. nac_cli-0.1.0/src/nac/terraform.py +142 -0
  34. nac_cli-0.1.0/src/nac/tools.py +169 -0
  35. nac_cli-0.1.0/tests/fixtures/nac-yaml/aci-lean/defaults.yaml +1 -0
  36. nac_cli-0.1.0/tests/fixtures/nac-yaml/aci-lean/nac.yaml +5 -0
  37. nac_cli-0.1.0/tests/fixtures/nac-yaml/filters-absent/nac.yaml +1 -0
  38. nac_cli-0.1.0/tests/fixtures/nac-yaml/filters-present/nac.yaml +1 -0
  39. nac_cli-0.1.0/tests/fixtures/nac-yaml/filters-present/tests/filters/.gitkeep +0 -0
  40. nac_cli-0.1.0/tests/fixtures/nac-yaml/invalid-syntax/nac.yaml +2 -0
  41. nac_cli-0.1.0/tests/fixtures/nac-yaml/iosxe-lean/nac.yaml +5 -0
  42. nac_cli-0.1.0/tests/fixtures/nac-yaml/minimal/nac.yaml +1 -0
  43. nac_cli-0.1.0/tests/fixtures/nac-yaml/nxos-full/nac.yaml +28 -0
  44. nac_cli-0.1.0/tests/fixtures/nac-yaml/nxos-lean/nac.yaml +10 -0
  45. nac_cli-0.1.0/tests/fixtures/nac-yaml/templates-absent/nac.yaml +1 -0
  46. nac_cli-0.1.0/tests/fixtures/nac-yaml/templates-present/nac.yaml +1 -0
  47. nac_cli-0.1.0/tests/fixtures/nac-yaml/templates-present/tests/templates/.gitkeep +0 -0
  48. nac_cli-0.1.0/tests/integration/fixtures/local_only/.terraform.lock.hcl +76 -0
  49. nac_cli-0.1.0/tests/integration/fixtures/local_only/broken/main.tf +13 -0
  50. nac_cli-0.1.0/tests/integration/fixtures/local_only/main.tf +22 -0
  51. nac_cli-0.1.0/tests/integration/fixtures/validate_test/.schema.yaml +1 -0
  52. nac_cli-0.1.0/tests/integration/fixtures/validate_test/data/main.yaml +1 -0
  53. nac_cli-0.1.0/tests/integration/fixtures/validate_test/templates/test.robot +6 -0
  54. nac_cli-0.1.0/tests/integration/test_streaming.py +184 -0
  55. nac_cli-0.1.0/tests/integration/test_validate_test.py +86 -0
  56. nac_cli-0.1.0/tests/unit/conftest.py +56 -0
  57. nac_cli-0.1.0/tests/unit/test_bootstrap.py +399 -0
  58. nac_cli-0.1.0/tests/unit/test_cli_commands.py +945 -0
  59. nac_cli-0.1.0/tests/unit/test_cli_setup.py +349 -0
  60. nac_cli-0.1.0/tests/unit/test_cli_versions.py +297 -0
  61. nac_cli-0.1.0/tests/unit/test_config.py +362 -0
  62. nac_cli-0.1.0/tests/unit/test_manifest.py +190 -0
  63. nac_cli-0.1.0/tests/unit/test_output.py +119 -0
  64. nac_cli-0.1.0/tests/unit/test_placeholder.py +11 -0
  65. nac_cli-0.1.0/tests/unit/test_runner.py +254 -0
  66. nac_cli-0.1.0/tests/unit/test_terraform.py +314 -0
  67. nac_cli-0.1.0/tests/unit/test_tools.py +494 -0
  68. nac_cli-0.1.0/uv.lock +872 -0
@@ -0,0 +1,20 @@
1
+ # Please see the documentation for all configuration options:
2
+ # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
3
+ version: 2
4
+ updates:
5
+ - package-ecosystem: 'github-actions'
6
+ directory: '/'
7
+ schedule:
8
+ interval: 'daily'
9
+ cooldown:
10
+ default-days: 3
11
+
12
+ # Native uv support updates pyproject.toml and uv.lock in the same pull request
13
+ - package-ecosystem: 'uv'
14
+ directory: '/'
15
+ schedule:
16
+ interval: 'daily'
17
+ allow:
18
+ - dependency-type: 'all'
19
+ cooldown:
20
+ default-days: 3
@@ -0,0 +1,148 @@
1
+ # Actions are pinned to commit SHAs (not version tags) to prevent supply chain attacks.
2
+ # Version comments (e.g., "# v7") allow Dependabot to track and update to newer versions.
3
+ name: Release
4
+
5
+ on:
6
+ push:
7
+ tags:
8
+ - "v*"
9
+
10
+ jobs:
11
+ test:
12
+ uses: ./.github/workflows/test.yml
13
+
14
+ build:
15
+ needs: test
16
+ runs-on: ubuntu-latest
17
+ permissions:
18
+ contents: read
19
+ steps:
20
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
21
+
22
+ - name: Install uv
23
+ uses: astral-sh/setup-uv@07a047e608db84b3d8c06cd9a7c7dcc9c355c18e # v10.3.2
24
+
25
+ - name: Check tag matches project version
26
+ env:
27
+ TAG: ${{ github.ref_name }}
28
+ run: |
29
+ VERSION=$(uv version --short)
30
+ if [ "$TAG" != "v$VERSION" ]; then
31
+ echo "Tag $TAG does not match pyproject.toml version $VERSION"
32
+ exit 1
33
+ fi
34
+
35
+ - name: Extract release notes from CHANGELOG.md
36
+ env:
37
+ TAG: ${{ github.ref_name }}
38
+ run: |
39
+ # Take the body of the "## <version>" section, up to the next "## " heading.
40
+ # Fails here, before anything is published, if the section is missing,
41
+ # still marked "unreleased" or empty.
42
+ VERSION="${TAG#v}"
43
+ rc=0
44
+ notes=$(awk -v v="$VERSION" '
45
+ /^## / {
46
+ if (found) exit
47
+ h = $2
48
+ gsub(/[][]/, "", h)
49
+ if (h == v) {
50
+ found = 1
51
+ if (tolower($0) ~ /unreleased/) unreleased = 1
52
+ }
53
+ next
54
+ }
55
+ found { print }
56
+ END {
57
+ if (!found) exit 2
58
+ if (unreleased) exit 3
59
+ }
60
+ ' CHANGELOG.md) || rc=$?
61
+ if [ "$rc" -eq 2 ]; then
62
+ echo "::error::No '## $VERSION' section found in CHANGELOG.md"
63
+ exit 1
64
+ elif [ "$rc" -eq 3 ]; then
65
+ echo "::error::CHANGELOG.md section for $VERSION is still marked unreleased"
66
+ exit 1
67
+ elif [ "$rc" -ne 0 ]; then
68
+ echo "::error::Failed to read CHANGELOG.md"
69
+ exit 1
70
+ fi
71
+ if [ -z "$(printf '%s' "$notes" | tr -d '[:space:]')" ]; then
72
+ echo "::error::CHANGELOG.md section for $VERSION is empty"
73
+ exit 1
74
+ fi
75
+ printf '%s\n' "$notes" > release-notes.md
76
+
77
+ - name: Build
78
+ run: uv build
79
+
80
+ - name: Upload distributions
81
+ uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
82
+ with:
83
+ name: dist
84
+ path: dist/
85
+ if-no-files-found: error
86
+
87
+ - name: Upload release notes
88
+ uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
89
+ with:
90
+ name: release-notes
91
+ path: release-notes.md
92
+ if-no-files-found: error
93
+
94
+ # Publishing is a separate job so that only it can mint the OIDC token PyPI
95
+ # trusts; the build step above never has access to it.
96
+ publish:
97
+ needs: build
98
+ runs-on: ubuntu-latest
99
+ environment: pypi
100
+ permissions:
101
+ contents: read
102
+ id-token: write # Required for PyPI trusted publishing
103
+ steps:
104
+ - name: Install uv
105
+ uses: astral-sh/setup-uv@07a047e608db84b3d8c06cd9a7c7dcc9c355c18e # v10.3.2
106
+
107
+ - name: Download distributions
108
+ uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
109
+ with:
110
+ name: dist
111
+ path: dist/
112
+
113
+ - name: Publish to PyPI
114
+ run: uv publish --trusted-publishing always
115
+
116
+ github-release:
117
+ needs: publish
118
+ runs-on: ubuntu-latest
119
+ permissions:
120
+ contents: write # Required to create the release
121
+ steps:
122
+ - name: Download distributions
123
+ uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
124
+ with:
125
+ name: dist
126
+ path: dist/
127
+
128
+ - name: Download release notes
129
+ uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
130
+ with:
131
+ name: release-notes
132
+
133
+ - name: Create GitHub release
134
+ env:
135
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
136
+ GH_REPO: ${{ github.repository }}
137
+ TAG: ${{ github.ref_name }}
138
+ run: |
139
+ # Versions with a letter (e.g. 1.0.0rc1) are marked as pre-releases
140
+ PRERELEASE=""
141
+ if [[ "${TAG#v}" =~ [a-zA-Z] ]]; then
142
+ PRERELEASE="--prerelease"
143
+ fi
144
+ gh release create "$TAG" dist/* \
145
+ --verify-tag \
146
+ --title "$TAG" \
147
+ --notes-file release-notes.md \
148
+ $PRERELEASE
@@ -0,0 +1,194 @@
1
+ # Actions are pinned to commit SHAs (not version tags) to prevent supply chain attacks.
2
+ # Version comments (e.g., "# v7") allow Dependabot to track and update to newer versions.
3
+ name: Tests
4
+ on:
5
+ workflow_dispatch:
6
+ pull_request:
7
+ push:
8
+ branches:
9
+ - main
10
+ workflow_call:
11
+
12
+ jobs:
13
+ security:
14
+ name: Security Scan
15
+ runs-on: ubuntu-latest
16
+ timeout-minutes: 5
17
+ permissions:
18
+ contents: read
19
+ steps:
20
+ - name: Checkout
21
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
22
+
23
+ - name: Install uv
24
+ uses: astral-sh/setup-uv@07a047e608db84b3d8c06cd9a7c7dcc9c355c18e # v10.3.2
25
+ with:
26
+ enable-cache: true
27
+
28
+ - name: Install bandit
29
+ run: uv tool install bandit[toml]
30
+
31
+ - name: Run bandit security scan
32
+ run: bandit -r src/nac/ -ll -f json -o bandit-security-report.json
33
+
34
+ - name: Upload security report
35
+ uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
36
+ if: always()
37
+ with:
38
+ name: bandit-security-report
39
+ path: bandit-security-report.json
40
+
41
+ lint:
42
+ name: Lint
43
+ runs-on: ubuntu-latest
44
+ timeout-minutes: 5
45
+ permissions:
46
+ contents: read
47
+ steps:
48
+ - name: Checkout
49
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
50
+
51
+ - name: Install uv
52
+ uses: astral-sh/setup-uv@07a047e608db84b3d8c06cd9a7c7dcc9c355c18e # v10.3.2
53
+ with:
54
+ enable-cache: true
55
+
56
+ - name: Sync dependencies
57
+ run: uv sync --extra dev
58
+
59
+ - name: Check License Headers
60
+ run: uv run python scripts/license_headers.py
61
+
62
+ - name: Pre-commit Checks
63
+ uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1
64
+
65
+ test:
66
+ name: Tests
67
+ runs-on: ${{ matrix.os }}
68
+ timeout-minutes: 10
69
+ permissions:
70
+ contents: read
71
+ strategy:
72
+ matrix:
73
+ os:
74
+ - ubuntu-latest
75
+ - macos-latest
76
+ - windows-latest
77
+ python:
78
+ - "3.11"
79
+ - "3.12"
80
+ - "3.13"
81
+ - "3.14"
82
+ steps:
83
+ - name: Checkout
84
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
85
+
86
+ - name: Python Setup
87
+ uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
88
+ with:
89
+ python-version: ${{ matrix.python }}
90
+
91
+ - name: Install uv
92
+ uses: astral-sh/setup-uv@07a047e608db84b3d8c06cd9a7c7dcc9c355c18e # v10.3.2
93
+ with:
94
+ enable-cache: true
95
+
96
+ - name: Install OpenTofu
97
+ uses: opentofu/setup-opentofu@a1320f892987e89d278cc92dc5adc984fb93aca4 # v2.0.2
98
+ with:
99
+ tofu_version: latest
100
+ tofu_wrapper: false
101
+
102
+ - name: Test
103
+ shell: bash
104
+ run: |
105
+ uv sync --extra dev
106
+ uv run pytest
107
+
108
+ notification:
109
+ name: Notification
110
+ # github.workflow is the caller's name when run via workflow_call, so this
111
+ # skips the release run (reusable workflows don't receive the Webex secrets)
112
+ if: always() && github.event_name != 'pull_request' && github.workflow == 'Tests' && github.actor != 'dependabot[bot]'
113
+ needs: [security, lint, test]
114
+ runs-on: ubuntu-latest
115
+ timeout-minutes: 5
116
+ permissions: {}
117
+ steps:
118
+ - name: Check Job Success
119
+ run: |
120
+ if [ ${{ needs.security.result }} == 'success' ] && [ ${{ needs.lint.result }} == 'success' ] && [ ${{ needs.test.result }} == 'success' ]; then
121
+ echo "All jobs succeeded"
122
+ echo "JOB_SUCCESS=success" >> $GITHUB_ENV
123
+ else
124
+ echo "Not all jobs succeeded"
125
+ echo "JOB_SUCCESS=fail" >> $GITHUB_ENV
126
+ fi
127
+ id: print_status
128
+
129
+ - name: Sanitize notification variables
130
+ env:
131
+ # Pass through env to avoid shell injection from server-side expression expansion
132
+ RAW_COMMIT_MSG: ${{ github.event.head_commit.message }}
133
+ RAW_PR_TITLE: ${{ github.event.pull_request.title }}
134
+ run: |
135
+ # Truncate to stay within Webex message size limits (500 chars for commit
136
+ # messages, 200 chars for PR titles are generous practical limits)
137
+ COMMIT_MSG=$(echo "$RAW_COMMIT_MSG" | head -c 500)
138
+ PR_TITLE=$(echo "$RAW_PR_TITLE" | head -c 200)
139
+
140
+ {
141
+ echo "SAFE_COMMIT_MSG<<EOF"
142
+ echo "$COMMIT_MSG"
143
+ echo "EOF"
144
+ echo "SAFE_PR_TITLE<<EOF"
145
+ echo "$PR_TITLE"
146
+ echo "EOF"
147
+ } >> "$GITHUB_ENV"
148
+
149
+ # Using direct curl to Webex API instead of a GitHub Action because:
150
+ # - qsnyder/action-wxt is unmaintained (last commit 2022, no releases)
151
+ # - Direct API call has no third-party action dependency to maintain
152
+ # - Gives full control over error handling and message format
153
+ - name: Webex Notification
154
+ if: always()
155
+ env:
156
+ # Pass sanitized env vars and static context values through env to avoid
157
+ # server-side expression expansion inside the shell script
158
+ JOB_SUCCESS: ${{ env.JOB_SUCCESS }}
159
+ SAFE_COMMIT_MSG: ${{ env.SAFE_COMMIT_MSG }}
160
+ SAFE_PR_TITLE: ${{ env.SAFE_PR_TITLE }}
161
+ WEBEX_TOKEN: ${{ secrets.WEBEX_TOKEN }}
162
+ WEBEX_ROOM_ID: ${{ secrets.WEBEX_ROOM_ID }}
163
+ COMMIT_URL: ${{ github.event.head_commit.url }}
164
+ AUTHOR: ${{ github.event.sender.login }}
165
+ REF: ${{ github.ref }}
166
+ HEAD_REF: ${{ github.head_ref }}
167
+ EVENT: ${{ github.event_name }}
168
+ REPO: ${{ github.repository }}
169
+ RUN_NUM: ${{ github.run_number }}
170
+ RUN_ID: ${{ github.run_id }}
171
+ run: |
172
+ # jq -n --arg handles all JSON escaping; avoids double-encoding from jq -Rs
173
+ PAYLOAD=$(jq -n \
174
+ --arg room_id "$WEBEX_ROOM_ID" \
175
+ --arg status "$JOB_SUCCESS" \
176
+ --arg repo "$REPO" \
177
+ --arg run_num "$RUN_NUM" \
178
+ --arg run_url "https://github.com/$REPO/actions/runs/$RUN_ID" \
179
+ --arg commit_msg "$SAFE_COMMIT_MSG" \
180
+ --arg commit_url "$COMMIT_URL" \
181
+ --arg pr_title "$SAFE_PR_TITLE" \
182
+ --arg author "$AUTHOR" \
183
+ --arg ref "$REF" \
184
+ --arg head_ref "$HEAD_REF" \
185
+ --arg event "$EVENT" \
186
+ '{roomId: $room_id, markdown: ("[**[\($status)] \($repo) #\($run_num)**](\($run_url))\n* Commit: [\($commit_msg)](\($commit_url))\($pr_title)\n* Author: \($author)\n* Branch: \($ref) \($head_ref)\n* Event: \($event)")}')
187
+ # --fail-with-body: exit non-zero on HTTP 4xx/5xx so the || warning fires
188
+ curl --silent --fail-with-body --max-time 30 \
189
+ --request POST \
190
+ --url https://webexapis.com/v1/messages \
191
+ --header "Authorization: Bearer $WEBEX_TOKEN" \
192
+ --header "Content-Type: application/json" \
193
+ --data "$PAYLOAD" \
194
+ || echo "::warning::Webex notification failed"
@@ -0,0 +1,85 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ env/
12
+ build/
13
+ develop-eggs/
14
+ dist/
15
+ downloads/
16
+ eggs/
17
+ .eggs/
18
+ lib/
19
+ lib64/
20
+ parts/
21
+ sdist/
22
+ var/
23
+ wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+
28
+ # PyInstaller
29
+ # Usually these files are written by a python script from a template
30
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
31
+ *.manifest
32
+ *.spec
33
+
34
+ # Installer logs
35
+ pip-log.txt
36
+ pip-delete-this-directory.txt
37
+
38
+ # Unit test / coverage reports
39
+ htmlcov/
40
+ .tox/
41
+ .coverage
42
+ .coverage.*
43
+ .cache
44
+ coverage.xml
45
+ *,cover
46
+ .pytest_cache/
47
+
48
+ # Translations
49
+ *.mo
50
+ *.pot
51
+
52
+ # Sphinx documentation
53
+ docs/_build/
54
+
55
+ # PyBuilder
56
+ target/
57
+
58
+ # pyenv
59
+ .python-version
60
+
61
+ # dotenv
62
+ .env
63
+
64
+ # virtualenv
65
+ .venv/
66
+ venv/
67
+ ENV/
68
+
69
+ # Editor temp files
70
+ .*.swp
71
+
72
+ # Visual Studio Code
73
+ .vscode
74
+
75
+ tmp/
76
+
77
+ .mypy_cache/
78
+
79
+ logs/
80
+
81
+ # Terraform/OpenTofu working dirs created when running fixtures directly
82
+ .terraform/
83
+ *.tfstate
84
+ *.tfstate.backup
85
+
@@ -0,0 +1,34 @@
1
+ repos:
2
+ - repo: local
3
+ hooks:
4
+ - id: license-headers
5
+ name: Check license headers
6
+ entry: uv run python scripts/license_headers.py --fix
7
+ language: system
8
+ types: [python]
9
+
10
+ - id: ruff-check
11
+ name: ruff check
12
+ entry: uv run ruff check --fix --exit-non-zero-on-fix
13
+ language: system
14
+ types: [python]
15
+
16
+ - id: ruff-format
17
+ name: ruff format
18
+ entry: uv run ruff format
19
+ language: system
20
+ types: [python]
21
+
22
+ - id: ty
23
+ name: ty
24
+ entry: uv run ty check
25
+ language: system
26
+ types: [python]
27
+ pass_filenames: false
28
+
29
+ - id: bandit
30
+ name: bandit
31
+ entry: uv run bandit -r src/nac/ -ll
32
+ language: system
33
+ types: [python]
34
+ pass_filenames: false
@@ -0,0 +1,3 @@
1
+ ## 0.1.0
2
+
3
+ - Initial release