#!/usr/bin/env bash # # Renders the chart across a matrix of configurations and checks each result. # # Two things are being verified, and `helm lint` only covers the first: # # 1. Every configuration that should work renders to YAML that *parses*. A # template bug that only triggers under a non-default value produces valid # YAML in review and an unapplyable manifest in production, so each render is # fed back through a YAML parser rather than trusted because helm exited zero. # # 2. Every configuration that should be refused is refused, and the refusal says # which value to change. A validation guard that silently accepts a bad value # is worse than no guard, because it moves the failure to run time. # # The matrix lives in a script rather than in the Makefile because it is a list of # cases, and a list is easier to read, review and extend as shell than as make. set -uo pipefail CHART="${CHART:-deploy/helm/go-bananas}" RELEASE="${RELEASE:-go-bananas}" pass=0 fail=0 work="$(mktemp -d)" trap 'rm -rf "$work"' EXIT # yaml_ok verifies that a rendered file is parseable YAML. yaml_ok() { python3 -c ' import sys try: import yaml except ImportError: sys.exit(3) list(yaml.safe_load_all(open(sys.argv[1]))) ' "$1" 2>/dev/null } # ok renders a configuration that must succeed. ok() { local desc="$1" shift if ! helm template "$RELEASE" "$CHART" "$@" >"$work/out.yaml" 2>"$work/err.txt"; then printf 'FAIL %s (should have rendered)\n' "$desc" sed -n '1,3p' "$work/err.txt" | sed 's/^/ /' fail=$((fail + 1)) return fi yaml_ok "$work/out.yaml" case $? in 0) printf 'ok %s\n' "$desc"; pass=$((pass + 1)) ;; 3) printf 'ok %s (PyYAML absent, skipped the parse check)\n' "$desc"; pass=$((pass + 1)) ;; *) printf 'FAIL %s (rendered output is not valid YAML)\n' "$desc"; fail=$((fail + 1)) ;; esac } # rejected renders a configuration that must fail, and requires the message to name # the problem rather than being a bare stack trace. rejected() { local desc="$1" shift if helm template "$RELEASE" "$CHART" "$@" >"$work/out.yaml" 2>"$work/err.txt"; then printf 'FAIL %s (should have been refused but rendered)\n' "$desc" fail=$((fail + 1)) return fi if grep -qE 'execution error|Error:' "$work/err.txt"; then printf 'ok %s (refused: %s)\n' "$desc" \ "$(grep -oE '(execution error.*|Error: .*)' "$work/err.txt" | head -1 | cut -c1-100)" pass=$((pass + 1)) else printf 'FAIL %s (refused, but without an actionable message)\n' "$desc" sed -n '1,3p' "$work/err.txt" | sed 's/^/ /' fail=$((fail + 1)) fi } echo "== configurations that must render ==" ok "default values" ok "oidc with an existing secret" \ --set identity.oidc.generateSecret=false \ --set identity.oidc.existingSecret=go-bananas-oidc ok "oidc with a generated secret" \ --set identity.mode=oidc \ --set config.publicBaseUrl=https://themes.example.com ok "production over https" \ --set config.environment=production \ --set config.publicBaseUrl=https://themes.example.com ok "identity disabled" --set identity.mode=disabled ok "development bypass" --set identity.mode=dev ok "ingress with TLS" \ --set ingress.enabled=true \ --set ingress.hosts[0].host=themes.example.com \ --set ingress.tls[0].secretName=themes-tls \ --set ingress.tls[0].hosts[0]=themes.example.com ok "ephemeral storage" --set persistence.type=emptyDir ok "existing claim" --set persistence.existingClaim=bananas-data ok "autoscaling and disruption budget" \ --set autoscaling.enabled=true --set podDisruptionBudget.enabled=true ok "network policy" --set networkPolicy.enabled=true ok "service monitor" --set metrics.serviceMonitor.enabled=true ok "image pinned by digest" --set image.digest=sha256:deadbeef ok "image in another registry" --set image.repository=quay.io/urpi/go-bananas ok "base path" --set config.basePath=/themes ok "extra environment" --set 'extraEnv[0].name=FOO' --set 'extraEnv[0].value=bar' ok "snapshot cronjob" --set persistence.snapshot.enabled=true echo echo "== configurations that must be refused ==" rejected "both an existing and a generated secret" \ --set identity.oidc.existingSecret=go-bananas-oidc rejected "neither an existing nor a generated secret" \ --set identity.oidc.generateSecret=false rejected "an empty public base URL" \ --set config.publicBaseUrl= rejected "a public base URL with a trailing slash" \ --set config.publicBaseUrl=https://themes.example.com/ rejected "a public base URL that is not a URL" \ --set config.publicBaseUrl=themes.example.com rejected "TLS verification disabled in production" \ --set identity.oidc.skipTLSVerify=true --set config.environment=production rejected "an unknown identity mode" --set identity.mode=nonsense rejected "an OIDC mode with no issuer" --set identity.mode=oidc --set identity.oidc.issuer= rejected "a shutdown timeout longer than the grace period" \ --set config.shutdownTimeoutMs=60000 --set deployment.terminationGracePeriodSeconds=30 rejected "an unknown top-level value" --set thisKeyDoesNotExist=1 echo if [ "$fail" -gt 0 ]; then printf '%d of %d checks failed\n' "$fail" "$((pass + fail))" exit 1 fi printf 'all %d chart checks passed\n' "$pass"