From 3da70f83028436625c78e2a56e72e49fd0007c7e Mon Sep 17 00:00:00 2001 From: ahfoysal Date: Mon, 6 Jul 2026 01:59:14 +0600 Subject: [PATCH] feat: add CLI --verbose shortcut for log level --- README.md | 4 ++++ docksec/cli.py | 5 ++++- tests/test_cli.py | 51 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 59 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a15f782..dabe216 100644 --- a/README.md +++ b/README.md @@ -134,6 +134,9 @@ docksec -i myapp:latest --image-only --baseline .docksec-baseline.json --fail-on # Reduce output to warnings, errors, and the result summary docksec Dockerfile --scan-only --quiet +# Enable INFO-level logs for this run +docksec Dockerfile --scan-only --verbose + # Disable colored output (also honors the NO_COLOR env var) docksec Dockerfile --no-color ``` @@ -141,6 +144,7 @@ docksec Dockerfile --no-color Every scan ends with a result summary: a severity table, the security score with a rating, a "Quick take" action block, the generated reports, and a suggested next command. Use `--quiet` for a compact result and `--no-color` for plain output. +Use `--verbose` or `-v` to force INFO-level logs when needed. ### Machine-readable output diff --git a/docksec/cli.py b/docksec/cli.py index 5159e90..d9743fe 100644 --- a/docksec/cli.py +++ b/docksec/cli.py @@ -73,6 +73,7 @@ def main() -> None: parser.add_argument('--offline', dest='offline', action='store_true', help='Run without network access: use the local Trivy DB (no DB update) and skip AI analysis') parser.add_argument('--baseline', dest='baseline', metavar='FILE', help='Path to a baseline file; with --fail-on, only findings not present in the baseline trigger the gate') parser.add_argument('--update-baseline', dest='update_baseline', action='store_true', help='Write the current scan findings to --baseline instead of gating against it') + parser.add_argument('-v', '--verbose', action='store_true', help='Enable verbose INFO-level logging for this run') parser.add_argument('--quiet', action='store_true', help='Reduce output to warnings, errors, and the result summary') parser.add_argument('--no-color', action='store_true', help='Disable colored output (also honors the NO_COLOR env var)') parser.add_argument('--version', action='version', version=f'DockSec {get_version()}') @@ -93,6 +94,8 @@ def main() -> None: os.environ["LLM_PROVIDER"] = args.provider if args.model: os.environ["LLM_MODEL"] = args.model + if args.verbose and not os.getenv("DOCKSEC_LOG_LEVEL"): + os.environ["DOCKSEC_LOG_LEVEL"] = "INFO" # Set compact output mode if requested if args.compact_output: @@ -679,4 +682,4 @@ def _suggest_next_command(args, results, run_ai, run_compose_analysis): return "" if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/tests/test_cli.py b/tests/test_cli.py index 1e943be..d552963 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -249,6 +249,57 @@ def test_quiet_and_no_color_flags_are_accepted(self): main() self.assertNotEqual(ctx.exception.code, 2) + @patch('sys.argv', ['docksec', 'Dockerfile', '--scan-only', '-v']) + @patch('docksec.cli.DockerSecurityScanner', create=True) + def test_verbose_flag_sets_log_level(self, mock_scanner_class): + """`-v/--verbose` enables INFO logs when no override env var exists.""" + from docksec.cli import main + + scanner = Mock() + scanner.run_full_scan.return_value = { + "json_data": [], + "scan_mode": "full", + "dockerfile_scan": {"skipped": False, "success": True}, + "image_scan": {"skipped": True, "success": True}, + "timestamp": "2026-01-01 00:00:00", + "dockerfile": "Dockerfile", + } + scanner.get_security_score.return_value = 88 + scanner.generate_all_reports.return_value = {} + scanner.RESULTS_DIR = '/tmp' + mock_scanner_class.return_value = scanner + + with patch('builtins.print'): + with patch.dict(os.environ, {}, clear=True): + main() + self.assertEqual(os.environ.get("DOCKSEC_LOG_LEVEL"), "INFO") + + + @patch('sys.argv', ['docksec', 'Dockerfile', '--scan-only', '-v']) + @patch('docksec.cli.DockerSecurityScanner', create=True) + def test_verbose_respects_existing_log_level(self, mock_scanner_class): + """`-v` should not override an explicit DOCKSEC_LOG_LEVEL from env.""" + from docksec.cli import main + + scanner = Mock() + scanner.run_full_scan.return_value = { + "json_data": [], + "scan_mode": "full", + "dockerfile_scan": {"skipped": False, "success": True}, + "image_scan": {"skipped": True, "success": True}, + "timestamp": "2026-01-01 00:00:00", + "dockerfile": "Dockerfile", + } + scanner.get_security_score.return_value = 88 + scanner.generate_all_reports.return_value = {} + scanner.RESULTS_DIR = '/tmp' + mock_scanner_class.return_value = scanner + + with patch('builtins.print'): + with patch.dict(os.environ, {"DOCKSEC_LOG_LEVEL": "WARNING"}, clear=True): + main() + self.assertEqual(os.environ.get("DOCKSEC_LOG_LEVEL"), "WARNING") + class TestCLIHelpers(unittest.TestCase): """Test cases for the CLI summary helper functions."""