Skip to content

Go

Go #102

Workflow file for this run

name: Go
on:
push:
branches: [ "main" ]
tags: [ "v*" ]
pull_request:
branches: [ "main" ]
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v6
- name: Set up Go
uses: actions/setup-go@v6
with:
go-version-file: 'go.mod'
- name: Cache Go build artifacts
uses: actions/cache@v5
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.mod') }}
restore-keys: |
${{ runner.os }}-go-
- name: Verify dependencies
run: go mod verify
- name: Build
run: go build -v -buildvcs=false ./...
- name: Test
run: go test -v -race ./...
- name: Run golangci-lint
uses: golangci/golangci-lint-action@v9
with:
version: latest
# Single job that owns release creation. It runs once (not a matrix), so
# exactly one job ever creates the release for the tag. The upload-assets
# matrix below then only adds files to this already-existing release and can
# never race to create it (the race that made action-gh-release v3 fail with
# "already_exists (tag_name)").
#
# The release is created as a draft so it is not publicly visible while the
# upload matrix is still attaching assets. The publish-release job flips it to
# published only after all assets are present, so the public never sees a
# partial or empty release.
create-release:
needs: build
if: startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Create draft release for tag
uses: softprops/action-gh-release@v3
with:
draft: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
upload-assets:
needs: create-release
if: startsWith(github.ref, 'refs/tags/v')
permissions:
contents: write
strategy:
matrix:
include:
- os: ubuntu-latest
goos: linux
goarch: amd64
artifact_name: subenum
asset_name: subenum-linux-amd64
- os: windows-latest
goos: windows
goarch: amd64
artifact_name: subenum.exe
asset_name: subenum-windows-amd64.exe
- os: macos-latest
goos: darwin
goarch: arm64
artifact_name: subenum
asset_name: subenum-macos-arm64
- os: ubuntu-latest
goos: darwin
goarch: amd64
artifact_name: subenum
asset_name: subenum-macos-amd64
- os: ubuntu-latest
goos: linux
goarch: arm64
artifact_name: subenum
asset_name: subenum-linux-arm64
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v6
- name: Set up Go
uses: actions/setup-go@v6
with:
go-version-file: 'go.mod'
- name: Cache Go build artifacts
uses: actions/cache@v5
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.mod') }}
restore-keys: |
${{ runner.os }}-go-
# TEMPORARY failure injection for rc5 cleanup validation. REMOVE before merge.
- name: Inject failure for cleanup validation
if: matrix.goos == 'windows'
shell: bash
run: |
echo "Intentional failure to validate cleanup-failed-release."
exit 1
- name: Build
env:
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goarch }}
run: go build -v -buildvcs=false -o ${{ matrix.artifact_name }}
- name: Rename for release
shell: bash
run: |
if [ "${{ matrix.artifact_name }}" != "${{ matrix.asset_name }}" ]; then
cp ${{ matrix.artifact_name }} ${{ matrix.asset_name }}
fi
- name: Generate checksum
shell: bash
run: sha256sum ${{ matrix.asset_name }} > ${{ matrix.asset_name }}.sha256
# The release already exists (created by create-release), so each matrix leg
# only adds its binary and checksum. No matrix job creates the release, which
# avoids the concurrent-create race entirely. draft: true is set explicitly
# so these update calls keep the release in draft (the action's draft input
# defaults to false and would otherwise publish it early).
- name: Upload assets to draft release
uses: softprops/action-gh-release@v3
with:
draft: true
files: |
${{ matrix.asset_name }}
${{ matrix.asset_name }}.sha256
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Single job that publishes the release once, only after the full asset set is
# attached. Flipping draft to false here is the moment the release becomes
# public, so there is no window where a partial or empty release is visible.
publish-release:
needs: upload-assets
if: startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Publish release
uses: softprops/action-gh-release@v3
with:
draft: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Failure-path cleanup. If the upload matrix fails partway, publish-release is
# skipped and the draft release created above would otherwise linger
# invisibly until someone removes it by hand. This job runs only when a
# tag-triggered release run failed. It deletes the draft release for this tag
# and the tag ref so a partial release leaves nothing behind. It only ever
# deletes a draft (never a published release) and is a no-op when there is
# nothing to clean, so it is safe and idempotent.
cleanup-failed-release:
needs: [create-release, upload-assets]
if: ${{ failure() && startsWith(github.ref, 'refs/tags/v') }}
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Remove draft release and tag for failed run
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ github.ref_name }}
REPO: ${{ github.repository }}
shell: bash
run: |
set -euo pipefail
rel=$(gh api "repos/${REPO}/releases" --jq "([.[] | select(.tag_name==\"${TAG}\")][0]) // empty")
if [ -z "${rel}" ]; then
echo "No release found for ${TAG}; nothing to clean."
exit 0
fi
id=$(printf '%s' "${rel}" | jq -r '.id')
draft=$(printf '%s' "${rel}" | jq -r '.draft')
if [ "${draft}" != "true" ]; then
echo "Release ${id} for ${TAG} is published (draft=${draft}); leaving it intact."
exit 0
fi
echo "Deleting draft release ${id} for ${TAG}."
gh api -X DELETE "repos/${REPO}/releases/${id}"
echo "Deleting tag ref ${TAG}."
gh api -X DELETE "repos/${REPO}/git/refs/tags/${TAG}" || echo "Tag ref already removed."
echo "Cleanup complete for ${TAG}."