feat: Ensure repo update is only run once (#2378)

* feat: Ensure repo update is only run once

Perform a single Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "glm-bitnami" chart repository
...Unable to get an update from the "fluent" chart repository (https://fluent.github.io/helm-charts):
	Get "https://fluent.github.io/helm-charts/index.yaml": read tcp 192.168.0.104:51893->185.199.108.153:443: read: connection reset by peer
...Unable to get an update from the "grafana" chart repository (https://grafana.github.io/helm-charts):
	Get "https://grafana.github.io/helm-charts/index.yaml": read tcp 192.168.0.104:51897->185.199.109.153:443: read: connection reset by peer
...Unable to get an update from the "ingress-nginx" chart repository (https://kubernetes.github.io/ingress-nginx):
	Get "https://kubernetes.github.io/ingress-nginx/index.yaml": read tcp 192.168.0.104:51894->185.199.110.153:443: read: connection reset by peer
...Unable to get an update from the "chartmuseum" chart repository (https://chartmuseum.github.io/charts):
	Get "https://chartmuseum.github.io/charts/index.yaml": read tcp 192.168.0.104:51896->185.199.110.153:443: read: connection reset by peer
...Successfully got an update from the "glm-chartmuseum" chart repository
...Successfully got an update from the "apollo" chart repository
...Successfully got an update from the "kyverno" chart repository
...Unable to get an update from the "mysql-operator" chart repository (https://mysql.github.io/mysql-operator/):
	Get "https://mysql.github.io/mysql-operator/index.yaml": read tcp 192.168.0.104:51903->185.199.111.153:443: read: connection reset by peer
...Unable to get an update from the "metallb" chart repository (https://metallb.github.io/metallb):
	Get "https://metallb.github.io/metallb/index.yaml": read tcp 192.168.0.104:51904->185.199.111.153:443: read: connection reset by peer
...Unable to get an update from the "dragonfly" chart repository (https://dragonflyoss.github.io/helm-charts/):
	Get "https://dragonflyoss.github.io/helm-charts/index.yaml": read tcp 192.168.0.104:51905->185.199.108.153:443: read: connection reset by peer
...Unable to get an update from the "openfga" chart repository (https://openfga.github.io/helm-charts):
	Get "https://openfga.github.io/helm-charts/index.yaml": read tcp 192.168.0.104:51907->185.199.111.153:443: read: connection reset by peer
...Unable to get an update from the "cnpg" chart repository (https://cloudnative-pg.github.io/charts):
	Get "https://cloudnative-pg.github.io/charts/index.yaml": read tcp 192.168.0.104:51910->185.199.111.153:443: read: connection reset by peer
...Unable to get an update from the "metrics-server" chart repository (https://kubernetes-sigs.github.io/metrics-server/):
	Get "https://kubernetes-sigs.github.io/metrics-server/index.yaml": read tcp 192.168.0.104:51913->185.199.111.153:443: read: connection reset by peer
...Unable to get an update from the "ot-helm" chart repository (https://ot-container-kit.github.io/helm-charts/):
	Get "https://ot-container-kit.github.io/helm-charts/index.yaml": read tcp 192.168.0.104:51914->185.199.111.153:443: read: connection reset by peer
...Unable to get an update from the "coredns" chart repository (https://coredns.github.io/helm):
	Get "https://coredns.github.io/helm/index.yaml": read tcp 192.168.0.104:51917->185.199.111.153:443: read: connection reset by peer
...Unable to get an update from the "redis-operator" chart repository (https://ot-container-kit.github.io/helm-charts/):
	Get "https://ot-container-kit.github.io/helm-charts/index.yaml": read tcp 192.168.0.104:51912->185.199.111.153:443: read: connection reset by peer
...Unable to get an update from the "andrcuns" chart repository (https://andrcuns.github.io/charts):
	Get "https://andrcuns.github.io/charts/index.yaml": read tcp 192.168.0.104:51915->185.199.111.153:443: read: connection reset by peer
...Successfully got an update from the "gitlab-jh" chart repository
...Successfully got an update from the "hashicorp" chart repository
...Successfully got an update from the "incubator" chart repository
...Successfully got an update from the "jenkins" chart repository
...Successfully got an update from the "nvidia" chart repository
...Successfully got an update from the "elastic" chart repository
...Successfully got an update from the "projectcalico" chart repository
...Unable to get an update from the "juicefs" chart repository (https://juicedata.github.io/charts/):
	Get "https://juicedata.github.io/charts/index.yaml": read tcp 192.168.0.104:51919->185.199.111.153:443: read: connection reset by peer
...Successfully got an update from the "bitnami" chart repository
Update Complete. ⎈Happy Helming!⎈ before running any
commands, allowing us to safely pass --skip-refresh to avoid redundant
repo updates for each chart with external dependencies.

This reduces the number of repository refresh operations from O(n) to O(1)
where n is the number of charts with remote dependencies.

Co-authored-by: Javex <github@javex.eu>
Signed-off-by: yxxhero <aiopsclub@163.com>

* fix: ensure repo update only runs when repositories are configured

This fixes CI issues where tests fail with 'no repositories found' error.

The PR #2378 adds a single helm.UpdateRepo() call before running helm dep
build commands. However, when no repositories are configured, this call
fails. The fix adds a check for len(st.Repositories) > 0 before calling
UpdateRepo().

Additionally, updated snapshot files to reflect the new output ordering
where repo update happens before building dependencies.

Signed-off-by: yxxhero <aiopsclub@163.com>

* feat: Update test snapshots for single repo update

The code changes in PR #2378 ensure that helm repo update is only run once
before building dependencies. This requires updating test snapshots to include
the 'Updating repo' output that now appears before 'Building dependency' messages.

Updated snapshots:
- chart_need/output.yaml
- chart_need_enable_live_output/output.yaml
- release_template_inheritance/output.yaml
- environments_releases_without_same_yaml_part/output.yaml
- environment_missing_in_subhelmfile/output.yaml
- pr_560/output.yaml
- environments_values_gotmpl_with_environment_name/output.yaml
- postrenderer/output.yaml (fixed YAML structure)
- oci_need/output.yaml

Signed-off-by: yxxhero <aiopsclub@163.com>

* fix: Correctly update test snapshots based on repository configuration

Only update snapshots for tests that have repositories defined:
- chart_need/output.yaml (has repositories - shows 'Updating repo')
- chart_need_enable_live_output/output.yaml (has repositories - shows 'Updating repo')
- release_template_inheritance/output.yaml (has repositories - shows 'Updating repo')

Tests without repositories should NOT show 'Updating repo':
- environments_releases_without_same_yaml_part/output.yaml
- environments_values_gotmpl_with_environment_name/output.yaml
- pr_560/output.yaml
- environment_missing_in_subhelmfile/output.yaml
- postrenderer/output.yaml (uses OCI dependencies)
- oci_need/output.yaml (uses OCI dependencies)

This matches the conditional logic in the code that only runs
helm.UpdateRepo() when len(st.Repositories) > 0.

Signed-off-by: yxxhero <aiopsclub@163.com>

* fix: correct snapshot test expectations for repo update optimization

- Re-add trailing newlines to environment_missing_in_subhelmfile output
- Restore correct chart paths (/... instead of ../../...)
- Restore postrenderer output with cm2 ConfigMap and correct field order
- Fixes CI test failures introduced by incorrect snapshot updates

Signed-off-by: yxxhero <aiopsclub@163.com>

* fix: update integration test expected lint output for repo update

Include 'Updating repo' messages in expected lint output files
to match the new behavior where helm repo update is run once
before building dependencies.

Signed-off-by: yxxhero <aiopsclub@163.com>

* fix: remove extra blank line from lint output files

Integration test output files had an extra blank line that was
not present in the expected output, causing test failures.

Signed-off-by: yxxhero <aiopsclub@163.com>

* fix: update lint output for single repo update

With the repo update optimization, lint runs only once
with 'Updating repo' messages instead of running twice.
Update expected output to match new single-run behavior.

Signed-off-by: yxxhero <aiopsclub@163.com>

* fix: filter out repo update messages in lint test

Update test runner to filter out repo update messages that are
now generated by the single helm.UpdateRepo() call, keeping
the expected lint output consistent with the original behavior.

Signed-off-by: yxxhero <aiopsclub@163.com>

* fix: filter repo update messages from diff test

Filter out repo update messages in diff test output to
match new behavior where helm.UpdateRepo() is called once.

Signed-off-by: yxxhero <aiopsclub@163.com>

* Fix missing closing parenthesis in grep command

Signed-off-by: yxxhero <aiopsclub@163.com>

* fix: prevent --args flags from being passed to helm repo commands

When helmfile template --args is used, the extra flags were being
passed to helm repo update and helm repo add commands, which don't
support all flags that helm template/install support. This caused
failures when flags like --dry-run were passed via --args.

The fix saves the extra flags before executing helm repo commands,
clears them, and restores them afterwards to ensure repo commands
run without unsupported flags.

Fixes CI issue in PR #2378 where test issue-1749 fails
with "Error: unknown flag: --dry-run" during helm repo update.

Signed-off-by: yxxhero <aiopsclub@163.com>

---------

Signed-off-by: yxxhero <aiopsclub@163.com>
Co-authored-by: Javex <github@javex.eu>
This commit is contained in:
yxxhero 2026-01-30 08:15:44 +08:00 committed by GitHub
parent 11def2e7e8
commit 9964a2eacb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 40 additions and 6 deletions

View File

@ -204,6 +204,12 @@ func (helm *execer) AddRepo(name, repository, cafile, certfile, keyfile, usernam
return fmt.Errorf("empty field name")
}
savedExtra := helm.extra
helm.extra = []string{}
defer func() {
helm.extra = savedExtra
}()
switch managed {
case "acr":
helm.logger.Infof("Adding repo %v (acr)", name)
@ -252,6 +258,11 @@ func (helm *execer) AddRepo(name, repository, cafile, certfile, keyfile, usernam
func (helm *execer) UpdateRepo() error {
helm.logger.Info("Updating repo")
savedExtra := helm.extra
helm.extra = []string{}
defer func() {
helm.extra = savedExtra
}()
out, err := helm.exec([]string{"repo", "update"}, map[string]string{}, nil)
helm.info(out)
return err

View File

@ -1828,7 +1828,7 @@ func (st *HelmState) PrepareCharts(helm helmexec.Interface, dir string, concurre
}
if len(builds) > 0 {
if err := st.runHelmDepBuilds(helm, concurrency, builds); err != nil {
if err := st.runHelmDepBuilds(helm, concurrency, builds, opts); err != nil {
return nil, []error{err}
}
}
@ -1837,7 +1837,7 @@ func (st *HelmState) PrepareCharts(helm helmexec.Interface, dir string, concurre
}
// nolint: unparam
func (st *HelmState) runHelmDepBuilds(helm helmexec.Interface, concurrency int, builds []*chartPrepareResult) error {
func (st *HelmState) runHelmDepBuilds(helm helmexec.Interface, concurrency int, builds []*chartPrepareResult, opts ChartPrepareOptions) error {
// NOTES:
// 1. `helm dep build` fails when it was run concurrency on the same chart.
// To avoid that, we run `helm dep build` only once per each local chart.
@ -1848,7 +1848,21 @@ func (st *HelmState) runHelmDepBuilds(helm helmexec.Interface, concurrency int,
//
// See https://github.com/roboll/helmfile/issues/1521
// Perform an update of repos once before running `helm dep build` so that we
// can safely pass --skip-refresh to the command to avoid doing a repo update
// for every iteration of the loop where charts have external dependencies.
// Only do this if there are repositories configured.
if len(builds) > 0 && !opts.SkipRefresh && len(st.Repositories) > 0 {
if err := helm.UpdateRepo(); err != nil {
return fmt.Errorf("updating repo: %w", err)
}
}
for _, r := range builds {
// Never update the local repository cache here, since we've already
// updated it before entering the loop
r.skipRefresh = true
buildDepsFlags := getBuildDepsFlags(r)
if err := helm.BuildDeps(r.releaseName, r.chartPath, buildDepsFlags...); err != nil {
if r.chartFetchedByGoGetter {

View File

@ -1,10 +1,12 @@
Adding repo myrepo http://localhost:18080/
"myrepo" has been added to your repositories
Building dependency release=foo, chart=$WD/temp1/foo
Updating repo
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "myrepo" chart repository
Update Complete. ⎈Happy Helming!⎈
Building dependency release=foo, chart=$WD/temp1/foo
Saving 1 charts
Downloading raw from repo http://localhost:18080/
Deleting outdated charts

View File

@ -2,10 +2,12 @@ Live output is enabled
Adding repo myrepo http://localhost:18081/
"myrepo" has been added to your repositories
Building dependency release=foo, chart=$WD/temp1/foo
Updating repo
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "myrepo" chart repository
Update Complete. ⎈Happy Helming!⎈
Building dependency release=foo, chart=$WD/temp1/foo
Saving 1 charts
Downloading raw from repo http://localhost:18081/
Deleting outdated charts

View File

@ -1,6 +1,11 @@
Adding repo myrepo http://localhost:18084/
"myrepo" has been added to your repositories
Updating repo
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "myrepo" chart repository
Update Complete. ⎈Happy Helming!⎈
Building dependency release=foo1, chart=../../charts/raw-0.1.0
Building dependency release=foo2, chart=../../charts/raw-0.1.0
Templating release=foo1, chart=../../charts/raw-0.1.0

View File

@ -35,7 +35,7 @@ for i in $(seq 10); do
info "Comparing lint/chart-needs #$i"
# Remove azuredisk-csi-driver repo to ensure consistent output (repo addition message)
${helm} repo remove azuredisk-csi-driver &>/dev/null || true
${helmfile} -f ${chart_need_case_input_dir}/${config_file} lint --include-needs | grep -v Linting | grep -v "has been removed" | grep -Ev "(Warning:.*is not a valid SemVerV2|\[WARNING\].*is not a valid SemVerV2|failed to load plugins)" > ${chart_needs_lint_reverse} || fail "\"helmfile lint\" shouldn't fail"
${helmfile} -f ${chart_need_case_input_dir}/${config_file} lint --include-needs | grep -v Linting | grep -v "has been removed" | grep -Ev "(Updating repo|Hang tight while we grab|Update Complete|Successfully got an update from|Warning:.*is not a valid SemVerV2|\[WARNING\].*is not a valid SemVerV2|failed to load plugins)" > ${chart_needs_lint_reverse} || fail "\"helmfile lint\" shouldn't fail"
diff -u ${lint_out_file} ${chart_needs_lint_reverse} || fail "\"helmfile lint\" should be consistent"
done
@ -43,7 +43,7 @@ for i in $(seq 10); do
info "Comparing diff/chart-needs #$i"
# Remove azuredisk-csi-driver repo to ensure consistent output (repo addition message)
${helm} repo remove azuredisk-csi-driver &>/dev/null || true
${helmfile} -f ${chart_need_case_input_dir}/${config_file} diff --include-needs | grep -Ev "Comparing release=azuredisk-csi-storageclass, chart=.*/chartify.*/azuredisk-csi-storageclass" > ${chart_needs_diff_reverse}.tmp || fail "\"helmfile diff\" shouldn't fail"
${helmfile} -f ${chart_need_case_input_dir}/${config_file} diff --include-needs | grep -Ev "(Updating repo|Hang tight while we grab|Update Complete|Successfully got an update from|Comparing release=azuredisk-csi-storageclass, chart=.*/chartify.*/azuredisk-csi-storageclass)" > ${chart_needs_diff_reverse}.tmp || fail "\"helmfile diff\" shouldn't fail"
cat ${chart_needs_diff_reverse}.tmp | sed -E '/\*{20}/,/\*{20}/d' > ${chart_needs_diff_reverse}
# With --enable-live-output, there's a race condition that can cause non-deterministic ordering