Skip to content

Harden and simplify the release workflow (#4908) #344

Harden and simplify the release workflow (#4908)

Harden and simplify the release workflow (#4908) #344

Workflow file for this run

# Fleet release workflow
name: Fleet release
on:
push:
tags:
- v**
permissions:
contents: write
id-token: write # this is important, it's how we authenticate with Vault
pull-requests: read
checks: read
env:
GOARCH: amd64
CGO_ENABLED: 0
RELEASE_TAG: ${{ github.ref_name }}
jobs:
build-fleet:
runs-on: runs-on,runner=8cpu-linux-x64,mem=16,run-id=${{ github.run_id }}
env:
IS_HOTFIX: ${{ contains(github.ref, '-hotfix-') }}
if: github.repository == 'rancher/fleet'
steps:
- name: Check out Fleet
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
- name: Verify CI passed on tagged commit
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
COMMIT_SHA=$(git rev-list -n 1 "$RELEASE_TAG")
echo "Verifying CI checks for ${RELEASE_TAG} (${COMMIT_SHA})"
PR_NUMBER=$(gh api \
"repos/${{ github.repository }}/commits/${COMMIT_SHA}/pulls" \
--jq '[.[] | select(.merged_at != null)] | first | .number // empty')
if [[ -n "${PR_NUMBER:-}" ]]; then
echo "Found PR #${PR_NUMBER}, verifying required checks..."
gh pr checks "${PR_NUMBER}" \
--repo "${{ github.repository }}" \
--required
else
echo "No merged PR found (hotfix), verifying all commit checks..."
ISSUES=$(gh api \
"repos/${{ github.repository }}/commits/${COMMIT_SHA}/check-runs" \
--jq 'if .total_count == 0 then "no CI checks found" else ([.check_runs[] | select(.status != "completed" or (.conclusion != "success" and .conclusion != "skipped")) | .name] | join(", ")) end')
if [[ -n "${ISSUES}" ]]; then
echo "ERROR: ${ISSUES}"
exit 1
fi
echo "✓ All checks passed for ${RELEASE_TAG} (${COMMIT_SHA})"
fi
- name: Set up Go
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
with:
go-version-file: 'go.mod'
- name: Set up QEMU
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0
with:
# renovate: datasource=docker depName=tonistiigi/binfmt extractVersion=^qemu-v(?<version>.+)$
image: tonistiigi/binfmt:qemu-v10.2.1-65@sha256:d3b963f787999e6c0219a48dba02978769286ff61a5f4d26245cb6a6e5567ea3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
- name: Install Cosign
uses: sigstore/cosign-installer@ba7bc0a3fef59531c69a25acd34668d6d3fe6f22 # v4.1.0
- name: Install Slsactl
uses: rancherlabs/slsactl/actions/install-slsactl@3dc09f73d4b3a03fbb87eff19ed0262c231d8757 # v0.1.22
with:
version: v0.1.22
- name: "Read Vault Secrets"
uses: rancher-eio/read-vault-secrets@0da85151ad1f19ed7986c41587e45aac1ace74b6 # v3
with:
secrets: |
secret/data/github/repo/${{ github.repository }}/dockerhub/rancher/credentials username | DOCKER_USERNAME ;
secret/data/github/repo/${{ github.repository }}/dockerhub/rancher/credentials password | DOCKER_PASSWORD ;
secret/data/github/repo/${{ github.repository }}/rancher-prime-stg-registry/credentials username | STAGE_REGISTRY_USERNAME ;
secret/data/github/repo/${{ github.repository }}/rancher-prime-stg-registry/credentials password | STAGE_REGISTRY_PASSWORD ;
secret/data/github/repo/${{ github.repository }}/rancher-prime-stg-registry/credentials registry | STAGE_REGISTRY ;
secret/data/github/repo/${{ github.repository }}/rancher-prime-registry/credentials registry | PRIME_REGISTRY ;
- name: Log into Docker Container registry
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
if: ${{ env.IS_HOTFIX == 'false' }}
with:
username: ${{ env.DOCKER_USERNAME }}
password: ${{ env.DOCKER_PASSWORD }}
- name: Log into Staging registry
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
with:
username: ${{ env.STAGE_REGISTRY_USERNAME }}
password: ${{ env.STAGE_REGISTRY_PASSWORD }}
registry: ${{ env.STAGE_REGISTRY }}
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@ec59f474b9834571250b370d4735c50f8e2d1e29 # v7.0.0
id: goreleaser
with:
distribution: goreleaser
version: v2.14.3
args: release --clean --draft --verbose
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GORELEASER_CURRENT_TAG: ${{ env.RELEASE_TAG }}
STAGE_REGISTRY: ${{ env.STAGE_REGISTRY }}
PRIME_REGISTRY: ${{ env.PRIME_REGISTRY }}
- name: Attest provenance
# Note: This step only works if the release process was triggered by a commit
# (push with a tag). Manual workflow_dispatch runs will not have valid provenance.
shell: bash
env:
STAGE_REGISTRY: ${{ env.STAGE_REGISTRY }}
run: |
set -euo pipefail
if [[ ! -f dist/digests.txt ]] || [[ ! -s dist/digests.txt ]]; then
echo "ERROR: dist/digests.txt not found or empty"
exit 1
fi
# Extract staging images (exclude platform-specific tags)
# Format: <digest> <image:tag> → only staging registry images
staging_images=$(grep -v -- '-linux-' dist/digests.txt | \
awk '{print "sha256:" $1, $NF}' | \
grep -F -- "${STAGE_REGISTRY}" || true)
if [[ -z "$staging_images" ]]; then
echo "ERROR: No staging images found"
exit 1
fi
echo "=== Staging images for attestation ==="
echo "$staging_images"
echo ""
max_retries=3
while read -r digest image_ref; do
echo ""
echo "Attesting: ${image_ref}"
echo " Digest: ${digest}"
for platform in "linux/amd64" "linux/arm64"; do
echo " Platform ${platform}..."
prov_file="provenance_${platform/\//_}.json"
# Retry download
for ((i=1; i<=max_retries; i++)); do
echo " Attempt ${i}/${max_retries}..."
if slsactl download provenance --format=slsav1 --platform="${platform}" "${image_ref}@${digest}" > "${prov_file}"; then
echo " ✓ Downloaded provenance"
break
fi
if [ "${i}" -eq "${max_retries}" ]; then
echo "ERROR: Failed to download provenance for ${platform} of ${image_ref} after ${max_retries} attempts"
exit 1
fi
echo " Waiting before retry..."
sleep 5
done
# Attest (must succeed)
if ! cosign attest --yes --predicate "${prov_file}" --type slsaprovenance1 "${image_ref}@${digest}" 2>&1; then
echo "ERROR: cosign attest failed for ${platform} of ${image_ref}"
rm -f "${prov_file}"
exit 1
fi
echo " ✓ Attested"
rm -f "${prov_file}"
done
done <<< "$staging_images"
echo ""
echo "✓ Provenance attestation completed for all images"
- name: Upload charts to release
if: ${{ env.IS_HOTFIX == 'false' }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
repo: "rancher"
run: |
version=$(jq -r '.version' <<< '${{ steps.goreleaser.outputs.metadata }}')
tag=$(jq -r '.tag' <<< '${{ steps.goreleaser.outputs.metadata }}')
echo "publishing helm chart for (repo: $repo, tag: $tag, version: $version)"
# Replace rancher/fleet and rancher/fleet-agent and rancher/gitjob image names
sed -i \
-e "s@repository: rancher/\(fleet.*\|gitjob\).*@repository: $repo/\\1@" \
-e "s/tag:.*/tag: $tag/" \
charts/fleet/values.yaml
sed -i \
-e "s@repository: rancher/\(fleet.*\|gitjob\).*@repository: $repo/\\1@" \
-e "s/tag: dev/tag: $tag/" \
charts/fleet-agent/values.yaml
find charts/ -maxdepth 1 -mindepth 1 -type d -exec helm package --version="$version" --app-version="$version" -d ./dist {} \;
find dist/ -name '*.tgz' -exec gh release upload "$tag" {} +
- name: Add charts to branch
if: ${{ env.IS_HOTFIX == 'false' }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
version=$(jq -r '.version' <<< '${{ steps.goreleaser.outputs.metadata }}')
branch_version=v$(cut -d'.' -f1,2 <<< "$version")
charts_branch=charts/$branch_version
if [ ! -e ~/.gitconfig ]; then
git config --global user.name "fleet-bot"
git config --global user.email fleet@suse.de
fi
echo "publishing helm chart in the branch $charts_branch"
if ! git ls-remote --exit-code --heads origin "$charts_branch"; then
git checkout --orphan "$charts_branch"
git rm -rf .
echo "# Fleet Helm Charts for $branch_version versions" > README.md
echo "The documentation is centralized in a unique place, checkout https://fleet.rancher.io/." >> README.md
git checkout origin/main -- LICENSE .gitignore
git add README.md LICENSE .gitignore
git commit -m "Initial commit for $charts_branch"
else
git checkout .
git checkout "$charts_branch"
fi
mkdir -p charts
find dist/ -name '*.tgz' -exec tar -xf {} -C charts/ \;
git add charts/**/*
git commit -m "Update charts to version $version"
git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@github.com/${{ github.repository }}.git"
git push origin "$charts_branch"
- name: Publish Release
if: ${{ env.IS_HOTFIX == 'false' }}
run: gh release edit "${RELEASE_TAG}" --draft=false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}