nac-cli 0.1.0__tar.gz → 0.2.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.
- {nac_cli-0.1.0 → nac_cli-0.2.0}/.github/workflows/release.yml +2 -2
- {nac_cli-0.1.0 → nac_cli-0.2.0}/.github/workflows/test.yml +3 -3
- nac_cli-0.2.0/CHANGELOG.md +8 -0
- nac_cli-0.2.0/PKG-INFO +106 -0
- nac_cli-0.2.0/README.md +66 -0
- {nac_cli-0.1.0 → nac_cli-0.2.0}/pyproject.toml +1 -1
- nac_cli-0.2.0/release-notes.md +3 -0
- {nac_cli-0.1.0 → nac_cli-0.2.0}/src/nac/cli/setup.py +18 -7
- {nac_cli-0.1.0 → nac_cli-0.2.0}/src/nac/cli/test.py +18 -9
- {nac_cli-0.1.0 → nac_cli-0.2.0}/src/nac/cli/validate.py +16 -6
- {nac_cli-0.1.0 → nac_cli-0.2.0}/src/nac/exceptions.py +4 -0
- {nac_cli-0.1.0 → nac_cli-0.2.0}/src/nac/tools.py +55 -1
- {nac_cli-0.1.0 → nac_cli-0.2.0}/tests/unit/test_cli_commands.py +98 -7
- {nac_cli-0.1.0 → nac_cli-0.2.0}/tests/unit/test_cli_setup.py +52 -0
- {nac_cli-0.1.0 → nac_cli-0.2.0}/tests/unit/test_cli_versions.py +8 -8
- {nac_cli-0.1.0 → nac_cli-0.2.0}/tests/unit/test_tools.py +42 -2
- {nac_cli-0.1.0 → nac_cli-0.2.0}/uv.lock +136 -201
- nac_cli-0.1.0/CHANGELOG.md +0 -3
- nac_cli-0.1.0/PKG-INFO +0 -386
- nac_cli-0.1.0/README.md +0 -346
- nac_cli-0.1.0/release-notes.md +0 -2
- {nac_cli-0.1.0 → nac_cli-0.2.0}/.github/dependabot.yml +0 -0
- {nac_cli-0.1.0 → nac_cli-0.2.0}/.gitignore +0 -0
- {nac_cli-0.1.0 → nac_cli-0.2.0}/.pre-commit-config.yaml +0 -0
- {nac_cli-0.1.0 → nac_cli-0.2.0}/LICENSE +0 -0
- {nac_cli-0.1.0 → nac_cli-0.2.0}/scripts/license_headers.py +0 -0
- {nac_cli-0.1.0 → nac_cli-0.2.0}/src/nac/__init__.py +0 -0
- {nac_cli-0.1.0 → nac_cli-0.2.0}/src/nac/__main__.py +0 -0
- {nac_cli-0.1.0 → nac_cli-0.2.0}/src/nac/bootstrap.py +0 -0
- {nac_cli-0.1.0 → nac_cli-0.2.0}/src/nac/cli/__init__.py +0 -0
- {nac_cli-0.1.0 → nac_cli-0.2.0}/src/nac/cli/apply.py +0 -0
- {nac_cli-0.1.0 → nac_cli-0.2.0}/src/nac/cli/destroy.py +0 -0
- {nac_cli-0.1.0 → nac_cli-0.2.0}/src/nac/cli/init.py +0 -0
- {nac_cli-0.1.0 → nac_cli-0.2.0}/src/nac/cli/main.py +0 -0
- {nac_cli-0.1.0 → nac_cli-0.2.0}/src/nac/cli/options.py +0 -0
- {nac_cli-0.1.0 → nac_cli-0.2.0}/src/nac/cli/passthrough.py +0 -0
- {nac_cli-0.1.0 → nac_cli-0.2.0}/src/nac/cli/plan.py +0 -0
- {nac_cli-0.1.0 → nac_cli-0.2.0}/src/nac/cli/versions.py +0 -0
- {nac_cli-0.1.0 → nac_cli-0.2.0}/src/nac/config.py +0 -0
- {nac_cli-0.1.0 → nac_cli-0.2.0}/src/nac/manifest.py +0 -0
- {nac_cli-0.1.0 → nac_cli-0.2.0}/src/nac/output.py +0 -0
- {nac_cli-0.1.0 → nac_cli-0.2.0}/src/nac/runner.py +0 -0
- {nac_cli-0.1.0 → nac_cli-0.2.0}/src/nac/terraform.py +0 -0
- {nac_cli-0.1.0 → nac_cli-0.2.0}/tests/fixtures/nac-yaml/aci-lean/defaults.yaml +0 -0
- {nac_cli-0.1.0 → nac_cli-0.2.0}/tests/fixtures/nac-yaml/aci-lean/nac.yaml +0 -0
- {nac_cli-0.1.0 → nac_cli-0.2.0}/tests/fixtures/nac-yaml/filters-absent/nac.yaml +0 -0
- {nac_cli-0.1.0 → nac_cli-0.2.0}/tests/fixtures/nac-yaml/filters-present/nac.yaml +0 -0
- {nac_cli-0.1.0 → nac_cli-0.2.0}/tests/fixtures/nac-yaml/filters-present/tests/filters/.gitkeep +0 -0
- {nac_cli-0.1.0 → nac_cli-0.2.0}/tests/fixtures/nac-yaml/invalid-syntax/nac.yaml +0 -0
- {nac_cli-0.1.0 → nac_cli-0.2.0}/tests/fixtures/nac-yaml/iosxe-lean/nac.yaml +0 -0
- {nac_cli-0.1.0 → nac_cli-0.2.0}/tests/fixtures/nac-yaml/minimal/nac.yaml +0 -0
- {nac_cli-0.1.0 → nac_cli-0.2.0}/tests/fixtures/nac-yaml/nxos-full/nac.yaml +0 -0
- {nac_cli-0.1.0 → nac_cli-0.2.0}/tests/fixtures/nac-yaml/nxos-lean/nac.yaml +0 -0
- {nac_cli-0.1.0 → nac_cli-0.2.0}/tests/fixtures/nac-yaml/templates-absent/nac.yaml +0 -0
- {nac_cli-0.1.0 → nac_cli-0.2.0}/tests/fixtures/nac-yaml/templates-present/nac.yaml +0 -0
- {nac_cli-0.1.0 → nac_cli-0.2.0}/tests/fixtures/nac-yaml/templates-present/tests/templates/.gitkeep +0 -0
- {nac_cli-0.1.0 → nac_cli-0.2.0}/tests/integration/fixtures/local_only/.terraform.lock.hcl +0 -0
- {nac_cli-0.1.0 → nac_cli-0.2.0}/tests/integration/fixtures/local_only/broken/main.tf +0 -0
- {nac_cli-0.1.0 → nac_cli-0.2.0}/tests/integration/fixtures/local_only/main.tf +0 -0
- {nac_cli-0.1.0 → nac_cli-0.2.0}/tests/integration/fixtures/validate_test/.schema.yaml +0 -0
- {nac_cli-0.1.0 → nac_cli-0.2.0}/tests/integration/fixtures/validate_test/data/main.yaml +0 -0
- {nac_cli-0.1.0 → nac_cli-0.2.0}/tests/integration/fixtures/validate_test/templates/test.robot +0 -0
- {nac_cli-0.1.0 → nac_cli-0.2.0}/tests/integration/test_streaming.py +0 -0
- {nac_cli-0.1.0 → nac_cli-0.2.0}/tests/integration/test_validate_test.py +0 -0
- {nac_cli-0.1.0 → nac_cli-0.2.0}/tests/unit/conftest.py +0 -0
- {nac_cli-0.1.0 → nac_cli-0.2.0}/tests/unit/test_bootstrap.py +0 -0
- {nac_cli-0.1.0 → nac_cli-0.2.0}/tests/unit/test_config.py +0 -0
- {nac_cli-0.1.0 → nac_cli-0.2.0}/tests/unit/test_manifest.py +0 -0
- {nac_cli-0.1.0 → nac_cli-0.2.0}/tests/unit/test_output.py +0 -0
- {nac_cli-0.1.0 → nac_cli-0.2.0}/tests/unit/test_placeholder.py +0 -0
- {nac_cli-0.1.0 → nac_cli-0.2.0}/tests/unit/test_runner.py +0 -0
- {nac_cli-0.1.0 → nac_cli-0.2.0}/tests/unit/test_terraform.py +0 -0
|
@@ -20,7 +20,7 @@ jobs:
|
|
|
20
20
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
21
21
|
|
|
22
22
|
- name: Install uv
|
|
23
|
-
uses: astral-sh/setup-uv@
|
|
23
|
+
uses: astral-sh/setup-uv@68559e99cc807c1eb7e9238fd43a15bba460eec2 # v10.3.3
|
|
24
24
|
|
|
25
25
|
- name: Check tag matches project version
|
|
26
26
|
env:
|
|
@@ -102,7 +102,7 @@ jobs:
|
|
|
102
102
|
id-token: write # Required for PyPI trusted publishing
|
|
103
103
|
steps:
|
|
104
104
|
- name: Install uv
|
|
105
|
-
uses: astral-sh/setup-uv@
|
|
105
|
+
uses: astral-sh/setup-uv@68559e99cc807c1eb7e9238fd43a15bba460eec2 # v10.3.3
|
|
106
106
|
|
|
107
107
|
- name: Download distributions
|
|
108
108
|
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
|
|
@@ -21,7 +21,7 @@ jobs:
|
|
|
21
21
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
22
22
|
|
|
23
23
|
- name: Install uv
|
|
24
|
-
uses: astral-sh/setup-uv@
|
|
24
|
+
uses: astral-sh/setup-uv@68559e99cc807c1eb7e9238fd43a15bba460eec2 # v10.3.3
|
|
25
25
|
with:
|
|
26
26
|
enable-cache: true
|
|
27
27
|
|
|
@@ -49,7 +49,7 @@ jobs:
|
|
|
49
49
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
50
50
|
|
|
51
51
|
- name: Install uv
|
|
52
|
-
uses: astral-sh/setup-uv@
|
|
52
|
+
uses: astral-sh/setup-uv@68559e99cc807c1eb7e9238fd43a15bba460eec2 # v10.3.3
|
|
53
53
|
with:
|
|
54
54
|
enable-cache: true
|
|
55
55
|
|
|
@@ -89,7 +89,7 @@ jobs:
|
|
|
89
89
|
python-version: ${{ matrix.python }}
|
|
90
90
|
|
|
91
91
|
- name: Install uv
|
|
92
|
-
uses: astral-sh/setup-uv@
|
|
92
|
+
uses: astral-sh/setup-uv@68559e99cc807c1eb7e9238fd43a15bba460eec2 # v10.3.3
|
|
93
93
|
with:
|
|
94
94
|
enable-cache: true
|
|
95
95
|
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
## 0.2.0
|
|
2
|
+
|
|
3
|
+
- Only require `uv` when `nac-validate`/`nac-test` aren't installed locally in a satisfying version: `nac setup` no longer fails without `uv` if both tools resolve locally, and `nac validate`/`nac test` fail with a clear message (instead of a missing-executable error) when `uvx` would be needed but `uv` isn't on `PATH`
|
|
4
|
+
- Enable `nac-validate`'s and `nac-test`'s `--artifacts` option (schema, rules and test templates packaged with the module or with bundles) by default via `NAC_VALIDATE_ARTIFACTS`/`NAC_TEST_ARTIFACTS`; versions without the option ignore it, and an existing value of the env var or a passed-through `--no-artifacts` takes precedence
|
|
5
|
+
|
|
6
|
+
## 0.1.0
|
|
7
|
+
|
|
8
|
+
- Initial release
|
nac_cli-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: nac-cli
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: An umbrella CLI wrapping terraform/tofu, nac-validate, and nac-test for Network-as-Code workflows.
|
|
5
|
+
Project-URL: homepage, https://github.com/netascode/nac
|
|
6
|
+
Project-URL: repository, https://github.com/netascode/nac
|
|
7
|
+
Project-URL: documentation, https://github.com/netascode/nac
|
|
8
|
+
Author-email: Daniel Schmidt <danischm@cisco.com>
|
|
9
|
+
Maintainer-email: Daniel Schmidt <danischm@cisco.com>
|
|
10
|
+
License: MPL-2.0
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: automation,cli,nac,network-as-code,opentofu,terraform
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Environment :: Console
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
23
|
+
Classifier: Topic :: Software Development :: Build Tools
|
|
24
|
+
Requires-Python: >=3.11
|
|
25
|
+
Requires-Dist: httpx>=0.28
|
|
26
|
+
Requires-Dist: packaging>=24
|
|
27
|
+
Requires-Dist: platformdirs>=4.3
|
|
28
|
+
Requires-Dist: pydantic>=2.9
|
|
29
|
+
Requires-Dist: ruamel-yaml>=0.19.1
|
|
30
|
+
Requires-Dist: typer>=0.25.1
|
|
31
|
+
Provides-Extra: dev
|
|
32
|
+
Requires-Dist: bandit[toml]>=1.9.1; extra == 'dev'
|
|
33
|
+
Requires-Dist: pre-commit>=4.4.0; extra == 'dev'
|
|
34
|
+
Requires-Dist: pytest-cov>=7.0.0; extra == 'dev'
|
|
35
|
+
Requires-Dist: pytest-mock>=3.15.1; extra == 'dev'
|
|
36
|
+
Requires-Dist: pytest>=9.0.1; extra == 'dev'
|
|
37
|
+
Requires-Dist: ruff>=0.14.5; extra == 'dev'
|
|
38
|
+
Requires-Dist: ty>=0.0.78; extra == 'dev'
|
|
39
|
+
Description-Content-Type: text/markdown
|
|
40
|
+
|
|
41
|
+
[](https://github.com/netascode/nac/actions/workflows/test.yml)
|
|
42
|
+

|
|
43
|
+
|
|
44
|
+
# nac
|
|
45
|
+
|
|
46
|
+
An umbrella CLI for Network-as-Code workflows, driven by a single `nac.yaml`
|
|
47
|
+
config file.
|
|
48
|
+
|
|
49
|
+
Every Network-as-Code repository runs the same workflow, regardless of the
|
|
50
|
+
architecture: validate the data model, plan, apply and test. Each step uses a
|
|
51
|
+
different tool with its own options: `terraform` or `tofu`, `nac-validate` and
|
|
52
|
+
`nac-test`. `nac` is a single CLI for the whole workflow. The repository is
|
|
53
|
+
described once in `nac.yaml`, and the same commands work on a laptop and in a
|
|
54
|
+
CI/CD pipeline:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
nac setup # verify prerequisites and report resolved tool versions
|
|
58
|
+
nac init # terraform/tofu init
|
|
59
|
+
nac validate # nac-validate against the configured data paths
|
|
60
|
+
nac plan # terraform/tofu plan -> plan.tfplan
|
|
61
|
+
nac apply # apply the saved plan (or interactively, without one)
|
|
62
|
+
nac test # nac-test against the configured data/templates/output
|
|
63
|
+
nac destroy # destroy the managed infrastructure
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Full documentation: https://netascode.cisco.com/docs/tools/nac/overview
|
|
67
|
+
|
|
68
|
+
## How It Works
|
|
69
|
+
|
|
70
|
+
- **One configuration file:** `nac.yaml` defines where the data model,
|
|
71
|
+
schema, rules and test templates are located, which Terraform engine to use
|
|
72
|
+
and which environment variables are required. Every field has a default, so
|
|
73
|
+
a repository following the `data/` and `tests/` conventions works without a
|
|
74
|
+
`nac.yaml` at all.
|
|
75
|
+
- **Managed toolchain:** `nac-validate` and `nac-test` are used from a local
|
|
76
|
+
installation if one satisfies the configured version, or run via `uvx`
|
|
77
|
+
otherwise. If neither `terraform` nor `tofu` is installed, `nac setup` can
|
|
78
|
+
install OpenTofu.
|
|
79
|
+
- **Tested versions:** a Network-as-Code Terraform module can declare the
|
|
80
|
+
`nac-validate` and `nac-test` versions it was tested with in
|
|
81
|
+
`nac/manifest.yaml`. `nac` then uses a compatible version by default and
|
|
82
|
+
warns about versions outside of the tested range.
|
|
83
|
+
- **Pass-through:** arguments not known to `nac` are forwarded to the wrapped
|
|
84
|
+
tool, and the exit code of the wrapped tool is returned.
|
|
85
|
+
|
|
86
|
+
## Installation
|
|
87
|
+
|
|
88
|
+
`nac` is published on PyPI as **`nac-cli`**; the command it installs is `nac`.
|
|
89
|
+
Install it with [uv](https://docs.astral.sh/uv/getting-started/installation/):
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
uv tool install nac-cli
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
> [!WARNING]
|
|
96
|
+
> Don't `pip install nac` or run `uvx nac` -- that is a different, unrelated
|
|
97
|
+
> package.
|
|
98
|
+
|
|
99
|
+
See the [installation docs](https://netascode.cisco.com/docs/tools/nac/installation/)
|
|
100
|
+
for prerequisites, and [usage](https://netascode.cisco.com/docs/tools/nac/usage/)
|
|
101
|
+
and [configuration](https://netascode.cisco.com/docs/tools/nac/configuration/)
|
|
102
|
+
for the full command and `nac.yaml` reference.
|
|
103
|
+
|
|
104
|
+
## License
|
|
105
|
+
|
|
106
|
+
[MPL-2.0](LICENSE)
|
nac_cli-0.2.0/README.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
[](https://github.com/netascode/nac/actions/workflows/test.yml)
|
|
2
|
+

|
|
3
|
+
|
|
4
|
+
# nac
|
|
5
|
+
|
|
6
|
+
An umbrella CLI for Network-as-Code workflows, driven by a single `nac.yaml`
|
|
7
|
+
config file.
|
|
8
|
+
|
|
9
|
+
Every Network-as-Code repository runs the same workflow, regardless of the
|
|
10
|
+
architecture: validate the data model, plan, apply and test. Each step uses a
|
|
11
|
+
different tool with its own options: `terraform` or `tofu`, `nac-validate` and
|
|
12
|
+
`nac-test`. `nac` is a single CLI for the whole workflow. The repository is
|
|
13
|
+
described once in `nac.yaml`, and the same commands work on a laptop and in a
|
|
14
|
+
CI/CD pipeline:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
nac setup # verify prerequisites and report resolved tool versions
|
|
18
|
+
nac init # terraform/tofu init
|
|
19
|
+
nac validate # nac-validate against the configured data paths
|
|
20
|
+
nac plan # terraform/tofu plan -> plan.tfplan
|
|
21
|
+
nac apply # apply the saved plan (or interactively, without one)
|
|
22
|
+
nac test # nac-test against the configured data/templates/output
|
|
23
|
+
nac destroy # destroy the managed infrastructure
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Full documentation: https://netascode.cisco.com/docs/tools/nac/overview
|
|
27
|
+
|
|
28
|
+
## How It Works
|
|
29
|
+
|
|
30
|
+
- **One configuration file:** `nac.yaml` defines where the data model,
|
|
31
|
+
schema, rules and test templates are located, which Terraform engine to use
|
|
32
|
+
and which environment variables are required. Every field has a default, so
|
|
33
|
+
a repository following the `data/` and `tests/` conventions works without a
|
|
34
|
+
`nac.yaml` at all.
|
|
35
|
+
- **Managed toolchain:** `nac-validate` and `nac-test` are used from a local
|
|
36
|
+
installation if one satisfies the configured version, or run via `uvx`
|
|
37
|
+
otherwise. If neither `terraform` nor `tofu` is installed, `nac setup` can
|
|
38
|
+
install OpenTofu.
|
|
39
|
+
- **Tested versions:** a Network-as-Code Terraform module can declare the
|
|
40
|
+
`nac-validate` and `nac-test` versions it was tested with in
|
|
41
|
+
`nac/manifest.yaml`. `nac` then uses a compatible version by default and
|
|
42
|
+
warns about versions outside of the tested range.
|
|
43
|
+
- **Pass-through:** arguments not known to `nac` are forwarded to the wrapped
|
|
44
|
+
tool, and the exit code of the wrapped tool is returned.
|
|
45
|
+
|
|
46
|
+
## Installation
|
|
47
|
+
|
|
48
|
+
`nac` is published on PyPI as **`nac-cli`**; the command it installs is `nac`.
|
|
49
|
+
Install it with [uv](https://docs.astral.sh/uv/getting-started/installation/):
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
uv tool install nac-cli
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
> [!WARNING]
|
|
56
|
+
> Don't `pip install nac` or run `uvx nac` -- that is a different, unrelated
|
|
57
|
+
> package.
|
|
58
|
+
|
|
59
|
+
See the [installation docs](https://netascode.cisco.com/docs/tools/nac/installation/)
|
|
60
|
+
for prerequisites, and [usage](https://netascode.cisco.com/docs/tools/nac/usage/)
|
|
61
|
+
and [configuration](https://netascode.cisco.com/docs/tools/nac/configuration/)
|
|
62
|
+
for the full command and `nac.yaml` reference.
|
|
63
|
+
|
|
64
|
+
## License
|
|
65
|
+
|
|
66
|
+
[MPL-2.0](LICENSE)
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
|
|
2
|
+
- Only require `uv` when `nac-validate`/`nac-test` aren't installed locally in a satisfying version: `nac setup` no longer fails without `uv` if both tools resolve locally, and `nac validate`/`nac test` fail with a clear message (instead of a missing-executable error) when `uvx` would be needed but `uv` isn't on `PATH`
|
|
3
|
+
- Enable `nac-validate`'s and `nac-test`'s `--artifacts` option (schema, rules and test templates packaged with the module or with bundles) by default via `NAC_VALIDATE_ARTIFACTS`/`NAC_TEST_ARTIFACTS`; versions without the option ignore it, and an existing value of the env var or a passed-through `--no-artifacts` takes precedence
|
|
@@ -25,10 +25,6 @@ from .versions import emit_warnings, get_manifest, tool_warnings
|
|
|
25
25
|
|
|
26
26
|
logger = logging.getLogger(__name__)
|
|
27
27
|
|
|
28
|
-
UV_NOT_FOUND = (
|
|
29
|
-
"`uv` not found on PATH -- install it: "
|
|
30
|
-
"https://docs.astral.sh/uv/getting-started/installation/"
|
|
31
|
-
)
|
|
32
28
|
TENV_NOT_FOUND = (
|
|
33
29
|
"`tenv` not found on PATH -- required because `tools.terraform.version` "
|
|
34
30
|
"is set. Install tenv: https://github.com/tofuutils/tenv"
|
|
@@ -89,9 +85,7 @@ def setup(ctx: typer.Context, yes: Yes = False, prewarm: Prewarm = False) -> Non
|
|
|
89
85
|
cfg = get_config(ctx)
|
|
90
86
|
no_color = get_no_color(ctx)
|
|
91
87
|
|
|
92
|
-
|
|
93
|
-
fail(UV_NOT_FOUND)
|
|
94
|
-
lines: list[str] = ["uv: found"]
|
|
88
|
+
lines: list[str] = []
|
|
95
89
|
|
|
96
90
|
if cfg.tools.terraform.version is not None and shutil.which("tenv") is None:
|
|
97
91
|
fail(TENV_NOT_FOUND)
|
|
@@ -151,6 +145,23 @@ def setup(ctx: typer.Context, yes: Yes = False, prewarm: Prewarm = False) -> Non
|
|
|
151
145
|
test_line, test_source, test_constraint = _tool_summary_line(
|
|
152
146
|
"nac-test", cfg.tools.nac_test
|
|
153
147
|
)
|
|
148
|
+
uv_errors: list[str] = []
|
|
149
|
+
if validate_source.mode == "uvx":
|
|
150
|
+
uv_errors.append(
|
|
151
|
+
tools.uv_not_found_message(
|
|
152
|
+
"nac-validate", validate_source, validate_constraint
|
|
153
|
+
)
|
|
154
|
+
)
|
|
155
|
+
if test_source.mode == "uvx":
|
|
156
|
+
uv_errors.append(
|
|
157
|
+
tools.uv_not_found_message("nac-test", test_source, test_constraint)
|
|
158
|
+
)
|
|
159
|
+
if tools.uv_available():
|
|
160
|
+
lines.insert(0, "uv: found")
|
|
161
|
+
elif uv_errors:
|
|
162
|
+
fail("\n".join(uv_errors))
|
|
163
|
+
else:
|
|
164
|
+
lines.insert(0, "uv: not found (not needed, all tools resolve locally)")
|
|
154
165
|
lines.append(validate_line)
|
|
155
166
|
lines.append(test_line)
|
|
156
167
|
warnings = [
|
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
import typer
|
|
7
7
|
|
|
8
8
|
from nac import runner, tools
|
|
9
|
-
from nac.
|
|
9
|
+
from nac.exceptions import UvNotFoundError
|
|
10
10
|
|
|
11
|
-
from .main import app, get_config, get_no_color
|
|
11
|
+
from .main import app, fail, get_config, get_no_color
|
|
12
12
|
from .options import Artifacts
|
|
13
13
|
from .passthrough import (
|
|
14
14
|
PASSTHROUGH_CONTEXT_SETTINGS,
|
|
@@ -34,15 +34,24 @@ def test_(ctx: typer.Context, artifacts: Artifacts = False) -> None:
|
|
|
34
34
|
log_path = cfg.working_dir / "test.txt" if artifacts else None
|
|
35
35
|
manifest = get_manifest(ctx, cfg)
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
ctx,
|
|
37
|
+
try:
|
|
38
|
+
tool_argv = (
|
|
40
39
|
tools.build_help_argv(
|
|
41
40
|
"nac-test",
|
|
42
41
|
tools.tool_constraint("nac-test", cfg.tools.nac_test, manifest),
|
|
43
|
-
)
|
|
42
|
+
)
|
|
43
|
+
if wants_help(ctx)
|
|
44
|
+
else tools.build_test_argv(cfg, manifest) + ctx.args
|
|
45
|
+
)
|
|
46
|
+
except UvNotFoundError as exc:
|
|
47
|
+
fail(str(exc))
|
|
48
|
+
|
|
49
|
+
if wants_help(ctx):
|
|
50
|
+
show_help_and_exit(
|
|
51
|
+
ctx,
|
|
52
|
+
tool_argv,
|
|
44
53
|
cwd=cfg.working_dir,
|
|
45
|
-
env=
|
|
54
|
+
env=tools.build_tool_env("nac-test", no_color=no_color),
|
|
46
55
|
no_color=no_color,
|
|
47
56
|
tool_label="nac-test",
|
|
48
57
|
usage_as="nac test",
|
|
@@ -52,9 +61,9 @@ def test_(ctx: typer.Context, artifacts: Artifacts = False) -> None:
|
|
|
52
61
|
log_written = emit_warnings(warnings, no_color=no_color, log_path=log_path)
|
|
53
62
|
|
|
54
63
|
code = runner.run_streaming(
|
|
55
|
-
|
|
64
|
+
tool_argv,
|
|
56
65
|
cwd=cfg.working_dir,
|
|
57
|
-
env=
|
|
66
|
+
env=tools.build_tool_env("nac-test", no_color=no_color),
|
|
58
67
|
log_path=log_path,
|
|
59
68
|
append=log_written,
|
|
60
69
|
no_color=no_color,
|
|
@@ -8,9 +8,10 @@ import logging
|
|
|
8
8
|
import typer
|
|
9
9
|
|
|
10
10
|
from nac import runner, terraform, tools
|
|
11
|
-
from nac.
|
|
11
|
+
from nac.exceptions import UvNotFoundError
|
|
12
|
+
from nac.output import GREEN, color, echo_summary
|
|
12
13
|
|
|
13
|
-
from .main import app, get_config, get_no_color, resolve_engine_or_exit
|
|
14
|
+
from .main import app, fail, get_config, get_no_color, resolve_engine_or_exit
|
|
14
15
|
from .options import Artifacts
|
|
15
16
|
from .passthrough import (
|
|
16
17
|
PASSTHROUGH_CONTEXT_SETTINGS,
|
|
@@ -36,12 +37,21 @@ def validate(ctx: typer.Context, artifacts: Artifacts = False) -> None:
|
|
|
36
37
|
manifest = get_manifest(ctx, cfg)
|
|
37
38
|
constraint = tools.tool_constraint("nac-validate", cfg.tools.nac_validate, manifest)
|
|
38
39
|
|
|
40
|
+
try:
|
|
41
|
+
tool_argv = (
|
|
42
|
+
tools.build_help_argv("nac-validate", constraint)
|
|
43
|
+
if wants_help(ctx)
|
|
44
|
+
else tools.build_validate_argv(cfg, manifest) + ctx.args
|
|
45
|
+
)
|
|
46
|
+
except UvNotFoundError as exc:
|
|
47
|
+
fail(str(exc))
|
|
48
|
+
|
|
39
49
|
if wants_help(ctx):
|
|
40
50
|
show_help_and_exit(
|
|
41
51
|
ctx,
|
|
42
|
-
|
|
52
|
+
tool_argv,
|
|
43
53
|
cwd=cfg.working_dir,
|
|
44
|
-
env=
|
|
54
|
+
env=tools.build_tool_env("nac-validate", no_color=no_color),
|
|
45
55
|
no_color=no_color,
|
|
46
56
|
tool_label="nac-validate",
|
|
47
57
|
usage_as="nac validate",
|
|
@@ -79,9 +89,9 @@ def validate(ctx: typer.Context, artifacts: Artifacts = False) -> None:
|
|
|
79
89
|
)
|
|
80
90
|
|
|
81
91
|
code = runner.run_streaming(
|
|
82
|
-
|
|
92
|
+
tool_argv,
|
|
83
93
|
cwd=cfg.working_dir,
|
|
84
|
-
env=
|
|
94
|
+
env=tools.build_tool_env("nac-validate", no_color=no_color),
|
|
85
95
|
log_path=log_path,
|
|
86
96
|
append=log_written,
|
|
87
97
|
no_color=no_color,
|
|
@@ -32,3 +32,7 @@ class BootstrapDeclinedError(BootstrapError):
|
|
|
32
32
|
|
|
33
33
|
class ChecksumMismatchError(BootstrapError):
|
|
34
34
|
"""The downloaded archive's SHA-256 didn't match the published checksum."""
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class UvNotFoundError(NacError):
|
|
38
|
+
"""A wrapped tool needs `uvx` (no usable local install) but uv isn't on PATH."""
|
|
@@ -12,14 +12,17 @@ from packaging.specifiers import InvalidSpecifier, SpecifierSet
|
|
|
12
12
|
from packaging.version import InvalidVersion, Version
|
|
13
13
|
|
|
14
14
|
from nac.config import NacConfig, resolve_test_data, resolve_validate_data
|
|
15
|
+
from nac.exceptions import UvNotFoundError
|
|
15
16
|
from nac.manifest import Manifest, compatible_range
|
|
16
|
-
from nac.output import format_command
|
|
17
|
+
from nac.output import apply_no_color_env, format_command
|
|
17
18
|
|
|
18
19
|
logger = logging.getLogger(__name__)
|
|
19
20
|
|
|
20
21
|
_SPECIFIER_PREFIXES = ("==", "!=", ">=", "<=", "~=", ">", "<")
|
|
21
22
|
_VERSION_RE = re.compile(r"\d+(?:\.\d+)+(?:[a-zA-Z0-9.\-+]*)?")
|
|
22
23
|
|
|
24
|
+
UV_INSTALL_URL = "https://docs.astral.sh/uv/getting-started/installation/"
|
|
25
|
+
|
|
23
26
|
|
|
24
27
|
def _pin_suffix(version: str) -> str:
|
|
25
28
|
v = version.strip()
|
|
@@ -117,13 +120,42 @@ def resolve_tool_source(
|
|
|
117
120
|
return ToolSource(mode="uvx", path=None, version=version)
|
|
118
121
|
|
|
119
122
|
|
|
123
|
+
def uv_available() -> bool:
|
|
124
|
+
"""Whether uv (which provides `uvx`) is on PATH."""
|
|
125
|
+
return shutil.which("uv") is not None
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def uv_not_found_message(
|
|
129
|
+
tool: Literal["nac-validate", "nac-test"],
|
|
130
|
+
source: ToolSource,
|
|
131
|
+
constraint: str | None,
|
|
132
|
+
) -> str:
|
|
133
|
+
"""Explain why `tool` needs uv and how to fix it (install uv or the tool)."""
|
|
134
|
+
if source.version is not None:
|
|
135
|
+
reason = f"local {tool} v{source.version} does not satisfy {constraint}"
|
|
136
|
+
else:
|
|
137
|
+
reason = f"{tool} is not installed locally"
|
|
138
|
+
wanted = f"{tool}{_pin_suffix(constraint)}" if constraint is not None else tool
|
|
139
|
+
return (
|
|
140
|
+
f"{reason}, so it would be run via `uvx`, but `uv` was not found on "
|
|
141
|
+
f"PATH -- install uv ({UV_INSTALL_URL}) or install {wanted} yourself."
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
|
|
120
145
|
def _tool_prefix(
|
|
121
146
|
tool: Literal["nac-validate", "nac-test"], constraint: str | None
|
|
122
147
|
) -> list[str]:
|
|
148
|
+
"""Command prefix for `tool`: the local binary, else `uvx [--from ...]`.
|
|
149
|
+
|
|
150
|
+
Raises `UvNotFoundError` if `uvx` would be needed but isn't on PATH, so
|
|
151
|
+
`nac` works without uv as long as suitable local installs exist.
|
|
152
|
+
"""
|
|
123
153
|
source = resolve_tool_source(tool, constraint)
|
|
124
154
|
if source.mode == "local":
|
|
125
155
|
assert source.path is not None
|
|
126
156
|
return [source.path]
|
|
157
|
+
if not uv_available():
|
|
158
|
+
raise UvNotFoundError(uv_not_found_message(tool, source, constraint))
|
|
127
159
|
argv = ["uvx"]
|
|
128
160
|
if constraint is not None:
|
|
129
161
|
argv += ["--from", f"{tool}{_pin_suffix(constraint)}"]
|
|
@@ -156,6 +188,28 @@ def build_test_argv(cfg: NacConfig, manifest: Manifest | None = None) -> list[st
|
|
|
156
188
|
return argv
|
|
157
189
|
|
|
158
190
|
|
|
191
|
+
# Env vars turning on bundle/module artifact discovery in nac-validate/nac-test
|
|
192
|
+
# (`--artifacts`). Set via env rather than the CLI flag so that tool versions
|
|
193
|
+
# predating the option simply ignore it.
|
|
194
|
+
_ARTIFACTS_ENV = {
|
|
195
|
+
"nac-validate": "NAC_VALIDATE_ARTIFACTS",
|
|
196
|
+
"nac-test": "NAC_TEST_ARTIFACTS",
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def build_tool_env(
|
|
201
|
+
tool: Literal["nac-validate", "nac-test"], *, no_color: bool
|
|
202
|
+
) -> dict[str, str]:
|
|
203
|
+
"""Subprocess env for `tool`, with artifacts enabled by default.
|
|
204
|
+
|
|
205
|
+
A value already set in the environment wins, and a passed-through
|
|
206
|
+
`--no-artifacts` overrides the env var, so users can still opt out.
|
|
207
|
+
"""
|
|
208
|
+
env = apply_no_color_env(no_color)
|
|
209
|
+
env.setdefault(_ARTIFACTS_ENV[tool], "true")
|
|
210
|
+
return env
|
|
211
|
+
|
|
212
|
+
|
|
159
213
|
def build_prewarm_argv(
|
|
160
214
|
tool: Literal["nac-validate", "nac-test"], version: str | None
|
|
161
215
|
) -> list[str]:
|
|
@@ -41,7 +41,9 @@ def _tofu_on_path(mocker):
|
|
|
41
41
|
"""Make `tofu` resolve as the engine everywhere via a shared shutil.which."""
|
|
42
42
|
mocker.patch(
|
|
43
43
|
"shutil.which",
|
|
44
|
-
side_effect=_which(
|
|
44
|
+
side_effect=_which(
|
|
45
|
+
{"tofu": "/usr/bin/tofu", "terraform": None, "uv": "/usr/bin/uv"}
|
|
46
|
+
),
|
|
45
47
|
)
|
|
46
48
|
|
|
47
49
|
|
|
@@ -108,7 +110,7 @@ def test_init_propagates_nonzero_exit_code(tmp_path, mocker, _tofu_on_path):
|
|
|
108
110
|
@pytest.mark.unit
|
|
109
111
|
def test_init_fails_cleanly_when_no_engine_available(tmp_path, mocker):
|
|
110
112
|
config_path = _write_config(tmp_path, "")
|
|
111
|
-
mocker.patch("shutil.which",
|
|
113
|
+
mocker.patch("shutil.which", side_effect=_which({"uv": "/usr/bin/uv"}))
|
|
112
114
|
run_streaming_mock = mocker.patch("nac.cli.init.runner.run_streaming")
|
|
113
115
|
|
|
114
116
|
result = cli.invoke(app, ["--config", str(config_path), "init"])
|
|
@@ -125,7 +127,7 @@ def test_init_fails_cleanly_when_no_engine_available(tmp_path, mocker):
|
|
|
125
127
|
@pytest.mark.unit
|
|
126
128
|
def test_validate_without_render_target_skips_render_step(tmp_path, mocker):
|
|
127
129
|
config_path = _write_config(tmp_path, "")
|
|
128
|
-
mocker.patch("shutil.which",
|
|
130
|
+
mocker.patch("shutil.which", side_effect=_which({"uv": "/usr/bin/uv"}))
|
|
129
131
|
run_streaming_mock = mocker.patch(
|
|
130
132
|
"nac.cli.validate.runner.run_streaming", return_value=0
|
|
131
133
|
)
|
|
@@ -139,6 +141,42 @@ def test_validate_without_render_target_skips_render_step(tmp_path, mocker):
|
|
|
139
141
|
assert "nac-validate" in argv
|
|
140
142
|
|
|
141
143
|
|
|
144
|
+
@pytest.mark.unit
|
|
145
|
+
def test_validate_without_uv_uses_local_install(tmp_path, mocker):
|
|
146
|
+
config_path = _write_config(tmp_path, "")
|
|
147
|
+
mocker.patch(
|
|
148
|
+
"shutil.which",
|
|
149
|
+
side_effect=_which({"nac-validate": "/usr/bin/nac-validate"}),
|
|
150
|
+
)
|
|
151
|
+
mocker.patch("nac.tools._local_tool_version", return_value="1.2.3")
|
|
152
|
+
run_streaming_mock = mocker.patch(
|
|
153
|
+
"nac.cli.validate.runner.run_streaming", return_value=0
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
result = cli.invoke(app, ["--config", str(config_path), "validate"])
|
|
157
|
+
|
|
158
|
+
assert result.exit_code == 0
|
|
159
|
+
argv = run_streaming_mock.call_args_list[0].args[0]
|
|
160
|
+
assert argv[0] == "/usr/bin/nac-validate"
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
@pytest.mark.unit
|
|
164
|
+
def test_validate_without_uv_or_local_install_fails_before_render(tmp_path, mocker):
|
|
165
|
+
config_path = _write_config(
|
|
166
|
+
tmp_path,
|
|
167
|
+
"render:\n target: module.example\n output: rendered/\n",
|
|
168
|
+
)
|
|
169
|
+
mocker.patch("shutil.which", side_effect=_which({"tofu": "/usr/bin/tofu"}))
|
|
170
|
+
run_streaming_mock = mocker.patch("nac.cli.validate.runner.run_streaming")
|
|
171
|
+
|
|
172
|
+
result = cli.invoke(app, ["--config", str(config_path), "validate"])
|
|
173
|
+
|
|
174
|
+
assert result.exit_code == 1
|
|
175
|
+
assert "nac-validate is not installed locally" in result.output
|
|
176
|
+
assert "`uv` was not found on PATH" in result.output
|
|
177
|
+
run_streaming_mock.assert_not_called()
|
|
178
|
+
|
|
179
|
+
|
|
142
180
|
@pytest.mark.unit
|
|
143
181
|
def test_validate_with_render_target_runs_render_then_validate(
|
|
144
182
|
tmp_path, mocker, _tofu_on_path
|
|
@@ -575,7 +613,7 @@ def test_destroy_propagates_nonzero_exit_code(tmp_path, mocker, _tofu_on_path):
|
|
|
575
613
|
@pytest.mark.unit
|
|
576
614
|
def test_destroy_fails_cleanly_when_no_engine_available(tmp_path, mocker):
|
|
577
615
|
config_path = _write_config(tmp_path, "")
|
|
578
|
-
mocker.patch("shutil.which",
|
|
616
|
+
mocker.patch("shutil.which", side_effect=_which({"uv": "/usr/bin/uv"}))
|
|
579
617
|
run_streaming_mock = mocker.patch("nac.cli.destroy.runner.run_streaming")
|
|
580
618
|
|
|
581
619
|
result = cli.invoke(app, ["--config", str(config_path), "destroy"])
|
|
@@ -592,7 +630,7 @@ def test_destroy_fails_cleanly_when_no_engine_available(tmp_path, mocker):
|
|
|
592
630
|
@pytest.mark.unit
|
|
593
631
|
def test_test_streams_test_argv_and_propagates_exit_code(tmp_path, mocker):
|
|
594
632
|
config_path = _write_config(tmp_path, "")
|
|
595
|
-
mocker.patch("shutil.which",
|
|
633
|
+
mocker.patch("shutil.which", side_effect=_which({"uv": "/usr/bin/uv"}))
|
|
596
634
|
run_streaming_mock = mocker.patch(
|
|
597
635
|
"nac.cli.test.runner.run_streaming", return_value=0
|
|
598
636
|
)
|
|
@@ -606,6 +644,19 @@ def test_test_streams_test_argv_and_propagates_exit_code(tmp_path, mocker):
|
|
|
606
644
|
assert run_streaming_mock.call_args_list[0].kwargs.get("log_path") is None
|
|
607
645
|
|
|
608
646
|
|
|
647
|
+
@pytest.mark.unit
|
|
648
|
+
def test_test_without_uv_or_local_install_fails_cleanly(tmp_path, mocker):
|
|
649
|
+
config_path = _write_config(tmp_path, "")
|
|
650
|
+
mocker.patch("shutil.which", side_effect=_which({}))
|
|
651
|
+
run_streaming_mock = mocker.patch("nac.cli.test.runner.run_streaming")
|
|
652
|
+
|
|
653
|
+
result = cli.invoke(app, ["--config", str(config_path), "test"])
|
|
654
|
+
|
|
655
|
+
assert result.exit_code == 1
|
|
656
|
+
assert "nac-test is not installed locally" in result.output
|
|
657
|
+
run_streaming_mock.assert_not_called()
|
|
658
|
+
|
|
659
|
+
|
|
609
660
|
@pytest.mark.unit
|
|
610
661
|
def test_test_with_artifacts_writes_test_txt(tmp_path, mocker):
|
|
611
662
|
config_path = _write_config(tmp_path, "")
|
|
@@ -654,7 +705,7 @@ def test_validate_extra_args_are_appended(tmp_path, mocker):
|
|
|
654
705
|
@pytest.mark.unit
|
|
655
706
|
def test_validate_help_shows_own_and_tool_help(tmp_path, mocker):
|
|
656
707
|
config_path = _write_config(tmp_path, "")
|
|
657
|
-
mocker.patch("shutil.which",
|
|
708
|
+
mocker.patch("shutil.which", side_effect=_which({"uv": "/usr/bin/uv"}))
|
|
658
709
|
run_mock = mocker.patch(
|
|
659
710
|
"nac.cli.passthrough.subprocess.run",
|
|
660
711
|
return_value=subprocess.CompletedProcess(
|
|
@@ -747,7 +798,7 @@ def test_test_extra_args_are_appended(tmp_path, mocker):
|
|
|
747
798
|
@pytest.mark.unit
|
|
748
799
|
def test_test_help_shows_own_and_tool_help(tmp_path, mocker):
|
|
749
800
|
config_path = _write_config(tmp_path, "")
|
|
750
|
-
mocker.patch("shutil.which",
|
|
801
|
+
mocker.patch("shutil.which", side_effect=_which({"uv": "/usr/bin/uv"}))
|
|
751
802
|
run_mock = mocker.patch(
|
|
752
803
|
"nac.cli.passthrough.subprocess.run",
|
|
753
804
|
return_value=subprocess.CompletedProcess(
|
|
@@ -943,3 +994,43 @@ def test_run_shows_nac_as_program_name_under_alias(mocker, capsys):
|
|
|
943
994
|
out = strip_ansi(capsys.readouterr().out)
|
|
944
995
|
assert "Usage: nac [OPTIONS]" in out
|
|
945
996
|
assert "nac-cli" not in out
|
|
997
|
+
|
|
998
|
+
|
|
999
|
+
@pytest.mark.unit
|
|
1000
|
+
@pytest.mark.parametrize(
|
|
1001
|
+
("command", "env_var"),
|
|
1002
|
+
[("validate", "NAC_VALIDATE_ARTIFACTS"), ("test", "NAC_TEST_ARTIFACTS")],
|
|
1003
|
+
)
|
|
1004
|
+
def test_tool_artifacts_enabled_by_default(
|
|
1005
|
+
tmp_path, mocker, monkeypatch, command, env_var
|
|
1006
|
+
):
|
|
1007
|
+
monkeypatch.delenv(env_var, raising=False)
|
|
1008
|
+
config_path = _write_config(tmp_path, "")
|
|
1009
|
+
run_streaming_mock = mocker.patch(
|
|
1010
|
+
f"nac.cli.{command}.runner.run_streaming", return_value=0
|
|
1011
|
+
)
|
|
1012
|
+
|
|
1013
|
+
result = cli.invoke(app, ["--config", str(config_path), command])
|
|
1014
|
+
|
|
1015
|
+
assert result.exit_code == 0
|
|
1016
|
+
assert run_streaming_mock.call_args.kwargs["env"][env_var] == "true"
|
|
1017
|
+
|
|
1018
|
+
|
|
1019
|
+
@pytest.mark.unit
|
|
1020
|
+
@pytest.mark.parametrize(
|
|
1021
|
+
("command", "env_var"),
|
|
1022
|
+
[("validate", "NAC_VALIDATE_ARTIFACTS"), ("test", "NAC_TEST_ARTIFACTS")],
|
|
1023
|
+
)
|
|
1024
|
+
def test_tool_artifacts_env_override_is_kept(
|
|
1025
|
+
tmp_path, mocker, monkeypatch, command, env_var
|
|
1026
|
+
):
|
|
1027
|
+
monkeypatch.setenv(env_var, "false")
|
|
1028
|
+
config_path = _write_config(tmp_path, "")
|
|
1029
|
+
run_streaming_mock = mocker.patch(
|
|
1030
|
+
f"nac.cli.{command}.runner.run_streaming", return_value=0
|
|
1031
|
+
)
|
|
1032
|
+
|
|
1033
|
+
result = cli.invoke(app, ["--config", str(config_path), command])
|
|
1034
|
+
|
|
1035
|
+
assert result.exit_code == 0
|
|
1036
|
+
assert run_streaming_mock.call_args.kwargs["env"][env_var] == "false"
|