Skip to content

Create v2 Release (pin + lockstep tags) #3

Create v2 Release (pin + lockstep tags)

Create v2 Release (pin + lockstep tags) #3

name: Create v2 Release (pin + lockstep tags)
# One manual workflow that cuts a full v2 release from main:
# 1. Pre-flight: the modularization gates + the GOWORK=off release-mode build.
# 2. Pin every submodule's cross-module require to the release version and
# commit that in-CI (the commit the tags point at). Deterministic; runs
# scripts/release-prep-pin.sh.
# 3. Push the 16 per-module tags in dependency order, then verify the proxy.
#
# Supersedes the old two-step flow (manual release-prep-pin.sh on a throwaway
# branch + a tag-only workflow). Procedure documented in
# docs/v2-release-runbook.md. Run with dry_run=true first to preview the tags.
on:
workflow_dispatch:
inputs:
version:
description: 'Version to tag (e.g. v2.0.0-beta.1). Applied to every submodule.'
required: true
type: string
ref:
description: 'Base ref/SHA to release from.'
required: false
type: string
default: main
dry_run:
description: 'If true, pin and print the tag commands without pushing.'
required: false
type: boolean
default: true
# Default to read-only. Only the tagging job is granted write, so the pre-flight
# job cannot push even though it runs scripts from a dispatched ref.
permissions:
contents: read
jobs:
verify:
name: Pre-flight checks
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
ref: ${{ inputs.ref }}
persist-credentials: false
- run: bash scripts/check-acyclic-deps.sh
- run: bash scripts/check-module-deps.sh
- run: bash scripts/check-single-source.sh
- run: bash scripts/check-siv-placement.sh
- name: Set up mise
uses: jdx/mise-action@5228313ee0372e111a38da051671ca30fc5a96db # v3
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Validates the exact release-mode build (pin + GOWORK=off external
# consumer) in a throwaway, so it runs against the un-pinned ref.
- name: Release-mode build (GOWORK=off external consumer)
run: bash scripts/check-release-mode.sh
tag-and-push:
name: Pin, tag, push
needs: verify
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: write
env:
VERSION: ${{ inputs.version }}
# Single source of truth for the module list and tag order (mirrors ORDER
# in scripts/check-release-mode.sh and the runbook tag sequence).
MODULE_PATHS: >-
modules/core
modules/ssh modules/httphelper modules/dnshelper
modules/docker modules/packer modules/database modules/opa
modules/aws modules/azure modules/gcp modules/k8s modules/helm
modules/terraform modules/terragrunt modules/teststructure
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
ref: ${{ inputs.ref }}
fetch-depth: 0
- name: Validate version
run: |
if [[ ! "$VERSION" =~ ^v2\.[0-9]+\.[0-9]+(-[0-9A-Za-z.]+)?$ ]]; then
echo "::error::version '$VERSION' must look like v2.<minor>.<patch>[-suffix] (e.g. v2.0.0-beta.1)"
exit 1
fi
- name: Set up mise
uses: jdx/mise-action@5228313ee0372e111a38da051671ca30fc5a96db # v3
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Configure git
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
# Pin the cross-module requires to the real version and commit in-CI. This
# is the commit the tags point at. It intentionally breaks the go.work
# build (the tags do not exist yet); that only matters until the push.
- name: Pin cross-module requires and commit
run: |
bash scripts/release-prep-pin.sh "$VERSION"
if git diff --quiet; then
echo "no placeholders to pin (ref already pinned to $VERSION)"
else
git commit -am "release-prep: pin cross-module requires to $VERSION"
fi
echo "PINNED_SHA=$(git rev-parse HEAD)" >> "$GITHUB_ENV"
- name: Tag in dependency order
env:
DRY_RUN: ${{ inputs.dry_run }}
run: |
set -e
for path in $MODULE_PATHS; do
tag="${path}/${VERSION}"
if [ "$DRY_RUN" = "true" ]; then
echo "DRY-RUN would tag ${PINNED_SHA:0:12}: $tag"
else
git tag -a "$tag" -m "v2 lockstep release $VERSION"
git push origin "$tag"
echo "pushed $tag"
fi
done
- name: Verify proxy serves each tag
if: ${{ inputs.dry_run == false }}
run: |
# Allow the proxy a moment to populate after push.
sleep 30
fail=0
for path in $MODULE_PATHS; do
url="https://proxy.golang.org/github.com/gruntwork-io/terratest/${path}/v2/@v/${VERSION}.info"
code=$(curl -s -o /dev/null -w '%{http_code}' "$url")
if [ "$code" = "200" ]; then
echo " $path: 200"
else
echo "::warning::$path: $code from $url"
fail=1
fi
done
if [ "$fail" -ne 0 ]; then
echo "::warning::Proxy has not served every tag yet. The tags are already pushed and immutable; the proxy usually populates within a few minutes. Re-run this check if needed."
fi
# One human-facing GitHub Release for the whole version (not 16). Attached
# to the core tag; -beta/-rc suffixes are marked prerelease.
- name: Create GitHub Release
if: ${{ inputs.dry_run == false }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
prerelease=""
case "$VERSION" in *-*) prerelease="--prerelease" ;; esac
{
echo "Terratest v2 lockstep release. All 16 submodules are tagged at ${VERSION}."
echo
echo "## Install"
echo
echo '```'
echo "go get github.com/gruntwork-io/terratest/modules/aws/v2@${VERSION}"
echo '```'
echo
echo "## Modules"
echo
echo "All at \`github.com/gruntwork-io/terratest/modules/<name>/v2\`:"
echo
for p in $MODULE_PATHS; do printf '%s ' "${p#modules/}"; done
echo
} > release_notes.md
# shellcheck disable=SC2086
gh release create "modules/core/${VERSION}" \
--title "Terratest ${VERSION}" \
--notes-file release_notes.md \
$prerelease