How to cut a coordinated release of the v2 submodules. Read this before tagging.
The tag push and proxy verification are automated by the v2 Release workflow
(.github/workflows/v2-release.yml, run via workflow_dispatch). This runbook
explains the procedure that workflow follows and how to do it by hand if needed.
v2 is split into per-domain modules under modules/<name>/, each declaring
module github.com/gruntwork-io/terratest/modules/<name>/v2. Local development
uses the root go.work, which resolves every submodule to its local tree, so the
submodule go.mod files do not need internal require lines or replace
directives during normal development.
The submodules’ go.mod files are deliberately left without cross-module
require lines on main. Pinning a sibling require to the to-be-published
version (e.g. core/v2 v2.0.0-beta.1) BREAKS the workspace build until that tag
actually exists: go.work does not shadow an unpublished required version, so
go build and go work sync try to fetch the missing revision and fail. The pin
must therefore happen on a short-lived release-prep branch, immediately before the
tags are pushed, never on the modularization PR.
CI validates the release-mode build continuously without committing the pin: the
GOWORK=off check generates the pinned state with throwaway replace directives,
builds a consumer, and discards it (see scripts/).
v2.0.0-beta.1.replace for EVERY sibling it transitively needs, not just
its direct imports. Tidy follows transitive edges, so a partial replace set
fails with unknown revision on a deeper sibling.go.mod’s exact dependency versions before
tidying (copy its require lines in). Otherwise tidy floats deps to the
latest compatible release and you get silent version drift, e.g. the Azure
SDK advancing to a release that drops a symbol the code uses
(undefined: armmonitor.DiagnosticSettingsClient). The code is tested
against the root’s pinned versions; the submodules must inherit them.GOWORK=off go mod tidy to populate external requires and go.sum.scripts/check-release-mode.sh performs this exact pin (transitive replaces +
root-version seeding + an all-module external consumer build under GOWORK=off)
in a throwaway and reverts it, and runs in CI on every PR so the release-mode
build is validated continuously without committing the pin or needing tags.
require to the exact version being tagged, then DROP all
internal replace directives. Do not run go work sync against the unpinned
tree.replace directives remain in any
modules/*/go.mod. A committed replace would publish a module pointing at a
local path and break consumers.test/ to its own module here too if not already done, and pin it the
same way (it is test-only, so committed replaces are acceptable for it).All tags point at the same release commit. Each tag name puts the /v2 SIV in the
tag itself: modules/<name>/v2/<version> (NOT modules/<name>/<version>, which
the proxy cannot associate with the /v2 module path). Push in dependency order:
modules/core/v2/v2.0.0-beta.1ssh, httphelper, dnshelperdocker, packer, database, opaaws, azure, gcp, then k8s, helmterraform, terragrunt, teststructureAfter each tier, probe the proxy before continuing:
curl -o /dev/null -w '%{http_code}' https://proxy.golang.org/github.com/gruntwork-io/terratest/modules/<name>/v2/@v/<version>.info
should return 200.
Build a throwaway external consumer that imports every published module with
GOWORK=off. This is what scripts/check-release-mode.sh generates in a temp
directory, except now it resolves against the real published tags rather than
local replaces. A clean consumer should resolve, build, and test green with
zero local references.
Proxy tags are immutable. Recover by cutting the next patch (v2.0.0-beta.2),
never by editing in place. The pre-flight checks exist to keep this rare.
After the beta soaks (suggested two weeks minimum), repeat the same procedure at
v2.0.0 with no suffix: same release commit shape, same tag sequence, same proxy
verification, then announce.