diff --git a/.github/actions/update-sbom/action.yml b/.github/actions/update-sbom/action.yml new file mode 100644 index 000000000..9821701f4 --- /dev/null +++ b/.github/actions/update-sbom/action.yml @@ -0,0 +1,99 @@ +name: Generate SBOM +description: Generates CycloneDX SBOM using CycloneDX PHP Composer plugin +inputs: + output-file: + description: "Output filename for the SBOM" + required: false + default: "sbom.json" +outputs: + HAS_CHANGES: + description: "Whether the SBOM has meaningful changes compared to the existing version" + value: ${{ steps.check_changes.outputs.HAS_CHANGES }} +runs: + using: composite + steps: + - name: Allow CycloneDX plugin + shell: bash + run: composer config allow-plugins.cyclonedx/cyclonedx-php-composer true + + - name: Install CycloneDX plugin + shell: bash + run: composer require --dev cyclonedx/cyclonedx-php-composer + + - name: Generate SBOM + id: generate-sbom + shell: bash + run: | + echo "Generating SBOM for 'php' project..." + composer CycloneDX:make-sbom --output-file=sbom-new.json --output-format=json --spec-version=1.5 + + - name: Validate SBOM presence + shell: bash + run: | + if [ ! -f "sbom-new.json" ]; then + echo "Error: SBOM file not found" + exit 1 + fi + echo "SBOM file generated: sbom-new.json" + + - name: Download CycloneDX CLI + shell: bash + 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 + shell: bash + run: /tmp/cyclonedx validate --input-file sbom-new.json --fail-on-errors + + - name: Check for SBOM changes + id: check_changes + if: steps.generate-sbom.outcome == 'success' + shell: bash + env: + SBOM_FILE: ${{ inputs.output-file }} + run: | + JQ_NORMALIZER='del(.serialNumber) | del(.metadata.timestamp) | walk(if type == "object" and .timestamp then .timestamp = "TIMESTAMP_NORMALIZED" else . end)' + + if [ -f "$SBOM_FILE" ]; then + echo "Comparing new SBOM with existing $SBOM_FILE..." + + # First try cyclonedx diff for component-level comparison + DIFF_OUTPUT=$(/tmp/cyclonedx diff "$SBOM_FILE" sbom-new.json --component-versions 2>/dev/null || true) + + if echo "$DIFF_OUTPUT" | grep -q "^None$"; then + echo "No component changes detected via cyclonedx diff" + + # Double-check with jq normalization (excludes metadata like timestamps) + if diff -q \ + <(cat "$SBOM_FILE" | jq -r "$JQ_NORMALIZER") \ + <(cat sbom-new.json | jq -r "$JQ_NORMALIZER") > /dev/null 2>&1; then + echo "HAS_CHANGES=false" >> $GITHUB_OUTPUT + echo "No meaningful changes detected in SBOM" + rm sbom-new.json + else + echo "HAS_CHANGES=true" >> $GITHUB_OUTPUT + echo "Changes detected in SBOM (non-component changes)" + mv sbom-new.json "$SBOM_FILE" + fi + else + echo "Component changes detected:" + echo "$DIFF_OUTPUT" + echo "HAS_CHANGES=true" >> $GITHUB_OUTPUT + mv sbom-new.json "$SBOM_FILE" + fi + else + echo "No existing $SBOM_FILE found, creating initial version" + echo "HAS_CHANGES=true" >> $GITHUB_OUTPUT + mv sbom-new.json "$SBOM_FILE" + fi + continue-on-error: true + + - name: Final SBOM validation + shell: bash + run: | + if [ ! -f "${{ inputs.output-file }}" ]; then + echo "Error: Final SBOM file not found at ${{ inputs.output-file }}" + exit 1 + fi + echo "SBOM file validated: ${{ inputs.output-file }}" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 37bb2ed3f..244756616 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,6 +14,9 @@ on: type: "string" env: + PHP_VERSION: "8.2" + DRIVER_VERSION: "mongodb/mongo-php-driver@${{ inputs.version }}" + SBOM_FILE: "sbom.json" default-release-message: | The PHP team is happy to announce that version {0} of the MongoDB PHP library is now available. @@ -48,12 +51,6 @@ jobs: - name: "Create release output" run: echo '🎬 Release process for version ${{ inputs.version }} started by @${{ github.triggering_actor }}' >> $GITHUB_STEP_SUMMARY - - name: "Generate token and checkout repository" - uses: mongodb-labs/drivers-github-tools/secure-checkout@v3 - with: - app_id: ${{ vars.APP_ID }} - private_key: ${{ secrets.APP_PRIVATE_KEY }} - - name: "Store version numbers in env variables" run: | echo RELEASE_VERSION=${{ inputs.version }} >> $GITHUB_ENV @@ -94,9 +91,17 @@ jobs: git push origin ${RELEASE_BRANCH} # - # Preliminary checks done - commence the release process + # Preliminary checks done - setting up the environment # + - name: "Generate token and checkout repository" + uses: mongodb-labs/drivers-github-tools/secure-checkout@v3 + with: + app_id: ${{ vars.APP_ID }} + private_key: ${{ secrets.APP_PRIVATE_KEY }} + submodules: true + fetch-depth: 0 + - name: "Set up drivers-github-tools" uses: mongodb-labs/drivers-github-tools/setup@v3 with: @@ -104,6 +109,75 @@ jobs: aws_region_name: ${{ vars.AWS_REGION_NAME }} aws_secret_id: ${{ secrets.AWS_SECRET_ID }} + - name: "Setup PHP environment" + id: setup-php + uses: ./.github/actions/setup + with: + php-version: ${{ env.PHP_VERSION }} + driver-version: ${{ env.DRIVER_VERSION }} + working-directory: '.' + continue-on-error: true + + # + # Preliminary checks and setup done - generate SBOM before tagging + # + + - name: "Generate/Update composer.lock" + id: composer-lock + run: | + echo "Resolving dependencies and generating composer.lock..." + composer update --no-install + echo "composer.lock generated with resolved versions" + continue-on-error: true + + - name: "Generate SBOM" + id: generate-sbom + if: steps.composer-lock.outcome == 'success' + uses: ./.github/actions/update-sbom + with: + output-file: ${{ env.SBOM_FILE }} + continue-on-error: true + + - name: "Determine branch to merge up to" + if: steps.generate-sbom.outputs.HAS_CHANGES == 'true' + id: get-next-branch + uses: alcaeus/automatic-merge-up-action/get-next-branch@1.0.1 + with: + ref: ${{ github.ref_name }} + branchNamePattern: 'v.' + devBranchNamePattern: 'v.x' + ignoredBranches: ${{ vars.IGNORED_MERGE_UP_BRANCHES }} + + - name: "Manually merge up changes" + if: steps.generate-sbom.outputs.HAS_CHANGES == 'true' && steps.get-next-branch.outputs.hasNextBranch == 'true' + run: | + git checkout ${NEXT_BRANCH} + git merge --strategy=ours ${RELEASE_BRANCH} + git push origin ${NEXT_BRANCH} + git checkout ${RELEASE_BRANCH} + env: + NEXT_BRANCH: ${{ steps.get-next-branch.outputs.branchName }} + + - name: "Commit SBOM changes" + if: steps.generate-sbom.outputs.HAS_CHANGES == 'true' + run: | + git add ${{ env.SBOM_FILE }} + git commit -m "chore: Update SBOM for release ${{ inputs.version }}" + git push + echo "SBOM updated and committed" >> $GITHUB_STEP_SUMMARY + continue-on-error: true + + - name: "Report SBOM status" + run: | + if [[ "${{ steps.generate-sbom.outcome }}" == "success" ]]; then + echo "SBOM generation completed successfully" >> $GITHUB_STEP_SUMMARY + else + echo "SBOM generation skipped or failed - continuing with release" >> $GITHUB_STEP_SUMMARY + fi + + # + # Preliminary checks done - commence the release process + # - name: "Prepare release message" run: | cat > release-message <<'EOL'