chore(pipe): add kind install + upgrade test for Helm charts - #1833
chore(pipe): add kind install + upgrade test for Helm charts#1833guimoreirar wants to merge 6 commits into
Conversation
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.
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThe pull request adds a GitHub Actions workflow and shell script for Helm chart integration tests. The tests run in kind, validate installs and upgrades, and optionally upgrade the ChangesHelm install testing
Sequence Diagram(s)sequenceDiagram
participant GitHubActions
participant KindCluster
participant InstallTest
participant Helm
participant Kubernetes
GitHubActions->>KindCluster: Create and configure kind cluster
GitHubActions->>InstallTest: Run selected chart test
InstallTest->>Helm: Build dependencies
InstallTest->>Kubernetes: Create referenced namespaces
InstallTest->>Helm: Install PR chart
Helm->>Kubernetes: Create chart resources
InstallTest->>Kubernetes: Validate deployment and objects
InstallTest->>Helm: Upgrade PR release
InstallTest->>Helm: Install origin/main baseline when available
InstallTest->>Helm: Upgrade baseline release to PR chart
InstallTest-->>GitHubActions: Return test status
Possibly related PRs
✨ Finishing Touches✨ Simplify code
Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/scripts/install-test.sh:
- Around line 35-39: Update the cleanup function’s both helm uninstall commands
to wait for completion using --wait --cascade=foreground --timeout "$TIMEOUT",
and remove --wait=false from the kubectl namespace deletion so namespace cleanup
waits synchronously before the next install.
- Around line 72-80: Update the baseline setup around BASE_DIR and the
`${REL}-base` Helm install to source `VARGS` from the archived `origin/main`
chart rather than the PR workspace, and make dependency-build or
baseline-install failures call `fail` instead of being skipped. Preserve the
existing skip behavior only when the earlier chart-existence check at line 70
confirms the chart is absent from `origin/main`.
In @.github/workflows/helm-install-test.yml:
- Around line 77-82: The chart-selection loop should also recognize changes
under .github/configs/helm-render-values/<chart>.yaml and add the corresponding
chart to scope, while preserving existing chart-path handling and
library/deleted-chart filtering. Update the logic around the changed-path
extraction before writing the charts GitHub output so render-values-only changes
select the chart consumed by install-test.sh.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: f9438d4c-36c8-42ba-9658-86ebee6f5e1d
📒 Files selected for processing (2)
.github/scripts/install-test.sh.github/workflows/helm-install-test.yml
| 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 | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -eu
printf '%s\n' '--- .github/scripts/install-test.sh ---'
nl -ba .github/scripts/install-test.sh | sed -n '1,130p'
printf '%s\n' '--- related Helm/Kubernetes commands ---'
rg -n -C 3 'helm (install|uninstall)|kubectl delete ns|cleanup|trap|NSB?|REL' .github/scripts/install-test.shRepository: LerianStudio/helm
Length of output: 4049
🏁 Script executed:
#!/bin/bash
set -eu
printf '%s\n' '--- script and callers ---'
sed -n '1,110p' .github/scripts/install-test.sh
rg -n -C 4 'install-test\.sh|install_test|CHARTS|charts/' .github . 2>/dev/null | head -240
printf '%s\n' '--- available workflow/script files ---'
git ls-files '.github' | grep -E '(^|/)([^/]+\.ya?ml|[^/]+\.sh)$' | head -200Repository: LerianStudio/helm
Length of output: 21927
🏁 Script executed:
#!/bin/bash
set -eu
python3 - <<'PY'
from pathlib import Path
import re
script = Path(".github/scripts/install-test.sh").read_text()
workflow = Path(".github/workflows/helm-install-test.yml").read_text()
checks = {
"cleanup namespace deletion is async":
bool(re.search(r'kubectl delete ns "\$NS" "\$NSB" --wait=false', script)),
"baseline starts after async PR namespace deletion":
bool(re.search(
r'helm uninstall "\$REL" -n "\$NS".*?'
r'kubectl delete ns "\$NS" --wait=false.*?'
r'helm install "\$\{REL\}-base"',
script, re.S)),
"final cleanup is called":
script.count("cleanup") >= 3,
"workflow invokes script in a sequential loop":
bool(re.search(
r'for c in \$CHARTS;.*?bash \.github/scripts/install-test\.sh "charts/\$c"',
workflow, re.S)),
}
for name, result in checks.items():
print(f"{name}: {'yes' if result else 'no'}")
PYRepository: LerianStudio/helm
Length of output: 330
🌐 Web query:
kubectl delete --wait=false behavior Helm uninstall --wait default official documentation
💡 Result:
The behavior of kubectl delete and helm uninstall regarding the --wait flag differs in its default state and mechanism. kubectl delete The --wait flag defaults to true [1][2]. When enabled, kubectl delete waits for all resources specified to be removed from the API (including waiting for finalizers to complete) before returning [1][2]. To prevent this blocking behavior and return immediately after the delete request is acknowledged by the API, you must explicitly pass --wait=false [3][2]. helm uninstall The --wait flag for helm uninstall behaves differently [4][5]. Unlike kubectl, the default behavior when the --wait flag is omitted is not to wait for full resource deletion; it instead relies on its internal "hookOnly" strategy [4][5]. When --wait is explicitly provided, it triggers a "watcher" strategy that monitors resources until they are ready or deleted, up to the defined --timeout [4][5]. Crucially, because Helm defaults to "background" cascade strategy, simply adding --wait may not be sufficient to ensure all resources—especially those with finalizers—are fully cleaned up before the command returns [4][5]. Official Helm documentation recommends using the flag combination --cascade foreground --wait to ensure resources with finalizers are fully deleted before the command exits [4][5]. If a user encounters issues where resources persist after an uninstall, this combination is the standard workaround [6].
Citations:
- 1: https://kubernetes.io/docs/reference/kubectl/generated/kubectl_delete/
- 2: https://dokk.org/manpages/debian/12/kubernetes-client/kubectl-delete.1.en
- 3: kubectl delete should wait for resource to be deleted before returning kubernetes/kubernetes#42594
- 4: https://github.com/helm/helm-www/blob/main/docs/helm/helm_uninstall.md
- 5: https://helm.sh/docs/helm/helm_uninstall
- 6: helm uninstall --wait misses some resources helm/helm#31651
Wait for Helm and namespace deletion before the next install.
Add --wait --cascade=foreground --timeout "$TIMEOUT" to both helm uninstall commands, and remove --wait=false from both namespace deletions. Otherwise, the baseline install and the next chart can run while previous workloads still consume kind-node capacity, causing flaky or out-of-memory test runs.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/scripts/install-test.sh around lines 35 - 39, Update the cleanup
function’s both helm uninstall commands to wait for completion using --wait
--cascade=foreground --timeout "$TIMEOUT", and remove --wait=false from the
kubectl namespace deletion so namespace cleanup waits synchronously before the
next install.
| 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)" |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -e
printf '%s\n' '--- .github/scripts/install-test.sh ---'
cat -n .github/scripts/install-test.sh | sed -n '1,110p'
printf '%s\n' '--- related references ---'
rg -n -C 3 'VARGS|origin/main|baseline|git archive|helm dependency build|helm install|helm upgrade' .github/scripts .github 2>/dev/null | head -n 240Repository: LerianStudio/helm
Length of output: 24975
🏁 Script executed:
#!/bin/bash
set -e
printf '%s\n' '--- workflow invocation ---'
rg -n -C 5 'install-test\.sh|helm-render-values|values-file|values' .github/workflows .github/scripts 2>/dev/null | head -n 220
printf '%s\n' '--- chart and render-value files ---'
git ls-files 'charts/*/Chart.yaml' '.github/configs/helm-render-values/*' | head -n 160
printf '%s\n' '--- origin/main chart tree for a representative comparison ---'
git ls-tree --name-only origin/main:charts 2>/dev/null | head -n 80
printf '%s\n' '--- relevant branch/ref state ---'
git branch --show-current
git show-ref --verify --quiet refs/remotes/origin/main && git rev-parse --short refs/remotes/origin/main || trueRepository: LerianStudio/helm
Length of output: 19700
🏁 Script executed:
#!/bin/bash
set -e
python3 - <<'PY'
from pathlib import Path
p = Path(".github/scripts/install-test.sh")
text = p.read_text()
section = text.split("# ---- 3. Real upgrade path", 1)[1].split('echo "===== [$CHART] OK ====="', 1)[0]
assert 'helm dependency build "$BASE_DIR" >/dev/null 2>&1 || echo' in section
assert 'if helm install "${REL}-base" "$BASE_DIR"' in section
assert ' else\n echo " (baseline install failed — likely unrelated to this PR; skipping)"' in section
assert ' echo " (new chart — not on origin/main; skipping baseline upgrade)"' in section
print("source checks: baseline dependency failure is non-fatal; baseline install failure is a skip; chart absence is the separate skip path")
PY
tmp_script="$(mktemp)"
cat >"$tmp_script" <<'BASH'
set -uo pipefail
CHART=demo
CHART_DIR=charts/demo
REL=demo
NSB=it-demo-base
TIMEOUT=180s
VARGS=(-f .github/configs/helm-render-values/demo.yaml)
HOOKS=(--no-hooks)
fail() { printf '%s\n' "FAIL: $1"; exit 1; }
git() {
if [[ "$1" == cat-file ]]; then return 0; fi
return 1
}
helm() {
case "$2" in
dependency) return 1 ;;
install) return 1 ;;
upgrade) printf '%s\n' 'STATUS: deployed'; return 0 ;;
esac
}
if git cat-file -e "origin/main:charts/${CHART}/Chart.yaml" 2>/dev/null; then
BASE_DIR="$(mktemp -d)/$CHART"; mkdir -p "$BASE_DIR"
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 ====="
BASH
output="$(bash "$tmp_script")"
rm -f "$tmp_script"
printf '%s\n' '--- mocked baseline-failure execution ---'
printf '%s\n' "$output"
grep -q 'baseline install failed' <<<"$output"
grep -q '===== \[demo\] OK =====' <<<"$output"
! grep -q 'origin/main -> PR upgrade OK' <<<"$output"
printf '%s\n' 'behavior check: baseline install failure reaches success output without running the upgrade'Repository: LerianStudio/helm
Length of output: 288
🏁 Script executed:
#!/bin/bash
set -e
tmp_script="$(mktemp)"
cat >"$tmp_script" <<'BASH'
set -uo pipefail
CHART=demo
CHART_DIR=charts/demo
REL=demo
NSB=it-demo-base
TIMEOUT=180s
VARGS=(-f .github/configs/helm-render-values/demo.yaml)
HOOKS=(--no-hooks)
fail() { printf '%s\n' "FAIL: $1"; exit 1; }
git() {
if [[ "$1" == cat-file ]]; then return 0; fi
return 1
}
helm() {
case "$1" in
dependency) return 1 ;;
install) return 1 ;;
upgrade) printf '%s\n' 'STATUS: deployed'; return 0 ;;
*) return 1 ;;
esac
}
if git cat-file -e "origin/main:charts/${CHART}/Chart.yaml" 2>/dev/null; then
BASE_DIR="$(mktemp -d)/$CHART"; mkdir -p "$BASE_DIR"
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 ====="
BASH
output="$(bash "$tmp_script")"
status=$?
rm -f "$tmp_script"
printf '%s\n' '--- corrected mocked baseline-failure execution ---'
printf '%s\n' "$output"
test "$status" -eq 0
grep -q 'base dep build failed' <<<"$output"
grep -q 'baseline install failed' <<<"$output"
grep -q '===== \[demo\] OK =====' <<<"$output"
! grep -q 'origin/main -> PR upgrade OK' <<<"$output"
printf '%s\n' 'behavior check: baseline install failure reaches success output without running the upgrade'Repository: LerianStudio/helm
Length of output: 443
Fail when the baseline upgrade does not run.
Lines 74–80 ignore baseline dependency-build failures and treat every baseline-install failure as a successful skip. VARGS comes from the PR workspace, so PR-only values changes can make origin/main fail to install and bypass the baseline-to-PR upgrade test.
Use values from origin/main for the baseline install. Fail when baseline setup fails. Skip only when Line 70 proves that the chart does not exist on origin/main.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/scripts/install-test.sh around lines 72 - 80, Update the baseline
setup around BASE_DIR and the `${REL}-base` Helm install to source `VARGS` from
the archived `origin/main` chart rather than the PR workspace, and make
dependency-build or baseline-install failures call `fail` instead of being
skipped. Preserve the existing skip behavior only when the earlier
chart-existence check at line 70 confirms the chart is absent from
`origin/main`.
| 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" |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Select charts for render-values changes.
A pull request that changes only .github/configs/helm-render-values/<chart>.yaml triggers this workflow at Line 17. Lines 77-81 only extract charts/<chart>/... paths, so Line 82 emits an empty scope and the job succeeds without testing the changed input. .github/scripts/install-test.sh consumes this values file for the matching chart.
Map render-values filenames to their chart, or include render-values changes in the full-chart scope.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/helm-install-test.yml around lines 77 - 82, The
chart-selection loop should also recognize changes under
.github/configs/helm-render-values/<chart>.yaml and add the corresponding chart
to scope, while preserving existing chart-path handling and
library/deleted-chart filtering. Update the logic around the changed-path
extraction before writing the charts GitHub output so render-values-only changes
select the chart consumed by install-test.sh.
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.
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).
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/scripts/install-test.sh:
- Around line 52-56: Update the dependency repository extraction before the helm
repo add loop to parse Chart.yaml as YAML, or otherwise support plain,
double-quoted, and single-quoted repository scalars. Ensure every valid HTTP(S)
repository URL is emitted uniquely so helm repo add registers it before helm
dependency build.
- Around line 51-57: Update the Helm repository setup around the depn loop to
prevent cross-chart repository-name collisions by using an isolated Helm
repository configuration for each chart or registering repositories with
--force-update. Remove the unconditional || true so helm repo add failures are
surfaced, while preserving repository deduplication and the conditional helm
repo update behavior.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 3a45b530-74de-4b4a-a96a-d94666b14b88
📒 Files selected for processing (1)
.github/scripts/install-test.sh
| 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 |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
rg -n -C 4 \
'HELM_(CONFIG_HOME|REPOSITORY_CONFIG|REPOSITORY_CACHE)|helm repo (add|remove)|install-test\.sh|for c in \$CHARTS' \
.github/scripts/install-test.sh .github/workflows/helm-install-test.yml || trueRepository: LerianStudio/helm
Length of output: 3739
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
printf '%s\n' '--- install-test.sh ---'
cat -n .github/scripts/install-test.sh | sed -n '1,90p'
printf '%s\n' '--- workflow execution context ---'
cat -n .github/workflows/helm-install-test.yml | sed -n '80,120p'
printf '%s\n' '--- repository declarations ---'
rg -n -g 'Chart.yaml' '^[[:space:]]*repository:' charts .github || true
printf '%s\n' '--- Helm availability and relevant configuration ---'
command -v helm || true
rg -n 'HELM_(CONFIG_HOME|REPOSITORY_CONFIG|REPOSITORY_CACHE)|XDG_CONFIG_HOME|XDG_CACHE_HOME' .github charts || trueRepository: LerianStudio/helm
Length of output: 11714
🌐 Web query:
Helm 3 repo add same name existing repository force-update dependency build repository URL behavior
💡 Result:
In Helm 3, attempting to add a repository using a name that is already configured will result in an error [1][2][3]. This behavior was introduced as a security measure in Helm 3.3.2 to prevent accidental or unauthorized overwriting of existing repository configurations [1][2][4]. If you need to update the URL for an existing repository, you must explicitly use the --force-update flag [5][2][3]: helm repo add [NAME] [URL] --force-update When --force-update is used, Helm will overwrite the existing repository configuration for that name with the new URL provided [3][4]. If you attempt to add a repository with the same name and the exact same URL, recent versions of Helm treat this as a no-op (no operation) and exit with a success code, making the command idempotent [4]. Regarding dependency management: 1. The helm dependency build command requires that all repositories containing dependencies be manually added to your local Helm configuration beforehand [6][7][8]. 2. If a required repository is not in your local list, helm dependency build will fail with an error [8][9]. It does not automatically add repositories that are missing from your configuration [6][8]. 3. For this reason, in automated environments or CI/CD pipelines, you should ensure that the necessary repositories are added using helm repo add (with --force-update if you need to ensure the URL is correct) before running helm dependency build [3][4][8].
Citations:
- 1: helm repo add now fails in v3.3.2 and v3.3.3 helm/helm#8771
- 2: https://helm.sh/docs/v3/faq/troubleshooting/
- 3: https://devopsil.com/articles/2026-04-01-helm-error-repository-name-already-exists-complete-fix-guide
- 4: Fixing issue with idempotent repo add helm/helm#8777
- 5: https://helm.sh/docs/helm/helm_repo_add/
- 6: Remove support of unadded repositories in helm dep update helm/helm#9903
- 7: https://github.com/helm/helm-www/blob/main/docs/helm/helm_dependency_build.md
- 8: 'helm dep build' requires repositories to be added, but 'helm dep update' doesn't helm/helm#8036
- 9: helm dependecy build: missing repos are not added automatically in presence of Chart.lock helm/helm#9840
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
printf '%s\n' '--- dependency names and HTTP repository order ---'
python3 - <<'PY'
from pathlib import Path
import re
for path in sorted(Path("charts").glob("*/Chart.yaml")):
text = path.read_text()
deps = re.findall(r'(?ms)^\s*-\s*name:\s*([^\n]+).*?^\s*repository:\s*["\']?([^"\'\s]+)', text)
http = sorted({url for _, url in deps if url.startswith(("http://", "https://"))})
if http:
print(f"{path}:")
print(" dependencies:", ", ".join(name.strip().strip('"\\'') for name, _ in deps))
print(" extracted HTTP URLs:", " | ".join(http))
printf '%s\n' '--- chart scope construction ---'
cat -n .github/workflows/helm-install-test.yml | sed -n '45,85p'Repository: LerianStudio/helm
Length of output: 543
🌐 Web query:
site:github.com/helm/helm source dependency build repository URL repositories.yaml chartutil ResolveChartVersion repo name
💡 Result:
In Helm, the chartutil package and the broader dependency management system use specific processes to resolve chart versions and repository URLs, primarily mediated by the repositories.yaml configuration file [1][2][3]. The function ResolveChartVersion (typically found within pkg/downloader/chart_downloader.go) is responsible for resolving a chart reference—often in the format repo_name/chart_name—to a specific download URL [1]. Key aspects of this process include: 1. Repository Configuration: Helm maintains a local list of chart repositories, usually located at ~/.config/helm/repositories.yaml [4]. When resolving a dependency, Helm identifies the target repository by looking up the repo_name alias defined in this file [1][2]. 2. URL Resolution: Once the repository configuration (including its base URL) is retrieved from repositories.yaml, Helm locates the specific chart version by inspecting the repository's index file (typically index.yaml) [1][3]. If the dependency provides an explicit URL in Chart.yaml, Helm may use that to bypass the local repository cache in certain contexts, though security practices generally mandate that repositories be added via helm repo add [4][5]. 3. Dependency Build vs. Update: - When running helm dependency build, Helm relies on the Chart.lock file to ensure reproducible builds [4]. - A common point of confusion is that helm dependency build often fails if the required repository is not already present in the user's local repositories.yaml configuration [6][7][8]. This requirement is enforced for security reasons to prevent unauthorized or unexpected chart sources [7][8][9]. 4. Resolving Conflicts: Recent updates have improved Helm's ability to handle dependencies that share the same name but originate from different repositories by using aliases to disambiguate them [10]. If you encounter "no repository definition" errors, it confirms that the repository URL specified in your Chart.yaml has not been registered in your local Helm environment [6][7]. You must register it using helm repo add <alias> <url> before helm dependency build can successfully locate the chart [6][8].
Citations:
- 1: https://github.com/helm/helm/blob/340b06d8/pkg/downloader/chart_downloader.go
- 2: https://github.com/helm/helm/blob/827a960e/pkg/downloader/manager.go
- 3: https://github.com/helm/helm/blob/340b06d8/pkg/downloader/manager.go
- 4: https://github.com/helm/helm-www/blob/main/docs/helm/helm_dependency.md
- 5: https://github.com/helm/helm/blob/master/internal/resolver/resolver.go
- 6:
helm dependency builddoesn't work with repository url helm/helm#12920 - 7: helm dependecy build: missing repos are not added automatically in presence of Chart.lock helm/helm#9840
- 8: 'helm dep build' requires repositories to be added, but 'helm dep update' doesn't helm/helm#8036
- 9: helm dep update is not working correctly if the repository is not present in the local repo list helm/helm#8118
- 10: Fix multiple dependencies with same name different repo (update) helm/helm#30976
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
printf '%s\n' '--- dependency names and HTTP repository order ---'
python3 - <<'PY'
from pathlib import Path
import re
for path in sorted(Path("charts").glob("*/Chart.yaml")):
text = path.read_text()
deps = re.findall(
r'(?ms)^\s*-\s*name:\s*([^\n]+).*?^\s*repository:\s*["\']?([^"\'\s]+)',
text,
)
http = sorted({url for _, url in deps if url.startswith(("http://", "https://"))})
if http:
print(f"{path}:")
print(" dependencies:", ", ".join(name.strip().strip('"\'') for name, _ in deps))
print(" extracted HTTP URLs:", " | ".join(http))
printf '%s\n' '--- chart scope construction ---'
cat -n .github/workflows/helm-install-test.yml | sed -n '45,85p'Repository: LerianStudio/helm
Length of output: 413
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
printf '%s\n' '--- dependency names and HTTP repository order ---'
python3 - <<'PY'
from pathlib import Path
import re
for path in sorted(Path("charts").glob("*/Chart.yaml")):
text = path.read_text()
deps = re.findall(
r'(?ms)^\s*-\s*name:\s*([^\n]+).*?^\s*repository:\s*["\']?([^"\'\s]+)',
text,
)
http = sorted({url for _, url in deps if url.startswith(("http://", "https://"))})
if http:
names = [name.strip().strip("\"'") for name, _ in deps]
print(f"{path}:")
print(" dependencies:", ", ".join(names))
print(" extracted HTTP URLs:", " | ".join(http))
PY
printf '%s\n' '--- chart scope construction ---'
cat -n .github/workflows/helm-install-test.yml | sed -n '45,85p'
printf '%s\n' '--- Chart.lock repository fields ---'
rg -n -C 2 '^[[:space:]]*repository:' charts -g 'Chart.lock' || true
printf '%s\n' '--- Helm dependency manager source ---'
curl -fsSL https://raw.githubusercontent.com/helm/helm/main/pkg/downloader/manager.go \
| rg -n -C 5 'Repository|repository|ResolveChartVersion|Chart.lock|No repository definition' \
| sed -n '1,240p'Repository: LerianStudio/helm
Length of output: 23260
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
printf '%s\n' '--- Helm build flow and URL matching ---'
curl -fsSL https://raw.githubusercontent.com/helm/helm/main/pkg/downloader/manager.go \
| sed -n '86,240p;458,630p'
printf '%s\n' '--- deterministic sequential repository simulation ---'
python3 - <<'PY'
from pathlib import Path
import re
charts = []
for path in sorted(Path("charts").glob("*/Chart.yaml")):
urls = re.findall(r'repository:\s*"?(https?://[^"\s]+)', path.read_text())
urls = sorted(set(urls))
if urls:
charts.append((str(path), urls))
repo_by_name = {}
for path, urls in charts:
collisions = []
missing = []
for index, url in enumerate(urls):
name = f"dep{index}"
previous = repo_by_name.get(name)
if previous is not None and previous != url:
collisions.append((name, previous, url))
elif previous is None:
repo_by_name[name] = url
configured_urls = set(repo_by_name.values())
missing = [url for url in urls if url not in configured_urls]
print(path)
print(" URLs:", " | ".join(urls))
print(" duplicate-name URL changes:", collisions or "none")
print(" URLs absent after non-forcing adds:", missing or "none")
if missing:
print(" RESULT: dependency build cannot find every repository")
break
PYRepository: LerianStudio/helm
Length of output: 12001
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
printf '%s\n' '--- Helm build flow and URL matching ---'
curl -fsSL https://raw.githubusercontent.com/helm/helm/main/pkg/downloader/manager.go \
| sed -n '86,240p;458,630p'
printf '%s\n' '--- deterministic sequential repository simulation ---'
python3 - <<'PY'
from pathlib import Path
import re
charts = []
for path in sorted(Path("charts").glob("*/Chart.yaml")):
urls = re.findall(r'repository:\s*"?(https?://[^"\s]+)', path.read_text())
urls = sorted(set(urls))
if urls:
charts.append((str(path), urls))
repo_by_name = {}
for path, urls in charts:
collisions = []
for index, url in enumerate(urls):
name = f"dep{index}"
previous = repo_by_name.get(name)
if previous is not None and previous != url:
collisions.append((name, previous, url))
elif previous is None:
repo_by_name[name] = url
configured_urls = set(repo_by_name.values())
missing = [url for url in urls if url not in configured_urls]
print(path)
print(" URLs:", " | ".join(urls))
print(" duplicate-name URL changes:", collisions or "none")
print(" URLs absent after non-forcing adds:", missing or "none")
if missing:
print(" RESULT: dependency build cannot find every repository")
break
PYRepository: LerianStudio/helm
Length of output: 12001
Prevent repository-name collisions across chart runs.
When multiple charts run in one job, depN persists in Helm's repository configuration. On a full-scope run, dep0 points to Bitnami before otel-collector-lerian tries to register its OpenTelemetry repository. helm repo add fails, || true hides the failure, and helm dependency build cannot find the OpenTelemetry repository URL.
Use an isolated Helm repository configuration per chart or add repositories with --force-update. Do not suppress repository-registration failures.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/scripts/install-test.sh around lines 51 - 57, Update the Helm
repository setup around the depn loop to prevent cross-chart repository-name
collisions by using an isolated Helm repository configuration for each chart or
registering repositories with --force-update. Remove the unconditional || true
so helm repo add failures are surfaced, while preserving repository
deduplication and the conditional helm repo update behavior.
Source: MCP tools
| 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) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
rg -n -C 2 "repository:[[:space:]]*'https?://" charts --glob 'Chart.yaml' || true
rg -n -C 2 'repository:' charts --glob 'Chart.yaml' || trueRepository: LerianStudio/helm
Length of output: 10415
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
printf '%s\n' '--- install-test.sh ---'
cat -n .github/scripts/install-test.sh | sed -n '1,90p'
printf '%s\n' '--- repository declarations ---'
rg -n "repository:" charts --glob 'Chart.yaml' | head -80
printf '%s\n' '--- regex behavior probe ---'
python3 - <<'PY'
import re
pattern = re.compile(r'repository:[\s]*"?https?://')
extract = re.compile(r'https?://[^"\s]+')
samples = [
'repository: https://repo.example',
'repository: "https://repo.example"',
"repository: 'https://repo.example'",
]
for sample in samples:
matched = bool(pattern.search(sample))
value = extract.search(sample)
print(f"{sample!r}: line_match={matched}, extracted={value.group(0) if value else None}")
PYRepository: LerianStudio/helm
Length of output: 9614
Parse Chart.yaml as YAML before registering dependency repositories.
The pattern matches plain and double-quoted HTTP(S) URLs but skips valid single-quoted scalars such as repository: 'https://repo.example'. A chart using this form can reach helm dependency build without its repository registered. Use a YAML-aware parser or test all supported scalar forms.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/scripts/install-test.sh around lines 52 - 56, Update the dependency
repository extraction before the helm repo add loop to parse Chart.yaml as YAML,
or otherwise support plain, double-quoted, and single-quoted repository scalars.
Ensure every valid HTTP(S) repository URL is emitted uniquely so helm repo add
registers it before helm dependency build.
Source: MCP tools
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.
… bug found by the gate)
What
Adds a CI gate that installs each chart into a throwaway kind cluster and exercises the upgrade path, complementing the existing render-gate.
Why it matters: the render-gate only runs
helm template. It cannot catch:service.port=99999999renders buthelm installfails: must be between 1 and 65535);helm upgradecatches these.How (
.github/scripts/install-test.sh, per chart)helm install(PR) → server-side manifest validation.helm upgradein place → upgrade code path.helm install origin/mainthenhelm upgradeto PR (isolated namespace) → immutable-field breaks. Skipped for charts not yet onmain(the productized ones).Deliberate scope (honest limits): hook Jobs are skipped (
--no-hooks) and app pods are not waited on — the app images are private (GHCR closed-source) and hooks need real backing services, so neither can complete in a credential-less CI cluster. This gate proves steady-state manifest validity + the upgrade path, not app readiness (that's a follow-up gated on a GHCR pull secret). Only one release is installed at a time so subchart-heavy charts don't exhaust a single-node kind.Scope
Changed charts on a PR; the full app-chart set when the workflow/script/
lerian-commonchange (this PR → full sweep). Library charts skipped.Local validation
Ran the script against
streaming-hub(new),notifications(private-image hooks),fetcher(subcharts + baseline),midaz(mongodb+postgres+valkey+baseline) — all install + upgrade clean — and a negative case that correctly fails.