Skip to content
Merged
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
71 changes: 71 additions & 0 deletions scripts/f4_linter_linux_provisioning.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

from __future__ import annotations

import argparse
import importlib
import json
import sys
Expand Down Expand Up @@ -122,6 +123,9 @@

LINUX_MANIFEST_SCHEMA_VERSION = 1
LINUX_MANIFEST_FILENAME = "f4-linux-tools.json"
EXIT_OK = 0
EXIT_INVALID = 1
EXIT_OFFICIAL_EVIDENCE_DRIFT = 2

LINUX_PACKAGE_MANAGER_ARTIFACT_IDS: tuple[str, ...] = (
"deb",
Expand Down Expand Up @@ -1356,6 +1360,17 @@ def linux_official_distro_evidence_drift_errors() -> list[str]:
return errors


def linux_official_distro_evidence_audit_report() -> dict[str, Any]:
"""Return a deterministic release-facing official evidence audit report."""
drift_errors = linux_official_distro_evidence_drift_errors()
return {
"summary": linux_official_distro_evidence_summary(),
"matrix": list(linux_official_distro_evidence_matrix()),
"drift_errors": drift_errors,
"ok": not drift_errors,
}


def _generated_official_distro_evidence_record(
row: Mapping[str, Any],
) -> tuple[LinuxDistroMappingEvidenceRecord | None, str | None]:
Expand Down Expand Up @@ -2780,3 +2795,59 @@ def _tool_path_errors(
except (KeyError, TypeError, ValueError) as exc:
errors.append(f"{prefix}: {exc}")
return errors


class _ContractArgumentParser(argparse.ArgumentParser):
def error(self, message: str) -> None: # type: ignore[override]
self.print_usage(sys.stderr)
print(f"{self.prog}: error: {message}", file=sys.stderr)
raise SystemExit(EXIT_INVALID)


def build_parser() -> argparse.ArgumentParser:
parser = _ContractArgumentParser(
prog="f4_linter_linux_provisioning.py",
description="Audit Linux F4 linter provisioning policy metadata.",
)
mode = parser.add_mutually_exclusive_group(required=True)
mode.add_argument(
"--official-evidence-audit",
action="store_true",
help="print the official distro evidence audit report as JSON",
)
mode.add_argument(
"--check-official-evidence-drift",
action="store_true",
help="fail if official distro evidence drift is detected",
)
return parser


def _official_evidence_drift_check_message(drift_errors: list[str]) -> str:
if not drift_errors:
return "PASS: Linux official distro evidence drift audit clean"
lines = ["FAIL: Linux official distro evidence drift detected"]
lines.extend(f"ERROR: {error}" for error in drift_errors)
return "\n".join(lines)


def main(argv: list[str] | None = None) -> int:
args = build_parser().parse_args(argv)
if args.official_evidence_audit:
print(
json.dumps(
linux_official_distro_evidence_audit_report(),
indent=2,
sort_keys=True,
)
)
return EXIT_OK

drift_errors = linux_official_distro_evidence_drift_errors()
message = _official_evidence_drift_check_message(drift_errors)
print(message, file=sys.stderr if drift_errors else sys.stdout)
return EXIT_OFFICIAL_EVIDENCE_DRIFT if drift_errors else EXIT_OK


if __name__ == "__main__":
raise SystemExit(main())
Comment thread
SSobol77 marked this conversation as resolved.
61 changes: 61 additions & 0 deletions tests/packaging/test_f4_linter_linux_provisioning.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
from __future__ import annotations

import json
import subprocess
import sys
from pathlib import Path
from types import ModuleType
from typing import Any
Expand Down Expand Up @@ -118,6 +120,23 @@ def _manifest_distro_evidence(
return _manifest_tool(manifest, tool_id)["distro_mapping"]["evidence"]


def _run_linux_provisioning_script(
repo_root: Path,
*args: str,
) -> subprocess.CompletedProcess[str]:
return subprocess.run(
[
sys.executable,
str(repo_root / "scripts/f4_linter_linux_provisioning.py"),
*args,
],
cwd=repo_root,
capture_output=True,
text=True,
check=False,
)


def _complete_verified_evidence(evidence: dict[str, Any]) -> dict[str, Any]:
promoted = dict(evidence)
promoted.update(
Expand Down Expand Up @@ -367,6 +386,48 @@ def test_official_distro_evidence_drift_errors_are_empty(
assert linux_helper.linux_official_distro_evidence_drift_errors() == []


def test_official_distro_evidence_audit_report_is_clean(
linux_helper: ModuleType,
) -> None:
report = linux_helper.linux_official_distro_evidence_audit_report()

assert report["ok"] is True
assert report["drift_errors"] == []
assert report["summary"]["official_override_count"] == 6
assert len(report["matrix"]) == 6
assert [(row["artifact_entry_id"], row["tool_id"]) for row in report["matrix"]] == (
list(DEBIAN_OFFICIAL_EVIDENCE_KEYS)
)


def test_official_distro_evidence_audit_cli_prints_json_report(
repo_root: Path,
) -> None:
result = _run_linux_provisioning_script(repo_root, "--official-evidence-audit")

assert result.returncode == 0
assert result.stderr == ""
report = json.loads(result.stdout)
assert report["ok"] is True
assert report["drift_errors"] == []
assert report["summary"]["official_override_count"] == 6
assert report["summary"]["non_debian_override_count"] == 0
assert len(report["matrix"]) == 6


def test_official_distro_evidence_drift_check_cli_passes(
repo_root: Path,
) -> None:
result = _run_linux_provisioning_script(
repo_root,
"--check-official-evidence-drift",
)

assert result.returncode == 0
assert result.stderr == ""
assert result.stdout == "PASS: Linux official distro evidence drift audit clean\n"


def test_official_distro_evidence_drift_comparison_rejects_mismatched_url(
linux_helper: ModuleType,
) -> None:
Expand Down
Loading