ci: run the freesailarr install-test for real instead of skipping it
freesailarr was excluded from the install-test because gluetun never becomes healthy without a real Wireguard key and no app container behind its kill switch can become Ready. The green "Install-test freesailarr pass" was a 25-second SKIP on the most complex chart in the repo - a signal that proved nothing. With a CI-only Mullvad key in 1Password the reason is gone: - ci/run-install-test.sh learns a general rule: ci/<chart>/fixtures.env.yaml is rendered with envsubst before it is applied, restricted to the variable names a chart declares in REQUIRED_ENV. A missing or empty declared variable fails the run loudly and names the variable - it never skips the chart. - The install-test job loads the key from 1Password Connect for freesailarr only, following the pattern publish-helm-chart.yml documents: values via steps.<id>.outputs.*, mapped into the step's own env, quoted, with ":?" guards that name the missing op:// field before a cluster is created. - Fixtures for freesailarr: three media claims, the gluetun secret template with variable names only, firewallOutboundSubnets so the kill switch does not block cluster DNS, and INSTALL_TIMEOUT=900 for nine containers. freesailarr/Chart.yaml goes 3.0.0+up1.0.0 -> 3.0.1+up1.0.0. That bump is deliberately empty of content: the install-test matrix follows changed chart directories, and without it this PR could not run the test it adds.
This commit is contained in:
9 files changed
+215
-7
No files matched your search
@@ -1,5 +1,4 @@
|
||||
# Charts excluded from the PR install-test, one per line: <chart> <reason>.
|
||||
# ci/run-install-test.sh skips these with the given reason.
|
||||
freesailarr gluetun never becomes healthy without a real Wireguard key and VPN connectivity, and every app container sits behind its firewall - the pod cannot become Ready on a CI runner
|
||||
satisfactory the game-server image is multiple GB and Steam downloads 10GB+ game files on first start - impractical on a CI runner
|
||||
template-chart scaffold with placeholder image "ghcr.io/ORG/IMAGE:xxx", never published
|
||||
@@ -0,0 +1,22 @@
|
||||
# The gluetun credentials Secret the 1Password operator materializes on the
|
||||
# real cluster - here as a template with variable NAMES only. This file must
|
||||
# never contain a value; ci/run-install-test.sh renders it with envsubst,
|
||||
# restricted to the names declared in ci/freesailarr/settings.env
|
||||
# (REQUIRED_ENV), and pipes the result straight into kubectl.
|
||||
#
|
||||
# The values come from a 1Password item holding a CI-only Mullvad key, loaded in
|
||||
# the install-test job of .github/workflows/validate-charts.yml. A CI-only key
|
||||
# on purpose: Mullvad ties an address to a key and sends the tunnel to the
|
||||
# endpoint it saw last, so a shared key would let every pull request pull the
|
||||
# tunnel out from under the running stack.
|
||||
#
|
||||
# Name and keys follow the chart: <release>-<chart>-gluetun-secret with the
|
||||
# fields the StatefulSet reads via secretKeyRef. Release name is ci.
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: ci-freesailarr-gluetun-secret
|
||||
type: Opaque
|
||||
stringData:
|
||||
wireguard-private-key: "${FREESAILARR_WG_KEY}"
|
||||
wireguard-addresses: "${FREESAILARR_WG_ADDRESSES}"
|
||||
@@ -0,0 +1,43 @@
|
||||
# Fixtures for the freesailarr install-test: the three media claims the chart
|
||||
# mounts but never creates (media.*.existingClaim). The config volumes come from
|
||||
# the StatefulSet's own volumeClaimTemplates and need nothing here.
|
||||
#
|
||||
# Default StorageClass (k3d's local-path), so the claims stay Pending until the
|
||||
# pod is scheduled - WaitForFirstConsumer, which is exactly what the chart sees
|
||||
# on a real cluster. local-path creates its directories mode 0777, so the claim
|
||||
# roots are writable for PUID/PGID 1000 without a chown job.
|
||||
#
|
||||
# The gluetun credentials Secret is NOT here: it carries values that must not be
|
||||
# committed and lives in fixtures.env.yaml.
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: ci-freesailarr-downloads
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
resources:
|
||||
requests:
|
||||
storage: 1Gi
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: ci-freesailarr-movies
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
resources:
|
||||
requests:
|
||||
storage: 1Gi
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: ci-freesailarr-tv
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
resources:
|
||||
requests:
|
||||
storage: 1Gi
|
||||
@@ -0,0 +1,10 @@
|
||||
# Nine containers in one pod, several GB of images to pull, and the app
|
||||
# containers only start once gluetun has completed its Wireguard handshake -
|
||||
# each of them then runs its own startup probe (150s budget, 300s for seerr's
|
||||
# first-boot migrations). The default of 300s is not enough for that.
|
||||
INSTALL_TIMEOUT=900
|
||||
|
||||
# The gluetun credentials for ci/freesailarr/fixtures.env.yaml. Missing or empty
|
||||
# values fail the install-test loudly instead of skipping it - see
|
||||
# ci/run-install-test.sh.
|
||||
REQUIRED_ENV="FREESAILARR_WG_KEY FREESAILARR_WG_ADDRESSES"
|
||||
@@ -0,0 +1,39 @@
|
||||
# freesailarr: the whole stack in one pod - gluetun plus eight app containers
|
||||
# that share its network namespace and sit behind its kill switch. Nothing here
|
||||
# is a shortcut around that: the install-test brings up a real Wireguard tunnel
|
||||
# with a CI-only Mullvad key (see ci/freesailarr/fixtures.env.yaml), because a
|
||||
# gluetun without a handshake keeps every app container from becoming Ready.
|
||||
#
|
||||
# Release name is ci, so the chart looks for the gluetun credentials in the
|
||||
# Secret ci-freesailarr-gluetun-secret, which the fixtures create. The chart's
|
||||
# own OnePasswordItem CR applies against the stub CRD and stays unreconciled -
|
||||
# there is no 1Password operator in the CI cluster.
|
||||
secrets:
|
||||
# Required by the chart. Only the CR's itemPath, never resolved in CI.
|
||||
gluetunSecretRef: op://ci/freesailarr-gluetun
|
||||
|
||||
media:
|
||||
# The three media claims are pre-created in fixtures.yaml (one claim per
|
||||
# category, no subPath - the shared-claim layout is a values question, not a
|
||||
# startup question). local-path hands out its directories mode 0777, so the
|
||||
# claim roots are writable for PUID/PGID as the chart requires.
|
||||
downloads:
|
||||
existingClaim: ci-freesailarr-downloads
|
||||
movies:
|
||||
existingClaim: ci-freesailarr-movies
|
||||
tv:
|
||||
existingClaim: ci-freesailarr-tv
|
||||
|
||||
gluetun:
|
||||
vpn:
|
||||
# Without this the kill switch drops cluster-internal egress as well, and
|
||||
# the very first DNS lookup against cluster DNS fails. k3s defaults:
|
||||
# 10.42.0.0/16 pods, 10.43.0.0/16 services.
|
||||
firewallOutboundSubnets: "10.42.0.0/16,10.43.0.0/16"
|
||||
|
||||
ingress:
|
||||
# Shared-domain mode with a placeholder host: the Ingress objects only have to
|
||||
# be accepted by the API server. There is no ingress-nginx and no cert-manager
|
||||
# in the CI cluster, and the install-test checks readiness, not routing.
|
||||
host: freesailarr.example.com
|
||||
clusterIssuer: ci
|
||||
+45
-1
@@ -9,7 +9,8 @@
|
||||
# ci/common/ fixtures applied for every chart (OnePasswordItem stub CRD)
|
||||
# ci/<chart>/test-values.yaml values for the CI install (required)
|
||||
# ci/<chart>/fixtures.yaml dummy secrets / throwaway infra / static PVs (optional)
|
||||
# ci/<chart>/settings.env optional overrides, e.g. INSTALL_TIMEOUT=600
|
||||
# ci/<chart>/fixtures.env.yaml fixtures with ${VARS} filled in from the environment (optional)
|
||||
# ci/<chart>/settings.env optional overrides, e.g. INSTALL_TIMEOUT=600, REQUIRED_ENV=...
|
||||
# ci/excluded-charts.txt charts that cannot run in CI, with reasons
|
||||
set -euo pipefail
|
||||
|
||||
@@ -20,8 +21,11 @@ ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
CI_DIR="$ROOT/ci"
|
||||
|
||||
# Per-chart override via ci/<chart>/settings.env (INSTALL_TIMEOUT in seconds).
|
||||
# REQUIRED_ENV is a space-separated list of environment variables the chart's
|
||||
# fixtures need; see the check below.
|
||||
INSTALL_TIMEOUT=300
|
||||
FIXTURE_JOB_TIMEOUT=600
|
||||
REQUIRED_ENV=""
|
||||
if [ -f "$CI_DIR/$CHART/settings.env" ]; then
|
||||
# shellcheck disable=SC1090
|
||||
source "$CI_DIR/$CHART/settings.env"
|
||||
@@ -47,6 +51,32 @@ if [ ! -f "$CI_DIR/$CHART/test-values.yaml" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# --- Required environment for ci/<chart>/fixtures.env.yaml ------------------
|
||||
# A chart whose fixtures need a value that must never be committed (freesailarr
|
||||
# needs a real Wireguard key, or gluetun's kill switch keeps every app container
|
||||
# from becoming Ready) declares the variable NAMES in settings.env:
|
||||
#
|
||||
# REQUIRED_ENV="FREESAILARR_WG_KEY FREESAILARR_WG_ADDRESSES"
|
||||
#
|
||||
# The values come from the environment - in CI from 1Password Connect, see the
|
||||
# install-test job in .github/workflows/validate-charts.yml.
|
||||
#
|
||||
# A missing or empty value is a hard error on purpose. Skipping the chart
|
||||
# instead would restore exactly the green-but-meaningless signal this mechanism
|
||||
# exists to remove, and an empty key produces a gluetun without a handshake,
|
||||
# which reads as a broken chart rather than as a missing secret. The one
|
||||
# legitimate case of absent secrets - a pull request from a fork - belongs in an
|
||||
# "if" on the workflow job, not in this script.
|
||||
# Only the variable name is ever printed, never the value.
|
||||
for VAR in $REQUIRED_ENV; do
|
||||
if [ -z "${!VAR:-}" ]; then
|
||||
echo "ERROR: required environment variable '$VAR' is unset or empty." >&2
|
||||
echo "ci/$CHART/settings.env declares REQUIRED_ENV=\"$REQUIRED_ENV\"; ci/$CHART/fixtures.env.yaml" >&2
|
||||
echo "cannot be rendered without it, and the install-test does not skip charts over missing values." >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
# --- Failure diagnostics ----------------------------------------------------
|
||||
diagnose() {
|
||||
echo "=================== INSTALL-TEST FAILED: DIAGNOSTICS ==================="
|
||||
@@ -72,6 +102,20 @@ kubectl apply -f "$CI_DIR/common/"
|
||||
if [ -f "$CI_DIR/$CHART/fixtures.yaml" ]; then
|
||||
kubectl apply -f "$CI_DIR/$CHART/fixtures.yaml"
|
||||
fi
|
||||
# Fixtures with values that must not be committed are rendered first: the
|
||||
# checked-in fixtures.env.yaml holds ${VARIABLE} placeholders and nothing else.
|
||||
# envsubst is restricted to the names from REQUIRED_ENV (its SHELL-FORMAT
|
||||
# argument), so a variable that is not declared there is never substituted, no
|
||||
# unrelated "$..." in the manifest is touched, and no other variable of the
|
||||
# runner environment can end up in the cluster. The rendered document is piped
|
||||
# straight into kubectl and never printed - kubectl reports object names only.
|
||||
if [ -f "$CI_DIR/$CHART/fixtures.env.yaml" ]; then
|
||||
SHELL_FORMAT=""
|
||||
for VAR in $REQUIRED_ENV; do
|
||||
SHELL_FORMAT="$SHELL_FORMAT\${$VAR}"
|
||||
done
|
||||
envsubst "$SHELL_FORMAT" < "$CI_DIR/$CHART/fixtures.env.yaml" | kubectl apply -f -
|
||||
fi
|
||||
echo "::endgroup::"
|
||||
|
||||
# --- 2. Wait for fixture jobs (node setup, data seeding) and infra ----------
|
||||
|
||||
Reference in new issue
Block a user