Skip to content
Open
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
115 changes: 115 additions & 0 deletions .github/workflows/sbom.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
name: Generate SBOM

# This workflow uses cyclonedx-py and publishes an sbom.json artifact.
# It runs on manual trigger or when package files change on the target
# branches, and creates a PR with the updated SBOM.
# Internal documentation: go/sbom-scope

on:
workflow_dispatch: {}
push:
branches: ['main', '5.2.x']
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added the 5.2.x branch here, but I'm not sure if it works correctly. Will the job checkout this branch and send the PR to it?

paths:
- 'pyproject.toml'
- 'requirements.txt'
Comment on lines +12 to +14
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's possible for these files to have changes that don't involve any dependency changes. Is there logic in this job that prevents a PR from being opened in this case? (I guess GitHub may disallow a PR with no changes, or possibly it would be closed immediately; just want to confirm.)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure myself, but I would say that if it:

  1. Has no changes, it wouldn't populate
  2. Does make a new one, we just close it. The SBOM generation matters most right before we push a release.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Depending on the answer to #451 (comment), it might be better to simply make this part of the pre-release process.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the file changes at all, it will trigger the action to run and make an sbom PR. you'd just see the dates/metadata of the sbom.json to be changed. In that scenario the team can just close the PR.

Our team does require that the master branch have an up-to-date sbom for tracking. I can definitely add the logic if you think it's worth the extra step (actions will still get ran regardless, but we'd just compare the sbom files to see if any components actually got changed)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it's straightforward, I think it would be valuable to avoid useless PRs, especially if this script has to be applied to many repositories.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And incidentally, we don't have any dependencies in pyproject.toml (besides the optional ones for building the docs which your script doesn't install, so won't be detected anyway) so do we need to have this job watch that file?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pyproject.toml stores the info for the actual django-mngodb-backend. Changes to meta data will be reflected into sbom (description, versions, etc). With the new check for component diff check, this should solve the false positive change issue if you're modifying something that won't update the sbom.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to clarify: we do have dependencies in pyproject.toml. The fact that pyproject.toml is configured to read requirements.txt via tool.hatch.metadata.hooks.requirements_txt is an implementation detail. In fact, we may want to consider moving those dependencies to pyproject.toml and removing requirements.txt because we have so few dependencies and don't really need a separate file to list them.

Copy link
Collaborator

@timgraham timgraham Dec 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@thanhnguyen-mdb FYI, see what pymongo did: mongodb/mongo-python-driver#2647 removing pyproject.toml from the watched paths). If I understand correctly, maybe it would have been better to use the alternative approach you implemented here (although I'm not sure if there's any practical difference).


permissions:
contents: write
pull-requests: write

jobs:
sbom:
name: Generate SBOM and Create PR
runs-on: ubuntu-latest
concurrency:
group: sbom-${{ github.ref }}
cancel-in-progress: false
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
persist-credentials: false
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.x"
- name: Generate SBOM
run: |
python -m venv .venv
source .venv/bin/activate
pip install .
pip uninstall -y pip setuptools
deactivate
python -m venv .venv-sbom
source .venv-sbom/bin/activate
pip install cyclonedx-bom==7.2.1
cyclonedx-py environment --spec-version 1.5 --output-format JSON --output-file sbom-new.json .venv
# Add PURL for django-mongodb-backend (local package doesn't get PURL automatically)
jq '(.components[] | select(.name == "django-mongodb-backend" and .purl == null)) |= (. + {purl: ("pkg:pypi/django-mongodb-backend@" + .version)})' sbom-new.json > sbom.tmp.json && mv sbom.tmp.json sbom-new.json
- name: Download CycloneDX CLI
run: |
curl -L -s -o /tmp/cyclonedx "https://github.com/CycloneDX/cyclonedx-cli/releases/download/v0.29.1/cyclonedx-linux-x64"
chmod +x /tmp/cyclonedx
- name: Validate SBOM
run: /tmp/cyclonedx validate --input-file sbom-new.json --fail-on-errors
- name: Check for changes
id: check_changes
run: |
if [ -f sbom.json ]; then
echo "Comparing new SBOM with existing sbom.json..."
# Use cyclonedx diff to check for component changes
DIFF_OUTPUT=$(/tmp/cyclonedx diff sbom.json sbom-new.json --component-versions)

# Check if there are meaningful changes (output contains more than just "None")
if echo "$DIFF_OUTPUT" | grep -q "^None$"; then
echo "No component changes detected (only metadata differs)"
echo "Keeping existing sbom.json"
rm sbom-new.json
else
echo "Component changes detected:"
echo "$DIFF_OUTPUT"
echo "Updating sbom.json"
mv sbom-new.json sbom.json
fi
else
echo "No existing sbom.json found, creating initial version"
mv sbom-new.json sbom.json
fi
- name: Cleanup
if: always()
run: rm -rf .venv .venv-sbom
- name: Upload SBOM artifact
uses: actions/upload-artifact@v6
with:
name: sbom
path: sbom.json
if-no-files-found: error
- name: Create Pull Request
uses: peter-evans/create-pull-request@84ae59a2cdc2258d6fa0732dd66352dddae2a412 # v7.0.9
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: 'Update SBOM after dependency changes'
branch: auto-update-sbom-${{ github.run_id }}
delete-branch: true
title: 'Update SBOM'
body: |
## Automated SBOM Update

This PR was automatically generated because dependency manifest files changed.

### Changes
- Updated `sbom.json` to reflect current dependencies

### Verification
The SBOM was generated using cyclonedx-py with the current Python environment.

### Triggered by
- Commit: ${{ github.sha }}
- Workflow run: ${{ github.run_id }}

---
_This PR was created automatically by the [SBOM workflow](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})_
labels: |
sbom
automated
dependencies