Skip to content

build(deps): bump go_modules minor-and-patch group (6 updates) #1747

build(deps): bump go_modules minor-and-patch group (6 updates)

build(deps): bump go_modules minor-and-patch group (6 updates) #1747

Workflow file for this run

# CI pipeline for OpenDecree server, SDKs, and CLI.
#
# Jobs: changes ──┬── tools, server-image → integration (e2e|examples|stress) ─→ check (alls-green gate)
# ├── → upgrade ───────────────────────────┤
# ├── lint, docs ──────────────────────────────────────────────┤
# └── test, sdk-compat, govulncheck, deps-review ──────────────┘
#
# The changes job runs dorny/paths-filter; code jobs skip on docs-only PRs;
# the docs job additionally skips unless proto/CLI/mkdocs files changed.
#
# The tools job builds/caches the decree-tools Docker image (goose/migrate),
# and the server-image job builds the decree server image ONCE and pushes it to
# ghcr by digest. Both use a registry-backed buildx cache (:buildcache tag) so
# unchanged layers are pulled from ghcr and only modified layers rebuild. The
# integration legs (e2e/examples/stress) and the upgrade job then just `docker
# pull` the prebuilt server image instead of rebuilding it once per leg.
#
# Security posture (issue #8):
# - Workflow-level permissions default to `contents: read` — jobs opt into more.
# - Only `tools` and `server-image` have `packages: write` (they push the
# tools and server images); consumers get `packages: read` to pull.
# - All actions/checkout steps use `persist-credentials: false` so the
# GITHUB_TOKEN isn't left in .git/config for later steps.
# - `deps-review` blocks PRs that add dependencies with known high-severity
# vulnerabilities.
# - `govulncheck` scans Go code for known vulnerabilities in imported packages.
#
# The check job aggregates all results for branch protection (skipped jobs allowed).
#
# Cache budget: Docker layer caches live on ghcr.io as `:buildcache` tags on
# their respective image repos (no 10GB GHA cache limit). Writers:
# - main.yml → ghcr.io/<owner>/decree:buildcache, decree-cli:buildcache
# - release.yml → same refs (shared with main.yml, mode=max)
# - tools job → ghcr.io/<owner>/decree-tools:buildcache
# - server-image job → reads ghcr.io/<owner>/decree:buildcache (main.yml is
# the canonical writer) and writes the shared `server-build` GHA scope.
# ci.yml integration (e2e/examples/stress) and upgrade are read-only consumers:
# they `docker pull` the prebuilt server image and no longer build it.
# E2E benchmarks moved to release.yml — too slow for per-PR CI.
# The unit/infra `bench` job lives in bench.yml — it ran push-to-main only and
# was purely informational, but sat in check.needs and so delayed the alls-green
# gate 7-10 min on every push. Relocated to a nightly schedule (issue #970).
name: CI
on:
pull_request:
branches: [main]
push:
branches: [main]
workflow_call:
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}
cancel-in-progress: true
permissions:
contents: read
env:
TOOLS_IMAGE: ghcr.io/${{ github.repository_owner }}/decree-tools
SERVER_IMAGE: ghcr.io/${{ github.repository_owner }}/decree
jobs:
# Detects which paths changed so downstream jobs can skip on docs-only PRs.
changes:
name: Detect changes
runs-on: ubuntu-latest
permissions:
pull-requests: read
timeout-minutes: 5
outputs:
code: ${{ steps.filter.outputs.code }}
helm: ${{ steps.filter.outputs.helm }}
migrations: ${{ steps.filter.outputs.migrations }}
docs: ${{ steps.filter.outputs.docs }}
non_grpctransport_code: ${{ steps.filter.outputs.non_grpctransport_code }}
go_code: ${{ steps.filter.outputs.go_code }}
steps:
- name: Checkout
uses: actions/checkout@v7
with:
persist-credentials: false
- name: Run path filter
# dorny/paths-filter@v4
uses: dorny/paths-filter@7b450fff21473bca461d4b92ce414b9d0420d706
id: filter
with:
filters: |
code:
- '**/*.go'
- '**/go.mod'
- '**/go.sum'
- 'proto/**'
- 'db/**'
- 'build/**'
- '.dockerignore'
- 'Makefile'
- 'docker-compose*.yml'
- '.github/workflows/**'
helm:
- 'deploy/helm/**'
- '.github/workflows/ci.yml'
migrations:
- 'db/migrations/**'
docs:
- 'proto/**'
- 'cmd/decree/**'
- 'internal/version/**'
- 'Makefile'
- 'mkdocs.yml'
non_grpctransport_code:
- '**/*.go'
- '!sdk/grpctransport/**'
- '**/go.mod'
- '**/go.sum'
- 'proto/**'
- 'db/**'
- 'build/**'
- 'Makefile'
- 'docker-compose*.yml'
- '.github/workflows/**'
go_code:
- '**/*.go'
- '**/go.mod'
- '**/go.sum'
- 'proto/**'
- 'db/**'
# Builds/pushes the shared decree-tools Docker image used by downstream jobs.
# Skips the build if an image tagged with the Dockerfile hash already exists
# on ghcr; registry-backed buildx cache reuses unchanged layers on miss.
tools:
name: Tools image
runs-on: ubuntu-latest
needs: changes
if: needs.changes.outputs.code == 'true'
permissions:
contents: read
packages: write
timeout-minutes: 10
steps:
- name: Checkout
uses: actions/checkout@v7
with:
persist-credentials: false
- name: Compute Dockerfile hash
id: hash
run: echo "tag=sha-$(sha256sum build/Dockerfile.tools | head -c 12)" >> $GITHUB_OUTPUT
- name: Check if image exists
id: check
run: |
if docker manifest inspect "${{ env.TOOLS_IMAGE }}:${{ steps.hash.outputs.tag }}" > /dev/null 2>&1; then
echo "exists=true" >> $GITHUB_OUTPUT
else
echo "exists=false" >> $GITHUB_OUTPUT
fi
env:
DOCKER_CLI_EXPERIMENTAL: enabled
- name: Set up Docker Buildx
if: steps.check.outputs.exists == 'false'
uses: docker/setup-buildx-action@v4
- name: Log in to ghcr.io
if: steps.check.outputs.exists == 'false'
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push tools image
if: steps.check.outputs.exists == 'false'
uses: docker/build-push-action@v7
with:
context: .
file: build/Dockerfile.tools
push: true
tags: ${{ env.TOOLS_IMAGE }}:${{ steps.hash.outputs.tag }}
cache-from: type=registry,ref=${{ env.TOOLS_IMAGE }}:buildcache
cache-to: type=registry,ref=${{ env.TOOLS_IMAGE }}:buildcache,mode=max
outputs:
image: ${{ env.TOOLS_IMAGE }}:${{ steps.hash.outputs.tag }}
# Builds the server image ONCE and pushes it to ghcr by digest, so the
# integration matrix (e2e/examples/stress) and the upgrade job each pull a
# single prebuilt image instead of rebuilding it 4×. Runs parallel to `tools`
# (both gated only on `changes`). Uses the same two-tier cache as before:
# the registry :buildcache (read-only; main.yml is the canonical writer) plus
# the shared GHA scope. Pushing by digest avoids tag churn on ghcr.
server-image:
name: Server image
runs-on: ubuntu-latest
needs: changes
if: needs.changes.outputs.code == 'true'
permissions:
contents: read
packages: write
timeout-minutes: 15
steps:
- name: Checkout
uses: actions/checkout@v7
with:
persist-credentials: false
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Log in to ghcr.io
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push server image by digest
id: build
uses: docker/build-push-action@v7
with:
context: .
file: build/Dockerfile
cache-from: |
type=registry,ref=${{ env.SERVER_IMAGE }}:buildcache
type=gha,scope=server-build
cache-to: type=gha,scope=server-build,mode=max
outputs: type=image,name=${{ env.SERVER_IMAGE }},push-by-digest=true,name-canonical=true,push=true
outputs:
image: ${{ env.SERVER_IMAGE }}@${{ steps.build.outputs.digest }}
# Runs golangci-lint against Go code and buf lint/breaking against protos.
# buf is installed via go install (no Docker image needed), so this job runs
# parallel to tools instead of waiting for it.
# buf breaking needs the tip of main, fetched explicitly in the next step.
lint:
name: Lint
runs-on: ubuntu-latest
needs: [changes]
if: needs.changes.outputs.code == 'true'
permissions:
contents: read
timeout-minutes: 10
steps:
- name: Checkout
uses: actions/checkout@v7
with:
persist-credentials: false
- name: Fetch main branch for buf breaking
if: github.ref != 'refs/heads/main'
run: git fetch origin main:main --depth=1
- name: Check supply-chain pins
run: ./scripts/check-supply-chain-pins.sh
- name: Lint workflow files
# rhysd/actionlint:1.7.12 — catches invalid workflow syntax (e.g. a
# context used somewhere it isn't available) before it reaches GitHub,
# where such errors silently produce zero jobs instead of a clear
# failure. See #931. shellcheck/pyflakes are disabled here: they lint
# the shell/Python embedded in `run:` steps, a different and much
# noisier concern than workflow-file validity — out of scope for this
# gate.
uses: docker://rhysd/actionlint@sha256:b1934ee5f1c509618f2508e6eb47ee0d3520686341fec936f3b79331f9315667
with:
args: -color -shellcheck= -pyflakes=
- name: Set up Go
uses: actions/setup-go@v7
with:
go-version-file: go.mod
cache-dependency-path: "**/go.sum"
- name: Go lint
# golangci/golangci-lint-action@v9
uses: golangci/golangci-lint-action@ba0d7d2ec06a0ea1cb5fa41b2e4a3ab91d21278a
with:
version: v2.11.4
- name: Go format check
run: golangci-lint fmt --diff
- name: Go lint (contrib + SDK modules + CLI)
# golangci-lint-action only lints the root module's working directory.
# Loop over the same module list used by `make test`/`build`/`vet` so
# contrib/decree-docs, the SDK modules, and cmd/decree are covered too.
run: |
for mod in sdk/retry sdk/configclient sdk/adminclient sdk/configwatcher sdk/grpctransport sdk/tools sdk/contrib/viper sdk/contrib/envconfig sdk/contrib/koanf contrib/decree-docs cmd/decree; do
echo "::group::golangci-lint: $mod"
(cd "$mod" && golangci-lint run ./... && golangci-lint fmt --diff)
echo "::endgroup::"
done
- name: Install buf
env:
BUF_VERSION: v1.66.1
# sha256 of buf-Linux-x86_64 from the official v1.66.1 release (sha256.txt).
BUF_SHA256: ef835cb38ed973849f68e0e6a88153cb2168507e09eecbec43a2ada2f6a698be
run: |
mkdir -p "$HOME/.local/bin"
curl -fsSL "https://github.com/bufbuild/buf/releases/download/${BUF_VERSION}/buf-Linux-x86_64" -o "$HOME/.local/bin/buf"
echo "${BUF_SHA256} $HOME/.local/bin/buf" | sha256sum -c -
chmod +x "$HOME/.local/bin/buf"
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
"$HOME/.local/bin/buf" --version
- name: Proto lint + breaking
run: buf lint && buf breaking --against ".git#branch=main"
# Runs the unit tests under the race detector with coverage, then enforces
# the internal-module coverage ratchet. Split from the SDK/CLI/tools tests
# (test-modules) so the two heavy sequential steps run on separate runners
# concurrently. -race coverage of internal/ is preserved here unchanged.
test-internal:
name: Test (internal)
runs-on: ubuntu-latest
needs: changes
if: needs.changes.outputs.code == 'true'
timeout-minutes: 15
steps:
- name: Checkout
uses: actions/checkout@v7
with:
persist-credentials: false
- name: Set up Go
uses: actions/setup-go@v7
with:
go-version-file: go.mod
cache-dependency-path: "**/go.sum"
- name: Unit tests
run: go test ./internal/... -race -count=1 -coverprofile=coverage-internal.out -covermode=atomic
- name: Coverage ratchet
run: ./scripts/check-coverage.sh --profiles-only --profile internal=coverage-internal.out
- name: Upload coverage to Codecov
# codecov/codecov-action@v6.0.0
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f
with:
files: coverage-internal.out
token: ${{ secrets.CODECOV_TOKEN }}
# Runs the SDK, CLI, and tools module tests in parallel, then enforces the
# coverage ratchet for those modules. Split from test-internal so it runs on
# a separate runner concurrently with the race-enabled unit tests.
test-modules:
name: Test (modules)
runs-on: ubuntu-latest
needs: changes
if: needs.changes.outputs.code == 'true'
timeout-minutes: 15
steps:
- name: Checkout
uses: actions/checkout@v7
with:
persist-credentials: false
- name: Set up Go
uses: actions/setup-go@v7
with:
go-version-file: go.mod
cache-dependency-path: "**/go.sum"
- name: SDK + CLI + tools tests
run: |
pids=()
cd sdk/configclient && go test ./... -count=1 -coverprofile=../../cov-configclient.out -covermode=atomic & pids+=($!)
cd sdk/adminclient && go test ./... -count=1 -coverprofile=../../cov-adminclient.out -covermode=atomic & pids+=($!)
cd sdk/configwatcher && go test ./... -count=1 -coverprofile=../../cov-configwatcher.out -covermode=atomic & pids+=($!)
cd sdk/grpctransport && go test ./... -count=1 -coverprofile=../../cov-grpctransport.out -covermode=atomic & pids+=($!)
cd sdk/tools && go test ./... -count=1 -coverprofile=../../cov-tools.out -covermode=atomic & pids+=($!)
cd sdk/contrib/viper && go test ./... -count=1 -coverprofile=../../cov-contrib-viper.out -covermode=atomic & pids+=($!)
cd sdk/contrib/envconfig && go test ./... -count=1 -coverprofile=../../cov-contrib-envconfig.out -covermode=atomic & pids+=($!)
cd sdk/contrib/koanf && go test ./... -count=1 -coverprofile=../../cov-contrib-koanf.out -covermode=atomic & pids+=($!)
cd contrib/decree-docs && go test ./... -count=1 -coverprofile=../../cov-decree-docs.out -covermode=atomic & pids+=($!)
cd cmd/decree && go test ./... -count=1 -coverprofile=../../cov-decree.out -covermode=atomic & pids+=($!)
fail=0
for pid in "${pids[@]}"; do wait "$pid" || fail=1; done
exit $fail
- name: Coverage ratchet
run: |
./scripts/check-coverage.sh --profiles-only \
--profile sdk/configclient=cov-configclient.out \
--profile sdk/adminclient=cov-adminclient.out \
--profile sdk/configwatcher=cov-configwatcher.out \
--profile sdk/grpctransport=cov-grpctransport.out \
--profile sdk/tools=cov-tools.out \
--profile contrib/decree-docs=cov-decree-docs.out \
--profile cmd/decree=cov-decree.out
- name: Merge coverage profiles
run: |
echo "mode: atomic" > coverage.out
for f in cov-configclient.out cov-adminclient.out cov-configwatcher.out cov-grpctransport.out cov-tools.out cov-contrib-viper.out cov-contrib-envconfig.out cov-contrib-koanf.out cov-decree-docs.out cov-decree.out; do
[ -f "$f" ] && grep -v "^mode:" "$f" >> coverage.out || true
done
- name: Upload coverage to Codecov
# codecov/codecov-action@v6.0.0
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f
with:
files: coverage.out
token: ${{ secrets.CODECOV_TOKEN }}
# Proves the SDK core modules still build and test against the floor Go
# version we advertise support for (1.22). Testing 1.23+ is redundant:
# if 1.22 passes, later versions pass too. Only the floor matters.
sdk-compat:
name: SDK compat (Go ${{ matrix.go }})
runs-on: ubuntu-latest
needs: changes
if: needs.changes.outputs.code == 'true'
timeout-minutes: 10
strategy:
matrix:
go: ["1.22"]
steps:
- name: Checkout
uses: actions/checkout@v7
with:
persist-credentials: false
- name: Set up Go ${{ matrix.go }}
uses: actions/setup-go@v7
with:
go-version: ${{ matrix.go }}
cache-dependency-path: "**/go.sum"
- name: Build SDK core modules
run: |
cd sdk/configclient && go build ./...
cd ../adminclient && go build ./...
cd ../configwatcher && go build ./...
- name: Test SDK core modules
run: |
cd sdk/configclient && go test ./... -count=1 &
cd sdk/adminclient && go test ./... -count=1 &
cd sdk/configwatcher && go test ./... -count=1 &
wait
# Regenerates docs and fails if the output differs from what's committed.
# Catches forgotten `make generate docs` runs. Gated on docs-relevant paths
# (proto, cmd/decree, internal/version, Makefile, mkdocs.yml) so pure Go
# or config-only PRs skip this job entirely.
docs:
name: Docs check
runs-on: ubuntu-latest
needs: [changes]
if: needs.changes.outputs.docs == 'true'
permissions:
contents: read
timeout-minutes: 10
steps:
- name: Checkout
uses: actions/checkout@v7
with:
persist-credentials: false
- name: Set up Go
uses: actions/setup-go@v7
with:
go-version-file: go.mod
cache-dependency-path: "**/go.sum"
- name: Install buf
env:
BUF_VERSION: v1.66.1
# sha256 of buf-Linux-x86_64 from the official v1.66.1 release (sha256.txt).
BUF_SHA256: ef835cb38ed973849f68e0e6a88153cb2168507e09eecbec43a2ada2f6a698be
run: |
mkdir -p "$HOME/.local/bin"
curl -fsSL "https://github.com/bufbuild/buf/releases/download/${BUF_VERSION}/buf-Linux-x86_64" -o "$HOME/.local/bin/buf"
echo "${BUF_SHA256} $HOME/.local/bin/buf" | sha256sum -c -
chmod +x "$HOME/.local/bin/buf"
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
"$HOME/.local/bin/buf" --version
- name: Install protoc-gen-doc
run: go install github.com/pseudomuto/protoc-gen-doc/cmd/protoc-gen-doc@v1.5.1
- name: Regenerate docs
run: |
# Remove generated CLI + man pages (all prefixed "decree") before
# regenerating, so a removed command's orphaned page surfaces in the
# freshness diff below — gen-docs only writes, never deletes. Matching
# the "decree" prefix preserves hand-written pages such as guide.md.
rm -f docs/cli/decree*.md docs/man/decree-*.1
buf generate --template buf.gen.doc.yaml
make docs-cli docs-man
- name: Check docs are up-to-date
run: |
if ! git diff --exit-code docs/; then
echo "::error::Generated docs are out of date. Run 'make generate docs' and commit."
exit 1
fi
# E2E, examples, and stress share identical setup (checkout, Go, Docker,
# pull tools image, pull prebuilt server image). Collapsed into one matrix job
# to eliminate the 3× YAML duplication. Legs run in parallel by default.
integration:
name: ${{ matrix.name }}
runs-on: ubuntu-latest
needs: [tools, server-image, changes]
if: needs.changes.outputs.code == 'true'
permissions:
contents: read
packages: read
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
include:
- suite: e2e
name: E2E
- suite: examples
name: Examples
- suite: stress
name: Stress tests
steps:
- name: Checkout
uses: actions/checkout@v7
with:
persist-credentials: false
- name: Set up Go
uses: actions/setup-go@v7
with:
go-version-file: go.mod
cache-dependency-path: "**/go.sum"
- name: Log in to ghcr.io
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Pull tools image
run: docker pull "${{ needs.tools.outputs.image }}" && docker tag "${{ needs.tools.outputs.image }}" decree-tools
- name: Pull server image
run: docker pull "${{ needs.server-image.outputs.image }}"
- name: Run ${{ matrix.name }}
run: |
case "${{ matrix.suite }}" in
e2e)
docker compose up -d --wait service
cd e2e && go test -tags=e2e -v -race -count=1 ./... || (cd .. && docker compose down -v && exit 1)
docker compose down -v
;;
examples)
make examples
;;
stress)
CI=true make stress
;;
esac
env:
TOOLS_IMAGE: ${{ needs.tools.outputs.image }}
SERVICE_IMAGE: ${{ needs.server-image.outputs.image }}
# Runs Postgres integration tests using testcontainers-go. Docker is
# available on ubuntu-latest runners; no server image or tools image required.
pg-integration:
name: PG integration tests
runs-on: ubuntu-latest
needs: changes
if: needs.changes.outputs.code == 'true'
permissions:
contents: read
timeout-minutes: 15
steps:
- name: Checkout
uses: actions/checkout@v7
with:
persist-credentials: false
- name: Set up Go
uses: actions/setup-go@v7
with:
go-version-file: go.mod
cache-dependency-path: "**/go.sum"
- name: PG integration tests
run: make integration-test
# Blocks PRs that introduce dependencies with known high-severity
# vulnerabilities (GitHub Advisory Database). Fails closed on severity >= high.
deps-review:
name: Dependency review
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
permissions:
contents: read
pull-requests: write
timeout-minutes: 5
steps:
- name: Checkout
uses: actions/checkout@v7
with:
persist-credentials: false
- name: Review dependencies
uses: actions/dependency-review-action@v5
with:
fail-on-severity: high
# Scans imported Go packages (and reachable call sites) for known CVEs
# using the Go vulnerability database. Complements deps-review by also
# catching vulnerabilities in transitive deps already on main.
govulncheck:
name: Go vuln scan
runs-on: ubuntu-latest
needs: changes
if: needs.changes.outputs.code == 'true'
permissions:
contents: read
timeout-minutes: 10
steps:
- name: Checkout
uses: actions/checkout@v7
with:
persist-credentials: false
# No separate "Set up Go" step here: govulncheck-action sets up its own
# Go toolchain (with its own module cache restore) via go-version-input.
# A second explicit actions/setup-go step used to restore the same GHA
# cache key into the same GOMODCACHE dir, and the resulting tar extract
# collided with files the first restore had already written
# ("Cannot open: File exists"), failing this job on every PR.
- name: Run govulncheck
# golang/govulncheck-action@v1
uses: golang/govulncheck-action@032d45514ae346b1db93c04b0c90b841c370344f
with:
go-version-input: "1.25.13"
work-dir: .
# Validates every checked-in *.decree.schema.yaml and *.decree.config.yaml
# against the v0.1.0 meta-schemas, and asserts every fixture under
# schemas/v0.1.0/testdata/invalid/ FAILS validation. Closes #124.
#
# Runs unconditionally — no needs.changes gate — because the script also
# cross-checks the meta-schema sources themselves and is fast enough that
# path-filtering would add more complexity than it saves.
meta-schemas:
name: Meta-schemas check
runs-on: ubuntu-latest
permissions:
contents: read
timeout-minutes: 5
steps:
- name: Checkout
uses: actions/checkout@v7
with:
persist-credentials: false
- name: Set up Python
uses: actions/setup-python@v7
with:
python-version: "3.x"
- name: Install jsonschema
run: pip install --no-cache-dir 'jsonschema>=4.21' 'PyYAML>=6'
- name: Validate meta-schemas
run: make validate-meta-schemas
# Renders the Helm chart with default + NetworkPolicy-enabled values and
# asserts the documented requests/limits, NetworkPolicy egress rules, and
# imagePullPolicy. Also runs `helm lint`.
helm:
name: Helm chart
runs-on: ubuntu-latest
needs: changes
if: needs.changes.outputs.helm == 'true'
permissions:
contents: read
timeout-minutes: 5
steps:
- name: Checkout
uses: actions/checkout@v7
with:
persist-credentials: false
- name: Set up Helm
# azure/setup-helm@v4.3.1
uses: azure/setup-helm@9bc31f4ebc9c6b171d7bfbaa5d006ae7abdb4310
with:
version: v3.18.4
- name: Run chart render tests
run: ./deploy/helm/decree/tests/template_test.sh
# Upgrade safety: runs the previous-release binary against a fresh DB,
# populates fixture data, applies new migrations, then asserts the new binary
# can read the old data and accept new writes. Only runs on PRs that touch
# db/migrations/, so it's zero-cost for non-migration changes.
upgrade:
name: Upgrade test
runs-on: ubuntu-latest
needs: [tools, server-image, changes]
if: needs.changes.outputs.migrations == 'true'
permissions:
contents: read
packages: read
timeout-minutes: 20
steps:
- name: Checkout
uses: actions/checkout@v7
with:
persist-credentials: false
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v7
with:
go-version-file: go.mod
cache-dependency-path: "**/go.sum"
- name: Log in to ghcr.io
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Pull tools image
run: docker pull "${{ needs.tools.outputs.image }}" && docker tag "${{ needs.tools.outputs.image }}" decree-tools
- name: Pull server image
run: docker pull "${{ needs.server-image.outputs.image }}"
- name: Run upgrade test
run: ./scripts/run-upgrade-test.sh
env:
TOOLS_IMAGE: decree-tools
SERVICE_IMAGE: ${{ needs.server-image.outputs.image }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Posts a synthetic codecov/patch:success status when no Go code changed so
# branch protection is not blocked waiting for a Codecov upload that will
# never arrive (the test job skips on docs-only PRs). Fires only on
# pull_request events — push-to-main always runs tests and gets a real status.
codecov-skip:
name: Codecov skip (docs-only)
runs-on: ubuntu-latest
needs: changes
if: needs.changes.outputs.code != 'true' && github.event_name == 'pull_request'
permissions:
statuses: write
timeout-minutes: 5
steps:
- name: Post codecov/patch success
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh api repos/${{ github.repository }}/statuses/${{ github.event.pull_request.head.sha }} \
--method POST \
-f state=success \
-f context=codecov/patch \
-f description="No coverage change (docs-only PR)" \
-f target_url="https://codecov.io/gh/${{ github.repository }}"
# Posts a synthetic codecov/patch:success when all code changes are within
# sdk/grpctransport/** (fully excluded from Codecov). The test job still runs
# for these PRs, but Codecov silently skips the patch webhook — leaving a
# required check permanently pending. Mirrors codecov-skip for docs-only PRs.
codecov-skip-grpctransport:
name: Codecov skip (grpctransport-only)
runs-on: ubuntu-latest
needs: changes
if: needs.changes.outputs.code == 'true' && needs.changes.outputs.non_grpctransport_code != 'true' && github.event_name == 'pull_request'
permissions:
statuses: write
timeout-minutes: 5
steps:
- name: Post codecov/patch success
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh api repos/${{ github.repository }}/statuses/${{ github.event.pull_request.head.sha }} \
--method POST \
-f state=success \
-f context=codecov/patch \
-f description="No coverage change (grpctransport-only PR)" \
-f target_url="https://codecov.io/gh/${{ github.repository }}"
# Posts a synthetic codecov/patch:success when no Go/proto/db source changed
# (e.g. .github/workflows/**, build/** Dockerfiles, Makefile, docker-compose).
# Those paths count as "code" (so the test job runs) but produce no coverage
# diff, so Codecov never uploads — leaving codecov/patch pending forever.
codecov-skip-ci-only:
name: Codecov skip (ci-only)
runs-on: ubuntu-latest
needs: changes
if: needs.changes.outputs.code == 'true' && needs.changes.outputs.go_code != 'true' && github.event_name == 'pull_request'
permissions:
statuses: write
timeout-minutes: 5
steps:
- name: Post codecov/patch success
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh api repos/${{ github.repository }}/statuses/${{ github.event.pull_request.head.sha }} \
--method POST \
-f state=success \
-f context=codecov/patch \
-f description="No coverage change (ci-only PR)" \
-f target_url="https://codecov.io/gh/${{ github.repository }}"
# Aggregates all job results for branch protection. A single required check
# that passes iff every listed job passed or was legitimately skipped.
check:
name: CI check
if: always()
needs: [lint, test-internal, test-modules, sdk-compat, docs, integration, pg-integration, govulncheck, deps-review, meta-schemas, helm, upgrade, codecov-skip, codecov-skip-grpctransport, codecov-skip-ci-only]
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: All green gate
# re-actors/alls-green@release/v1
uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe
with:
jobs: ${{ toJSON(needs) }}
allowed-skips: lint, test-internal, test-modules, sdk-compat, docs, integration, pg-integration, govulncheck, deps-review, helm, upgrade, codecov-skip, codecov-skip-grpctransport, codecov-skip-ci-only