From 9825bc2792569ba8e7773d6921cc432ed194d8fa Mon Sep 17 00:00:00 2001 From: Guilherme Moreira Rodrigues Date: Tue, 4 Aug 2026 18:12:40 -0300 Subject: [PATCH 1/6] ci: add kind install + upgrade test for Helm charts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The render-gate only runs `helm template` — it cannot catch manifests that render fine but the API server rejects (out-of-range values, bad API versions), nor immutable-field breaks on upgrade. This workflow installs each changed chart into a throwaway kind cluster and exercises the upgrade path, so the real API server validates every object server-side. .github/scripts/install-test.sh, per chart: 1. helm install (PR) — server-side manifest validation 2. helm upgrade in place — upgrade code path 3. helm install origin/main then upgrade to PR (isolated ns) — catches immutable-field breaks; skipped for charts not yet on main Hook Jobs are skipped (--no-hooks) — they pull private images and need real backing services, so they can't complete in a credential-less cluster; app pods are not waited on for the same reason. Steady-state manifest validation + the upgrade path are what this gate proves. Only ONE release is installed at a time (the PR release is removed before the baseline) so subchart-heavy charts don't exhaust a single-node kind cluster. Scope: changed charts on a PR; the full app-chart set when the workflow, the script, or lerian-common changes. Library charts are skipped. --- .github/scripts/install-test.sh | 87 +++++++++++++++++++ .github/workflows/helm-install-test.yml | 107 ++++++++++++++++++++++++ 2 files changed, 194 insertions(+) create mode 100755 .github/scripts/install-test.sh create mode 100644 .github/workflows/helm-install-test.yml diff --git a/.github/scripts/install-test.sh b/.github/scripts/install-test.sh new file mode 100755 index 000000000..d46282447 --- /dev/null +++ b/.github/scripts/install-test.sh @@ -0,0 +1,87 @@ +#!/usr/bin/env bash +# install-test.sh — install + upgrade a chart into the current kube context. +# Core value over `helm template`: the API server validates every manifest +# server-side (rejects invalid/immutable/admission-failing objects), and the +# upgrade path catches immutable-field breaks. Does NOT wait for app pods to +# become Ready (images are private; readiness is a separate, creds-gated concern). +# +# Usage: install-test.sh [values-file] +set -uo pipefail + +CHART_DIR="$1" +CHART="$(basename "$CHART_DIR")" +# Values: explicit arg wins; else the render-gate's vetted sample values, if any. +VALUES_FILE="${2:-}" +[[ -z "$VALUES_FILE" && -f ".github/configs/helm-render-values/${CHART}.yaml" ]] \ + && VALUES_FILE=".github/configs/helm-render-values/${CHART}.yaml" +NS="it-${CHART}" +NSB="it-${CHART}-base" # isolated namespace for the origin/main -> PR baseline upgrade +REL="$CHART" +TIMEOUT="${IT_TIMEOUT:-180s}" +VARGS=() +[[ -n "$VALUES_FILE" && -f "$VALUES_FILE" ]] && { VARGS=(-f "$VALUES_FILE"); echo " values: $VALUES_FILE"; } +# --no-hooks: hook Jobs (migrations/bootstrap) pull private images and need real +# backing services, so they never complete in a credential-less CI cluster. We +# validate the steady-state manifests server-side; hook Jobs are out of scope here. +HOOKS=(--no-hooks) + +# Library charts are not installable. +if grep -qiE '^type:[[:space:]]*library([[:space:]]|$)' "$CHART_DIR/Chart.yaml"; then + echo "::notice::$CHART is a library chart — skipping install test." + exit 0 +fi + +fail() { echo "::error::[$CHART] $1"; kubectl get events -n "$NS" --sort-by=.lastTimestamp 2>/dev/null | tail -15; cleanup; exit 1; } +cleanup() { + helm uninstall "$REL" -n "$NS" >/dev/null 2>&1 || true + helm uninstall "${REL}-base" -n "$NSB" >/dev/null 2>&1 || true + kubectl delete ns "$NS" "$NSB" --wait=false >/dev/null 2>&1 || true +} + +cleanup # idempotent: clear any stale release/namespace from a prior aborted run + +echo "===== [$CHART] dependency build =====" +helm dependency build "$CHART_DIR" >/dev/null 2>&1 || fail "helm dependency build failed" + +# ---- 1. Fresh install of the PR chart (server-side manifest validation) ---- +echo "===== [$CHART] install (PR) =====" +if ! helm install "$REL" "$CHART_DIR" ${VARGS[@]+"${VARGS[@]}"} "${HOOKS[@]}" -n "$NS" --create-namespace --timeout "$TIMEOUT" 2>&1; then + fail "helm install failed (invalid manifest / admission / hook)" +fi +[[ "$(helm status "$REL" -n "$NS" -o json | grep -o '"status":"[a-z]*"' | head -1)" == '"status":"deployed"' ]] \ + || fail "release not in deployed state" +n=$(kubectl get all -n "$NS" --no-headers 2>/dev/null | wc -l | tr -d ' ') +echo " created $n objects" +[[ "$n" -gt 0 ]] || fail "install produced no objects" + +# ---- 2. Upgrade the PR chart in place (upgrade code path + hook re-run) ---- +echo "===== [$CHART] upgrade (PR -> PR, benign change) =====" +helm upgrade "$REL" "$CHART_DIR" ${VARGS[@]+"${VARGS[@]}"} "${HOOKS[@]}" -n "$NS" --timeout "$TIMEOUT" \ + --set-string podAnnotations.helm-install-test="$(date +%s 2>/dev/null || echo x)" 2>&1 \ + | grep -qE 'STATUS: deployed' || fail "in-place upgrade failed" + +# Free the PR release before the baseline test so only ONE release is ever +# installed at a time — two full installs of a subchart-heavy chart exhaust a +# single-node kind cluster's memory and make the baseline flaky. +helm uninstall "$REL" -n "$NS" >/dev/null 2>&1 || true +kubectl delete ns "$NS" --wait=false >/dev/null 2>&1 || true + +# ---- 3. Real upgrade path: base (origin/main) -> PR, when the chart exists on main ---- +if git cat-file -e "origin/main:charts/${CHART}/Chart.yaml" 2>/dev/null; then + echo "===== [$CHART] upgrade (origin/main -> PR) =====" + BASE_DIR="$(mktemp -d)/$CHART"; mkdir -p "$BASE_DIR" + git archive "origin/main" "charts/${CHART}" | tar -x --strip-components=2 -C "$BASE_DIR" 2>/dev/null + helm dependency build "$BASE_DIR" >/dev/null 2>&1 || echo " (base dep build failed — skipping baseline upgrade)" + if helm install "${REL}-base" "$BASE_DIR" ${VARGS[@]+"${VARGS[@]}"} "${HOOKS[@]}" -n "$NSB" --create-namespace --timeout "$TIMEOUT" >/dev/null 2>&1; then + helm upgrade "${REL}-base" "$CHART_DIR" ${VARGS[@]+"${VARGS[@]}"} "${HOOKS[@]}" -n "$NSB" --timeout "$TIMEOUT" 2>&1 \ + | grep -qE 'STATUS: deployed' || fail "upgrade from origin/main failed (immutable-field break?)" + echo " origin/main -> PR upgrade OK" + else + echo " (baseline install failed — likely unrelated to this PR; skipping)" + fi +else + echo " (new chart — not on origin/main; skipping baseline upgrade)" +fi + +echo "===== [$CHART] OK =====" +cleanup diff --git a/.github/workflows/helm-install-test.yml b/.github/workflows/helm-install-test.yml new file mode 100644 index 000000000..647477c67 --- /dev/null +++ b/.github/workflows/helm-install-test.yml @@ -0,0 +1,107 @@ +name: Helm Install Test + +# Installs each changed chart into a throwaway kind cluster and exercises the +# upgrade path. The value over the render-gate (which only runs `helm template`) +# is that the real API server VALIDATES every manifest server-side — it rejects +# invalid field values, bad API versions, and immutable-field changes on upgrade +# that `helm template` renders happily. Hook Jobs (migrations/bootstrap) are +# skipped (--no-hooks in the script): they pull private images and need real +# backing services, so they cannot complete in a credential-less CI cluster. +# App pods are NOT waited on for the same reason — steady-state manifest +# validation + the upgrade path are what this gate proves. + +on: + pull_request: + paths: + - "charts/**" + - ".github/configs/helm-render-values/**" + - ".github/scripts/install-test.sh" + - ".github/workflows/helm-install-test.yml" + workflow_dispatch: {} + +concurrency: + group: helm-install-test-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + install-test: + name: Install + upgrade (kind) + runs-on: blacksmith-4vcpu-ubuntu-2404 + timeout-minutes: 30 # a full --all sweep (workflow/lib change) installs every app chart sequentially + steps: + - name: Checkout code + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + fetch-depth: 0 # full history so the script can `git archive origin/main` for the baseline upgrade + + - name: Fetch base branch + run: git fetch --no-tags --depth=1 origin main + + - name: Setup Helm + uses: azure/setup-helm@1a275c3b69536ee54be43f2070a358922e12c8d4 # v4 + + - name: Create kind cluster + uses: helm/kind-action@a1b0e391336a6ee6713a0583f8c6240d70863de3 # v1.10.0 + with: + cluster_name: chart-install-test + + - name: Determine chart scope + id: scope + shell: bash + env: + EVENT_NAME: ${{ github.event_name }} + BASE_SHA: ${{ github.event.pull_request.base.sha }} + HEAD_SHA: ${{ github.sha }} + run: | + set -euo pipefail + all_app_charts() { + for d in charts/*/; do + c="$(basename "$d")" + grep -qiE '^type:[[:space:]]*library([[:space:]]|$)' "charts/$c/Chart.yaml" 2>/dev/null && continue + printf '%s ' "$c" + done + } + if [[ "$EVENT_NAME" != "pull_request" ]]; then + echo "charts=$(all_app_charts)" >> "$GITHUB_OUTPUT"; exit 0 + fi + changed="$(git diff --name-only "$BASE_SHA" "$HEAD_SHA")" + # An infra change (script/workflow/shared library/fixtures) can affect every + # chart, so test them all; otherwise only the charts whose files changed. + if grep -Eq '^(\.github/scripts/install-test\.sh|\.github/workflows/helm-install-test\.yml|charts/lerian-common/)' <<< "$changed"; then + echo "charts=$(all_app_charts)" >> "$GITHUB_OUTPUT"; exit 0 + fi + scope="" + for c in $(awk -F/ '/^charts\/[^/]+\// {print $2}' <<< "$changed" | sort -u); do + [[ -d "charts/$c" ]] || continue # skip deleted charts + grep -qiE '^type:[[:space:]]*library([[:space:]]|$)' "charts/$c/Chart.yaml" && continue + scope="${scope:+$scope }$c" + done + echo "charts=$scope" >> "$GITHUB_OUTPUT" + + - name: Install + upgrade each chart + shell: bash + env: + CHARTS: ${{ steps.scope.outputs.charts }} + run: | + set -uo pipefail + if [[ -z "${CHARTS// }" ]]; then + echo "No installable charts in scope — nothing to test."; exit 0 + fi + echo "Charts in scope: $CHARTS" + fails="" + for c in $CHARTS; do + echo "::group::install-test $c" + if bash .github/scripts/install-test.sh "charts/$c"; then + echo "::endgroup::" + else + echo "::endgroup::"; fails="${fails:+$fails }$c" + fi + done + if [[ -n "$fails" ]]; then + echo "::error::Install/upgrade test failed for: $fails" + exit 1 + fi + echo "All charts in scope installed and upgraded cleanly." From a3ddc3423a63bfa892628f6a0158846428bbc797 Mon Sep 17 00:00:00 2001 From: Guilherme Moreira Rodrigues Date: Tue, 4 Aug 2026 18:28:26 -0300 Subject: [PATCH 2/6] ci: robust namespace handling + allow-failure list for install test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Chart-pinned namespaces: many Lerian charts render resources into a namespace they pin themselves (namespaceOverride / global.namespace), and a few span more than one. The script now creates EVERY namespace the render references (not just `-n`) and counts objects across all of them, so charts like go-boilerplate-ddd (single pinned ns) and product-console (release ns + a fixed one) install cleanly. Allow-failure list (.github/configs/helm-install-test-allow-failure.txt): two charts have a known failure in a bare kind cluster — tracer (a real bug: its PDB sets both minAvailable and maxUnavailable, which the API server rejects) and reporter (needs external KEDA CRDs). They are reported as warnings so the gate can land and protect the other 17 app charts + every future change; each is documented to be fixed and removed. 17/19 app charts install + upgrade cleanly. --- .../helm-install-test-allow-failure.txt | 9 +++ .github/scripts/install-test.sh | 56 +++++++++++-------- .github/workflows/helm-install-test.yml | 15 ++++- 3 files changed, 54 insertions(+), 26 deletions(-) create mode 100644 .github/configs/helm-install-test-allow-failure.txt diff --git a/.github/configs/helm-install-test-allow-failure.txt b/.github/configs/helm-install-test-allow-failure.txt new file mode 100644 index 000000000..3ab5adcf5 --- /dev/null +++ b/.github/configs/helm-install-test-allow-failure.txt @@ -0,0 +1,9 @@ +# Charts with a KNOWN install/upgrade failure in a bare, credential-less kind +# cluster. The install-test gate reports these as a warning instead of failing, +# so the gate can protect the other charts and all future changes. Remove a chart +# from this list once its issue is fixed — a clean chart must stay clean. +# +# Format: # reason +# +tracer # PodDisruptionBudget sets BOTH minAvailable and maxUnavailable — the API server rejects it ("cannot be both set"). Real chart bug: the PDB template/values must emit only one. +reporter # References external KEDA custom resources (keda.sh/v1alpha1: ScaledObject / ScaledJob / TriggerAuthentication) whose CRDs are not present in a bare kind cluster. Needs the KEDA CRDs pre-installed (a future enhancement) or the chart guarded behind a toggle. diff --git a/.github/scripts/install-test.sh b/.github/scripts/install-test.sh index d46282447..c04e7939e 100755 --- a/.github/scripts/install-test.sh +++ b/.github/scripts/install-test.sh @@ -14,9 +14,9 @@ CHART="$(basename "$CHART_DIR")" VALUES_FILE="${2:-}" [[ -z "$VALUES_FILE" && -f ".github/configs/helm-render-values/${CHART}.yaml" ]] \ && VALUES_FILE=".github/configs/helm-render-values/${CHART}.yaml" -NS="it-${CHART}" -NSB="it-${CHART}-base" # isolated namespace for the origin/main -> PR baseline upgrade REL="$CHART" +NS="it-${CHART}" # release namespace (helm -n); also created +TARGETS="" # every distinct namespace the chart's manifests reference (created too) TIMEOUT="${IT_TIMEOUT:-180s}" VARGS=() [[ -n "$VALUES_FILE" && -f "$VALUES_FILE" ]] && { VARGS=(-f "$VALUES_FILE"); echo " values: $VALUES_FILE"; } @@ -34,47 +34,57 @@ fi fail() { echo "::error::[$CHART] $1"; kubectl get events -n "$NS" --sort-by=.lastTimestamp 2>/dev/null | tail -15; cleanup; exit 1; } cleanup() { helm uninstall "$REL" -n "$NS" >/dev/null 2>&1 || true - helm uninstall "${REL}-base" -n "$NSB" >/dev/null 2>&1 || true - kubectl delete ns "$NS" "$NSB" --wait=false >/dev/null 2>&1 || true + helm uninstall "${REL}-base" -n "$NS" >/dev/null 2>&1 || true + for x in "$NS" $TARGETS; do kubectl delete ns "$x" --wait=false >/dev/null 2>&1 || true; done } - -cleanup # idempotent: clear any stale release/namespace from a prior aborted run +# (Re)create the release namespace + every namespace the chart pins, then install. +do_install() { # + for x in "$NS" $TARGETS; do kubectl create ns "$x" >/dev/null 2>&1 || true; done + helm install "$1" "$2" ${VARGS[@]+"${VARGS[@]}"} "${HOOKS[@]}" -n "$NS" --timeout "$TIMEOUT" 2>&1 +} +deployed() { [[ "$(helm status "$1" -n "$NS" -o json 2>/dev/null | tr -d ' \n' | grep -o '"status":"[a-z]*"' | head -1)" == '"status":"deployed"' ]]; } echo "===== [$CHART] dependency build =====" helm dependency build "$CHART_DIR" >/dev/null 2>&1 || fail "helm dependency build failed" +# Lerian charts pin their own namespaces (namespaceOverride / global.namespace), so +# resources land there regardless of `-n` — and some charts even span MORE than one +# (most in the release ns, a few in a fixed one). Create EVERY namespace the render +# references instead of fighting it; the release itself lives in $NS. +TARGETS="$(helm template "$REL" "$CHART_DIR" ${VARGS[@]+"${VARGS[@]}"} "${HOOKS[@]}" -n "$NS" 2>/dev/null \ + | awk '/^ namespace:/{gsub(/"/,"",$2); print $2}' | awk 'NF' | sort -u | grep -vxF "$NS" | tr '\n' ' ')" +echo " namespaces: $NS${TARGETS:+ + $TARGETS}" + +cleanup # idempotent: clear any stale release/namespace from a prior aborted run + # ---- 1. Fresh install of the PR chart (server-side manifest validation) ---- echo "===== [$CHART] install (PR) =====" -if ! helm install "$REL" "$CHART_DIR" ${VARGS[@]+"${VARGS[@]}"} "${HOOKS[@]}" -n "$NS" --create-namespace --timeout "$TIMEOUT" 2>&1; then - fail "helm install failed (invalid manifest / admission / hook)" -fi -[[ "$(helm status "$REL" -n "$NS" -o json | grep -o '"status":"[a-z]*"' | head -1)" == '"status":"deployed"' ]] \ - || fail "release not in deployed state" -n=$(kubectl get all -n "$NS" --no-headers 2>/dev/null | wc -l | tr -d ' ') +do_install "$REL" "$CHART_DIR" || fail "helm install failed (invalid manifest / admission / hook)" +deployed "$REL" || fail "release not in deployed state" +n=0; for x in "$NS" $TARGETS; do n=$((n + $(kubectl get all -n "$x" --no-headers 2>/dev/null | wc -l))); done echo " created $n objects" [[ "$n" -gt 0 ]] || fail "install produced no objects" # ---- 2. Upgrade the PR chart in place (upgrade code path + hook re-run) ---- echo "===== [$CHART] upgrade (PR -> PR, benign change) =====" helm upgrade "$REL" "$CHART_DIR" ${VARGS[@]+"${VARGS[@]}"} "${HOOKS[@]}" -n "$NS" --timeout "$TIMEOUT" \ - --set-string podAnnotations.helm-install-test="$(date +%s 2>/dev/null || echo x)" 2>&1 \ - | grep -qE 'STATUS: deployed' || fail "in-place upgrade failed" + --set-string podAnnotations.helm-install-test="upgrade" >/dev/null 2>&1 && deployed "$REL" \ + || fail "in-place upgrade failed" -# Free the PR release before the baseline test so only ONE release is ever -# installed at a time — two full installs of a subchart-heavy chart exhaust a -# single-node kind cluster's memory and make the baseline flaky. -helm uninstall "$REL" -n "$NS" >/dev/null 2>&1 || true -kubectl delete ns "$NS" --wait=false >/dev/null 2>&1 || true +# Free the PR release before the baseline so only ONE release is ever installed at +# a time — two full installs of a subchart-heavy chart exhaust a single-node kind +# cluster's memory. The baseline reuses the same (chart-pinned) namespace. +cleanup # ---- 3. Real upgrade path: base (origin/main) -> PR, when the chart exists on main ---- if git cat-file -e "origin/main:charts/${CHART}/Chart.yaml" 2>/dev/null; then echo "===== [$CHART] upgrade (origin/main -> PR) =====" BASE_DIR="$(mktemp -d)/$CHART"; mkdir -p "$BASE_DIR" git archive "origin/main" "charts/${CHART}" | tar -x --strip-components=2 -C "$BASE_DIR" 2>/dev/null - helm dependency build "$BASE_DIR" >/dev/null 2>&1 || echo " (base dep build failed — skipping baseline upgrade)" - if helm install "${REL}-base" "$BASE_DIR" ${VARGS[@]+"${VARGS[@]}"} "${HOOKS[@]}" -n "$NSB" --create-namespace --timeout "$TIMEOUT" >/dev/null 2>&1; then - helm upgrade "${REL}-base" "$CHART_DIR" ${VARGS[@]+"${VARGS[@]}"} "${HOOKS[@]}" -n "$NSB" --timeout "$TIMEOUT" 2>&1 \ - | grep -qE 'STATUS: deployed' || fail "upgrade from origin/main failed (immutable-field break?)" + helm dependency build "$BASE_DIR" >/dev/null 2>&1 || echo " (base dep build failed — skipping baseline)" + if do_install "${REL}-base" "$BASE_DIR" >/dev/null 2>&1 && deployed "${REL}-base"; then + helm upgrade "${REL}-base" "$CHART_DIR" ${VARGS[@]+"${VARGS[@]}"} "${HOOKS[@]}" -n "$NS" --timeout "$TIMEOUT" >/dev/null 2>&1 \ + && deployed "${REL}-base" || fail "upgrade from origin/main failed (immutable-field break?)" echo " origin/main -> PR upgrade OK" else echo " (baseline install failed — likely unrelated to this PR; skipping)" diff --git a/.github/workflows/helm-install-test.yml b/.github/workflows/helm-install-test.yml index 647477c67..33ea8135c 100644 --- a/.github/workflows/helm-install-test.yml +++ b/.github/workflows/helm-install-test.yml @@ -90,18 +90,27 @@ jobs: if [[ -z "${CHARTS// }" ]]; then echo "No installable charts in scope — nothing to test."; exit 0 fi + # Charts with a KNOWN pre-existing install/upgrade defect (documented in the + # allow-failure file) are reported but don't fail the gate — same baseline + # pattern the chart-standard check uses. A PR that touches such a chart should + # remove it from the file once fixed. New/changed clean charts must still pass. + ALLOW="$(grep -vE '^\s*(#|$)' .github/configs/helm-install-test-allow-failure.txt 2>/dev/null | awk '{print $1}' || true)" echo "Charts in scope: $CHARTS" - fails="" + fails=""; allowed_fails="" for c in $CHARTS; do echo "::group::install-test $c" if bash .github/scripts/install-test.sh "charts/$c"; then echo "::endgroup::" + elif grep -qxF "$c" <<< "$ALLOW"; then + echo "::endgroup::"; echo "::warning::[$c] install test failed but is allow-listed (known pre-existing defect)" + allowed_fails="${allowed_fails:+$allowed_fails }$c" else echo "::endgroup::"; fails="${fails:+$fails }$c" fi done + [[ -n "$allowed_fails" ]] && echo "Allow-listed failures (not blocking):$allowed_fails" if [[ -n "$fails" ]]; then - echo "::error::Install/upgrade test failed for: $fails" + echo "::error::Install/upgrade test failed for:$fails" exit 1 fi - echo "All charts in scope installed and upgraded cleanly." + echo "All charts in scope installed and upgraded cleanly (allow-listed excepted)." From 2c8af1689bf00931f5870d2ffe22ef5c81edc532 Mon Sep 17 00:00:00 2001 From: Guilherme Moreira Rodrigues Date: Tue, 4 Aug 2026 18:34:38 -0300 Subject: [PATCH 3/6] ci: register HTTP dependency repos before install-test dependency build MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On a fresh CI runner (no local cache) `helm dependency build` fails for charts with HTTP subchart repos (bitnami/keda/seaweedfs/valkey/groundhog2k) because the repos aren't registered — every subchart-heavy chart errored with "dependency build failed". Mirror the render-gate's Go tool: parse each `repository: https://…` from Chart.yaml and `helm repo add` it (oci:// and file:// don't need this) before building. Verified in a cache-cleared local run (fetcher: repos added → build → install 7 objects). --- .github/scripts/install-test.sh | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/scripts/install-test.sh b/.github/scripts/install-test.sh index c04e7939e..ff0e44583 100755 --- a/.github/scripts/install-test.sh +++ b/.github/scripts/install-test.sh @@ -45,6 +45,16 @@ do_install() { # deployed() { [[ "$(helm status "$1" -n "$NS" -o json 2>/dev/null | tr -d ' \n' | grep -o '"status":"[a-z]*"' | head -1)" == '"status":"deployed"' ]]; } echo "===== [$CHART] dependency build =====" +# `helm dependency build` needs every HTTP dependency repo registered first +# (oci:// and file:// deps don't). Mirror what the render-gate's Go tool does: +# register each `repository: https://…` from Chart.yaml before building. +depn=0 +while IFS= read -r repo_url; do + [[ -z "$repo_url" ]] && continue + helm repo add "dep${depn}" "$repo_url" >/dev/null 2>&1 || true + depn=$((depn + 1)) +done < <(grep -E 'repository:[[:space:]]*"?https?://' "$CHART_DIR/Chart.yaml" | grep -Eo 'https?://[^"[:space:]]+' | sort -u) +[[ "$depn" -gt 0 ]] && helm repo update >/dev/null 2>&1 helm dependency build "$CHART_DIR" >/dev/null 2>&1 || fail "helm dependency build failed" # Lerian charts pin their own namespaces (namespaceOverride / global.namespace), so From 2da0a47bc70e24d117d12869620a8a0a9e7b67b4 Mon Sep 17 00:00:00 2001 From: Guilherme Moreira Rodrigues Date: Tue, 4 Aug 2026 18:42:59 -0300 Subject: [PATCH 4/6] ci: don't force a value change on the in-place upgrade MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The in-place upgrade injected a podAnnotations key to force a diff, but charts with a strict root-closed schema (br-sfn, br-consignado-gw, br-sisbajud) reject an unknown top-level key — the upgrade failed on schema validation, not a real defect. A no-change `helm upgrade` still re-renders and re-applies (new revision, STATUS deployed), which is what we want to exercise; value-change-driven immutable breaks are already covered by the origin/main -> PR baseline. Verified all four charts install + upgrade cleanly in a cache-cleared local run. --- .github/scripts/install-test.sh | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/scripts/install-test.sh b/.github/scripts/install-test.sh index ff0e44583..d3c7ff021 100755 --- a/.github/scripts/install-test.sh +++ b/.github/scripts/install-test.sh @@ -75,11 +75,13 @@ n=0; for x in "$NS" $TARGETS; do n=$((n + $(kubectl get all -n "$x" --no-headers echo " created $n objects" [[ "$n" -gt 0 ]] || fail "install produced no objects" -# ---- 2. Upgrade the PR chart in place (upgrade code path + hook re-run) ---- -echo "===== [$CHART] upgrade (PR -> PR, benign change) =====" -helm upgrade "$REL" "$CHART_DIR" ${VARGS[@]+"${VARGS[@]}"} "${HOOKS[@]}" -n "$NS" --timeout "$TIMEOUT" \ - --set-string podAnnotations.helm-install-test="upgrade" >/dev/null 2>&1 && deployed "$REL" \ - || fail "in-place upgrade failed" +# ---- 2. Upgrade the PR chart in place (upgrade code path) ---- +# Same values on purpose: a no-change upgrade still re-renders and re-applies +# (new revision, STATUS deployed). Forcing a value change is unsafe — a strict +# root-closed schema (e.g. br-sfn) rejects an injected podAnnotations key. +echo "===== [$CHART] upgrade (PR -> PR) =====" +helm upgrade "$REL" "$CHART_DIR" ${VARGS[@]+"${VARGS[@]}"} "${HOOKS[@]}" -n "$NS" --timeout "$TIMEOUT" >/dev/null 2>&1 \ + && deployed "$REL" || fail "in-place upgrade failed" # Free the PR release before the baseline so only ONE release is ever installed at # a time — two full installs of a subchart-heavy chart exhaust a single-node kind From faf017546fdd01992c7504c69139a82dfaaf831a Mon Sep 17 00:00:00 2001 From: Guilherme Moreira Rodrigues Date: Tue, 4 Aug 2026 18:48:49 -0300 Subject: [PATCH 5/6] ci: retry dependency build via update + surface the real error on failure --- .github/scripts/install-test.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/scripts/install-test.sh b/.github/scripts/install-test.sh index d3c7ff021..b5e3c5354 100755 --- a/.github/scripts/install-test.sh +++ b/.github/scripts/install-test.sh @@ -55,7 +55,11 @@ while IFS= read -r repo_url; do depn=$((depn + 1)) done < <(grep -E 'repository:[[:space:]]*"?https?://' "$CHART_DIR/Chart.yaml" | grep -Eo 'https?://[^"[:space:]]+' | sort -u) [[ "$depn" -gt 0 ]] && helm repo update >/dev/null 2>&1 -helm dependency build "$CHART_DIR" >/dev/null 2>&1 || fail "helm dependency build failed" +# Build with one retry (dependency fetches are network-flaky) and surface the real +# error on final failure instead of a bare "build failed". +db_out="$(helm dependency build "$CHART_DIR" 2>&1)" \ + || db_out="$(helm dependency update "$CHART_DIR" 2>&1)" \ + || { echo "$db_out" | tail -8 | sed 's/^/ /'; fail "helm dependency build failed"; } # Lerian charts pin their own namespaces (namespaceOverride / global.namespace), so # resources land there regardless of `-n` — and some charts even span MORE than one From 873dd09821f62da027c528958fdb42f60eed23dc Mon Sep 17 00:00:00 2001 From: Guilherme Moreira Rodrigues Date: Tue, 4 Aug 2026 18:54:51 -0300 Subject: [PATCH 6/6] =?UTF-8?q?ci:=20allow-list=20otel-collector-lerian=20?= =?UTF-8?q?(duplicate=20GOMEMLIMIT=20env=20=E2=80=94=20real=20bug=20found?= =?UTF-8?q?=20by=20the=20gate)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/configs/helm-install-test-allow-failure.txt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/configs/helm-install-test-allow-failure.txt b/.github/configs/helm-install-test-allow-failure.txt index 3ab5adcf5..ae390c14d 100644 --- a/.github/configs/helm-install-test-allow-failure.txt +++ b/.github/configs/helm-install-test-allow-failure.txt @@ -5,5 +5,6 @@ # # Format: # reason # -tracer # PodDisruptionBudget sets BOTH minAvailable and maxUnavailable — the API server rejects it ("cannot be both set"). Real chart bug: the PDB template/values must emit only one. -reporter # References external KEDA custom resources (keda.sh/v1alpha1: ScaledObject / ScaledJob / TriggerAuthentication) whose CRDs are not present in a bare kind cluster. Needs the KEDA CRDs pre-installed (a future enhancement) or the chart guarded behind a toggle. +tracer # PodDisruptionBudget sets BOTH minAvailable and maxUnavailable — the API server rejects it ("cannot be both set"). Real chart bug: the PDB template/values must emit only one. +reporter # References external KEDA custom resources (keda.sh/v1alpha1: ScaledObject / ScaledJob / TriggerAuthentication) whose CRDs are not present in a bare kind cluster. Needs the KEDA CRDs pre-installed (a future enhancement) or the chart guarded behind a toggle. +otel-collector-lerian # DaemonSet renders a DUPLICATE env var GOMEMLIMIT — server-side apply rejects it ("duplicate entries for key GOMEMLIMIT"). Real chart bug: drop the duplicate (likely set in both the chart values and the opentelemetry-collector subchart).