Merge pull request #119 from dell/release/v5.10.2 #23
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| --- | |
| # Copyright (c) 2025 Dell Inc., or its subsidiaries. All Rights Reserved. | |
| # | |
| # Licensed under the Apache License, Version 2.0 (the "License"); | |
| # you may not use this file except in compliance with the License. | |
| # You may obtain a copy of the License at | |
| # | |
| # http://www.apache.org/licenses/LICENSE-2.0 | |
| name: Publish Docker Image | |
| # Trigger on version tags only | |
| on: | |
| push: | |
| tags: | |
| - 'v*' | |
| workflow_dispatch: | |
| inputs: | |
| version: | |
| description: 'Version to build (without v prefix, e.g., 5.0.2). Defaults to VERSION file.' | |
| required: false | |
| dry_run: | |
| description: 'Dry run - build but do not push' | |
| required: false | |
| type: boolean | |
| default: false | |
| concurrency: | |
| group: docker-publish-${{ github.ref }} | |
| cancel-in-progress: true | |
| env: | |
| REGISTRY: ghcr.io | |
| IMAGE_NAME: dell/storage-performance-tool | |
| jobs: | |
| # Gate: ensure CI has passed for this commit before publishing | |
| # For tag pushes, we verify the CI workflow succeeded for the tagged commit | |
| verify-ci: | |
| name: Verify CI Status | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Check CI workflow status | |
| if: github.event_name == 'push' | |
| uses: actions/github-script@v9 | |
| with: | |
| script: | | |
| const sha = context.sha; | |
| const ciCheckNames = ['Engine Build & Tests', 'Go Quality Suite']; | |
| const maxAttempts = 60; | |
| const intervalMs = 30000; | |
| console.log(`Checking CI status for commit ${sha}`); | |
| console.log(`Will poll up to ${maxAttempts} times (${maxAttempts * intervalMs / 1000}s) for: ${ciCheckNames.join(', ')}`); | |
| for (let attempt = 1; attempt <= maxAttempts; attempt++) { | |
| const { data: checkRuns } = await github.rest.checks.listForRef({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| ref: sha, | |
| }); | |
| const passed = new Set(); | |
| const pending = []; | |
| for (const name of ciCheckNames) { | |
| const run = checkRuns.check_runs.find(r => r.name === name); | |
| if (!run) { | |
| pending.push(name); | |
| console.log(`Attempt ${attempt}/${maxAttempts}: "${name}" not found yet.`); | |
| } else if (run.conclusion === 'success') { | |
| passed.add(name); | |
| } else if (run.conclusion) { | |
| core.setFailed(`CI check "${name}" failed (conclusion: ${run.conclusion}). Cannot publish image.`); | |
| return; | |
| } else { | |
| pending.push(name); | |
| console.log(`Attempt ${attempt}/${maxAttempts}: "${name}" still in progress...`); | |
| } | |
| } | |
| if (passed.size === ciCheckNames.length) { | |
| console.log(`All CI checks passed: ${[...passed].join(', ')}`); | |
| return; | |
| } | |
| console.log(`Attempt ${attempt}/${maxAttempts}: ${passed.size}/${ciCheckNames.length} passed, waiting on: ${pending.join(', ')}`); | |
| if (attempt < maxAttempts) { | |
| await new Promise(r => setTimeout(r, intervalMs)); | |
| } | |
| } | |
| core.setFailed(`CI checks did not complete within ${maxAttempts * intervalMs / 1000}s. Re-run this workflow after CI passes.`); | |
| prepare: | |
| name: Prepare Version | |
| needs: verify-ci | |
| runs-on: ubuntu-latest | |
| outputs: | |
| version: ${{ steps.version.outputs.version }} | |
| major: ${{ steps.version.outputs.major }} | |
| minor: ${{ steps.version.outputs.minor }} | |
| is_prerelease: ${{ steps.version.outputs.is_prerelease }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| - name: Determine version | |
| id: version | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| # Priority: manual input > tag > VERSION file | |
| version_input="${{ inputs.version }}" | |
| ref_type="${GITHUB_REF_TYPE:-}" | |
| ref_name="${GITHUB_REF_NAME:-}" | |
| version_file="$(< VERSION)" | |
| version_file="${version_file//$'\n'/}" | |
| if [[ -n "${version_input}" ]]; then | |
| version="${version_input}" | |
| elif [[ "${ref_type}" == "tag" && "${ref_name}" == v* ]]; then | |
| version="${ref_name#v}" | |
| else | |
| version="${version_file}" | |
| fi | |
| echo "Resolved version: ${version}" | |
| # Parse version components | |
| # Handle versions like 5.0.2, 5.0.2-rc.1, 5.0.2-SNAPSHOT | |
| base_version="${version%%-*}" | |
| IFS='.' read -r major minor patch <<< "${base_version}" | |
| # Check if prerelease | |
| is_prerelease="false" | |
| if [[ "${version}" == *-* ]]; then | |
| is_prerelease="true" | |
| fi | |
| { | |
| echo "version=${version}" | |
| echo "major=${major}" | |
| echo "minor=${minor}" | |
| echo "is_prerelease=${is_prerelease}" | |
| } >> "${GITHUB_OUTPUT}" | |
| echo "Version: ${version}" | |
| echo "Major: ${major}, Minor: ${minor}" | |
| echo "Is prerelease: ${is_prerelease}" | |
| build-and-push: | |
| name: Build and Push Multi-Arch Image | |
| needs: prepare | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| packages: write | |
| id-token: write # Required for SBOM/provenance attestations | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| - name: Set up QEMU | |
| uses: docker/setup-qemu-action@v4 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v4 | |
| - name: Log in to GHCR | |
| if: ${{ !inputs.dry_run }} | |
| uses: docker/login-action@v4 | |
| with: | |
| registry: ${{ env.REGISTRY }} | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Extract Docker metadata | |
| id: meta | |
| uses: docker/metadata-action@v6 | |
| with: | |
| images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} | |
| tags: | | |
| # Exact version tag: v5.0.2 | |
| type=semver,pattern=v{{version}},value=${{ needs.prepare.outputs.version }} | |
| # Floating minor tag: v5.0 (only for stable releases) | |
| type=semver,pattern=v{{major}}.{{minor}},value=${{ needs.prepare.outputs.version }},enable=${{ needs.prepare.outputs.is_prerelease == 'false' }} | |
| # Floating major tag: v5 (only for stable releases) | |
| type=semver,pattern=v{{major}},value=${{ needs.prepare.outputs.version }},enable=${{ needs.prepare.outputs.is_prerelease == 'false' }} | |
| # Latest tag (only for stable releases) | |
| type=raw,value=latest,enable=${{ needs.prepare.outputs.is_prerelease == 'false' }} | |
| labels: | | |
| org.opencontainers.image.title=Dell Storage Performance Tool | |
| org.opencontainers.image.description=High-performance S3-compatible storage benchmarking tool | |
| org.opencontainers.image.vendor=Dell Inc. | |
| - name: Install JDK 21 | |
| uses: actions/setup-java@v5 | |
| with: | |
| distribution: 'temurin' | |
| java-version: '21' | |
| - name: Setup Gradle | |
| uses: gradle/actions/setup-gradle@v6 | |
| - name: Install RDMA build dependencies | |
| run: | | |
| sudo apt-get update -qq | |
| sudo apt-get install -y --no-install-recommends \ | |
| libibverbs-dev \ | |
| librdmacm-dev | |
| - name: Build Engine distribution | |
| working-directory: engine | |
| run: | | |
| # CI already ran tests - just build the distribution | |
| ./gradlew :bundle:assembleDist --no-daemon | |
| - name: Verify version alignment | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| expected_version="${{ needs.prepare.outputs.version }}" | |
| # Extract version from the built JAR | |
| # Output format is typically "spt vX.Y.Z" or similar - extract the version number | |
| version_output=$(java -jar engine/bundle/build/dist/spt.jar --version 2>&1 || true) | |
| echo "Raw version output: ${version_output}" | |
| # Match version patterns like 5.0.2, v5.0.2, 5.0.2-rc.1, etc. | |
| jar_version=$(echo "${version_output}" | grep -oE 'v?[0-9]+\.[0-9]+\.[0-9]+[^[:space:]]*' | sed 's/^v//' | head -1 || echo "unknown") | |
| echo "Expected version: ${expected_version}" | |
| echo "JAR version: ${jar_version}" | |
| if [[ "${jar_version}" != "${expected_version}" ]]; then | |
| echo "::error::Version mismatch! Tag/input version (${expected_version}) does not match JAR version (${jar_version})" | |
| echo "Ensure VERSION file and Gradle version are updated before tagging." | |
| exit 1 | |
| fi | |
| echo "✓ Version alignment verified: ${expected_version}" | |
| - name: Build and push Docker image | |
| uses: docker/build-push-action@v7 | |
| with: | |
| context: engine/bundle | |
| file: engine/bundle/Dockerfile | |
| platforms: linux/amd64,linux/arm64 | |
| # Push on tag events, or on workflow_dispatch if dry_run is false | |
| push: ${{ github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && !inputs.dry_run) }} | |
| tags: ${{ steps.meta.outputs.tags }} | |
| labels: ${{ steps.meta.outputs.labels }} | |
| build-args: | | |
| SPT_VERSION=${{ needs.prepare.outputs.version }} | |
| SPT_RELEASE_VERSION=${{ needs.prepare.outputs.version }} | |
| # Supply-chain security: generate SBOM and provenance attestations | |
| provenance: true | |
| sbom: true | |
| # Cache with explicit scope for better cross-run efficiency | |
| cache-from: type=gha,scope=spt-engine | |
| cache-to: type=gha,mode=max,scope=spt-engine | |
| - name: Output image info | |
| run: | | |
| echo "## Docker Image Published" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "**Version:** ${{ needs.prepare.outputs.version }}" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "**Tags:**" >> $GITHUB_STEP_SUMMARY | |
| echo '```' >> $GITHUB_STEP_SUMMARY | |
| echo "${{ steps.meta.outputs.tags }}" >> $GITHUB_STEP_SUMMARY | |
| echo '```' >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "**Platforms:** linux/amd64, linux/arm64" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "**Supply Chain:**" >> $GITHUB_STEP_SUMMARY | |
| echo "- ✅ SBOM attestation generated" >> $GITHUB_STEP_SUMMARY | |
| echo "- ✅ Provenance attestation generated" >> $GITHUB_STEP_SUMMARY |