Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package helmfile for openSUSE:Factory checked in at 2026-08-06 16:20:54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/helmfile (Old) and /work/SRC/openSUSE:Factory/.helmfile.new.16738 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "helmfile" Thu Aug 6 16:20:54 2026 rev:100 rq:1369624 version:1.7.3 Changes: -------- --- /work/SRC/openSUSE:Factory/helmfile/helmfile.changes 2026-08-01 18:38:12.186156740 +0200 +++ /work/SRC/openSUSE:Factory/.helmfile.new.16738/helmfile.changes 2026-08-06 16:23:06.370333355 +0200 @@ -1,0 +2,24 @@ +Wed Aug 5 04:40:17 UTC 2026 - Manfred Hollstein <[email protected]> + +- Update to version 1.7.3: + * build(deps): bump docker/login-action from 4 to 4.5.2 by + @dependabot[bot] in #2723 + * fix: skip chartification when chart renders zero resources by + @ankit090701 in #2724 + * feat: add --skip-diff-validation-on-install CLI flag by + @henrichter-sap in #2728 + * bump helm-diff to v3.15.11 by @yxxhero with @Copilot in #2725 + * fix: update helm plugins via uninstall+reinstall to honor + pinned version by @yxxhero in #2727 + * build(deps): bump docker/login-action from 4.5.2 to 4.6.0 by + @dependabot[bot] in #2729 + * build(deps): bump gitpython from 3.1.54 to 3.1.57 in /docs by + @dependabot[bot] in #2730 + * build(deps): bump github.com/aws/aws-sdk-go-v2/service/s3 from + 1.106.2 to 1.106.3 by @dependabot[bot] in #2731 + * build(deps): bump github.com/aws/aws-sdk-go-v2/config from + 1.32.31 to 1.32.34 by @dependabot[bot] in #2732 + * build(deps): bump github.com/helmfile/chartify from v0.28.0 to + v0.28.1 by @yxxhero in #2733 + +------------------------------------------------------------------- Old: ---- helmfile-1.7.2.tar.gz New: ---- helmfile-1.7.3.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ helmfile.spec ++++++ --- /var/tmp/diff_new_pack.Odlwcs/_old 2026-08-06 16:23:08.266399574 +0200 +++ /var/tmp/diff_new_pack.Odlwcs/_new 2026-08-06 16:23:08.270399713 +0200 @@ -17,9 +17,9 @@ # -%define git_commit 7f748fec1b979a74aa7793e7a4d2f27944ad9748 +%define git_commit 5e68d499ecf3ef0bc92057f52fed4172b10d06d1 Name: helmfile -Version: 1.7.2 +Version: 1.7.3 Release: 0 Summary: Deploy Kubernetes Helm Charts License: MIT ++++++ _service ++++++ --- /var/tmp/diff_new_pack.Odlwcs/_old 2026-08-06 16:23:08.326401669 +0200 +++ /var/tmp/diff_new_pack.Odlwcs/_new 2026-08-06 16:23:08.330401809 +0200 @@ -5,7 +5,7 @@ <param name="exclude">.git</param> <param name="versionformat">@PARENT_TAG@</param> <param name="versionrewrite-pattern">v(.*)</param> - <param name="revision">v1.7.2</param> + <param name="revision">v1.7.3</param> <param name="changesgenerate">enable</param> <param name="changesauthor">[email protected]</param> </service> ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.Odlwcs/_old 2026-08-06 16:23:08.410404603 +0200 +++ /var/tmp/diff_new_pack.Odlwcs/_new 2026-08-06 16:23:08.418404882 +0200 @@ -1,7 +1,7 @@ <servicedata> <service name="tar_scm"> <param name="url">https://github.com/helmfile/helmfile.git</param> - <param name="changesrevision">7f748fec1b979a74aa7793e7a4d2f27944ad9748</param> + <param name="changesrevision">5e68d499ecf3ef0bc92057f52fed4172b10d06d1</param> </service> </servicedata> ++++++ helmfile-1.7.2.tar.gz -> helmfile-1.7.3.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/helmfile-1.7.2/cmd/apply.go new/helmfile-1.7.3/cmd/apply.go --- old/helmfile-1.7.2/cmd/apply.go 2026-07-31 16:22:31.000000000 +0200 +++ new/helmfile-1.7.3/cmd/apply.go 2026-08-04 12:31:11.000000000 +0200 @@ -50,6 +50,7 @@ f.BoolVar(&applyOptions.EnforceNeedsAreInstalled, "enforce-needs-are-installed", false, "enforce that all 'needs' dependencies are installable before applying changes") f.BoolVar(&applyOptions.IncludeTransitiveNeeds, "include-transitive-needs", false, `like --include-needs, but also includes transitive needs (needs of needs). Does nothing when --selector/-l flag is not provided. Overrides exclusions of other selectors and conditions.`) f.BoolVar(&applyOptions.SkipDiffOnInstall, "skip-diff-on-install", false, "Skips running helm-diff on releases being newly installed on this apply. Useful when the release manifests are too huge to be reviewed, or it's too time-consuming to diff at all") + f.BoolVar(&applyOptions.SkipDiffValidationOnInstall, "skip-diff-validation-on-install", false, "Disables K8s API validation (--disable-validation) when running helm-diff on releases being newly installed. Useful when charts include CRDs and CRs in the same release") f.BoolVar(&applyOptions.IncludeTests, "include-tests", false, "enable the diffing of the helm test hooks") f.StringArrayVar(&applyOptions.Suppress, "suppress", nil, "suppress specified Kubernetes objects in the diff output. Can be provided multiple times. For example: --suppress KeycloakClient --suppress VaultSecret") f.BoolVar(&applyOptions.SuppressSecrets, "suppress-secrets", false, "suppress secrets in the diff output. highly recommended to specify on CI/CD use-cases") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/helmfile-1.7.2/cmd/bind_diff_flags.go new/helmfile-1.7.3/cmd/bind_diff_flags.go --- old/helmfile-1.7.2/cmd/bind_diff_flags.go 2026-07-31 16:22:31.000000000 +0200 +++ new/helmfile-1.7.3/cmd/bind_diff_flags.go 2026-08-04 12:31:11.000000000 +0200 @@ -32,6 +32,7 @@ f.BoolVar(&opts.EnforceNeedsAreInstalled, "enforce-needs-are-installed", false, "enforce that all 'needs' dependencies are installable before applying changes") f.BoolVar(&opts.IncludeTransitiveNeeds, "include-transitive-needs", false, `like --include-needs, but also includes transitive needs (needs of needs). Does nothing when --selector/-l flag is not provided. Overrides exclusions of other selectors and conditions.`) f.BoolVar(&opts.SkipDiffOnInstall, "skip-diff-on-install", false, "Skips running helm-diff on releases being newly installed on this apply. Useful when the release manifests are too huge to be reviewed, or it's too time-consuming to diff at all") + f.BoolVar(&opts.SkipDiffValidationOnInstall, "skip-diff-validation-on-install", false, "Disables K8s API validation (--disable-validation) when running helm-diff on releases being newly installed. Useful when charts include CRDs and CRs in the same release") f.BoolVar(&opts.NoHooks, "no-hooks", false, "do not diff changes made by hooks.") f.BoolVar(&opts.StripTrailingCR, "strip-trailing-cr", false, "strip trailing carriage return on input") f.BoolVar(&opts.SuppressSecrets, "suppress-secrets", false, "suppress secrets in the output. highly recommended to specify on CI/CD use-cases") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/helmfile-1.7.2/cmd/sync.go new/helmfile-1.7.3/cmd/sync.go --- old/helmfile-1.7.2/cmd/sync.go 2026-07-31 16:22:31.000000000 +0200 +++ new/helmfile-1.7.3/cmd/sync.go 2026-08-04 12:31:11.000000000 +0200 @@ -74,6 +74,7 @@ f.BoolVar(&syncOptions.NoHooks, "no-hooks", false, "do not diff changes made by hooks (interactive preview only)") f.BoolVar(&syncOptions.SuppressDiff, "suppress-diff", false, "suppress diff in the output (interactive preview only). Usable in new installs") f.BoolVar(&syncOptions.SkipDiffOnInstall, "skip-diff-on-install", false, "Skips running helm-diff on releases being newly installed on this sync (interactive preview only). Useful when the release manifests are too huge to be reviewed, or it's too time-consuming to diff at all") + f.BoolVar(&syncOptions.SkipDiffValidationOnInstall, "skip-diff-validation-on-install", false, "Disables K8s API validation (--disable-validation) when running helm-diff on releases being newly installed (interactive preview only). Useful when charts include CRDs and CRs in the same release") f.BoolVar(&syncOptions.IncludeTests, "include-tests", false, "enable the diffing of the helm test hooks (interactive preview only)") f.BoolVar(&syncOptions.DetailedExitcode, "detailed-exitcode", false, "return a non-zero exit code 2 instead of 0 when releases are synced (use --interactive to also see a diff preview)") f.BoolVar(&syncOptions.StripTrailingCR, "strip-trailing-cr", false, "strip trailing carriage return on input (interactive preview only)") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/helmfile-1.7.2/docs/cli.md new/helmfile-1.7.3/docs/cli.md --- old/helmfile-1.7.2/docs/cli.md 2026-07-31 16:22:31.000000000 +0200 +++ new/helmfile-1.7.3/docs/cli.md 2026-08-04 12:31:11.000000000 +0200 @@ -161,6 +161,11 @@ you should be able to simply execute `helm plugin install https://github.com/databus23/helm-diff`. For more details please look at their [documentation](https://github.com/databus23/helm-diff#helm-diff-plugin). +#### Notable diff flags + +* `--skip-diff-on-install` — skip running `helm diff` entirely for releases that are not yet installed. The release is treated as changed and will be synced on `apply` without showing a diff. +* `--skip-diff-validation-on-install` — for releases that are not yet installed, pass `--disable-validation` to `helm diff` so the diff is shown without K8s API server validation. Useful when a chart bundles CRDs and CRs together: the CRs would fail API validation before the CRDs are installed. This is the CLI-flag equivalent of the per-release `disableValidationOnInstall` field. + ### doctor `helmfile doctor` runs `helmfile diff` and asks an OpenAI-compatible LLM to summarize the changes and flag risks diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/helmfile-1.7.2/docs/configuration.md new/helmfile-1.7.3/docs/configuration.md --- old/helmfile-1.7.2/docs/configuration.md 2026-07-31 16:22:31.000000000 +0200 +++ new/helmfile-1.7.3/docs/configuration.md 2026-08-04 12:31:11.000000000 +0200 @@ -265,6 +265,7 @@ # passes --disable-validation to helm diff plugin, this requires diff plugin >= 3.1.2 # It is useful when any release contains custom resources for CRDs that is not yet installed onto the cluster. # https://github.com/roboll/helmfile/pull/1618 + # To apply this to all releases without editing each one, use the --skip-diff-validation-on-install CLI flag. disableValidationOnInstall: false # passes --disable-openapi-validation to helm diff plugin, this requires diff plugin >= 3.1.2 # It may be helpful to deploy charts with helm api v1 CRDS diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/helmfile-1.7.2/docs/requirements.txt new/helmfile-1.7.3/docs/requirements.txt --- old/helmfile-1.7.2/docs/requirements.txt 2026-07-31 16:22:31.000000000 +0200 +++ new/helmfile-1.7.3/docs/requirements.txt 2026-08-04 12:31:11.000000000 +0200 @@ -2,7 +2,7 @@ click==8.1.2 ghp-import==2.0.2 gitdb==4.0.9 -GitPython==3.1.54 +GitPython==3.1.57 importlib-metadata==4.11.3 Jinja2==3.1.6 Markdown==3.8.1 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/helmfile-1.7.2/go.mod new/helmfile-1.7.3/go.mod --- old/helmfile-1.7.2/go.mod 2026-07-31 16:22:31.000000000 +0200 +++ new/helmfile-1.7.3/go.mod 2026-08-04 12:31:11.000000000 +0200 @@ -6,8 +6,8 @@ dario.cat/mergo v1.0.2 github.com/Masterminds/semver/v3 v3.5.0 github.com/Masterminds/sprig/v3 v3.3.0 - github.com/aws/aws-sdk-go-v2/config v1.32.31 - github.com/aws/aws-sdk-go-v2/service/s3 v1.106.2 + github.com/aws/aws-sdk-go-v2/config v1.32.34 + github.com/aws/aws-sdk-go-v2/service/s3 v1.106.3 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc github.com/go-test/deep v1.1.1 github.com/gofrs/flock v0.13.0 @@ -18,7 +18,7 @@ github.com/hashicorp/go-cty-funcs v0.1.0 github.com/hashicorp/go-getter/v2 v2.2.3 github.com/hashicorp/hcl/v2 v2.24.0 - github.com/helmfile/chartify v0.28.0 + github.com/helmfile/chartify v0.28.1 github.com/helmfile/vals v0.45.0 github.com/sashabaranov/go-openai v1.41.2 github.com/spf13/cobra v1.10.2 @@ -166,26 +166,26 @@ github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/atotto/clipboard v0.1.4 // indirect github.com/avelino/slugify v0.0.0-20180501145920-855f152bd774 // indirect - github.com/aws/aws-sdk-go-v2 v1.43.2 // indirect - github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.15 // indirect - github.com/aws/aws-sdk-go-v2/credentials v1.19.30 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.31 // indirect + github.com/aws/aws-sdk-go-v2 v1.43.3 // indirect + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.16 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.19.33 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.34 // indirect github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.22.28 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.33 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.33 // indirect - github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.34 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.14 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.26 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.33 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.34 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.34 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.34 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.35 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.15 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.27 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.34 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.35 // indirect github.com/aws/aws-sdk-go-v2/service/kms v1.54.1 // indirect github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.43.1 // indirect - github.com/aws/aws-sdk-go-v2/service/signin v1.5.0 // indirect + github.com/aws/aws-sdk-go-v2/service/signin v1.5.3 // indirect github.com/aws/aws-sdk-go-v2/service/ssm v1.72.0 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.33.0 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.38.0 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.45.0 // indirect - github.com/aws/smithy-go v1.27.5 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.33.3 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.38.3 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.45.3 // indirect + github.com/aws/smithy-go v1.27.6 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/blang/semver/v4 v4.0.0 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/helmfile-1.7.2/go.sum new/helmfile-1.7.3/go.sum --- old/helmfile-1.7.2/go.sum 2026-07-31 16:22:31.000000000 +0200 +++ new/helmfile-1.7.3/go.sum 2026-08-04 12:31:11.000000000 +0200 @@ -154,50 +154,50 @@ github.com/avelino/slugify v0.0.0-20180501145920-855f152bd774/go.mod h1:5wi5YYOpfuAKwL5XLFYopbgIl/v7NZxaJpa/4X6yFKE= github.com/aws/aws-sdk-go v1.55.8 h1:JRmEUbU52aJQZ2AjX4q4Wu7t4uZjOu71uyNmaWlUkJQ= github.com/aws/aws-sdk-go v1.55.8/go.mod h1:ZkViS9AqA6otK+JBBNH2++sx1sgxrPKcSzPPvQkUtXk= -github.com/aws/aws-sdk-go-v2 v1.43.2 h1:cl+IXwWb3qazClUcm08tGSsB6OiuV83JVJO9B0jQcPc= -github.com/aws/aws-sdk-go-v2 v1.43.2/go.mod h1:WEzLKBh/mEjXvx1FtQMWgSxMSTVqxQzjkRtk5fa3wkg= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.15 h1:rq/p1VNFfygoKEQ9hHMKsKBE98lspPvT8IxaFs5mFhw= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.15/go.mod h1:bELIhlPfW8OkpDhP1MvCjHDtvv8NhiBTz+K4o26zrXA= -github.com/aws/aws-sdk-go-v2/config v1.32.31 h1:n4nY9O3QKoHIkL85EX+V8RcMFtOhlpTFhGArg915PXk= -github.com/aws/aws-sdk-go-v2/config v1.32.31/go.mod h1:PN0NYDCCoOpGGsZ2+elDUidmHfQBPyYzN2GCgl8HEBs= -github.com/aws/aws-sdk-go-v2/credentials v1.19.30 h1:TTCvvzFU6gXa4iJecNG/0F/B0oYTiazoRECr2XyLHrY= -github.com/aws/aws-sdk-go-v2/credentials v1.19.30/go.mod h1:jKxAp2AEncnliinzpgOSZDFv6+VjvWhjw/AtbfsWT9U= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.31 h1:kfVL5wAunCJycL6MOQ6aNh6PlAYEymflcjuKmrWUA0o= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.31/go.mod h1:nWfRNDAppujCQgOUd43lKT4yeLv9z3nJ3bw1G3BgQKo= +github.com/aws/aws-sdk-go-v2 v1.43.3 h1:XJIcfv8uDs2ukdQsoAC8/Ebu1ejxwzlayl2ZsiFns2A= +github.com/aws/aws-sdk-go-v2 v1.43.3/go.mod h1:70vwSy16txshwG+g55WkpgPKDIByzHI8ccBsOteo3bQ= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.16 h1:aiuaKlDweRC5qExJondpWjOgyzMHpofpwspGXUtwn4c= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.16/go.mod h1:nG/LOlmox9BDe9HvQnXWzgcK8uKbgBMZ/Hp5pVt/21I= +github.com/aws/aws-sdk-go-v2/config v1.32.34 h1:o+YAizrX562nEZXaB38uYTK8RvIsvW0uuRP+e5e0Pfk= +github.com/aws/aws-sdk-go-v2/config v1.32.34/go.mod h1:wc0zYRChOniiufvdWiRVf3jgXSgbkvaD683IHHHc2ZQ= +github.com/aws/aws-sdk-go-v2/credentials v1.19.33 h1:/e5V3EWfeDiW6cuRxHsC8gbwko4/vvVYPJR2afBKFFY= +github.com/aws/aws-sdk-go-v2/credentials v1.19.33/go.mod h1:ZxAmkcyOM9beY/WO9oxp2oVPXiP3rq5N1/p4NbenJdE= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.34 h1:1EsGke6rTD2CG3j2MMVB77n6Q+FlbQWYI/dFdLWBNtM= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.34/go.mod h1:5B1Z/QbaWzqoWRzYxZfmCbDDRcvUHcfAIQw/S+KfDmc= github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.22.28 h1:ez4y5o7sa0uaRI8BquYOXtZpioUPhbQEh7Igm88oV9U= github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.22.28/go.mod h1:TpmZOrQA12XKEpVypgBGZSQBsm1WUTndCiSnbDsbvug= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.33 h1:HAp1wLFZzch054uh3FK7rcVYg4v7J2FxVf3h3IGNZas= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.33/go.mod h1:mJk5fmqnF+WUlMdPG37pR2Fh3oh6r8F6ZGUgPKvzu0c= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.33 h1:0YA0aCKgsJyno6xkFfaIgjE3/wK08+Qxo9nQfe1UrWM= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.33/go.mod h1:UZqj4WIdTH+ga8Y/DgpAuy/8cGjM3h7gDCliJYGg2SE= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.34 h1:HQYnjFnXpX8EbPW5M1QT8mXzesRPwly0HEPTcFlS02Y= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.34/go.mod h1:tGzj56niKYZBbDIRhwPGDqrULzmWv5b6uBQGqyNaFZw= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.14 h1:SA43nfaY7+1jjMNIc2ywu99JLJLButtIdLP6j+bT870= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.14/go.mod h1:Du3llKcwbQvHsTXSLzTOGQz0DTDBMEzdg7DAGu7inrY= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.26 h1:Eflerh7atY6HN0yz60peNLOkJA2ZKUyYjZexMbqwMCE= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.26/go.mod h1:dCAXNDmik9NuTjfsvCvW22S6ZFpxmtoliFoQu5XFkh8= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.33 h1:mqI7OrxN/DUH85F5OqVn3cIfuZ3+HVcebUm2N8mLlgQ= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.33/go.mod h1:eZ5jdEpvaaOU8nWWE4cTAJETSEA5FZoWxvNRao4piHY= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.34 h1:Lercr2QB2rOrCwyOusmnQ7IiopfkGcZAgMJbjcSdK/s= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.34/go.mod h1:W0xXPPCb2HAqa3cp2f/nRvE+jGgBmchiuXrfBRlfb1I= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.34 h1:vuIfjzoeqhQMGJyOBU3t0ZEjn2jrN8Bbg1N4CgjzM5Q= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.34/go.mod h1:hP28cN4CPJLZHirdQPrZR50JcLN4ApRJP2tzG8cRlhY= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.34 h1:9faHsnqxJ1vDvB4wMZy/ajIDyz5QhllQjjc72RJpXAw= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.34/go.mod h1:Yp6nIyejpa23nzlB/LhT63KTla9Jdi06nv/HH/OkAH8= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.35 h1:Oe8gMKJLO5awqpa5EhAGKVnBv1s+brdWVuxM2mDa7zA= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.35/go.mod h1:FZevcG9cOST/FWAAUhHIchjR9fXFXFRCWodOhx+PDLA= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.15 h1:JJLBQxwY+AFwuPAi5ivGc1ChnTdUt4cXMv7e76m2c/Y= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.15/go.mod h1:lQknBIe78MVL0cQOQDlag8KGflMbMEVFx9mB6O8ENvk= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.27 h1:zwB6ltUc0UiyOsRQaMQ8jNLjKECbjhadCyl4hqV0y/c= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.27/go.mod h1:ce9y+Y+hGLUyPKJZZJGoFLuFJNfCNuWZTujUJAsckQA= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.34 h1:sYg4qHWLqsjp15PzX7XCOHSOgKEGoZ5vQY43VvZ1pas= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.34/go.mod h1:N58SSz3roKf1HzW5qRaOiyk6MbDLTKgLPvlTfJ90iyI= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.35 h1:ohfdSAm4TA6nryIY7mLqe4mnSIAnAreoAPBM81ZVoIM= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.35/go.mod h1:uUjphnxMb3HH3vIiOHl4dH0fGNKL+csjqRQEabbfw5k= github.com/aws/aws-sdk-go-v2/service/kms v1.54.1 h1:aeJAJyvWS3gQ679pJbz8ZdOh3MViD1zvEdoZMVEawbg= github.com/aws/aws-sdk-go-v2/service/kms v1.54.1/go.mod h1:0RXNc6Yf3AvSMldGD6Lcch96Ojlw2TtGnHsqfD/L4u8= -github.com/aws/aws-sdk-go-v2/service/s3 v1.106.2 h1:lFSYDEyC1JHucMH3fdczMTnDaghqNttyRXKM8JY9EJQ= -github.com/aws/aws-sdk-go-v2/service/s3 v1.106.2/go.mod h1:aw1E7RCjxs5Sd8N6WdICMcMroff12Tzxte+ELXXNqRU= +github.com/aws/aws-sdk-go-v2/service/s3 v1.106.3 h1:oSfubHEP3a0nTRAtm99IDaws0f15qwf+fOwS1Esh5jI= +github.com/aws/aws-sdk-go-v2/service/s3 v1.106.3/go.mod h1:lWk6L5Q3YkaC7so1bQUJkvF7hj2KUFzdZ4w15wc2GHY= github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.43.1 h1:ZI18/nuaDBwwMJ95paJrb4NT2TbqEvptj/rlMkEO7DI= github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.43.1/go.mod h1:oUyL28WfxY0RqPhFpkrWZx26Cu4JlyrWMMcWq8qqhi0= -github.com/aws/aws-sdk-go-v2/service/signin v1.5.0 h1:OHH5iTQvVGmfHjX/5Q+vFuA/Rf2x6/95aJ/75QCQSm4= -github.com/aws/aws-sdk-go-v2/service/signin v1.5.0/go.mod h1:mCF3AK9PpL49oOrhniUXWAfhVBVQ/XbytoE5eccZUIs= +github.com/aws/aws-sdk-go-v2/service/signin v1.5.3 h1:togAtAmgV5IGMnQDuBDJeM8z5Y5RN6G7xeOgphWz+Yc= +github.com/aws/aws-sdk-go-v2/service/signin v1.5.3/go.mod h1:T7xKUUUvN7W3RW8UmMvKnD12xqh+Ux2gCPHPhnt64Dg= github.com/aws/aws-sdk-go-v2/service/ssm v1.72.0 h1:jl+7QcR+PEJVQXK1W5NSXw9EKd+w7Cu4Pwj/WvUIHb0= github.com/aws/aws-sdk-go-v2/service/ssm v1.72.0/go.mod h1:xabzRvdbMs3FG9kU5M6RUOuCW6wXDkpdIqoXXNzA1nQ= -github.com/aws/aws-sdk-go-v2/service/sso v1.33.0 h1:CaJyYhxBE0M/HJX/YvSaSmQlsI91VHB0lKU8LtLxL3A= -github.com/aws/aws-sdk-go-v2/service/sso v1.33.0/go.mod h1:+e6BMRMPjBQoCw/WovYR9GLy2IU0z4Q77smOB1DraSg= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.38.0 h1:tC323YV77QdafeBr6LUhLDTsboyuyHLNRwAyCP44kGU= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.38.0/go.mod h1:SfLK1sgviHmbI+MozR9iDwDjL4cdCVZtahsjoR+z7wg= -github.com/aws/aws-sdk-go-v2/service/sts v1.45.0 h1:Pd6PNlp4t8PTXxqzstICl52Wsy78vpjFZ7PRUj44mJc= -github.com/aws/aws-sdk-go-v2/service/sts v1.45.0/go.mod h1:rmQ0TnHzuLPmabgjPcsywhsSOmaBDgzR4zvDxSPsGdg= -github.com/aws/smithy-go v1.27.5 h1:d1ro7KpYOYwP6m73YFa+Kc/A130VsAdX68SpsJwARMM= -github.com/aws/smithy-go v1.27.5/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc= +github.com/aws/aws-sdk-go-v2/service/sso v1.33.3 h1:YjH64OUytnWZBHUtM9GMyi4ZWBiSQdEJkZuPykOIe44= +github.com/aws/aws-sdk-go-v2/service/sso v1.33.3/go.mod h1:5qoHcDZDTSJotoKk1bvVRPv1MXaL/NhfY9ng8D1g/ig= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.38.3 h1:A4o1di/XGaqtw6r3toSBrFX2U7mVSLqg7jo9wL4I+cU= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.38.3/go.mod h1:sKuKz2kHtrGVtFu34vbM3LWSA9CKD9YZUmm6e5PPqRA= +github.com/aws/aws-sdk-go-v2/service/sts v1.45.3 h1:Fi7+DiKN1+QphlajvE6FqeZ8GRbnnRul7zTdUiRpbGc= +github.com/aws/aws-sdk-go-v2/service/sts v1.45.3/go.mod h1:KCc3e27fHZUGtzpek7wZcp6dyCpGkJJo/+3PBujh/yU= +github.com/aws/smithy-go v1.27.6 h1:0zjT8jgK3jbrTT7JJ3EE6JsMhX8JTrZ+f1sEndYDXrA= +github.com/aws/smithy-go v1.27.6/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -536,8 +536,8 @@ github.com/hashicorp/jsonapi v1.4.3-0.20250220162346-81a76b606f3e/go.mod h1:kWfdn49yCjQvbpnvY1dxxAuAFzISwrrMDQOcu6NsFoM= github.com/hashicorp/vault/api v1.23.0 h1:gXgluBsSECfRWTSW9niY2jwg2e9mMJc4WoHNv4g3h6A= github.com/hashicorp/vault/api v1.23.0/go.mod h1:zransKiB9ftp+kgY8ydjnvCU7Wk8i9L0DYWpXeMj9ko= -github.com/helmfile/chartify v0.28.0 h1:AK04JykpFXQAtIbEN2DwAZsoSNdDZdAGY5W5FIoK8bk= -github.com/helmfile/chartify v0.28.0/go.mod h1:LKUELzZ2TaaCAD4EAFbaCkmRXNnt4uWlJts7GBdFVAQ= +github.com/helmfile/chartify v0.28.1 h1:dv7XfWT+OYtqSQ3f4SqXwUk6FyTuS6aH1M3+eF4DkLc= +github.com/helmfile/chartify v0.28.1/go.mod h1:LKUELzZ2TaaCAD4EAFbaCkmRXNnt4uWlJts7GBdFVAQ= github.com/helmfile/vals v0.45.0 h1:j5e9enLhBfaiYKKPJWEFAzW0DQJlVXyvnKPWWP3gdJg= github.com/helmfile/vals v0.45.0/go.mod h1:dJ5VGNN0cbusJoAFFINFeimbLV19hhtJJ1328ZFLJzA= github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog= diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/helmfile-1.7.2/pkg/app/app.go new/helmfile-1.7.3/pkg/app/app.go --- old/helmfile-1.7.2/pkg/app/app.go 2026-07-31 16:22:31.000000000 +0200 +++ new/helmfile-1.7.3/pkg/app/app.go 2026-08-04 12:31:11.000000000 +0200 @@ -1834,24 +1834,25 @@ detectedKubeVersion := a.detectKubeVersion(st) diffOpts := &state.DiffOpts{ - Color: c.Color(), - NoColor: c.NoColor(), - Context: c.Context(), - Output: c.DiffOutput(), - Set: c.Set(), - SkipCleanup: c.SkipCleanup(), - SkipDiffOnInstall: c.SkipDiffOnInstall(), - ReuseValues: c.ReuseValues(), - ResetValues: c.ResetValues(), - DiffArgs: c.DiffArgs(), - TemplateArgs: c.TemplateArgs(), - PostRenderer: c.PostRenderer(), - PostRendererArgs: c.PostRendererArgs(), - SkipSchemaValidation: c.SkipSchemaValidation(), - SuppressOutputLineRegex: c.SuppressOutputLineRegex(), - TakeOwnership: c.TakeOwnership(), - ServerSide: c.ServerSide(), - DetectedKubeVersion: detectedKubeVersion, + Color: c.Color(), + NoColor: c.NoColor(), + Context: c.Context(), + Output: c.DiffOutput(), + Set: c.Set(), + SkipCleanup: c.SkipCleanup(), + SkipDiffOnInstall: c.SkipDiffOnInstall(), + SkipDiffValidationOnInstall: c.SkipDiffValidationOnInstall(), + ReuseValues: c.ReuseValues(), + ResetValues: c.ResetValues(), + DiffArgs: c.DiffArgs(), + TemplateArgs: c.TemplateArgs(), + PostRenderer: c.PostRenderer(), + PostRendererArgs: c.PostRendererArgs(), + SkipSchemaValidation: c.SkipSchemaValidation(), + SuppressOutputLineRegex: c.SuppressOutputLineRegex(), + TakeOwnership: c.TakeOwnership(), + ServerSide: c.ServerSide(), + DetectedKubeVersion: detectedKubeVersion, } infoMsg, releasesToUpdate, releasesToDelete, diffErrs := r.diff(false, detailedExitCode, c, diffOpts) @@ -2122,23 +2123,24 @@ detectedKubeVersion := a.detectKubeVersion(st) opts := &state.DiffOpts{ - Context: c.Context(), - Output: c.DiffOutput(), - Color: c.Color(), - NoColor: c.NoColor(), - Set: c.Set(), - DiffArgs: c.DiffArgs(), - TemplateArgs: c.TemplateArgs(), - SkipDiffOnInstall: c.SkipDiffOnInstall(), - ReuseValues: c.ReuseValues(), - ResetValues: c.ResetValues(), - PostRenderer: c.PostRenderer(), - PostRendererArgs: c.PostRendererArgs(), - SkipSchemaValidation: c.SkipSchemaValidation(), - SuppressOutputLineRegex: c.SuppressOutputLineRegex(), - TakeOwnership: c.TakeOwnership(), - ServerSide: c.ServerSide(), - DetectedKubeVersion: detectedKubeVersion, + Context: c.Context(), + Output: c.DiffOutput(), + Color: c.Color(), + NoColor: c.NoColor(), + Set: c.Set(), + DiffArgs: c.DiffArgs(), + TemplateArgs: c.TemplateArgs(), + SkipDiffOnInstall: c.SkipDiffOnInstall(), + SkipDiffValidationOnInstall: c.SkipDiffValidationOnInstall(), + ReuseValues: c.ReuseValues(), + ResetValues: c.ResetValues(), + PostRenderer: c.PostRenderer(), + PostRendererArgs: c.PostRendererArgs(), + SkipSchemaValidation: c.SkipSchemaValidation(), + SuppressOutputLineRegex: c.SuppressOutputLineRegex(), + TakeOwnership: c.TakeOwnership(), + ServerSide: c.ServerSide(), + DetectedKubeVersion: detectedKubeVersion, } filtered := &Run{ @@ -2363,23 +2365,24 @@ if diffC, ok := c.(DiffConfigProvider); ok { detectedKubeVersion := a.detectKubeVersion(st) diffOpts := &state.DiffOpts{ - Context: diffC.Context(), - Output: diffC.DiffOutput(), - Color: diffC.Color(), - NoColor: diffC.NoColor(), - Set: diffC.Set(), - DiffArgs: diffC.DiffArgs(), - TemplateArgs: diffC.TemplateArgs(), - SkipDiffOnInstall: diffC.SkipDiffOnInstall(), - ReuseValues: diffC.ReuseValues(), - ResetValues: diffC.ResetValues(), - PostRenderer: diffC.PostRenderer(), - PostRendererArgs: diffC.PostRendererArgs(), - SkipSchemaValidation: diffC.SkipSchemaValidation(), - SuppressOutputLineRegex: diffC.SuppressOutputLineRegex(), - TakeOwnership: diffC.TakeOwnership(), - ServerSide: diffC.ServerSide(), - DetectedKubeVersion: detectedKubeVersion, + Context: diffC.Context(), + Output: diffC.DiffOutput(), + Color: diffC.Color(), + NoColor: diffC.NoColor(), + Set: diffC.Set(), + DiffArgs: diffC.DiffArgs(), + TemplateArgs: diffC.TemplateArgs(), + SkipDiffOnInstall: diffC.SkipDiffOnInstall(), + SkipDiffValidationOnInstall: diffC.SkipDiffValidationOnInstall(), + ReuseValues: diffC.ReuseValues(), + ResetValues: diffC.ResetValues(), + PostRenderer: diffC.PostRenderer(), + PostRendererArgs: diffC.PostRendererArgs(), + SkipSchemaValidation: diffC.SkipSchemaValidation(), + SuppressOutputLineRegex: diffC.SuppressOutputLineRegex(), + TakeOwnership: diffC.TakeOwnership(), + ServerSide: diffC.ServerSide(), + DetectedKubeVersion: detectedKubeVersion, } infoMsgPtr, _, _, diffErrs := r.diff(false, diffC.DetailedExitcode(), diffC, diffOpts) if len(diffErrs) > 0 { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/helmfile-1.7.2/pkg/app/app_test.go new/helmfile-1.7.3/pkg/app/app_test.go --- old/helmfile-1.7.2/pkg/app/app_test.go 2026-07-31 16:22:31.000000000 +0200 +++ new/helmfile-1.7.3/pkg/app/app_test.go 2026-08-04 12:31:11.000000000 +0200 @@ -2697,6 +2697,10 @@ return a.skipDiffOnInstall } +func (a applyConfig) SkipDiffValidationOnInstall() bool { + return false +} + func (a applyConfig) SyncArgs() string { return a.syncArgs } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/helmfile-1.7.2/pkg/app/config.go new/helmfile-1.7.3/pkg/app/config.go --- old/helmfile-1.7.2/pkg/app/config.go 2026-07-31 16:22:31.000000000 +0200 +++ new/helmfile-1.7.3/pkg/app/config.go 2026-08-04 12:31:11.000000000 +0200 @@ -86,6 +86,7 @@ Validate() bool SkipCleanup() bool SkipDiffOnInstall() bool + SkipDiffValidationOnInstall() bool DiffArgs() string SyncArgs() string @@ -182,6 +183,7 @@ NoHooks() bool SuppressDiff() bool SkipDiffOnInstall() bool + SkipDiffValidationOnInstall() bool DiffArgs() string TemplateArgs() string diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/helmfile-1.7.2/pkg/app/diff_test.go new/helmfile-1.7.3/pkg/app/diff_test.go --- old/helmfile-1.7.2/pkg/app/diff_test.go 2026-07-31 16:22:31.000000000 +0200 +++ new/helmfile-1.7.3/pkg/app/diff_test.go 2026-08-04 12:31:11.000000000 +0200 @@ -159,6 +159,10 @@ return a.skipDiffOnInstall } +func (a diffConfig) SkipDiffValidationOnInstall() bool { + return false +} + func (a diffConfig) Logger() *zap.SugaredLogger { return a.logger } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/helmfile-1.7.2/pkg/app/init.go new/helmfile-1.7.3/pkg/app/init.go --- old/helmfile-1.7.2/pkg/app/init.go 2026-07-31 16:22:31.000000000 +0200 +++ new/helmfile-1.7.3/pkg/app/init.go 2026-08-04 12:31:11.000000000 +0200 @@ -19,7 +19,7 @@ const ( HelmRequiredVersion = "v3.18.6" // Minimum required version (supports Helm 3.x and 4.x) - HelmDiffRecommendedVersion = "v3.15.10" + HelmDiffRecommendedVersion = "v3.15.11" HelmRecommendedVersion = "v4.2.3" // Recommended Helm 4 version HelmSecretsRecommendedVersion = "v4.7.4" // v4.7.0+ works with both Helm 3 (single plugin) and Helm 4 (split plugin architecture) HelmGitRecommendedVersion = "v1.3.0" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/helmfile-1.7.2/pkg/app/init_test.go new/helmfile-1.7.3/pkg/app/init_test.go --- old/helmfile-1.7.2/pkg/app/init_test.go 2026-07-31 16:22:31.000000000 +0200 +++ new/helmfile-1.7.3/pkg/app/init_test.go 2026-08-04 12:31:11.000000000 +0200 @@ -209,7 +209,7 @@ assert.Contains(t, err.Error(), "sh: not found") } -func TestCheckHelmPlugins_UpdateFailsFallbackToReinstall(t *testing.T) { +func TestCheckHelmPlugins_UpdateUsesUninstallReinstall(t *testing.T) { pluginsDir := t.TempDir() t.Setenv("HELM_PLUGINS", pluginsDir) @@ -221,9 +221,11 @@ // Track which plugin sub-commands were executed. var calledOps []string - // The mock runner simulates "helm plugin update" failing and falling back to - // "helm plugin uninstall" + "helm plugin install" which succeeds and writes the - // required version to disk. + // UpdatePlugin updates a plugin by uninstalling the stale version and reinstalling + // the exact pinned version (helm "plugin update" is intentionally NOT used because + // it does not honor --version and silently leaves the old version installed; see + // issues #2726 and #2548). This mock simulates a successful uninstall + install + // that writes the required version to disk. runner := &initMockRunner{ executeFunc: func(cmd string, args []string, env map[string]string, enableLiveOutput bool) ([]byte, error) { for _, a := range args { @@ -234,11 +236,13 @@ if len(args) >= 2 && args[0] == "plugin" { switch args[1] { case "update": + // UpdatePlugin must never invoke `helm plugin update` (it does not honor + // --version and silently leaves the old version installed; see #2726). + // Record the call so the NotContains assertion below can catch a regression. if len(args) >= 3 { calledOps = append(calledOps, "update:"+args[2]) } - // Simulate helm plugin update failing (as can happen with Helm 4) - return nil, helmexec.ExitError{Message: "plugin update failed", Code: 1} + return nil, helmexec.ExitError{Message: "plugin update must not be used", Code: 1} case "uninstall": if len(args) >= 3 { calledOps = append(calledOps, "uninstall:"+args[2]) @@ -266,13 +270,13 @@ h := NewHelmfileInit("helm", &mockInitConfigProvider{force: true}, newTestLogger(), runner) err := h.CheckHelmPlugins() - // Should succeed: update failed but fallback reinstall updated the plugin + // Should succeed: uninstall + reinstall updated each plugin to the pinned version assert.NoError(t, err) - // Verify that for each plugin the fallback path was taken: - // update was attempted, then uninstall + install were called. + // Verify that for each plugin the reliable update path was taken: + // the unreliable "plugin update" is never called; only uninstall + install are. for _, p := range helmPlugins { - assert.Contains(t, calledOps, "update:"+p.name, "expected update to be attempted for plugin %s", p.name) + assert.NotContains(t, calledOps, "update:"+p.name, "'plugin update' must not be used for plugin %s", p.name) assert.Contains(t, calledOps, "uninstall:"+p.name, "expected uninstall to be called for plugin %s", p.name) assert.Contains(t, calledOps, "install:"+p.name, "expected install to be called for plugin %s", p.name) } @@ -298,9 +302,8 @@ } // The mock runner simulates: - // 1. "helm plugin update" failing - // 2. "helm plugin uninstall" succeeding - // 3. "helm plugin install" writing the correct version but returning an error + // 1. "helm plugin uninstall" succeeding + // 2. "helm plugin install" writing the correct version but returning an error // (e.g., post-install script error on Windows) // In this case, UpdatePlugin returns the install error, but CheckHelmPlugins // verifies the version and warns instead of returning an error. @@ -313,8 +316,6 @@ } if len(args) >= 2 && args[0] == "plugin" { switch args[1] { - case "update": - return nil, helmexec.ExitError{Message: "plugin update failed", Code: 1} case "uninstall": return []byte{}, nil case "install": @@ -338,7 +339,7 @@ h := NewHelmfileInit("helm", &mockInitConfigProvider{force: true}, newTestLogger(), runner) err := h.CheckHelmPlugins() - // Should succeed: UpdatePlugin returned an error (from the fallback install step), + // Should succeed: UpdatePlugin returned an error (from the reinstall step), // but the plugin is present at the required version, so CheckHelmPlugins warns and continues. assert.NoError(t, err) } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/helmfile-1.7.2/pkg/config/apply.go new/helmfile-1.7.3/pkg/config/apply.go --- old/helmfile-1.7.2/pkg/config/apply.go 2026-07-31 16:22:31.000000000 +0200 +++ new/helmfile-1.7.3/pkg/config/apply.go 2026-08-04 12:31:11.000000000 +0200 @@ -37,6 +37,8 @@ EnforceNeedsAreInstalled bool // SkipDiffOnInstall is true if the diff should be skipped on install SkipDiffOnInstall bool + // SkipDiffValidationOnInstall disables K8s API validation when running helm-diff on a release being newly installed + SkipDiffValidationOnInstall bool // DiffArgs is the list of arguments to pass to the helm-diff. DiffArgs string // IncludeTests is true if the tests should be included @@ -199,6 +201,11 @@ return a.ApplyOptions.SkipDiffOnInstall } +// SkipDiffValidationOnInstall returns the skip diff validation on install. +func (a *ApplyImpl) SkipDiffValidationOnInstall() bool { + return a.ApplyOptions.SkipDiffValidationOnInstall +} + // DiffArgs is the list of arguments to pass to helm-diff. func (a *ApplyImpl) DiffArgs() string { return a.ApplyOptions.DiffArgs diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/helmfile-1.7.2/pkg/config/diff.go new/helmfile-1.7.3/pkg/config/diff.go --- old/helmfile-1.7.2/pkg/config/diff.go 2026-07-31 16:22:31.000000000 +0200 +++ new/helmfile-1.7.3/pkg/config/diff.go 2026-08-04 12:31:11.000000000 +0200 @@ -22,6 +22,8 @@ EnforceNeedsAreInstalled bool // SkipDiffOnInstall is the skip diff on install flag SkipDiffOnInstall bool + // SkipDiffValidationOnInstall disables K8s API validation when running helm-diff on a release being newly installed + SkipDiffValidationOnInstall bool // ShowSecrets is the show secrets flag ShowSecrets bool // NoHooks skips hooks during diff @@ -168,6 +170,11 @@ return t.DiffOptions.SkipDiffOnInstall } +// SkipDiffValidationOnInstall returns the skip diff validation on install +func (t *DiffImpl) SkipDiffValidationOnInstall() bool { + return t.DiffOptions.SkipDiffValidationOnInstall +} + // DiffArgs returns the list of arguments to pass to helm-diff. func (t *DiffImpl) DiffArgs() string { return t.DiffOptions.DiffArgs diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/helmfile-1.7.2/pkg/config/sync.go new/helmfile-1.7.3/pkg/config/sync.go --- old/helmfile-1.7.2/pkg/config/sync.go 2026-07-31 16:22:31.000000000 +0200 +++ new/helmfile-1.7.3/pkg/config/sync.go 2026-08-04 12:31:11.000000000 +0200 @@ -75,19 +75,20 @@ TemplateArgs string // Diff-related options for --interactive mode - SuppressOutputLineRegex []string - IncludeTests bool - Suppress []string - SuppressSecrets bool - ShowSecrets bool - NoHooks bool - SuppressDiff bool - SkipDiffOnInstall bool - DiffArgs string - DetailedExitcode bool - StripTrailingCR bool - Context int - DiffOutput string + SuppressOutputLineRegex []string + IncludeTests bool + Suppress []string + SuppressSecrets bool + ShowSecrets bool + NoHooks bool + SuppressDiff bool + SkipDiffOnInstall bool + SkipDiffValidationOnInstall bool + DiffArgs string + DetailedExitcode bool + StripTrailingCR bool + Context int + DiffOutput string } // NewSyncOptions creates a new Apply @@ -308,6 +309,11 @@ return t.SyncOptions.SkipDiffOnInstall } +// SkipDiffValidationOnInstall returns the SkipDiffValidationOnInstall. +func (t *SyncImpl) SkipDiffValidationOnInstall() bool { + return t.SyncOptions.SkipDiffValidationOnInstall +} + // DiffArgs returns the DiffArgs. func (t *SyncImpl) DiffArgs() string { return t.SyncOptions.DiffArgs diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/helmfile-1.7.2/pkg/helmexec/exec.go new/helmfile-1.7.3/pkg/helmexec/exec.go --- old/helmfile-1.7.2/pkg/helmexec/exec.go 2026-07-31 16:22:31.000000000 +0200 +++ new/helmfile-1.7.3/pkg/helmexec/exec.go 2026-08-04 12:31:11.000000000 +0200 @@ -10,6 +10,7 @@ "os" "path/filepath" "reflect" + "regexp" "strconv" "strings" "sync" @@ -1134,6 +1135,18 @@ // split plugin architecture (secrets, secrets-getter, secrets-post-renderer) with Helm 4. var helmSecretsV4SplitMinVersion = semver.MustParse("4.7.0") +// pluginMissingRe matches helm's "plugin absent" error emitted by `helm plugin +// uninstall` when the plugin is not installed: +// +// Helm 4: "plugin: <name> not found" +// Helm 3: "Plugin: <name> not found" +// +// It is intentionally specific so that unrelated failures that happen to contain +// "not found" (e.g. a missing helm binary -> "executable file not found", or an +// uninstall hook failing with "sh: ...: not found") are NOT mistaken for an +// absent plugin. It is case-insensitive and scoped to a single line. +var pluginMissingRe = regexp.MustCompile(`(?i)plugin: .* not found`) + // helmSecretsRequiresSplitInstall returns true when the given helm-secrets version // requires the split plugin architecture introduced in v4.7.0 for Helm 4. func helmSecretsRequiresSplitInstall(version string) bool { @@ -1167,22 +1180,28 @@ return helm.installHelmSecretsV4(version) } - // Try standard helm plugin update - out, err := helm.exec([]string{"plugin", "update", name}, map[string]string{}, nil) - helm.info(out) - if err != nil { - // If standard update failed, fall back to uninstall + reinstall with specific version - updateErr := err - helm.logger.Infof("helm plugin update %v failed (%v), falling back to reinstall with version %v", name, updateErr, version) - if uninstallErr := helm.uninstallPlugin(name); uninstallErr != nil { - return fmt.Errorf("helm plugin update failed (%w) and uninstall for reinstall also failed: %w", updateErr, uninstallErr) - } - if reinstallErr := helm.AddPlugin(name, repo, version); reinstallErr != nil { - return fmt.Errorf("helm plugin update failed (%w) and reinstall also failed: %w", updateErr, reinstallErr) + // `helm plugin update` re-installs the plugin from its cached source WITHOUT the + // `--version` flag, so it does not reliably install the specific version we need. + // On many setups it reports success (exit code 0) while `helm plugin list` still + // shows the old version, because the cached source is re-downloaded unchanged. + // See https://github.com/helmfile/helmfile/issues/2726 and + // https://github.com/helmfile/helmfile/issues/2548. + // + // The reliable way to update to a pinned version is to uninstall the existing + // plugin and reinstall it at the requested version. Only the expected + // "plugin already absent" case is tolerated: helm reports it as + // "plugin: <name> not found" (Helm 4) / "Plugin: <name> not found" (Helm 3). + // We match that specific message rather than a bare "not found", so that other + // failures (permissions, a missing helm binary whose error contains + // "executable file not found", a plugin uninstall hook failing with + // "sh: ...: not found", ...) are surfaced instead of being silently ignored. + if err := helm.uninstallPlugin(name); err != nil { + if !pluginMissingRe.MatchString(err.Error()) { + return fmt.Errorf("failed to uninstall helm plugin %q for reinstall: %w", name, err) } - return nil + helm.logger.Debugf("helm plugin %v not present during update, proceeding to install: %v", name, err) } - return nil + return helm.AddPlugin(name, repo, version) } func (helm *execer) exec(args []string, env map[string]string, overrideEnableLiveOutput *bool) ([]byte, error) { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/helmfile-1.7.2/pkg/helmexec/exec_test.go new/helmfile-1.7.3/pkg/helmexec/exec_test.go --- old/helmfile-1.7.2/pkg/helmexec/exec_test.go 2026-07-31 16:22:31.000000000 +0200 +++ new/helmfile-1.7.3/pkg/helmexec/exec_test.go 2026-08-04 12:31:11.000000000 +0200 @@ -20,6 +20,14 @@ "go.uber.org/zap" ) +// pluginCmd is the "plugin" helm subcommand used repeatedly across these tests. +// Extracted as constants so the repeated string literals do not trip goconst +// (min-occurrences: 8) once additional plugin tests are added. +const ( + pluginCmd = "plugin" + installCmd = "install" +) + // Mocking the command-line runner type mockRunner struct { @@ -1917,7 +1925,7 @@ // Verify that "plugin update" was NOT called (the Helm 4 secrets path should skip it). for _, args := range calledArgs { for i, a := range args { - if a == "plugin" && i+1 < len(args) && args[i+1] == "update" { + if a == pluginCmd && i+1 < len(args) && args[i+1] == "update" { t.Errorf("expected 'plugin update' to not be called for Helm 4 secrets, but it was: %v", args) } } @@ -1927,7 +1935,7 @@ checkUninstall := func(name string) { for _, args := range calledArgs { for i, a := range args { - if a == "plugin" && i+2 < len(args) && args[i+1] == "uninstall" && args[i+2] == name { + if a == pluginCmd && i+2 < len(args) && args[i+1] == "uninstall" && args[i+2] == name { return } } @@ -1942,7 +1950,7 @@ checkInstall := func(urlSubstring string) { for _, args := range calledArgs { for i, a := range args { - if a == "plugin" && i+2 < len(args) && args[i+1] == "install" && strings.Contains(args[i+2], urlSubstring) { + if a == pluginCmd && i+2 < len(args) && args[i+1] == installCmd && strings.Contains(args[i+2], urlSubstring) { return } } @@ -1954,6 +1962,204 @@ checkInstall("secrets-post-renderer-4.7.0.tgz") } +// Test_UpdatePlugin_GeneralPathUsesUninstallReinstall verifies that for a regular +// plugin, UpdatePlugin does NOT rely on the unreliable `helm plugin update` +// command. Instead it must uninstall the existing plugin and reinstall the exact +// pinned version. See issues #2726 and #2548: `helm plugin update` reports +// success but leaves `helm plugin list` showing the old version because it +// re-installs from the cached source without the --version flag. +func Test_UpdatePlugin_GeneralPathUsesUninstallReinstall(t *testing.T) { + var calledArgs [][]string + runner := &funcRunner{ + execute: func(cmd string, args []string, env map[string]string, enableLiveOutput bool) ([]byte, error) { + calledArgs = append(calledArgs, append([]string(nil), args...)) + return []byte{}, nil + }, + } + + var buffer bytes.Buffer + logger := NewLogger(&buffer, "debug") + helm := &execer{ + helmBinary: "helm", + version: semver.MustParse("3.16.4"), + logger: logger, + runner: runner, + } + + err := helm.UpdatePlugin("diff", "https://github.com/databus23/helm-diff", "v3.15.10") + require.NoError(t, err) + + // `plugin update` must never be used: it does not honor --version and silently + // leaves the old version installed. + for _, args := range calledArgs { + for i, a := range args { + if a == pluginCmd && i+1 < len(args) && args[i+1] == "update" { + t.Errorf("expected 'plugin update' to not be called, but it was: %v", args) + } + } + } + + // `plugin uninstall diff` must be called to clear the stale version. + uninstalled := false + for _, args := range calledArgs { + for i, a := range args { + if a == pluginCmd && i+2 < len(args) && args[i+1] == "uninstall" && args[i+2] == "diff" { + uninstalled = true + } + } + } + require.True(t, uninstalled, "expected 'plugin uninstall diff' to be called") + + // `plugin install <repo> --version <pinned>` must be called with the exact + // requested version so the installed plugin is updated to it. + installed := false + for _, args := range calledArgs { + for i, a := range args { + if a == pluginCmd && i+1 < len(args) && args[i+1] == installCmd { + hasRepo := false + hasVersion := false + for _, arg := range args[i+2:] { + if arg == "https://github.com/databus23/helm-diff" { + hasRepo = true + } + if arg == "v3.15.10" { + hasVersion = true + } + } + if hasRepo && hasVersion { + installed = true + } + } + } + } + require.True(t, installed, "expected 'plugin install' with the pinned version to be called") +} + +// Test_UpdatePlugin_NotFoundUninstallProceedsToInstall ensures that when the +// plugin is already absent (helm reports its "plugin not found" message), +// UpdatePlugin still proceeds to install the pinned version rather than treating +// it as an error. Both helm major formats are covered. +func Test_UpdatePlugin_NotFoundUninstallProceedsToInstall(t *testing.T) { + // Helm 4 reports "plugin: <name> not found"; Helm 3 reports "Plugin: <name> not found". + for _, tc := range []struct { + name string + version string + msg string + }{ + {name: "helm4", version: "4.2.3", msg: "plugin: diff not found"}, + {name: "helm3", version: "3.16.4", msg: "Plugin: diff not found"}, + } { + t.Run(tc.name, func(t *testing.T) { + var calledArgs [][]string + runner := &funcRunner{ + execute: func(cmd string, args []string, env map[string]string, enableLiveOutput bool) ([]byte, error) { + calledArgs = append(calledArgs, append([]string(nil), args...)) + if len(args) >= 2 && args[0] == pluginCmd && args[1] == "uninstall" { + return nil, ExitError{Message: tc.msg, Code: 1} + } + return []byte{}, nil + }, + } + + var buffer bytes.Buffer + logger := NewLogger(&buffer, "debug") + helm := &execer{ + helmBinary: "helm", + version: semver.MustParse(tc.version), + logger: logger, + runner: runner, + } + + err := helm.UpdatePlugin("diff", "https://github.com/databus23/helm-diff", "v3.15.10") + require.NoError(t, err, "a plugin-absent uninstall error should be ignored and install should proceed") + + // install with the pinned version must still be attempted + installed := false + for _, args := range calledArgs { + for i, a := range args { + if a == pluginCmd && i+1 < len(args) && args[i+1] == installCmd { + for _, arg := range args[i+2:] { + if arg == "v3.15.10" { + installed = true + } + } + } + } + } + require.True(t, installed, "expected install to proceed after a plugin-absent uninstall") + }) + } +} + +// Test_UpdatePlugin_RealUninstallFailureReturnsError ensures that a genuine +// uninstall failure (permissions, broken Helm, etc.) is surfaced instead of +// being ignored, which would otherwise mask the root cause behind a confusing +// "plugin already exists" error from the subsequent install. +func Test_UpdatePlugin_RealUninstallFailureReturnsError(t *testing.T) { + installCalled := false + runner := &funcRunner{ + execute: func(cmd string, args []string, env map[string]string, enableLiveOutput bool) ([]byte, error) { + if len(args) >= 2 && args[0] == pluginCmd && args[1] == "uninstall" { + return nil, ExitError{Message: "permission denied", Code: 1} + } + if len(args) >= 2 && args[0] == pluginCmd && args[1] == installCmd { + installCalled = true + } + return []byte{}, nil + }, + } + + var buffer bytes.Buffer + logger := NewLogger(&buffer, "debug") + helm := &execer{ + helmBinary: "helm", + version: semver.MustParse("3.16.4"), + logger: logger, + runner: runner, + } + + err := helm.UpdatePlugin("diff", "https://github.com/databus23/helm-diff", "v3.15.10") + require.Error(t, err, "a real uninstall failure should be returned") + assert.Contains(t, err.Error(), "uninstall") + assert.False(t, installCalled, "install must not be attempted after a real uninstall failure") +} + +// Test_UpdatePlugin_ExecutableNotFoundUninstallErrorIsNotSwallowed guards against +// an overly broad "not found" check: when helm itself is missing the runner +// surfaces an error containing "executable file not found", which must NOT be +// mistaken for an absent plugin (otherwise UpdatePlugin would silently log and +// proceed to install, masking the real problem). Only the helm-specific +// "plugin: <name> not found" message is tolerated. +func Test_UpdatePlugin_ExecutableNotFoundUninstallErrorIsNotSwallowed(t *testing.T) { + installCalled := false + runner := &funcRunner{ + execute: func(cmd string, args []string, env map[string]string, enableLiveOutput bool) ([]byte, error) { + if len(args) >= 2 && args[0] == pluginCmd && args[1] == "uninstall" { + // Mimics helmfile's ShellRunner when the helm binary is absent. + return nil, fmt.Errorf("unexpected error: exec: %q: executable file not found in $PATH", cmd) + } + if len(args) >= 2 && args[0] == pluginCmd && args[1] == installCmd { + installCalled = true + } + return []byte{}, nil + }, + } + + var buffer bytes.Buffer + logger := NewLogger(&buffer, "debug") + helm := &execer{ + helmBinary: "helm", + version: semver.MustParse("3.16.4"), + logger: logger, + runner: runner, + } + + err := helm.UpdatePlugin("diff", "https://github.com/databus23/helm-diff", "v3.15.10") + require.Error(t, err, "a missing-binary uninstall error must be returned, not swallowed") + assert.Contains(t, err.Error(), "executable file not found") + assert.False(t, installCalled, "install must not be attempted when the helm binary is missing") +} + func Test_dedupeWroteLines(t *testing.T) { cases := []struct { name string diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/helmfile-1.7.2/pkg/state/issue_1757_test.go new/helmfile-1.7.3/pkg/state/issue_1757_test.go --- old/helmfile-1.7.2/pkg/state/issue_1757_test.go 1970-01-01 01:00:00.000000000 +0100 +++ new/helmfile-1.7.3/pkg/state/issue_1757_test.go 2026-08-04 12:31:11.000000000 +0200 @@ -0,0 +1,200 @@ +package state + +import ( + "errors" + "os" + "os/exec" + "path/filepath" + "runtime" + "testing" + + "github.com/helmfile/chartify" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + + "github.com/helmfile/helmfile/pkg/filesystem" +) + +// TestIsChartifyEmptyRenderOutputError verifies that isChartifyEmptyRenderOutputError +// only matches chartify's specific "empty rendered output dir" assertion failure, not +// other, unrelated chartify errors. This is a regression test for issue #1757: a chart +// that renders zero resources (e.g. everything gated behind a falsy `if`) combined with +// transformers/jsonPatches/strategicMergePatches used to crash helmfile with +// +// assertion failed: unexpected dir entry "" it must be the abs path to the output directory +// +// because chartify expects exactly one rendered output directory and found none. +func TestIsChartifyEmptyRenderOutputError(t *testing.T) { + tests := []struct { + name string + err error + expected bool + }{ + { + name: "nil error", + err: nil, + expected: false, + }, + { + name: "chartify empty render assertion error", + err: errors.New(`assertion failed: unexpected dir entry "" it must be the abs path to the output directory`), + expected: true, + }, + { + // Same assertion, but chartOutputDir is a non-empty (merely + // relative) path rather than "" - a different, hypothetical + // chartify bug that happens to trip the same final assertion. + // This must NOT be treated as the empty-render no-op case: + // per review feedback (https://github.com/helmfile/helmfile/pull/2724), + // matching only on the trailing "...it must be the abs path to + // the output directory" phrase (without requiring the `""` + // empty-string dir entry) would have incorrectly matched this + // too, silently masking a genuinely different failure. + name: "same assertion with a non-empty dir entry is not the empty-render case", + err: errors.New(`assertion failed: unexpected dir entry "relative/path" it must be the abs path to the output directory`), + expected: false, + }, + { + name: "unrelated chartify error", + err: errors.New("exec: \"kustomize\": executable file not found in %PATH%"), + expected: false, + }, + { + name: "unrelated multiple-dir-entries assertion", + err: errors.New(`assertion failed: there should be only one dir entry under the helm output dir /tmp/foo`), + expected: false, + }, + { + name: "helm template failure", + err: errors.New("Error: template: mychart/templates/broken.yaml:3:5: executing..."), + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.expected, isChartifyEmptyRenderOutputError(tt.err)) + }) + } +} + +// TestProcessChartification_EmptyRenderReturnsSurvivingPath is an end-to-end +// regression test for a bug found in review of the #1757 fix: the empty-render +// no-op path must return the chart's original path, not the temp copy that +// rewriteChartDependencies creates when the chart has relative file:// deps - +// that temp dir is removed by a deferred cleanup as soon as processChartification +// returns, so returning it would hand the caller a path to a directory that no +// longer exists on disk. +// +// This exercises the real processChartification -> chartify.Chartify wiring +// (unlike TestIsChartifyEmptyRenderOutputError above, which only tests the pure +// string-matching helper), so it needs real helm and kustomize binaries on PATH +// and is skipped if either is missing. Verified to pass on Linux; skipped on +// Windows because the file:// dependency URL this test needs (to make +// rewriteChartDependencies actually produce a temp copy) hits an unrelated, +// pre-existing Windows path-handling issue in chartify/helm's dependency +// resolution (a Windows drive letter embedded in a file:// URL gets +// mis-joined onto another path), independent of the fix under test here. +func TestProcessChartification_EmptyRenderReturnsSurvivingPath(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("skipping on Windows: unrelated file:// dependency URL / drive letter handling issue in chartify's helm dependency resolution, not the code path under test; passes on Linux (CI)") + } + if _, err := exec.LookPath("helm"); err != nil { + t.Skip("helm not found on PATH, skipping") + } + if _, err := exec.LookPath("kustomize"); err != nil { + t.Skip("kustomize not found on PATH, skipping") + } + + tempDir := t.TempDir() + + // A sibling chart referenced via a relative file:// dependency, so that + // processChartification takes the rewriteChartDependencies path (line + // `if st.fs.DirectoryExistsAt(chartPath) { ... }`) and reassigns its local + // chartPath variable to a temp copy before ever calling chartify. + depChartDir := filepath.Join(tempDir, "dep-chart") + require.NoError(t, os.MkdirAll(depChartDir, 0755)) + require.NoError(t, os.WriteFile(filepath.Join(depChartDir, "Chart.yaml"), []byte(` +apiVersion: v2 +name: dep-chart +version: 0.1.0 +`), 0644)) + + chartDir := filepath.Join(tempDir, "chart") + require.NoError(t, os.MkdirAll(filepath.Join(chartDir, "templates"), 0755)) + require.NoError(t, os.WriteFile(filepath.Join(chartDir, "Chart.yaml"), []byte(` +apiVersion: v2 +name: emptychart +version: 0.1.0 +dependencies: + - name: dep-chart + version: 0.1.0 + repository: "file://../dep-chart" +`), 0644)) + require.NoError(t, os.WriteFile(filepath.Join(chartDir, "values.yaml"), []byte("enabled: false\n"), 0644)) + // Every template is gated behind a falsy condition, so helm renders zero + // resources - the exact condition that triggers chartify's empty-output + // assertion. + require.NoError(t, os.WriteFile(filepath.Join(chartDir, "templates", "deployment.yaml"), []byte(` +{{- if .Values.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: test +{{- end }} +`), 0644)) + + // A transformer is required for chartify to be invoked at all (see the + // call site in state.go: chartification is only non-nil, and processChartification + // only gets called, when transformers/jsonPatches/strategicMergePatches are set). + transformerPath := filepath.Join(tempDir, "transformer.yaml") + require.NoError(t, os.WriteFile(transformerPath, []byte(` +apiVersion: builtin +kind: AnnotationsTransformer +metadata: + name: notImportantHere +annotations: + area: "51" +fieldSpecs: + - path: metadata/annotations + create: true +`), 0644)) + + st := &HelmState{ + logger: zap.NewNop().Sugar(), + fs: filesystem.DefaultFileSystem(), + ReleaseSetSpec: ReleaseSetSpec{ + DefaultHelmBinary: "helm", + DefaultKustomizeBinary: "kustomize", + }, + } + + chartification := &Chartify{ + Opts: &chartify.ChartifyOpts{ + Transformers: []string{transformerPath}, + }, + } + release := &ReleaseSpec{} + release.Name = "empty-release" + + resultPath, buildDeps, err := st.processChartification( + chartification, release, chartDir, ChartPrepareOptions{}, false, "template", + ) + + require.NoError(t, err) + assert.True(t, buildDeps, "buildDeps should be true (!skipDeps) for the no-op path") + + // The returned path must still exist: it must be the original chart + // directory, not the deps-rewritten temp copy that gets deleted by + // rewriteChartDependencies' deferred cleanup on return. + info, statErr := os.Stat(resultPath) + require.NoError(t, statErr, "returned chart path %q must still exist after processChartification returns", resultPath) + assert.True(t, info.IsDir()) + + // It should specifically be the chart's own directory (or an + // equally-valid, not-yet-cleaned-up path to the same chart), not some + // other temp directory. Chart.yaml must be readable from it. + _, err = os.Stat(filepath.Join(resultPath, "Chart.yaml")) + assert.NoError(t, err, "Chart.yaml should be reachable from the returned path %q", resultPath) +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/helmfile-1.7.2/pkg/state/state.go new/helmfile-1.7.3/pkg/state/state.go --- old/helmfile-1.7.2/pkg/state/state.go 2026-07-31 16:22:31.000000000 +0200 +++ new/helmfile-1.7.3/pkg/state/state.go 2026-08-04 12:31:11.000000000 +0200 @@ -1868,6 +1868,11 @@ // If exists, it will also patch resources by json patches, strategic-merge patches, and injectors. // processChartification handles the chartification process func (st *HelmState) processChartification(chartification *Chartify, release *ReleaseSpec, chartPath string, opts ChartPrepareOptions, skipDeps bool, helmfileCommand string) (string, bool, error) { + // Preserved so the empty-render no-op path below can return this instead of the + // deps-rewritten temp copy assigned to chartPath further down, which the deferred + // cleanupTempChart() removes as soon as this function returns. + originalChartPath := chartPath + // Rewrite relative file:// dependencies in Chart.yaml to absolute paths before chartify processes them // This prevents errors like "Error: directory /tmp/chartify.../argocd-application not found" // when Chart.yaml contains dependencies like "file://../argocd-application" @@ -1939,6 +1944,21 @@ ) out, err := c.Chartify(release.Name, chartPath, chartify.WithChartifyOpts(chartifyOpts)) + if err != nil && isChartifyEmptyRenderOutputError(err) { + // The chart rendered zero resources (e.g. everything is gated behind a + // `{{- if .Values.enabled }}` that evaluated to false), so chartify has + // nothing to replace templates/charts/crds with and fails its internal + // "there must be exactly one rendered output dir" assertion. Treat this + // as a no-op: use the chart as-is, since there's nothing to chartify. + // See https://github.com/helmfile/helmfile/issues/1757 and the upstream + // tracking issue https://github.com/helmfile/chartify/issues/206. + st.logger.Debugf("release %q: chart rendered no resources, skipping chartification: %v", release.Name, err) + // Return originalChartPath, NOT chartPath: chartPath may have been reassigned + // above to the deps-rewritten temp copy, which the deferred cleanupTempChart() + // removes as soon as this function returns. Since chartify never actually ran, + // relative file:// deps resolve fine from the original location anyway. + return originalChartPath, !skipDeps, nil + } if err != nil { return "", false, err } @@ -1954,6 +1974,34 @@ return chartPath, buildDeps, nil } +// chartifyEmptyRenderOutputErrSubstring is the distinctive part of the error that +// github.com/helmfile/chartify (as of v0.28.0, see replace.go) returns when `helm template` +// renders zero resources for a chart: it expects exactly one directory entry under its +// `--output-dir`, and an empty render leaves that directory empty, so the "must be the abs +// path to the output directory" assertion fails on the resulting empty string. Chartify does +// not expose a typed/sentinel error for this case, so we match on the error text. +// +// The matched string includes the `unexpected dir entry ""` prefix (not just the trailing +// "...it must be the abs path to the output directory" phrase) so this can only match when +// chartify's chartOutputDir was genuinely empty - i.e. the exact empty-render case - and not +// some other, hypothetical failure of the same assertion against a non-empty (but still +// relative) path, which would be a different bug that should still be surfaced as an error. +// +// If this substring ever stops matching a real chartify error, chartify's wording has +// changed and this check needs to be revisited. +// +// Tracked upstream at https://github.com/helmfile/chartify/issues/206 - once chartify +// exposes a sentinel error (or treats an empty render as a no-op itself) and this repo +// bumps to that version, this text match can be retired. +const chartifyEmptyRenderOutputErrSubstring = `unexpected dir entry "" it must be the abs path to the output directory` + +// isChartifyEmptyRenderOutputError reports whether err is chartify's assertion failure caused +// by a chart rendering zero resources, as opposed to some other, unrelated chartify failure +// that should still be surfaced to the user. +func isChartifyEmptyRenderOutputError(err error) bool { + return err != nil && strings.Contains(err.Error(), chartifyEmptyRenderOutputErrSubstring) +} + func (st *HelmState) appendSkipSchemaValidationFlagToChartifyTemplateArgs(templateArgs string, release *ReleaseSpec, skipSchemaValidation bool) string { if !st.shouldSkipSchemaValidation(release, skipSchemaValidation) || hasTemplateArg(templateArgs, "--skip-schema-validation") { return templateArgs @@ -2942,7 +2990,7 @@ } var disableValidation bool - if release.DisableValidationOnInstall != nil && *release.DisableValidationOnInstall { + if (release.DisableValidationOnInstall != nil && *release.DisableValidationOnInstall) || opt.SkipDiffValidationOnInstall { installed, err := isInstalled(release) if err != nil { errs = append(errs, err) @@ -3042,11 +3090,12 @@ Color bool // NoColor forces disabling the color output on helm-diff. // If this is true, Color has no effect. - NoColor bool - Set []string - SkipCleanup bool - SkipDiffOnInstall bool - DiffArgs string + NoColor bool + Set []string + SkipCleanup bool + SkipDiffOnInstall bool + SkipDiffValidationOnInstall bool + DiffArgs string // TemplateArgs are extra args appended to the helm template/diff rendering // (e.g. "--dry-run=server" to enable the helm lookup function during `helmfile // apply`/`diff`, which render via helm-diff). See issue #1833. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/helmfile-1.7.2/pkg/state/state_test.go new/helmfile-1.7.3/pkg/state/state_test.go --- old/helmfile-1.7.2/pkg/state/state_test.go 2026-07-31 16:22:31.000000000 +0200 +++ new/helmfile-1.7.3/pkg/state/state_test.go 2026-08-04 12:31:11.000000000 +0200 @@ -4268,6 +4268,87 @@ } } +func TestPrepareDiffReleases_SkipDiffValidationOnInstall(t *testing.T) { + installedListOutput := "NAME\tNAMESPACE\tREVISION\tSTATUS\nfoo\tdefault\t1\tdeployed" + listFlags := "--uninstalling --deployed --failed --pending" + + tests := []struct { + name string + skipDiffValidationOnInstall bool + perReleaseDisableValidation bool + installed bool + wantDisableValidation bool + }{ + { + name: "flag-set-not-installed", + skipDiffValidationOnInstall: true, + installed: false, + wantDisableValidation: true, + }, + { + name: "flag-set-already-installed", + skipDiffValidationOnInstall: true, + installed: true, + wantDisableValidation: false, + }, + { + name: "flag-not-set-not-installed", + skipDiffValidationOnInstall: false, + installed: false, + wantDisableValidation: false, + }, + { + name: "per-release-flag-not-installed", + perReleaseDisableValidation: true, + installed: false, + wantDisableValidation: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + release := ReleaseSpec{ + Name: "foo", + Chart: "stable/foo", + } + if tt.perReleaseDisableValidation { + release.DisableValidationOnInstall = boolValue(true) + } + st := &HelmState{ + ReleaseSetSpec: ReleaseSetSpec{ + Releases: []ReleaseSpec{release}, + HelmDefaults: HelmSpec{}, + }, + logger: logger, + valsRuntime: valsRuntime, + } + lists := map[exectest.ListKey]string{} + if tt.installed { + lists[exectest.ListKey{Filter: "^foo$", Flags: listFlags}] = installedListOutput + } + helm := &exectest.Helm{Lists: lists} + + results, errs := st.prepareDiffReleases(helm, []string{}, 1, false, false, false, []string{}, false, false, false, &DiffOpts{ + SkipDiffValidationOnInstall: tt.skipDiffValidationOnInstall, + }) + + require.Len(t, errs, 0) + require.Len(t, results, 1) + + flags := results[0].flags + hasDisableValidation := false + for _, f := range flags { + if f == "--disable-validation" { + hasDisableValidation = true + break + } + } + require.Equal(t, tt.wantDisableValidation, hasDisableValidation, + "--disable-validation presence mismatch for case %q; flags: %v", tt.name, flags) + }) + } +} + func TestPrepareSyncReleases(t *testing.T) { tests := []struct { name string ++++++ vendor.tar.gz ++++++ /work/SRC/openSUSE:Factory/helmfile/vendor.tar.gz /work/SRC/openSUSE:Factory/.helmfile.new.16738/vendor.tar.gz differ: char 121, line 1
