From 5d5ce4f9e874fc9d31ca21c03123ea8e29475d8a Mon Sep 17 00:00:00 2001 From: Gustavo Bertoi Date: Wed, 1 Jul 2026 00:14:05 -0300 Subject: [PATCH] ci(nightly): scheduled nightly build + smoke + optional pre-release MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add .github/workflows/nightly.yml: a scheduled (07:00 UTC daily) + workflow_dispatch lane that runs the full nightly gate (fmt-check, vet, 4-target CGO-free cross-build, CGO=1 test-race, determinism) via a new `make nightly` target, plus an optional, default-OFF goreleaser snapshot pre-release job gated on the NIGHTLY_PRERELEASE repo variable that uploads artifacts (never a git tag, never a semver release). Both jobs are guarded by `github.repository == 'open-source-cloud/devstack'` so scheduled/dispatch runs no-op on forks. The pre-release path publishes goreleaser SNAPSHOT artifacts as workflow artifacts only — it never creates a `nightly` git tag, which would confuse goreleaser's git describe and break release-dryrun on every PR (MEMORY: no-non-semver-tags). Also adds `make cross` (4-target build smoke) and `make nightly` (make ci + cross + determinism) to mirror the workflow locally. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/nightly.yml | 83 +++++++++++++++++++++++++++++++++++ Makefile | 10 ++++- 2 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/nightly.yml diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml new file mode 100644 index 0000000..9061288 --- /dev/null +++ b/.github/workflows/nightly.yml @@ -0,0 +1,83 @@ +name: Nightly + +# Scheduled nightly health build (spec 07 toolchain, mirrors CI). This lane exists +# to catch drift that the per-PR CI can miss — a new Go point release, a transitive +# dependency that breaks a cross-compile target, or non-deterministic generation — +# BEFORE it bites a real release. It runs the full `make ci` equivalent plus the +# 4-target CGO-free cross-build and the determinism assertion. +# +# It NEVER cuts a real semver release and NEVER pushes a git tag: a stray non-semver +# tag (e.g. `nightly`) confuses `git describe` in goreleaser and breaks the +# release-dryrun lane on every PR (MEMORY: no-non-semver-tags). The optional +# pre-release path (default OFF) publishes goreleaser *snapshot* artifacts as +# workflow artifacts only — no tag is ever created. + +on: + schedule: + - cron: "0 7 * * *" # 07:00 UTC daily + workflow_dispatch: {} + +permissions: + contents: read + +concurrency: + group: nightly-${{ github.ref }} + cancel-in-progress: true + +env: + # Single enforced Go toolchain floor (DECISIONS, ARCHITECTURE §7.8) — same as CI. + GO_VERSION: "1.25" + +jobs: + # The full nightly gate. Guarded so scheduled runs no-op on forks (a fork inherits + # the schedule but not the org repo name); workflow_dispatch on a fork is likewise + # skipped, keeping this lane a first-party-only signal. + nightly: + if: github.repository == 'open-source-cloud/devstack' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: ${{ env.GO_VERSION }} + check-latest: true # nightly's whole point: pick up new Go point releases + cache: true # GOMODCACHE + GOCACHE, keyed by go.sum + + # `make ci` (fmt-check + vet + CGO=0 build + CGO=1 test-race) plus the 4-target + # cross-build and the byte-identical generation assertion, all via one target. + - name: nightly gate (fmt-check + vet + cross-build + test-race + determinism) + run: make nightly + + # Optional rolling pre-release. Default OFF: enable by setting the repo variable + # gh variable set NIGHTLY_PRERELEASE --body true + # Produces goreleaser SNAPSHOT artifacts (no real version, no git tag) and uploads + # them as workflow artifacts for manual smoke-testing of a nightly build. We do NOT + # create a GitHub Release or a `nightly` git tag — that would trip release-dryrun. + prerelease: + needs: nightly + if: github.repository == 'open-source-cloud/devstack' && vars.NIGHTLY_PRERELEASE == 'true' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # full history for goreleaser's snapshot versioning + - uses: actions/setup-go@v5 + with: + go-version: ${{ env.GO_VERSION }} + check-latest: true + cache: true + - uses: goreleaser/goreleaser-action@v6 + with: + version: "~> v2" + args: release --snapshot --clean + - name: upload nightly snapshot artifacts + uses: actions/upload-artifact@v4 + with: + name: nightly-snapshot + path: | + dist/*.tar.gz + dist/*.deb + dist/*.rpm + dist/checksums.txt + retention-days: 7 + if-no-files-found: error diff --git a/Makefile b/Makefile index c80e153..5af968b 100644 --- a/Makefile +++ b/Makefile @@ -21,7 +21,7 @@ BINDIR ?= $(or $(XDG_BIN_HOME),$(PREFIX)/bin) # The release binary MUST be CGO-free (static), but `go test -race` REQUIRES cgo. # So CGO is set per-target, never globally. -.PHONY: build run test test-race integration e2e test-one vet fmt fmt-check lint tidy vuln clean snapshot ci determinism install uninstall smoke help +.PHONY: build run test test-race integration e2e test-one vet fmt fmt-check lint tidy vuln clean snapshot cross ci nightly determinism install uninstall smoke help build: ## Build the static binary into ./dist CGO_ENABLED=0 go build -trimpath -ldflags '$(LDFLAGS)' -o dist/$(BINARY) ./cmd/devstack @@ -63,8 +63,16 @@ tidy: ## go mod tidy snapshot: ## Local goreleaser snapshot build (no publish) go run github.com/goreleaser/goreleaser/v2@latest release --snapshot --clean +cross: ## Cross-compile the 4 CGO-free release targets (build-only, output discarded) + @set -eu; for t in linux/amd64 linux/arm64 darwin/amd64 darwin/arm64; do \ + echo "-> $$t"; \ + GOOS=$${t%/*} GOARCH=$${t#*/} CGO_ENABLED=0 go build -trimpath -ldflags '$(LDFLAGS)' -o /dev/null ./cmd/devstack; \ + done + ci: fmt-check vet build test-race ## What CI runs +nightly: fmt-check vet cross test-race determinism ## Full nightly gate (make ci + 4-target cross-build + determinism) + determinism: build ## Assert generation is byte-identical across runs/paths (M1, spec 02) @set -eu; \ bin="$$PWD/dist/$(BINARY)"; \