Skip to content

Commit 538bbc5

Browse files
committed
feat(validate): surface containers block in success summary
The validate-success summary listed scanners, formats, and backend but ignored the top-level containers block entirely. With containers silently absent from the summary the user had no signal that argus validate had inspected it — a typo at the block name (e.g. ``containerz:``) would still show up as a top-level-key warning, but a correctly-named block with valid contents looked identical to no block at all. Add a Containers line that shows: Containers: 4 image(s) - ghcr.io/myorg/scanner-bandit:dev - ghcr.io/myorg/scanner-opengrep:dev ... Containers: discover from docker/, . Containers: 2 image(s) + discover from docker/ The line is only printed when the block is structurally a mapping; if no containers block exists, the validator stays silent (matching the optional nature of the block). Three new regression tests in TestCmdValidate cover the present, absent, and discover variants.
1 parent c6140bf commit 538bbc5

2 files changed

Lines changed: 104 additions & 0 deletions

File tree

argus/cli.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2609,6 +2609,31 @@ def cmd_validate(args: argparse.Namespace) -> int:
26092609
backend = data.get("execution", {}).get("backend", "auto")
26102610
print(f" Backend: {backend}")
26112611

2612+
# Containers: only printed when the block exists and is structurally
2613+
# sound (validate already surfaced any errors above). The line gives
2614+
# the user a "yes, your containers config was inspected" signal that
2615+
# was missing — without it, a typo'd top-level key like ``containerz``
2616+
# used to fail silently here too.
2617+
containers = data.get("containers")
2618+
if isinstance(containers, dict):
2619+
images = containers.get("images") or []
2620+
discover = containers.get("discover", False)
2621+
search_paths = containers.get("search_paths") or []
2622+
parts = []
2623+
if isinstance(images, list) and images:
2624+
parts.append(f"{len(images)} image(s)")
2625+
if discover:
2626+
paths_str = ", ".join(search_paths) if search_paths else "."
2627+
parts.append(f"discover from {paths_str}")
2628+
summary = " + ".join(parts) if parts else "no targets"
2629+
print(f" Containers: {summary}")
2630+
if isinstance(images, list) and images:
2631+
for entry in images:
2632+
if not isinstance(entry, dict):
2633+
continue
2634+
ref = entry.get("image") or entry.get("dockerfile") or "<unknown>"
2635+
print(f" - {ref}")
2636+
26122637
# Tool readiness check
26132638
unavailable = []
26142639
tool_statuses = []

argus/tests/test_cli.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -872,6 +872,85 @@ def test_validate_strict_fails_on_warnings(self, tmp_path, capsys):
872872
captured = capsys.readouterr()
873873
assert "strict" in captured.out.lower() or "warning" in captured.out.lower()
874874

875+
def test_validate_summary_lists_containers_when_block_present(
876+
self, tmp_path, capsys,
877+
):
878+
"""The summary should surface configured container targets so
879+
the user knows the ``containers:`` block was inspected."""
880+
config_file = tmp_path / "argus.yml"
881+
config_file.write_text(
882+
"scanners:\n"
883+
" bandit:\n"
884+
" enabled: true\n"
885+
"containers:\n"
886+
" images:\n"
887+
" - image: ghcr.io/myorg/app:1.0\n"
888+
" - image: myorg/inhouse:dev\n"
889+
" dockerfile: docker/Dockerfile\n"
890+
)
891+
args = argparse.Namespace(
892+
config=str(config_file),
893+
check_tools=False,
894+
strict=False,
895+
)
896+
result = cmd_validate(args)
897+
898+
assert result == EXIT_SUCCESS
899+
out = capsys.readouterr().out
900+
assert "Containers: 2 image(s)" in out
901+
assert "ghcr.io/myorg/app:1.0" in out
902+
assert "myorg/inhouse:dev" in out
903+
904+
def test_validate_summary_omits_containers_line_when_block_absent(
905+
self, tmp_path, capsys,
906+
):
907+
"""When no ``containers:`` block exists, the summary should not
908+
mention containers at all — the block is optional and silence
909+
is the right signal."""
910+
config_file = tmp_path / "argus.yml"
911+
config_file.write_text(
912+
"scanners:\n"
913+
" bandit:\n"
914+
" enabled: true\n"
915+
)
916+
args = argparse.Namespace(
917+
config=str(config_file),
918+
check_tools=False,
919+
strict=False,
920+
)
921+
result = cmd_validate(args)
922+
923+
assert result == EXIT_SUCCESS
924+
out = capsys.readouterr().out
925+
assert "Containers:" not in out
926+
927+
def test_validate_summary_shows_discover_when_set(self, tmp_path, capsys):
928+
"""``discover: true`` should surface in the summary alongside
929+
any configured search_paths."""
930+
config_file = tmp_path / "argus.yml"
931+
config_file.write_text(
932+
"scanners:\n"
933+
" bandit:\n"
934+
" enabled: true\n"
935+
"containers:\n"
936+
" discover: true\n"
937+
" search_paths:\n"
938+
" - docker/\n"
939+
" - .\n"
940+
)
941+
args = argparse.Namespace(
942+
config=str(config_file),
943+
check_tools=False,
944+
strict=False,
945+
)
946+
result = cmd_validate(args)
947+
948+
assert result == EXIT_SUCCESS
949+
out = capsys.readouterr().out
950+
assert "Containers:" in out
951+
assert "discover from" in out
952+
assert "docker/" in out
953+
875954

876955
class TestCmdReport:
877956
"""Integration tests for cmd_report."""

0 commit comments

Comments
 (0)