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
289 lines
No EOL
8.6 KiB
YAML
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" |