Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions stellargate/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,27 @@ def main(argv: list[str] | None = None) -> int:
run_parser.add_argument("--md-report", default=None, help="Write Markdown report to this path")
run_parser.add_argument("--fail-on", default=None, help="Override fail_on threshold from config")

validate_parser = subparsers.add_parser(
"validate-config",
help="Parse and validate the config without running any scan",
epilog=(
"exit codes:\n"
" 0 valid - the config parses and at least one tool is enabled\n"
" 2 error - configuration or argument error (config missing or "
"unparsable, invalid fail_on, unknown tool, no enabled tool, etc.)"
),
formatter_class=argparse.RawDescriptionHelpFormatter,
)
validate_parser.add_argument("--config", default="stellargate.yaml")

args = parser.parse_args(argv)

_configure_logging(_resolve_level(args))

if args.command == "run":
return _run(args)
if args.command == "validate-config":
return _validate_config(args)
return 1


Expand Down Expand Up @@ -120,5 +135,19 @@ def _run(args: argparse.Namespace) -> int:
return 0 if passed else 1


def _validate_config(args: argparse.Namespace) -> int:
try:
config = Config.load(args.config)
except ConfigError as e:
logger.error("Config error: %s", e)
return 2

enabled = [name for name, tc in config.tools.items() if tc.enabled]
for name, tc in config.tools.items():
print(f"Tool {name}: {'enabled' if tc.enabled else 'disabled'}")
print(f"Config ok: {len(enabled)} tool(s) enabled: {', '.join(enabled)}")
return 0


if __name__ == "__main__":
sys.exit(main())
47 changes: 47 additions & 0 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
from pathlib import Path

import pytest

from stellargate.cli import main

FIXTURES = Path(__file__).parent / "fixtures"


@pytest.fixture
def good_config(tmp_path):
path = tmp_path / "stellargate.yaml"
path.write_text(
"target: .\n"
"fail_on: high\n"
"tools:\n"
" rytscan:\n"
" enabled: true\n"
" path: ./\n"
" schemalock:\n"
" enabled: false\n"
" vaultsweep:\n"
" enabled: false\n"
" shieldscan:\n"
" enabled: false\n"
)
return str(path)


def test_validate_config_valid_returns_zero_and_reports_enabled(good_config, capsys):
assert main(["validate-config", "--config", good_config]) == 0
captured = capsys.readouterr()
assert "enabled" in captured.out
assert "disabled" in captured.out
assert "rytscan" in captured.out


def test_validate_config_missing_config_returns_two(capsys):
assert main(["validate-config", "--config", "/nonexistent.yaml"]) == 2
captured = capsys.readouterr()
assert "Config error" in captured.err


def test_validate_config_does_not_run_any_scan(good_config, monkeypatch):
with monkeypatch.context() as m:
m.setattr("stellargate.cli.run_all", lambda cfg: (_ for _ in ()).throw(AssertionError("must not run")))
assert main(["validate-config", "--config", good_config]) == 0
Loading