2 Commits
Author SHA1 Message Date
jklein 7efda51bc7 probe: both charts, complete state
Validate Helm Charts / Detect changed charts (pull_request) Successful in 3s
Validate Helm Charts / Resource quantities (pull_request) Successful in 7s
Validate Helm Charts / Build ${{ fromJSON(needs.detect-changed-charts.outputs.charts) }} (pull_request) Successful in 5s
Validate Helm Charts / Trivy config ${{ fromJSON(needs.detect-changed-charts.outputs.scan-charts) }} (pull_request) Successful in 12s
Validate Helm Charts / Install-test ${{ fromJSON(needs.detect-changed-charts.outputs.install-charts) }} (pull_request) Failing after 3m52s
2026-09-17 01:00:51 +02:00
jklein fdd798e1d9 install-test: provide envsubst, and say why it is not pinned like kubectl
ci/run-install-test.sh renders ci/<chart>/fixtures.env.yaml with
envsubst. GitHub's hosted image ships gettext-base, the Gitea runner
image does not, and without it the fixtures collapse to 'error: no
objects passed to apply' - gluetun never gets its Wireguard key and
nothing behind its kill switch becomes Ready. The script is left
untouched; it does nothing wrong, a standard tool is simply absent.

Checked for rather than pinned, and the difference from kubectl is
written next to it: kubectl's version changes what the test MEANS,
because its distance to the API server is a real source of failure, so
it is pinned, tracked and guarded. envsubst substitutes text and the
release performing it does not change the result, so a pin would be
upkeep without value. Present means do nothing, which keeps the GitHub
path free of any package install; missing means fetch it, so the
difference between the two runners is stated instead of hidden behind
an unconditional apt-get.

Also rewrites the reachability comment into one block. It had grown into
two stacked halves whose first half still described the behaviour of the
version before last - both reasons (the loop against a discovery 503,
counting Ready nodes against an empty list that exits 0) now stand
together with the evidence that the second one is what made the inotify
failure visible at all.
2026-09-17 01:00:38 +02:00
2 changed files with 59 additions and 24 deletions

No files matched your search

+58 -23
View File
@@ -330,6 +330,35 @@ jobs:
install -m 0755 /tmp/kubectl "$HOME/.local/bin/kubectl" install -m 0755 /tmp/kubectl "$HOME/.local/bin/kubectl"
echo "$HOME/.local/bin" >> "$GITHUB_PATH" echo "$HOME/.local/bin" >> "$GITHUB_PATH"
"$HOME/.local/bin/kubectl" version --client "$HOME/.local/bin/kubectl" version --client
- name: "Provide envsubst"
run: |
# ci/run-install-test.sh renders ci/<chart>/fixtures.env.yaml with
# envsubst - today only freesailarr has one, and without the tool
# the fixtures collapse to "error: no objects passed to apply",
# gluetun never gets its Wireguard key, and nothing behind its kill
# switch becomes Ready. GitHub's hosted image ships gettext-base,
# the Gitea runner image does not. The script is fine as it is; a
# standard tool is simply missing.
#
# CHECKED FOR, NOT PINNED - and that is a different decision from
# kubectl above, on purpose. kubectl's VERSION changes what the test
# means: its distance to the API server is a real source of failure,
# which is why it is pinned, tracked and guarded. envsubst
# substitutes text; which release performs the substitution does not
# change the result. Pinning it would buy nothing and add a line for
# Renovate to carry. So: present - do nothing, which keeps the
# GitHub path free of any package install; missing - fetch it, and
# the difference between the two runners is stated rather than
# hidden behind an unconditional apt-get.
if command -v envsubst >/dev/null 2>&1; then
echo "envsubst present at $(command -v envsubst) - nothing to do"
exit 0
fi
echo "envsubst missing on this runner image - installing gettext-base"
if [ "$(id -u)" -eq 0 ]; then SUDO=""; else SUDO="sudo"; fi
$SUDO apt-get update -qq
$SUDO apt-get install -y -qq --no-install-recommends gettext-base
command -v envsubst
# freesailarr is the one chart whose install-test needs credentials: # freesailarr is the one chart whose install-test needs credentials:
# without a real Wireguard key gluetun never completes a handshake, and # without a real Wireguard key gluetun never completes a handshake, and
# behind its kill switch not a single app container becomes Ready. The key # behind its kill switch not a single app container becomes Ready. The key
@@ -448,30 +477,36 @@ jobs:
k3d cluster create "$CLUSTER" --image "$K3S_IMAGE" --wait --timeout 180s k3d cluster create "$CLUSTER" --image "$K3S_IMAGE" --wait --timeout 180s
fi fi
# Proves reachability here rather than letting it surface two steps # Reachability is proven HERE, and the shape of this check is the
# later as a validation error on the fixtures, which is where it # product of getting it wrong twice. Both mistakes are written down
# showed up before and named nothing useful. # because both look like something to simplify away.
# #
# Polled, not asked once. k3d's --wait returns when the node # It is a LOOP and not a single call. k3d's --wait returns when the
# container reports ready, but the API server can still answer # node container reports ready, but the API server can still answer
# discovery with 503 for a moment afterwards. Measured the hard way: # discovery with 503 for a moment after that. Measured: a single
# a bare `kubectl cluster-info` right here failed with # `kubectl cluster-info` right here failed with "ServiceUnavailable:
# "ServiceUnavailable: the server is currently unable to handle the # the server is currently unable to handle the request" on one of
# request" on one of two legs of the same GitHub run while the other # two legs of the SAME GitHub run while the other passed. A check
# passed - a check that is itself racy is worse than no check, since # that is itself racy produces exactly the sporadic red this job
# it produces exactly the sporadic red this job must not have. The # must never have (#227) - and a test people have learned to re-run
# loop asks for the node list, which is the thing that has to work # is one they have stopped believing.
# and exercises discovery on the way. #
# It waits for a READY NODE, not for the API server to answer. The # It counts READY NODES and not "did kubectl answer". `kubectl get
# difference is not pedantry, it is the second bug this check had: # nodes` exits 0 on an EMPTY list, so the first version passed
# `kubectl get nodes` exits 0 on an EMPTY list, so the first version # against a cluster whose API server was up and whose node had never
# passed against a cluster whose server was up and whose node had # registered: the log said "No resources found", the step went
# not registered - the log said "No resources found" and the step # green, and the failure surfaced five minutes later as a pod stuck
# went green, and the failure surfaced five minutes later as a pod # in Pending with no node and no scheduling event - nowhere near its
# stuck in Pending with no node and no scheduling event. A check # cause. A check that a broken cluster passes is worse than no
# that a broken cluster passes is worse than none. `kubectl wait # check. `kubectl wait --for=condition=Ready nodes --all` is not the
# --for=condition=Ready nodes --all` has the same hole: with zero # shorter way to write this: it has the identical hole, because with
# nodes it succeeds vacuously. # zero nodes the condition is vacuously satisfied.
#
# The evidence that this version earns its keep: only after it
# counted nodes did the real cause become visible in the dump below
# - k3s could not start containerd ("error creating fsnotify
# watcher: too many open files", an inotify limit on the host), and
# nothing above this line had ever said so.
for ATTEMPT in $(seq 1 90); do for ATTEMPT in $(seq 1 90); do
READY_NODES="$(kubectl get nodes --no-headers 2>/dev/null | awk '$2 == "Ready"' | wc -l | tr -d ' ')" READY_NODES="$(kubectl get nodes --no-headers 2>/dev/null | awk '$2 == "Ready"' | wc -l | tr -d ' ')"
[ "$READY_NODES" -ge 1 ] && break [ "$READY_NODES" -ge 1 ] && break
+1 -1
View File
@@ -8,7 +8,7 @@ description: A Helm chart to deploy a Wireguard-tunneled media automation stack
type: application type: application
version: 3.0.5+up1.0.0 version: 3.0.6+up1.0.0
# The chart's own version marker, freely chosen and tied to no upstream. The # The chart's own version marker, freely chosen and tied to no upstream. The
# stack bundles nine independently versioned projects, so there is no single # stack bundles nine independently versioned projects, so there is no single
# "app version" to track here. # "app version" to track here.