ci: build each release once from its tag and publish it as a draft (#2754)

Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Claude <noreply@anthropic.com>
This commit is contained in:
Jan-Otto Kröpke
2026-10-16 12:16:30 +02:00
committed by GitHub
co-authored by Claude
parent 1e48663824
commit 4af7012048
3 changed files with 133 additions and 20 deletions
+60 -20
View File
@@ -1,8 +1,15 @@
name: CI
# Pull requests run the Windows tests only when Go sources, modules, test data,
# tools/, the configuration examples or this workflow change. Pushes, tags and
# releases always run them.
# tools/, the configuration examples or this workflow change. Pushes always run
# them.
#
# A release is built by exactly one run: the push of its tag, or the dispatch
# on its tag from the Create tag workflow. That run builds and signs the
# binaries once, pushes the container images and creates a draft release with
# the same executables, so the images and the release assets match. The draft
# is published by hand; with immutable releases, its assets and tag are locked
# from then on.
on:
push:
branches:
@@ -15,9 +22,6 @@ on:
- "v*"
pull_request:
workflow_dispatch:
release:
types:
- published
permissions: {}
@@ -43,7 +47,7 @@ jobs:
BASE_SHA: ${{ github.event.pull_request.base.sha }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: |
# Pushes to main and the release branches, tags, releases and manual
# Pushes to main, the release branches and tags, and manual
# runs always test, so a gap in the filter below cannot let a
# regression reach a branch that ships.
if [[ "$EVENT_NAME" != "pull_request" || -z "$BASE_SHA" ]]; then
@@ -85,6 +89,18 @@ jobs:
persist-credentials: 'false'
fetch-depth: '0'
# A published release is immutable. Rebuilding its tag would push images
# whose executable differs from the release assets, so stop before that.
- name: Check that the release is not published
if: ${{ startsWith(github.ref, 'refs/tags/') }}
env:
GH_TOKEN: ${{ github.token }}
run: |
if is_draft="$(gh release view "$GITHUB_REF_NAME" --json isDraft --jq .isDraft 2>/dev/null)" && [[ "$is_draft" == "false" ]]; then
echo "::error::Release $GITHUB_REF_NAME is already published"
exit 1
fi
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
# renovate: golang=go
@@ -142,6 +158,10 @@ jobs:
# renovate: github=goreleaser/goreleaser
version: v2.18.3
args: build --clean
env:
# Several tags can point at one commit, such as an rc on the last
# beta. Build the version of the tag this run is for.
GORELEASER_CURRENT_TAG: ${{ github.ref_name }}
- name: Verify Go overlay
run: go run ./tools/go-overlay verify dist/windows_exporter-amd64.exe dist/windows_exporter-arm64.exe
@@ -150,7 +170,11 @@ jobs:
run: |
mkdir -p output
version="$(git describe --tags --always)"
if [[ "$GITHUB_REF_TYPE" == "tag" ]]; then
version="$GITHUB_REF_NAME"
else
version="$(git describe --tags --always)"
fi
version="${version#v}"
version="${version//+/_}"
printf '%s\n' "$version" | tee output/VERSION
@@ -449,15 +473,14 @@ jobs:
annotations: ${{ steps.meta.outputs.labels }}
publish:
# Publishing a release fires both a tag push and a release:published run for
# the same tag. Upload only from the release run: it is the only one where
# the release is guaranteed to exist, and uploading from both runs would
# race on the same asset names.
if: github.repository == 'prometheus-community/windows_exporter' && github.event_name == 'release'
# Draft the release only once the images are pushed, so a published release
# always has them.
if: github.repository == 'prometheus-community/windows_exporter' && startsWith(github.ref, 'refs/tags/') && (github.event_name == 'push' || github.event_name == 'workflow_dispatch')
runs-on: ubuntu-26.04
needs:
- package
- windows-test
- docker
permissions:
contents: write
steps:
@@ -466,16 +489,33 @@ jobs:
with:
name: windows_exporter_binaries
- name: Publish Artifacts
- name: Create draft release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# The job runs without a checkout, so gh cannot infer the repository.
GH_REPO: ${{ github.repository }}
run: |
gh release upload "$GITHUB_REF_NAME" \
windows_exporter-*.exe \
windows_exporter-*.msi \
sha256sums.txt
assets=(windows_exporter-*.exe windows_exporter-*.msi sha256sums.txt)
# A re-run replaces the assets of its own draft. A published release
# is immutable and must not be touched.
if is_draft="$(gh release view "$GITHUB_REF_NAME" --json isDraft --jq .isDraft 2>&1)"; then
if [[ "$is_draft" != "true" ]]; then
echo "::error::Release $GITHUB_REF_NAME is already published"
exit 1
fi
gh release upload "$GITHUB_REF_NAME" --clobber "${assets[@]}"
exit 0
elif [[ "$is_draft" != *"release not found"* ]]; then
echo "::error::Cannot look up release $GITHUB_REF_NAME: $is_draft"
exit 1
fi
args=(--draft --verify-tag --generate-notes --title "$GITHUB_REF_NAME")
if [[ "$GITHUB_REF_NAME" == *-* ]]; then
args+=(--prerelease)
fi
gh release create "$GITHUB_REF_NAME" "${args[@]}" "${assets[@]}"
windows-test:
name: Windows tests (${{ matrix.os }})
@@ -1831,9 +1871,8 @@ jobs:
super-linter:
name: Super Linter
# Super Linter cannot derive the commit range VALIDATE_ALL_CODEBASE=false
# needs from release events; the tag push run already lints the same commit.
if: github.event_name != 'release'
# The push run for the branch a tag is cut from already linted the commit.
if: ${{ !startsWith(github.ref, 'refs/tags/') }}
runs-on: ubuntu-26.04
permissions:
contents: read
@@ -1884,6 +1923,7 @@ jobs:
- build
- package
- docker
- publish
- windows-test
- lint
- super-linter
+64
View File
@@ -0,0 +1,64 @@
name: Create tag
run-name: ${{ inputs.tag }}
# Creates a release tag on the commit the workflow runs on and starts CI for it.
# A tag created with GITHUB_TOKEN does not trigger the tag push run, but a
# workflow_dispatch does, so CI runs exactly once for the tag. That run builds
# and signs the binaries, pushes the container images and creates the draft
# release. Pushing a tag from a local clone triggers the same run directly.
on:
workflow_dispatch:
inputs:
tag:
description: Tag to create, such as v1.2.3 or v1.2.3-beta.1
required: true
type: string
permissions: {}
jobs:
create:
name: Create tag
if: github.repository == 'prometheus-community/windows_exporter'
runs-on: ubuntu-26.04
permissions:
actions: write
contents: write
steps:
- name: Create tag and start CI
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
TAG: ${{ inputs.tag }}
run: |
# The CI build derives the Windows file version from the tag and
# accepts only these forms: alpha.N and beta.N up to 99, rc.N up to
# 899.
if [[ ! "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-(alpha|beta|rc)\.([0-9]+))?$ ]]; then
echo "::error::Tag $TAG must look like v1.2.3, v1.2.3-beta.1 or v1.2.3-rc.1"
exit 1
fi
number="${BASH_REMATCH[3]}"
case "${BASH_REMATCH[2]}" in
alpha|beta) limit=99 ;;
rc) limit=899 ;;
esac
if [[ -n "$number" ]] && (( 10#$number > limit )); then
echo "::error::Pre-release number of tag $TAG does not fit the file version scheme"
exit 1
fi
# A retry after a failed dispatch finds the tag already on this
# commit and only starts CI again.
if existing="$(gh api "repos/$GH_REPO/git/ref/tags/$TAG" --jq .object.sha 2>/dev/null)"; then
if [[ "$existing" != "$GITHUB_SHA" ]]; then
echo "::error::Tag $TAG already exists on $existing"
exit 1
fi
else
gh api --method POST "repos/$GH_REPO/git/refs" \
-f "ref=refs/tags/$TAG" \
-f "sha=$GITHUB_SHA"
fi
gh workflow run ci.yml --ref "$TAG"