Skip to content

STG-4451/4475/4478: dual-mode CLI (monolith + microservices/Clark)#137

Merged
npinaev merged 6 commits into
mainfrom
feature/ms-dual-mode
Jul 9, 2026
Merged

STG-4451/4475/4478: dual-mode CLI (monolith + microservices/Clark)#137
npinaev merged 6 commits into
mainfrom
feature/ms-dual-mode

Conversation

@npinaev

@npinaev npinaev commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

What this is

Makes mdast-cli dual-mode: a single package that works against both the classic monolith installation of the DAST platform and the new microservices one (Kubernetes, behind the Clark facade). The command line is identical and the flags are the same; the mode is auto-detected or forced via an env var.

The monolith path is behaviourally unchanged: all microservices logic lives on a separate execution branch, and the monolith regression tests are in the suite.

How the mode is selected

  • MDAST_CLI_MODE=auto|monolith|microservices (default auto).
  • In auto, the CLI probes GET /rest/architectures/ and classifies the installation by the shape of the payload (string type + os_version -> microservices; integer type -> monolith), not by a bare HTTP 200. A monolith that happens to answer 200 to a Bearer probe is not misclassified.
  • No new CLI flags are introduced (OOS-671-03 contract), env only.

Microservices flow (Clark facade)

architectures -> resolve platform by ext -> testcase -> engines (active STARTED) -> dedup by md5 -> upload_info (md5-first multipart + X-File-Size, presigned MinIO POST) -> precheck gate -> create (MANUAL without testcase / AUTO with testcase) -> start (409 tolerated) -> poll to terminal (stage, status) -> reports (soft-fail).

  • A scan without a test case is created as MANUAL (install/launch/wait/stop); test-case replays are AUTO (STG-4475). scanyon rejects type=AUTO without a testcase_id.
  • POST /scans/{id}/start/ is not idempotent: a retry after a successful start returns 409, so the CLI confirms the scan state and continues, failing only on a genuine conflict.
  • Polling tolerates transient 5xx (502/503/504) and network drops; 4xx is terminal. A long CI poll survives flaky downstream blips.

Unified exit-code contract (both installations)

0 SUCCESS, 1 INTERNAL_ERROR, 2 INVALID_ARGS, 4 DOWNLOAD_FAILED, 5 SCAN_FAILED, 6 NETWORK_ERROR, 7 AUTH_ERROR, 8 PRECHECK_BLOCKED (there is no code 3).

  • 401/403 -> AUTH_ERROR(7) in both installations; network errors / transient 5xx on read/poll/upload -> NETWORK_ERROR(6) (the CI "retry" signal).
  • --architecture_id, --appium_script_path, --cr_report on microservices are either ignored or exit INVALID_ARGS(2) (not supported by the platform).

Reports

  • --report_format {pdf,json,all,none} (default pdf), shared by both flows; --pdf_report_file_name / --summary_report_json_file_name still force a format (backward compatible).
  • On microservices a report is soft-fail: a finished scan stays green (exit 0) even if the report render (Pepper) fails, because it is a separate service.

Security and robustness

  • TLS verification is secure by default (MDAST_TLS_VERIFY; unset/empty stay secure, disabled only by an explicit 0/false/no/off).
  • Server-controlled fields (message, ids, package_name) are sanitised before logging (CWE-117: guards against log-line forgery via CR/LF/ANSI).
  • Upload accepts any 2xx; it is idempotent by md5 (a retry does not re-upload the file).
  • Fixes a real crash in nexus2 when --nexus2_file_name is omitted (None into os.path.join).

Tests

151 tests (unit / contract / smoke / e2e), including:

  • positive "download-from-store -> scan" e2e across 8 sources (RuStore, RuMarket, AppGallery, Nexus 2/3, Firebase, Google Play, App Store);
  • negative Sting-side e2e (transient and terminal failures at each step of the flow);
  • monolith regression tests, client contract tests, mode resolution, poll/report-download resilience.

Documentation

README: installation-modes + env section, corrected default timeout (~1 hour), CR report relabelled Compliance (a Bank-of-Russia call-trace-immutability report, not a "change request"), Docker report-path note.

Compatibility

Monolith installation: behaviour and exit codes unchanged. Microservices installation: new support. Verified live on the dev stand (upload, MANUAL/AUTO scan, JSON/PDF reports).

npinaev added 6 commits July 8, 2026 02:57
…lation support

- mdast_cli_core/microservices.py: Clark facade client (Bearer auth, md5-first
  multipart upload with X-File-Size, application_md5 precheck, fsm_locked two-phase
  scan creation, scanyon-native (stage, status) polling, /rest/scans/{id}/report)
- mdast_cli_core/factory.py: installation mode resolution (MDAST_CLI_MODE env or
  probe autodetect), monolith path unchanged
- mdast_cli/ms_flow.py: microservices scan flow with pre-check gate (exit code 8),
  engines preflight by type/status, reports to user-provided paths
- tests/: 81 tests (unit, contract, smoke incl. monolith regression, security)
- CI: tests workflow + test gates before PyPI/Docker publishing
…start 409

Found by live e2e on dev:
- scanyon rejects type=AUTO without testcase_id (services/scan.py: 'Testcase ID
  should be set for auto scan'). The DEC-671-02 premise 'AUTO is the only CLI
  mode' is wrong: a scan without a test case must be MANUAL (server sets
  wait=True, install/launch/wait/stop semantics), only test-case replays are
  AUTO. create_manual_scan now sends type=MANUAL, precheck mirrors the type.
- POST /scans/{id}/start/ is not idempotent; the facade retries it, so a retry
  after the first (successful) unlock returns 409 'not in initial state' though
  the scan did start. CLI now confirms scan state on 409 and continues if the
  scan is past CREATED, fails only on a genuine conflict.
- Tests updated (MANUAL body, precheck type) + 2 new smoke tests for the 409
  retry-tolerance and genuine-conflict paths. 83 passed.
Live e2e hit a transient 502 (scanyon momentarily unavailable) on GET
/scans/{id}/ mid-poll and the CLI died, though the scan kept running. A long
CI poll must survive flaky downstream blips: _get_scan now retries transient
5xx (502/503/504) and network errors a few times before failing; 4xx stays
terminal. +4 unit tests. 87 passed.
…polling)

Report download (Pepper via Clark) can hit transient 502 right after a scan
finishes; wrap download in the same retry as _get_scan so a blip doesn't lose
a finished scan's report. Confirmed live: JSON report downloads, PDF currently
502s server-side (Pepper WeasyPrint CSS issue - separate server bug).
… and e2e tests

Report handling:
- --report_format {pdf,json,all,none} (default pdf), shared by both flows.
  resolve_report_targets() centralises output paths (scan_report_<id>.pdf/.json
  defaults; --pdf_report_file_name / --summary_report_json_file_name still force a
  format and keep backward compatibility). Microservices reports are soft-fail: a
  Pepper render failure keeps a finished scan green (exit 0 with a warning).

Exit-code parity and robustness:
- Required url/company_id/token validation, app-file existence guard, cr_report
  requires stingray creds. Both flows wrap RequestException -> NETWORK_ERROR(6).
- 401/403 -> AUTH_ERROR(7) consistently in both installations.
- Microservices upload accepts any 2xx (not only 201); server-controlled fields
  sanitised before logging (CWE-117); monolith architecture resolution guards
  non-dict payload items instead of crashing.
- factory: TLS verify secure-by-default (unset/empty stay on), architectures
  content-fingerprint probe (not a bare HTTP 200), clearer ambiguous/empty errors.
- nexus2: fix crash when --nexus2_file_name omitted (None into os.path.join).

Docs: README default timeout corrected to ~1h, CR report relabelled Compliance
(not Change Request), Docker report-path note added.

Tests: positive store-download -> scan e2e (8 stores), negative Sting-side e2e,
report target resolution, report-download resilience, mode-probe path fix. 151 passed.

.gitignore: ignore .venv/ and scan_report_* CLI artifacts.
@npinaev npinaev force-pushed the feature/ms-dual-mode branch from 1b44d3e to a5c96c6 Compare July 9, 2026 14:27
@npinaev npinaev merged commit a3901a7 into main Jul 9, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant