go-bananas/.github/workflows/ci.yml
Daniel Noulet 951dd8d26b
Some checks failed
ci / go (push) Has been cancelled
ci / admin-ui (push) Has been cancelled
ci / runtime-sdk (push) Has been cancelled
ci / helm (push) Has been cancelled
ci / openapi (push) Has been cancelled
ci / container (push) Has been cancelled
ci / release-config (push) Has been cancelled
ci / shared styling (push) Has been cancelled
ci / security (push) Has been cancelled
ci / all green (push) Has been cancelled
initial commit
2026-10-07 15:11:35 +02:00

289 lines
No EOL
8.6 KiB
YAML

name: ci
# One workflow for the whole pre-merge gate, with the jobs split so that a failure
# names the thing that broke instead of "the build".
#
# The job set is deliberately the same as `make verify`, and each job calls the
# Makefile rather than repeating the commands. A CI script that differs from the
# Makefile eventually differs in a way nobody notices, and then the Makefile stops
# being a usable local gate.
on:
push:
branches: [main]
pull_request:
merge_group:
# A new push to the same branch makes the in-flight run irrelevant: cancelling it
# saves runner minutes and stops an old result being reported against new code.
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
env:
# Matches the Go directive in go.mod. setup-go would read it from there, but
# pinning it here means a toolchain bump shows up as a reviewable diff here too.
GO_VERSION: "1.27.1"
NODE_VERSION: "24"
jobs:
go:
name: go
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-go@v6
with:
go-version: ${{ env.GO_VERSION }}
# The module and build caches live in the runner's cache rather than in
# the workspace, so a checkout never carries a stale artefact into a test.
cache: true
- name: Verify formatting
run: make fmt-check
- name: Vet
run: go vet ./cmd/... ./internal/...
- name: Lint
run: make lint-go
env:
# golangci-lint caches analysis keyed on the Go version and the source.
GOLANGCI_LINT_CACHE: ${{ github.workspace }}/.cache/golangci-lint
- name: Test with the race detector
run: make test-go
- name: Build
run: make build-go
web:
name: admin-ui
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-node@v5
with:
node-version: ${{ env.NODE_VERSION }}
cache: npm
cache-dependency-path: web/package-lock.json
- name: Install
# `npm ci`, never `npm install`: it installs exactly the lockfile, and it
# fails when the lockfile and package.json disagree instead of quietly
# resolving something new.
run: npm ci --no-audit --no-fund
working-directory: web
- name: Lint
run: make lint-web
- name: Test
run: make test-web
- name: Build
run: make build-web
sdk:
name: runtime-sdk
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-node@v5
with:
node-version: ${{ env.NODE_VERSION }}
cache: npm
cache-dependency-path: sdk/package-lock.json
- name: Install
run: npm ci --no-audit --no-fund
working-directory: sdk
- name: Lint and typecheck
run: make lint-sdk
- name: Test
run: make test-sdk
- name: Build
run: make build-sdk
helm:
name: helm
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: azure/setup-helm@v5
with:
version: v3.19.0
- name: Lint strictly
# --strict turns warnings into errors, which is what stops a chart shipping
# with an icon or maintainer field quietly missing.
run: helm lint --strict deploy/helm/go-bananas
- name: Render the whole configuration matrix
# Not `helm lint`: lint only sees the default values. This renders every
# documented configuration and checks both that each valid one produces
# parseable YAML and that each invalid one is refused with a message
# naming the value to change.
run: make helm-matrix
contract:
name: openapi
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: The document must be valid OpenAPI 3.1
# A specification that does not parse is worse than no specification,
# because every client generator will trust it.
run: npx --yes @redocly/cli@latest lint openapi.yaml
container:
name: container
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: docker/setup-buildx-action@v3
# Load the image into the local daemon so the steps after this can run it.
# Without `load` the result only exists in the build cache, and there is
# nothing to inspect.
- name: Build
uses: docker/build-push-action@v6
with:
context: .
file: Dockerfile
push: false
load: true
tags: go-bananas:ci
cache-from: type=gha
cache-to: type=gha,mode=max
- name: The image must actually serve
run: |
set -eu
docker run -d --name bananas \
-e BANANAS_ENVIRONMENT=development \
-e BANANAS_AUTH_MODE=dev \
-e BANANAS_PUBLIC_BASE_URL=http://localhost:8080 \
-p 8080:8080 \
go-bananas:ci
# Poll rather than sleeping a fixed time: a fixed sleep is either too
# short on a loaded runner or wasted time on a fast one.
for i in $(seq 1 60); do
if curl -fsS http://localhost:8080/healthz >/dev/null 2>&1; then
echo "healthy after ${i}s"
break
fi
if [ "$i" -eq 60 ]; then
echo "the container never became healthy"
docker logs bananas
exit 1
fi
sleep 1
done
# The admin UI must be served from the image, not just the API: a
# container that starts and then 404s on its own dashboard is a
# regression the health check would not catch.
curl -fsS http://localhost:8080/rt/v1/manifest.json >/dev/null
curl -fsS http://localhost:8080/api/v1/auth/me | grep -q '"mode"'
echo "--- running as ---"
docker exec bananas id
# The image declares USER 65532, so anything else means the build lost it.
test "$(docker exec bananas id -u)" = "65532"
- name: Logs on failure
if: failure()
run: docker logs bananas || true
- name: Clean up
if: always()
run: docker rm -f bananas || true
release-config:
name: release-config
runs-on: ubuntu-latest
# Validating the release configuration on every push is cheap, and a broken
# goreleaser config discovered at tag time is a release that cannot be cut.
steps:
- uses: actions/checkout@v5
- uses: actions/setup-go@v6
with:
go-version: ${{ env.GO_VERSION }}
- name: Install GoReleaser
uses: goreleaser/goreleaser-action@v6
with:
distribution: goreleaser
version: v2.18.2
args: check
styling:
name: shared styling
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-go@v6
with:
go-version: ${{ env.GO_VERSION }}
- uses: actions/setup-node@v6
with:
node-version: ${{ env.NODE_VERSION }}
# The two checks that cannot be done in jsdom: whether the rendered contract CSS
# resolves to the right computed values, and whether an application that has not
# touched its stylesheet actually follows the contract. The second is the one that
# decides whether the consolidation is real.
- name: Contract and controls
run: make check-contract
- name: go-loose pilot
run: make check-pilot
security:
name: security
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: Dependency review
uses: actions/dependency-review-action@v4
with:
# Only on pull requests: the API needs a base branch to compare against,
# and it is the pull request that the review is protecting.
fail-on-severity: moderate
all-green:
name: all green
# The single required status check. It depends on every job above and does
# nothing itself, so that a branch can be made to require one stable name
# instead of a list that has to be edited every time a job is added.
if: always()
needs: [go, web, sdk, helm, contract, container, styling, release-config, security]
runs-on: ubuntu-latest
steps:
- name: Fail if any job failed
run: |
results='${{ toJSON(needs) }}'
echo "$results" | jq -r 'to_entries[] | "\(.key): \(.value.result)"'
if echo "$results" | jq -e 'to_entries | any(.value.result != "success")' >/dev/null; then
echo "one or more jobs did not succeed"
exit 1
fi
echo "all jobs succeeded"