name: Generate SBOM on: workflow_run: workflows: ["Build and Publish Release"] types: - completed workflow_dispatch: inputs: release_version: description: "Version of the release to generate SBOM for" required: true pull_request: branches: - develop permissions: read-all concurrency: group: sbom-${{ github.ref }} cancel-in-progress: true env: REGISTRY: ghcr.io jobs: generate-sbom: runs-on: ubuntu-latest if: github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success' permissions: contents: write actions: read packages: read steps: - name: Checkout Repo (for version detection) if: github.event_name == 'workflow_run' uses: actions/checkout@v6.0.2 with: fetch-depth: 0 - name: Get Version From Workflow Run id: tag if: github.event_name == 'workflow_run' run: | # Get the SHA from the workflow run SHA="${{ github.event.workflow_run.head_sha }}" # Find the tag pointing to this SHA TAG=$(git tag --points-at $SHA | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | head -1) if [ -z "$TAG" ]; then echo "Error: No version tag found for SHA $SHA" exit 1 fi echo "TRIGGERED_TAG=$TAG" >> $GITHUB_ENV echo "Found tag: $TAG" - name: Determine Version Number id: version_number run: | if [ "${{ github.event_name }}" == "workflow_run" ]; then version=$TRIGGERED_TAG elif [ -n "${{ inputs.release_version }}" ]; then version=${{ inputs.release_version }} else version="" fi if [[ ${version:0:1} == "v" ]]; then echo "RELEASE_VERSION=${version:1}" >> $GITHUB_OUTPUT else echo "RELEASE_VERSION=$version" >> $GITHUB_OUTPUT fi - name: Make Branch Name id: branch if: github.event_name != 'pull_request' env: RELEASE_VERSION: ${{ steps.version_number.outputs.RELEASE_VERSION }} run: | echo "BRANCH_NAME=v${RELEASE_VERSION%.*}" >> $GITHUB_OUTPUT - name: Checkout Repo if: github.event_name != 'workflow_run' uses: actions/checkout@v6.0.2 with: ref: ${{ github.event_name != 'pull_request' && steps.branch.outputs.BRANCH_NAME || '' }} - name: Set OpenCost Image Tag id: image_tag if: github.event_name != 'pull_request' env: REPO_OWNER: ${{ github.repository_owner }} RELEASE_VERSION: ${{ steps.version_number.outputs.RELEASE_VERSION }} run: | echo "IMAGE_TAG=ghcr.io/$REPO_OWNER/opencost:$RELEASE_VERSION" >> $GITHUB_OUTPUT # Generate SBOM for source code using Trivy - name: Run Trivy SBOM for Source Code (SPDX) uses: aquasecurity/trivy-action@master with: scan-type: 'fs' scan-ref: '.' format: 'spdx-json' output: 'opencost-source-sbom.spdx.json' - name: Run Trivy SBOM for Source Code (CycloneDX) uses: aquasecurity/trivy-action@master with: scan-type: 'fs' scan-ref: '.' format: 'cyclonedx' output: 'opencost-source-sbom.cyclonedx.json' # Generate SBOM for container image using Trivy - name: Run Trivy SBOM for Container Image (SPDX) if: github.event_name != 'pull_request' uses: aquasecurity/trivy-action@master with: scan-type: 'image' image-ref: ${{ steps.image_tag.outputs.IMAGE_TAG }} format: 'spdx-json' output: 'opencost-container-sbom.spdx.json' - name: Run Trivy SBOM for Container Image (CycloneDX) if: github.event_name != 'pull_request' uses: aquasecurity/trivy-action@master with: scan-type: 'image' image-ref: ${{ steps.image_tag.outputs.IMAGE_TAG }} format: 'cyclonedx' output: 'opencost-container-sbom.cyclonedx.json' # Display SBOM contents on PRs for review - name: Display SBOM Contents if: github.event_name == 'pull_request' run: | echo "## SBOM Contents (SPDX Format)" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "### Package Count" >> $GITHUB_STEP_SUMMARY PACKAGE_COUNT=$(jq '.packages | length' opencost-source-sbom.spdx.json) echo "Total packages: $PACKAGE_COUNT" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "### Top-level Packages" >> $GITHUB_STEP_SUMMARY echo '```' >> $GITHUB_STEP_SUMMARY jq -r '.packages[] | select(.name != null) | "\(.name) - \(.versionInfo // "unknown")"' opencost-source-sbom.spdx.json | head -50 >> $GITHUB_STEP_SUMMARY echo '```' >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "
" >> $GITHUB_STEP_SUMMARY echo "Full SPDX SBOM (click to expand)" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo '```json' >> $GITHUB_STEP_SUMMARY cat opencost-source-sbom.spdx.json >> $GITHUB_STEP_SUMMARY echo '```' >> $GITHUB_STEP_SUMMARY echo "
" >> $GITHUB_STEP_SUMMARY # Upload SBOMs as artifacts - name: Upload SBOM Artifacts uses: actions/upload-artifact@v7 with: name: sbom-files path: | opencost-source-sbom.spdx.json opencost-source-sbom.cyclonedx.json opencost-container-sbom.spdx.json opencost-container-sbom.cyclonedx.json if-no-files-found: ignore # Attach SBOMs to GitHub release (only for releases, not PRs) - name: Attach SBOMs to GitHub Release if: github.event_name != 'pull_request' uses: softprops/action-gh-release@v2 with: tag_name: v${{ steps.version_number.outputs.RELEASE_VERSION }} files: | opencost-source-sbom.spdx.json opencost-source-sbom.cyclonedx.json opencost-container-sbom.spdx.json opencost-container-sbom.cyclonedx.json fail_on_unmatched_files: false # Create a summary of the SBOM generation - name: Generate Summary run: | echo "## SBOM Generation Summary" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "✅ Generated SBOMs for OpenCost ${{ steps.version_number.outputs.RELEASE_VERSION || 'PR build' }}" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "### Generated Artifacts:" >> $GITHUB_STEP_SUMMARY echo "- Source Code SBOM (SPDX)" >> $GITHUB_STEP_SUMMARY echo "- Source Code SBOM (CycloneDX)" >> $GITHUB_STEP_SUMMARY if [ "${{ github.event_name }}" != "pull_request" ]; then echo "- Container Image SBOM (SPDX)" >> $GITHUB_STEP_SUMMARY echo "- Container Image SBOM (CycloneDX)" >> $GITHUB_STEP_SUMMARY fi echo "" >> $GITHUB_STEP_SUMMARY if [ "${{ github.event_name }}" != "pull_request" ]; then echo "📦 SBOMs have been attached to the GitHub release" >> $GITHUB_STEP_SUMMARY fi