CAS-174: cascadeguard scan — Easiest Use Case

Overview

Add a scan CLI command that analyses a repository directory, discovers all container-related artifacts, presents them interactively for selection, and produces analysis results. This is the zero-config onboarding path for new users.

Additionally, create a one-shot wrapper script (install.sh) that downloads the tool and runs scan in a single command.

Updated per CAS-222: incorporates the state-driven model, repo-per-image catalogue structure, and check-upstream integration decisions.

CLI Interface

cascadeguard scan [--dir PATH] [--non-interactive] [--format json|text|yaml] [--output FILE]
  • --dir defaults to . (current working directory)
  • --non-interactive scans all discovered artifacts without prompting
  • --format controls output format (default: text)
  • --output writes results to file instead of stdout

Architecture

Discovery Protocol

Each discovery module implements a Discoverer protocol:

class Discoverer(Protocol):
    name: str  # e.g. "Dockerfiles"
    def discover(self, root: Path) -> list[DiscoveredArtifact]: ...

DiscoveredArtifact — dataclass: kind (dockerfile|actions|compose|k8s), path (relative), details (parsed metadata dict).

Discovery Modules

ModuleGlob PatternsReuses
Dockerfile**/Dockerfile, **/Dockerfile.*, **/*.dockerfile, **/Containerfile*CascadeGuardTool.parse_dockerfile_base_images()
GitHub Actions.github/workflows/*.yml, .github/workflows/*.yamlActionsPinner._USES_RE regex
Compose/Stack**/docker-compose*.yml, **/compose*.yml variants
K8s Manifests**/*.yaml filtered by apiVersion + kind heuristic

Performance: os.walk with early pruning of .git, node_modules, vendor, __pycache__, .venv.

Stretch: GitLab CI (.gitlab-ci.yml).

Interactive Selection UI

Python stdlib curses checkbox selector. All items default to selected. Arrow keys navigate, space toggles, enter confirms.

Fallback: When curses is unavailable (piped stdin, Windows, no TTY), fall back to --non-interactive mode that scans everything.

Analysis Output

Structured report per artifact:

scan:
  directory: /path/to/repo
  timestamp: "2026-04-04T18:45:00Z"
  artifacts:
    - kind: dockerfile
      path: Dockerfile
      base_images:
        - image: python:3.11-slim
          pinned: false
          recommendation: "Pin to digest"
    - kind: github_actions
      path: .github/workflows/ci.yaml
      actions:
        - uses: actions/checkout@v4
          pinned: false
          recommendation: "Pin to SHA (use cascadeguard actions pin)"
  summary:
    total_artifacts: 12
    selected: 10
    images_found: 15
    unpinned_images: 8
    unpinned_actions: 2

End-to-End Flow (per CAS-222)

This section documents how scan fits into the broader CascadeGuard lifecycle. The scan command is the discovery entry point; downstream stages use the state-driven model agreed in CAS-222.

User runs wrapper script or `cascadeguard scan`
      │
      ▼
  Discovery: walk repo, find Dockerfiles / Actions / Compose / K8s
      │
      ▼
  Interactive selection (or --non-interactive)
      │
      ▼
  Analysis: parse each artifact, extract image refs + action refs
      │
      ▼
  Report: summary + per-artifact recommendations
      │
      ▼
  (Optional) User acts on recommendations:
      │
      ├── `cascadeguard actions pin` — pin unpinned GH Actions to SHA
      ├── `cascadeguard enrol` — enrol discovered images into catalogue
      └── `cascadeguard check-upstream` — detect new upstream tags/digests
                │
                ▼
          CLI writes state to catalogue repo (state-driven, no notifications)
                │
                ▼
          CI reacts to committed state changes:
          - Rebuild if digest drifted
          - Open enrolment PR for new stable tags
          - Update dashboard/catalogue

Key Architectural Decisions (from CAS-222)

DecisionChoiceRationale
Catalogue structureRepo-per-image, central catalogueCI isolation, clean release tagging
State modelCLI writes state, CI reacts to commitsNo notification/issue-creation from CLI
Upstream detectioncheck-upstream CLI commandFits noun-verb CLI pattern
KargoNot usedNo k8s in CascadeGuard environment

File Structure

app/
├── app.py                  # Add scan subcommand + cmd_scan handler
├── scan/
│   ├── __init__.py         # Public API: run_scan()
│   ├── discoverers.py      # All Discoverer implementations
│   ├── models.py           # DiscoveredArtifact, ScanResult dataclasses
│   ├── ui.py               # Interactive selection (curses + fallback)
│   └── report.py           # Analysis and output formatting
├── tests/
│   ├── test_scan_discovery.py  # Unit tests for each discoverer
│   ├── test_scan_report.py     # Report generation tests
│   └── test_scan_ui.py         # UI tests (mocked curses)
install.sh                      # One-shot wrapper script

Implementation Phases

Phase 1: Core Discovery

  • Define DiscoveredArtifact model and Discoverer protocol in models.py
  • Implement Dockerfile and GitHub Actions discoverers (reuse existing parsing)
  • Wire scan subcommand into build_parser() and cmd_scan() handler
  • Unit tests for both discoverers

Phase 2: Extended Discovery

  • Compose/Stack discoverer
  • Kubernetes manifest discoverer
  • Integration tests with fixture repos

Phase 3: Interactive UI

  • Curses-based checkbox selector in ui.py
  • Fallback plain-text mode for non-TTY environments
  • --non-interactive flag

Phase 4: Analysis & Reporting

  • Structured output (YAML/JSON/text)
  • Recommendations engine (pin suggestions, enrol suggestions)
  • Summary statistics
  • Bridge to existing commands: output includes actionable cascadeguard commands the user can run next

Phase 5: Wrapper Script

  • install.sh — detect OS/arch, download release, run cascadeguard scan
  • Distribution: GitHub releases
  • Smoke tests

Technical Decisions

  • No new Python dependencies for Phases 1–4 (curses is stdlib, pyyaml already present)
  • Reuse existing code: Dockerfile parsing from CascadeGuardTool, action ref regex from ActionsPinner
  • K8s detection: Filter **/*.yaml by apiVersion + kind to avoid false positives
  • Large repos: os.walk with early directory pruning
  • curses on Windows: Fallback to --non-interactive; optionally support windows-curses
  • Scope: Dockerfile + Actions + Compose + K8s. GitLab CI is stretch.

Dependencies

  • Depends on: existing CascadeGuardTool class, ActionsPinner regex
  • Feeds into: check-upstream (CAS-222), actions pin, enrol commands
  • No new external Python packages required