222 lines
10 KiB
YAML
222 lines
10 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 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 "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
|
|
|
|
- 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}"
|