Skip to content
Open
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
10 changes: 7 additions & 3 deletions .github/workflows/sbom.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ on:
branches: ['master']
paths:
- 'requirements.txt'
- 'requirements/**.txt'
- '!requirements/docs.txt'
- '!requirements/test.txt'

permissions:
contents: write
Expand Down Expand Up @@ -39,7 +42,8 @@ jobs:
run: |
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
python tools/generate_sbom_requirements.py
pip install -r sbom-requirements.txt
pip install .
pip uninstall -y pip setuptools
deactivate
Expand All @@ -60,7 +64,7 @@ jobs:

- name: Cleanup
if: always()
run: rm -rf .venv .venv-sbom
run: rm -rf .venv .venv-sbom sbom-requirements.txt

- name: Upload SBOM artifact
uses: actions/upload-artifact@v5
Expand All @@ -76,7 +80,7 @@ jobs:
commit-message: 'chore: Update SBOM after dependency changes'
branch: auto-update-sbom-${{ github.run_id }}
delete-branch: true
title: 'chore: Update SBOM'
title: 'Automation: Update SBOM'
body: |
## Automated SBOM Update

Expand Down
74 changes: 74 additions & 0 deletions tools/generate_sbom_requirements.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
"""
Concatenate requirements files into sbom-requirements.txt at repository root.

- Includes repo_root/requirements.txt if present
- Includes all files matching repo_root/requirements/**/*.txt
- Excludes docs.txt and test.txt in the requirements folder
- Writes output to sbom-requirements.txt (overwrites)
"""
from __future__ import annotations

from pathlib import Path

EXCLUDED_NAMES = {"docs.txt", "test.txt"}


def collect_files(root: Path) -> list[Path]:
"""Collect requirement files to include in SBOM requirements.

Args:
root (Path): The root directory of the repository.

Returns:
list[Path]: A list of Paths to requirement files to include in the SBOM.
"""
files = []

# requirements.txt + all requirements/**/*.txt
for p in [root / "requirements.txt", *root.glob("requirements/**/*.txt")]:
if p.is_file() and p.name not in EXCLUDED_NAMES:
files.append(p)

return files


def write_combined_req_files(root: Path, files: list[Path], outname: str) -> Path:
"""Concatenate requirement files and write to outname file.

Args:
root (Path): The root directory of the repository.
files (list[Path]): A list of Paths to requirement files to include in the SBOM.
outname (str): The name of the output file.

Raises:
RuntimeError: If writing to the output file fails.

Returns:
Path: The path to the output file.
"""
outpath = root / outname
try:
with outpath.open("w", encoding="utf-8") as f:
for p in files:
with p.open("r", encoding="utf-8") as r:
content = r.read().rstrip()
if content:
f.write(content + "\n")
return outpath
except Exception as e:
raise RuntimeError(f"Failed to write {outpath}: {e}") from e


def main():
root = Path(__file__).parent.parent.resolve()

files = collect_files(root)
if not files:
raise FileNotFoundError(f"No requirement files found from {root}")

outpath = write_combined_req_files(root, files, "sbom-requirements.txt")
print(f"Wrote concatenated requirements to: {outpath}")


if __name__ == "__main__":
main()
Loading