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
3 changes: 3 additions & 0 deletions tools/sbom-diff-and-risk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,9 @@ JSON, Markdown, summary, policy sidecar, and SARIF examples with:
python scripts/regenerate-example-artifacts.py
python scripts/regenerate-example-artifacts.py --check
```

Use `python scripts/regenerate-example-artifacts.py --list` and `--only SLUG`
for focused checks such as `--only requirements`.

## Enforcement Mode

Expand Down
14 changes: 14 additions & 0 deletions tools/sbom-diff-and-risk/docs/example-artifact-regeneration.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,20 @@ python scripts/regenerate-example-artifacts.py --check
The test suite runs this check mode so stale local JSON, Markdown, summary, or
policy-sidecar examples fail predictably.

Use `--list` to see the available artifact set slugs:

```powershell
python scripts/regenerate-example-artifacts.py --list
```

Use `--only SLUG` to regenerate or check a focused subset:

```powershell
python scripts/regenerate-example-artifacts.py --check --only requirements
```

`--only` can be repeated when a change affects more than one artifact set.

## Boundaries

The regeneration script covers no-network JSON, Markdown, summary, policy
Expand Down
67 changes: 59 additions & 8 deletions tools/sbom-diff-and-risk/scripts/regenerate-example-artifacts.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

@dataclass(frozen=True)
class ExampleArtifactSet:
slug: str
name: str
base_args: tuple[str, ...]
outputs: tuple[tuple[str, str], ...]
Expand All @@ -22,6 +23,7 @@ class ExampleArtifactSet:

ARTIFACT_SETS: tuple[ExampleArtifactSet, ...] = (
ExampleArtifactSet(
slug="cyclonedx",
name="cyclonedx report, summary, and markdown",
base_args=(
"--before",
Expand All @@ -38,6 +40,7 @@ class ExampleArtifactSet:
),
),
ExampleArtifactSet(
slug="policy-warn",
name="warn-only policy report",
base_args=(
"--before",
Expand All @@ -53,6 +56,7 @@ class ExampleArtifactSet:
),
),
ExampleArtifactSet(
slug="policy-fail",
name="blocking policy report and sidecar",
base_args=(
"--before",
Expand All @@ -70,6 +74,7 @@ class ExampleArtifactSet:
expected_exit_codes=(1,),
),
ExampleArtifactSet(
slug="requirements",
name="requirements report",
base_args=(
"--before",
Expand All @@ -85,6 +90,7 @@ class ExampleArtifactSet:
),
),
ExampleArtifactSet(
slug="sarif",
name="strict-policy SARIF report",
base_args=(
"--before",
Expand Down Expand Up @@ -112,28 +118,73 @@ def main(argv: Sequence[str] | None = None) -> int:
action="store_true",
help="Generate artifacts into a temporary directory and fail if checked-in examples are stale.",
)
parser.add_argument(
"--list",
action="store_true",
help="List available artifact set slugs and exit.",
)
parser.add_argument(
"--only",
action="append",
default=[],
metavar="SLUG",
help="Regenerate or check only one artifact set slug. Repeat to select multiple sets.",
)
args = parser.parse_args(argv)

project_root = Path(__file__).resolve().parents[1]
artifact_sets = _select_artifact_sets(args.only, parser)
if args.list:
_print_artifact_sets(artifact_sets)
return 0
if args.check:
with tempfile.TemporaryDirectory(prefix="sbom-diff-risk-examples-") as temp_dir:
return _check_artifacts(project_root, Path(temp_dir))
return _write_artifacts(project_root, project_root / "examples")
return _check_artifacts(project_root, Path(temp_dir), artifact_sets)
return _write_artifacts(project_root, project_root / "examples", artifact_sets)


def _select_artifact_sets(
selected_slugs: Sequence[str],
parser: argparse.ArgumentParser,
) -> tuple[ExampleArtifactSet, ...]:
if not selected_slugs:
return ARTIFACT_SETS

by_slug = {artifact_set.slug: artifact_set for artifact_set in ARTIFACT_SETS}
unknown = [slug for slug in selected_slugs if slug not in by_slug]
if unknown:
parser.error(f"unknown artifact set slug: {unknown[0]}")

return tuple(by_slug[slug] for slug in selected_slugs)


def _print_artifact_sets(artifact_sets: Sequence[ExampleArtifactSet]) -> None:
for artifact_set in artifact_sets:
outputs = ", ".join(output_name for _, output_name in artifact_set.outputs)
print(f"{artifact_set.slug}: {artifact_set.name} ({outputs})")


def _write_artifacts(project_root: Path, output_root: Path) -> int:
for artifact_set in ARTIFACT_SETS:
def _write_artifacts(
project_root: Path,
output_root: Path,
artifact_sets: Sequence[ExampleArtifactSet],
) -> int:
for artifact_set in artifact_sets:
_run_artifact_set(project_root, output_root, artifact_set)
print(f"generated: {artifact_set.name}")
print(f"generated: {artifact_set.slug}")
return 0


def _check_artifacts(project_root: Path, output_root: Path) -> int:
_write_artifacts(project_root, output_root)
def _check_artifacts(
project_root: Path,
output_root: Path,
artifact_sets: Sequence[ExampleArtifactSet],
) -> int:
_write_artifacts(project_root, output_root, artifact_sets)

examples_dir = project_root / "examples"
stale_files: list[str] = []
for artifact_set in ARTIFACT_SETS:
for artifact_set in artifact_sets:
for _, output_name in artifact_set.outputs:
expected = (examples_dir / output_name).read_text(encoding="utf-8")
generated = (output_root / output_name).read_text(encoding="utf-8")
Expand Down
41 changes: 41 additions & 0 deletions tools/sbom-diff-and-risk/tests/test_example_artifacts.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,44 @@ def test_regenerate_example_artifacts_check_mode_passes() -> None:

assert result.returncode == 0, result.stdout + result.stderr
assert "all checked example artifacts are up to date" in result.stdout


def test_regenerate_example_artifacts_can_list_artifact_sets() -> None:
project_root = Path(__file__).resolve().parents[1]

result = subprocess.run(
[
sys.executable,
str(project_root / "scripts" / "regenerate-example-artifacts.py"),
"--list",
],
cwd=project_root,
text=True,
capture_output=True,
)

assert result.returncode == 0, result.stdout + result.stderr
assert "cyclonedx:" in result.stdout
assert "requirements:" in result.stdout
assert "sarif:" in result.stdout


def test_regenerate_example_artifacts_can_check_one_artifact_set() -> None:
project_root = Path(__file__).resolve().parents[1]

result = subprocess.run(
[
sys.executable,
str(project_root / "scripts" / "regenerate-example-artifacts.py"),
"--check",
"--only",
"requirements",
],
cwd=project_root,
text=True,
capture_output=True,
)

assert result.returncode == 0, result.stdout + result.stderr
assert "generated: requirements" in result.stdout
assert "generated: cyclonedx" not in result.stdout