vantagectl/ becomes the root of gitea.hostxtra.co.uk/vantage/vantage-ctl. The command keeps the name vantagectl; only the repository and the image path change, to vantage/vantage-ctl. chart-release.yml's render checks are repointed at the new image. The chart itself has no default backup.image and fails loudly without one, so an existing cluster keeps working until someone changes the value. go.work stays, now with a single use ./server entry: without it a go.work further up the developer's filesystem is picked up instead.
240 lines
11 KiB
YAML
240 lines
11 KiB
YAML
name: Chart Release
|
|
|
|
on:
|
|
# Every push that touches the chart is validated. Publishing is separate and
|
|
# deliberate: a chart version is immutable in the registry once pushed, so
|
|
# it must come from a tag someone chose, not from whatever landed on main.
|
|
# No `paths` filter on push, deliberately. A paths filter applies to tag
|
|
# pushes too, so tagging a commit that happened not to touch the chart
|
|
# would skip the publish entirely — a release that silently does nothing.
|
|
# Validation is seconds of helm rendering; running it on every push to main
|
|
# is cheaper than that failure mode.
|
|
push:
|
|
branches:
|
|
- main
|
|
tags:
|
|
- "chart/v*"
|
|
pull_request:
|
|
paths:
|
|
- "deploy/chart/**"
|
|
workflow_dispatch:
|
|
|
|
env:
|
|
CHART_DIR: deploy/chart/vantage
|
|
HELM_VERSION: v3.16.3
|
|
|
|
jobs:
|
|
chart:
|
|
runs-on: ubuntu-docker
|
|
container: alpine:3.21
|
|
steps:
|
|
# git for actions/checkout, curl for both the Helm download and the
|
|
# registry upload, tar because the Helm tarball is not self-extracting.
|
|
- name: Setup
|
|
run: apk add --no-cache bash curl git tar nodejs npm
|
|
|
|
- name: Install Helm
|
|
run: |
|
|
set -eu
|
|
curl -fsSL "https://get.helm.sh/helm-${HELM_VERSION}-linux-amd64.tar.gz" \
|
|
| tar -xz -C /tmp linux-amd64/helm
|
|
mv /tmp/linux-amd64/helm /usr/local/bin/helm
|
|
helm version --short
|
|
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Lint
|
|
run: helm lint "$CHART_DIR"
|
|
|
|
# Rendering is the real test. `helm lint` accepts a chart whose
|
|
# templates fail to execute, and every guard in this chart is a
|
|
# template `fail` that only fires during rendering.
|
|
- name: Render default values
|
|
run: helm template test "$CHART_DIR" > /dev/null
|
|
|
|
- name: Render a multi-replica install
|
|
run: |
|
|
helm template test "$CHART_DIR" \
|
|
--set server.replicaCount=3 \
|
|
--set web.replicaCount=3 > /dev/null
|
|
|
|
# The reaper deletes whole instances, so "does this env appear only
|
|
# in cloud mode" is worth asserting rather than eyeballing.
|
|
- name: Check the reaper is cloud-only
|
|
run: |
|
|
set -eu
|
|
if helm template test "$CHART_DIR" | grep -q FREE_INSTANCE_REAP_AFTER; then
|
|
echo "FREE_INSTANCE_REAP_AFTER is set on a self-hosted render"
|
|
exit 1
|
|
fi
|
|
if ! helm template test "$CHART_DIR" \
|
|
--set server.env.deploymentType=cloud \
|
|
| grep -q FREE_INSTANCE_REAP_AFTER; then
|
|
echo "FREE_INSTANCE_REAP_AFTER is missing from a cloud render"
|
|
exit 1
|
|
fi
|
|
echo "ok: reaper configured in cloud mode only"
|
|
|
|
- name: Render with backups enabled
|
|
run: |
|
|
helm template test "$CHART_DIR" \
|
|
--set backup.enabled=true \
|
|
--set backup.image=gitea.hostxtra.co.uk/vantage/vantage-ctl:latest \
|
|
--set backup.pvcName=vantage-backups > /dev/null
|
|
|
|
- name: Render against external Redis and MongoDB
|
|
run: |
|
|
helm template test "$CHART_DIR" \
|
|
--set redis.enabled=false \
|
|
--set redis.addr=redis.example.com:6379 \
|
|
--set mongo.enabled=false \
|
|
--set server.env.mongoUri=mongodb://mongo.example.com:27017/vantage > /dev/null
|
|
|
|
- name: Render with the Traefik ingress
|
|
run: |
|
|
helm template test "$CHART_DIR" \
|
|
--set ingress.enabled=true \
|
|
--set ingress.web.host=vantage.example.com \
|
|
--set ingress.grpc.host=agents.example.com \
|
|
--set ingress.tls.certResolver=letsencrypt \
|
|
--set server.env.grpcHost=agents.example.com:443 > /dev/null
|
|
|
|
# The shape the cloud deployment actually uses: a wildcard tenant
|
|
# namespace, /api and /auth routed at the edge, and no apex — that
|
|
# belongs to the marketing site, which this chart does not deploy.
|
|
- name: Render a wildcard host with edge-routed API paths
|
|
run: |
|
|
helm template test "$CHART_DIR" \
|
|
--set ingress.enabled=true \
|
|
--set 'ingress.web.host=*.vantage.example.com' \
|
|
--set ingress.api.enabled=true \
|
|
--set ingress.grpc.host=agents.example.com \
|
|
--set server.env.grpcHost=agents.example.com:443 \
|
|
--set ingress.tls.secretName=vantage-tls \
|
|
--set ingress.tls.grpcSecretName=agents-tls > /dev/null
|
|
|
|
# The guards are load-bearing, so their absence is a regression the
|
|
# same way a broken render is. Each of these must fail.
|
|
- name: Check the guards still refuse bad values
|
|
run: |
|
|
set -eu
|
|
|
|
refuses() {
|
|
desc="$1"; shift
|
|
if helm template test "$CHART_DIR" "$@" > /dev/null 2>&1; then
|
|
echo "GUARD MISSING: $desc was accepted"
|
|
exit 1
|
|
fi
|
|
echo "ok: refused $desc"
|
|
}
|
|
|
|
refuses "mongo disabled with an in-chart URI" \
|
|
--set mongo.enabled=false
|
|
refuses "redis disabled with no external address" \
|
|
--set redis.enabled=false
|
|
refuses "multiple replicas on a ReadWriteOnce volume" \
|
|
--set server.replicaCount=2 --set server.persistence.enabled=true
|
|
refuses "ingress with no web host" \
|
|
--set ingress.enabled=true
|
|
refuses "edge-routed API with an empty path list" \
|
|
--set ingress.enabled=true \
|
|
--set ingress.web.host=vantage.example.com \
|
|
--set ingress.grpc.enabled=false \
|
|
--set ingress.api.enabled=true \
|
|
--set 'ingress.api.paths=null'
|
|
refuses "gRPC ingress with no host" \
|
|
--set ingress.enabled=true \
|
|
--set ingress.web.host=vantage.example.com \
|
|
--set server.env.grpcHost=agents.example.com:443
|
|
refuses "an ingress that leaves /api unrouted" \
|
|
--set ingress.enabled=true \
|
|
--set ingress.web.host=vantage.example.com \
|
|
--set ingress.grpc.enabled=false \
|
|
--set ingress.api.enabled=false
|
|
refuses "gRPC ingress while grpcHost is still in-cluster" \
|
|
--set ingress.enabled=true \
|
|
--set ingress.web.host=vantage.example.com \
|
|
--set ingress.grpc.host=agents.example.com
|
|
refuses "backup enabled with no pvcName" \
|
|
--set backup.enabled=true \
|
|
--set backup.image=gitea.hostxtra.co.uk/vantage/vantage-ctl:latest
|
|
refuses "backup enabled with no image" \
|
|
--set backup.enabled=true \
|
|
--set backup.pvcName=vantage-backups
|
|
|
|
- name: Read the chart version
|
|
id: chart
|
|
run: |
|
|
set -eu
|
|
VERSION="$(grep '^version:' "$CHART_DIR/Chart.yaml" | awk '{print $2}')"
|
|
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
|
echo "chart version is $VERSION"
|
|
|
|
# Chart.yaml is the source of truth for the version; the tag only
|
|
# says "publish this one". A mismatch is a mistake worth stopping
|
|
# for — the alternative is stamping the tag over Chart.yaml, which
|
|
# leaves the repository disagreeing with what was published.
|
|
- name: Check the tag matches Chart.yaml
|
|
if: startsWith(github.ref, 'refs/tags/chart/v')
|
|
run: |
|
|
set -eu
|
|
TAG_VERSION="${GITHUB_REF_NAME#chart/v}"
|
|
CHART_VERSION="${{ steps.chart.outputs.version }}"
|
|
if [ "$TAG_VERSION" != "$CHART_VERSION" ]; then
|
|
echo "tag chart/v$TAG_VERSION does not match Chart.yaml version $CHART_VERSION"
|
|
echo "bump version: in $CHART_DIR/Chart.yaml, or retag."
|
|
exit 1
|
|
fi
|
|
|
|
- name: Package
|
|
run: |
|
|
set -eu
|
|
mkdir -p dist
|
|
helm package "$CHART_DIR" --destination dist
|
|
ls -l dist
|
|
|
|
- name: Publish to the Gitea chart registry
|
|
if: startsWith(github.ref, 'refs/tags/chart/v')
|
|
env:
|
|
# github.server_url is this Gitea instance, so the registry
|
|
# host needs no variable of its own and cannot drift from it.
|
|
REGISTRY: ${{ github.server_url }}/api/packages/${{ github.repository_owner }}/helm/api/charts
|
|
# The same pair server-deploy.yml uses for `docker login`.
|
|
# RELEASE_TOKEN, not REGISTRY_PASSWORD: the latter is named in
|
|
# the docs but set by no workflow, and an unset secret becomes
|
|
# an empty password, which Gitea reports as "Failed to
|
|
# authenticate user" rather than as a missing credential.
|
|
REGISTRY_USER: ${{ secrets.REGISTRY_USER }}
|
|
REGISTRY_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
|
CHART_VERSION: ${{ steps.chart.outputs.version }}
|
|
run: |
|
|
set -eu
|
|
PKG="dist/vantage-${CHART_VERSION}.tgz"
|
|
test -f "$PKG"
|
|
|
|
# Checked explicitly, because the failure it prevents is a
|
|
# 401 that looks like a permissions problem on the token that
|
|
# was never sent.
|
|
if [ -z "${REGISTRY_USER}" ] || [ -z "${REGISTRY_TOKEN}" ]; then
|
|
echo "REGISTRY_USER or RELEASE_TOKEN is not set on this repository."
|
|
echo "RELEASE_TOKEN needs the write:package scope to publish a chart."
|
|
exit 1
|
|
fi
|
|
|
|
echo "publishing to ${REGISTRY} as ${REGISTRY_USER}"
|
|
|
|
# --fail-with-body so an HTTP error is a failed step with the
|
|
# server's explanation, rather than a green run that published
|
|
# nothing. A repeated version is rejected by the registry;
|
|
# that is the intended behaviour, not something to retry past.
|
|
curl --fail-with-body -sS \
|
|
--user "${REGISTRY_USER}:${REGISTRY_TOKEN}" \
|
|
-X POST \
|
|
--upload-file "$PKG" \
|
|
"$REGISTRY"
|
|
|
|
echo "published vantage ${CHART_VERSION}"
|
|
echo " helm repo add vantage ${{ github.server_url }}/api/packages/${{ github.repository_owner }}/helm"
|
|
echo " helm install vantage vantage/vantage --version ${CHART_VERSION}"
|