Skip to content

Commit 7f4ba91

Browse files
authored
feat(cli): add --json flag to status command (#825)
Add JSON output option to vunnel status command for structured data consumption. The existing tree output remains the default, ensuring backward compatibility. - Add --json flag to status subcommand - Include root path and provider details in JSON response - Add test coverage for JSON output format Signed-off-by: James Gardner <[email protected]>
1 parent 174c1b4 commit 7f4ba91

File tree

2 files changed

+71
-12
lines changed

2 files changed

+71
-12
lines changed

src/vunnel/cli/cli.py

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import dataclasses
44
import enum
5+
import json
56
import logging
67
import sys
78
from dataclasses import dataclass
@@ -182,8 +183,9 @@ def clear_provider(cfg: config.Application, provider_names: str, _input: bool, r
182183
@cli.command(name="status", help="describe current provider state")
183184
@click.argument("provider_names", metavar="PROVIDER", nargs=-1)
184185
@click.option("--show-empty", default=False, is_flag=True, help="show providers with no state")
186+
@click.option("--json", "output_json", default=False, is_flag=True, help="output as JSON")
185187
@click.pass_obj
186-
def status_provider(cfg: config.Application, provider_names: str, show_empty: bool) -> None: # noqa: C901
188+
def status_provider(cfg: config.Application, provider_names: str, show_empty: bool, output_json: bool) -> None: # noqa: C901
187189
print(cfg.root)
188190
selected_names = provider_names if provider_names else providers.names()
189191

@@ -207,6 +209,14 @@ def format(self, fill: str) -> str:
207209
{fill} results: {self.count}
208210
{fill} from: {self.date}"""
209211

212+
def to_dict(self) -> dict:
213+
return {
214+
'count': self.count,
215+
'date': self.date,
216+
'error': self.error,
217+
'enabled': self.enabled
218+
}
219+
210220
# first pass: find the results that exist (which may be fewer than what is selected)
211221
results = {}
212222
for _idx, name in enumerate(selected_names):
@@ -227,17 +237,30 @@ def format(self, fill: str) -> str:
227237
except Exception as e:
228238
results[name] = CurrentState(enabled=False, error=str(e))
229239

230-
# second pass: show the state
231-
for idx, (name, result) in enumerate(sorted(results.items())):
232-
branch = "├──"
233-
fill = "│"
234-
if idx == len(results) - 1:
235-
branch = "└──"
236-
fill = " "
237-
238-
node = result.format(fill)
239-
240-
print(f"""{branch} {name}\n{node}""")
240+
# if --json is requested, output as JSON
241+
if output_json:
242+
json_output = {
243+
"root": cfg.root,
244+
"providers": [
245+
{"name": name, **result.to_dict()} # unpack to a dict
246+
for name, result in sorted(results.items())
247+
]
248+
}
249+
print(json.dumps(json_output, indent=2))
250+
# otherwise, output as a tree structure
251+
else:
252+
# existing tree output
253+
print(cfg.root)
254+
for idx, (name, result) in enumerate(sorted(results.items())):
255+
branch = "├──"
256+
fill = "│"
257+
if idx == len(results) - 1:
258+
branch = "└──"
259+
fill = " "
260+
261+
node = result.format(fill)
262+
263+
print(f"""{branch} {name}\n{node}""")
241264

242265

243266
@cli.command(name="list", help="list available providers")

tests/unit/cli/test_cli.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,42 @@ def test_status(helpers, tmpdir, monkeypatch) -> None:
4545
assert expected_output.strip() == res.output.strip()
4646

4747

48+
def test_status_json(helpers, tmpdir, monkeypatch) -> None:
49+
import json
50+
51+
data_path = helpers.local_dir("test-fixtures/data-1")
52+
53+
envs = {
54+
"NVD_API_KEY": "secret",
55+
"GITHUB_TOKEN": "secret",
56+
}
57+
monkeypatch.setattr(os, "environ", envs)
58+
59+
config = tmpdir.join("vunnel.yaml")
60+
config.write(f"root: {data_path}")
61+
62+
runner = CliRunner()
63+
res = runner.invoke(cli.cli, ["-c", str(config), "status", "--json"])
64+
assert res.exit_code == 0
65+
66+
# Parse and verify JSON output
67+
output_data = json.loads(res.output)
68+
69+
expected_output = {
70+
"root": data_path,
71+
"providers": [
72+
{
73+
"name": "wolfi",
74+
"count": 56,
75+
"date": "2023-01-17 14:58:13",
76+
"error": None,
77+
"enabled": True
78+
}
79+
]
80+
}
81+
82+
assert output_data == expected_output
83+
4884
def test_run(mocker, monkeypatch) -> None:
4985
populate_mock = MagicMock()
5086
create_mock = MagicMock(return_value=populate_mock)

0 commit comments

Comments
 (0)