Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package helm for openSUSE:Factory checked in at 2026-08-22 21:35:21 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/helm (Old) and /work/SRC/openSUSE:Factory/.helm.new.1258 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "helm" Sat Aug 22 21:35:21 2026 rev:112 rq:1372954 version:4.2.4 Changes: -------- --- /work/SRC/openSUSE:Factory/helm/helm.changes 2026-07-28 18:08:57.219741159 +0200 +++ /work/SRC/openSUSE:Factory/.helm.new.1258/helm.changes 2026-08-22 21:37:30.054131173 +0200 @@ -1,0 +2,30 @@ +Fri Aug 21 22:06:09 UTC 2026 - Dirk Müller <[email protected]> + +- update vendor.tar to fix + * bsc#1275025, CVE-2026-56864 + * bsc#1275024, CVE-2026-56865 + * bsc#1271462, CVE-2026-50163 + +------------------------------------------------------------------- +Fri Aug 14 05:28:46 UTC 2026 - Johannes Kastl <[email protected]> + +- update to 4.2.4: + * fix: Improve error reporting for helm template --debug with + --show-only- #31185 + * fix: fetch logs from all containers in test pods- #32099 + * fix(provenance): check error return in Digest and + encodeRelease- #32136 + * fix panic on repeated IsReachable calls- #32184 + * fix: set [pull,push] scope when helm push to a registry(use + token auth) - v4- #31211 + * Fix missing conflict retry with server-side apply- #32088 + * Properly format the extra field in gzipped packages- #31884 + * Fix vanishing empty lines- #32327 + * fix: pass registry client to downloader.Manager in upgrade- + #32400 + * chore(deps): bump google.golang.org/grpc from 1.80.0 to 1.82.1- + #32450 + * fix: bump go.opentelemetry.io/otel to v1.44.0 for GO-2026-5158- + #32521 + +------------------------------------------------------------------- Old: ---- helm-4.2.3.obscpio vendor.tar.gz New: ---- helm-4.2.4.obscpio vendor.tar.zst ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ helm.spec ++++++ --- /var/tmp/diff_new_pack.vMvqPH/_old 2026-08-22 21:37:31.627187403 +0200 +++ /var/tmp/diff_new_pack.vMvqPH/_new 2026-08-22 21:37:31.629187475 +0200 @@ -17,14 +17,14 @@ Name: helm -Version: 4.2.3 +Version: 4.2.4 Release: 0 Summary: The Kubernetes Package Manager License: Apache-2.0 Group: Development/Languages/Other URL: https://github.com/helm/helm Source0: %{name}-%{version}.tar.gz -Source1: vendor.tar.gz +Source1: vendor.tar.zst Patch1: CVE-2026-63308.patch BuildRequires: bash-completion BuildRequires: fish ++++++ _service ++++++ --- /var/tmp/diff_new_pack.vMvqPH/_old 2026-08-22 21:37:31.668188869 +0200 +++ /var/tmp/diff_new_pack.vMvqPH/_new 2026-08-22 21:37:31.672189012 +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">refs/tags/v4.2.3</param> + <param name="revision">refs/tags/v4.2.4</param> <param name="changesgenerate">disable</param> </service> <service name="set_version" mode="manual"> @@ -16,7 +16,10 @@ <param name="compression">gz</param> </service> <service name="go_modules" mode="manual"> + <param name="compression">zst</param> <param name="replace">golang.org/x/net=golang.org/x/[email protected]</param> + <param name="replace">golang.org/x/mod=golang.org/x/[email protected]</param> + <param name="replace">oras.land/oras-go/v2=oras.land/oras-go/[email protected]</param> </service> </services> ++++++ helm-4.2.3.obscpio -> helm-4.2.4.obscpio ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/helm-4.2.3/go.mod new/helm-4.2.4/go.mod --- old/helm-4.2.3/go.mod 2026-07-09 22:15:44.000000000 +0200 +++ new/helm-4.2.4/go.mod 2026-08-13 16:46:03.000000000 +0200 @@ -80,6 +80,7 @@ github.com/fxamacker/cbor/v2 v2.9.0 // indirect github.com/go-errors/errors v1.5.1 // indirect github.com/go-gorp/gorp/v3 v3.1.0 // indirect + github.com/go-jose/go-jose/v4 v4.1.4 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.21.1 // indirect @@ -137,7 +138,7 @@ go.opentelemetry.io/contrib/bridges/prometheus v0.67.0 // indirect go.opentelemetry.io/contrib/exporters/autoexport v0.67.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 // indirect - go.opentelemetry.io/otel v1.43.0 // indirect + go.opentelemetry.io/otel v1.44.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.18.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.19.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.42.0 // indirect @@ -150,11 +151,11 @@ go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.42.0 // indirect go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0 // indirect go.opentelemetry.io/otel/log v0.19.0 // indirect - go.opentelemetry.io/otel/metric v1.43.0 // indirect - go.opentelemetry.io/otel/sdk v1.43.0 // indirect + go.opentelemetry.io/otel/metric v1.44.0 // indirect + go.opentelemetry.io/otel/sdk v1.44.0 // indirect go.opentelemetry.io/otel/sdk/log v0.19.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.43.0 // indirect - go.opentelemetry.io/otel/trace v1.43.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.44.0 // indirect + go.opentelemetry.io/otel/trace v1.44.0 // indirect go.opentelemetry.io/proto/otlp v1.10.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect golang.org/x/mod v0.37.0 // indirect @@ -164,9 +165,9 @@ golang.org/x/sys v0.47.0 // indirect golang.org/x/time v0.15.0 // indirect golang.org/x/tools v0.47.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 // indirect - google.golang.org/grpc v1.80.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 // indirect + google.golang.org/grpc v1.82.1 // indirect google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/helm-4.2.3/go.sum new/helm-4.2.4/go.sum --- old/helm-4.2.3/go.sum 2026-07-09 22:15:44.000000000 +0200 +++ new/helm-4.2.4/go.sum 2026-08-13 16:46:03.000000000 +0200 @@ -105,6 +105,8 @@ github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs= github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpjH8TixEw= +github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA= +github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= @@ -326,8 +328,8 @@ go.opentelemetry.io/contrib/exporters/autoexport v0.67.0/go.mod h1:qTvIHMFKoxW7HXg02gm6/Wofhq5p3Ib/A/NNt1EoBSQ= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 h1:OyrsyzuttWTSur2qN/Lm0m2a8yqyIjUVBZcxFPuXq2o= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0/go.mod h1:C2NGBr+kAB4bk3xtMXfZ94gqFDtg/GkI7e9zqGh5Beg= -go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= -go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= +go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU= +go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.18.0 h1:deI9UQMoGFgrg5iLPgzueqFPHevDl+28YKfSpPTI6rY= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.18.0/go.mod h1:PFx9NgpNUKXdf7J4Q3agRxMs3Y07QhTCVipKmLsMKnU= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.19.0 h1:HIBTQ3VO5aupLKjC90JgMqpezVXwFuq6Ryjn0/izoag= @@ -352,18 +354,20 @@ go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0/go.mod h1:UI3wi0FXg1Pofb8ZBiBLhtMzgoTm1TYkMvn71fAqDzs= go.opentelemetry.io/otel/log v0.19.0 h1:KUZs/GOsw79TBBMfDWsXS+KZ4g2Ckzksd1ymzsIEbo4= go.opentelemetry.io/otel/log v0.19.0/go.mod h1:5DQYeGmxVIr4n0/BcJvF4upsraHjg6vudJJpnkL6Ipk= -go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM= -go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY= -go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg= -go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg= +go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc= +go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo= +go.opentelemetry.io/otel/metric/x v0.66.0 h1:YkCrx1zLOChi9ZcZ6euupOcsgzbVlec7D/xoEU1+cTA= +go.opentelemetry.io/otel/metric/x v0.66.0/go.mod h1:d1+BDj9t96do0/1LoU1ayfCv79ZgNE41qbhBvnMOBZk= +go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58= +go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0= go.opentelemetry.io/otel/sdk/log v0.19.0 h1:scYVLqT22D2gqXItnWiocLUKGH9yvkkeql5dBDiXyko= go.opentelemetry.io/otel/sdk/log v0.19.0/go.mod h1:vFBowwXGLlW9AvpuF7bMgnNI95LiW10szrOdvzBHlAg= go.opentelemetry.io/otel/sdk/log/logtest v0.19.0 h1:BEbF7ZBB6qQloV/Ub1+3NQoOUnVtcGkU3XX4Ws3GQfk= go.opentelemetry.io/otel/sdk/log/logtest v0.19.0/go.mod h1:Lua81/3yM0wOmoHTokLj9y9ADeA02v1naRrVrkAZuKk= -go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw= -go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A= -go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= -go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= +go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI= +go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA= +go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk= +go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE= go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g= go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -463,12 +467,12 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= -google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 h1:VPWxll4HlMw1Vs/qXtN7BvhZqsS9cdAittCNvVENElA= -google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:7QBABkRtR8z+TEnmXTqIqwJLlzrZKVfAUm7tY3yGv0M= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 h1:m8qni9SQFH0tJc1X0vmnpw/0t+AImlSvp30sEupozUg= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= -google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM= -google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4= +google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478 h1:yQugLulqltosq0B/f8l4w9VryjV+N/5gcW0jQ3N8Qec= +google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478/go.mod h1:C6ADNqOxbgdUUeRTU+LCHDPB9ttAMCTff6auwCVa4uc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 h1:RmoJA1ujG+/lRGNfUnOMfhCy5EipVMyvUE+KNbPbTlw= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/grpc v1.82.1 h1:NnAxzGRA0677vCa4BUkOAnO5+FfQqVl9iUXeD0IqcGE= +google.golang.org/grpc v1.82.1/go.mod h1:yzTZ1TB1Z3SG+LIYaI+WiE8D5+PZ3ArnrSp8zF3+/ZA= google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af h1:+5/Sw3GsDNlEmu7TfklWKPdQ0Ykja5VEmq2i817+jbI= google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/helm-4.2.3/internal/release/v2/util/manifest.go new/helm-4.2.4/internal/release/v2/util/manifest.go --- old/helm-4.2.3/internal/release/v2/util/manifest.go 2026-07-09 22:15:44.000000000 +0200 +++ new/helm-4.2.4/internal/release/v2/util/manifest.go 2026-08-13 16:46:03.000000000 +0200 @@ -34,7 +34,7 @@ } `json:"metadata,omitempty"` } -var sep = regexp.MustCompile("(?:^|\\s*\n)---\\s*") +var sep = regexp.MustCompile(`(?m)^---[ \t]*`) // SplitManifests takes a manifest string and returns a map containing individual manifests. // diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/helm-4.2.3/internal/release/v2/util/manifest_test.go new/helm-4.2.4/internal/release/v2/util/manifest_test.go --- old/helm-4.2.3/internal/release/v2/util/manifest_test.go 2026-07-09 22:15:44.000000000 +0200 +++ new/helm-4.2.4/internal/release/v2/util/manifest_test.go 2026-08-13 16:46:03.000000000 +0200 @@ -73,7 +73,7 @@ name: "whitespace-only doc after separator is skipped", input: "---\napiVersion: v1\nkind: ConfigMap\nmetadata:\n name: cm1\n---\n \n", expected: map[string]string{ - "manifest-0": "apiVersion: v1\nkind: ConfigMap\nmetadata:\n name: cm1", + "manifest-0": "apiVersion: v1\nkind: ConfigMap\nmetadata:\n name: cm1\n", }, }, { @@ -109,7 +109,8 @@ "manifest-0": `apiVersion: v1 kind: ConfigMap metadata: - name: cm1`, + name: cm1 +`, "manifest-1": `apiVersion: v1 kind: ConfigMap metadata: @@ -328,8 +329,8 @@ }, }, - // Multi-doc with block scalars: the regex consumes \s*\n before ---, - // so trailing newlines from non-last docs are stripped. + // Multi-doc with block scalars: the separator regex preserves trailing + // newlines from non-last documents. { name: "multi-doc block scalar clip (|) before separator", input: ` @@ -353,7 +354,8 @@ name: test data: key: | - hello`, + hello +`, "manifest-1": `apiVersion: v1 kind: ConfigMap metadata: @@ -386,7 +388,10 @@ name: test data: key: |+ - hello`, + hello + + +`, "manifest-1": `apiVersion: v1 kind: ConfigMap metadata: @@ -431,7 +436,8 @@ "manifest-0": `apiVersion: v1 kind: ConfigMap metadata: - name: cm1`, + name: cm1 +`, "manifest-1": `apiVersion: v1 kind: ConfigMap metadata: @@ -459,11 +465,13 @@ "manifest-0": `apiVersion: v1 kind: ConfigMap metadata: - name: cm1`, + name: cm1 +`, "manifest-1": `apiVersion: v1 kind: ConfigMap metadata: - name: cm2`, + name: cm2 +`, "manifest-2": `apiVersion: v1 kind: ConfigMap metadata: @@ -492,11 +500,13 @@ "manifest-0": `apiVersion: v1 kind: ConfigMap metadata: - name: cm1`, + name: cm1 +`, "manifest-1": `apiVersion: v1 kind: ConfigMap metadata: - name: cm2`, + name: cm2 +`, "manifest-2": `apiVersion: v1 kind: ConfigMap metadata: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/helm-4.2.3/pkg/action/action_test.go new/helm-4.2.4/pkg/action/action_test.go --- old/helm-4.2.3/pkg/action/action_test.go 2026-07-09 22:15:44.000000000 +0200 +++ new/helm-4.2.4/pkg/action/action_test.go 2026-08-13 16:46:03.000000000 +0200 @@ -1199,9 +1199,9 @@ `, }, - // Multi-doc tests: block scalar doc is NOT the last document. - // SplitManifests' regex consumes \s*\n before ---, so trailing - // newlines from non-last docs are always stripped. + // Multi-doc block scalar tests where the block scalar document is NOT the last: + // the separator regex does not consume trailing newlines, so YAML chomping + // indicators (|, |+, |-) are respected. // | (clip) in multi-doc (first doc) { @@ -1230,7 +1230,7 @@ annotations: postrenderer.helm.sh/postrender-filename: 'templates/cm.yaml' data: - key: |- + key: | hello --- apiVersion: v1 @@ -1270,7 +1270,7 @@ annotations: postrenderer.helm.sh/postrender-filename: 'templates/cm.yaml' data: - key: |- + key: | hello --- apiVersion: v1 @@ -1311,7 +1311,7 @@ annotations: postrenderer.helm.sh/postrender-filename: 'templates/cm.yaml' data: - key: |- + key: | hello --- apiVersion: v1 @@ -1474,7 +1474,7 @@ annotations: postrenderer.helm.sh/postrender-filename: 'templates/cm.yaml' data: - key: |- + key: | hello --- apiVersion: v1 @@ -1514,8 +1514,9 @@ annotations: postrenderer.helm.sh/postrender-filename: 'templates/cm.yaml' data: - key: |- + key: |+ hello + --- apiVersion: v1 kind: ConfigMap @@ -1555,8 +1556,10 @@ annotations: postrenderer.helm.sh/postrender-filename: 'templates/cm.yaml' data: - key: |- + key: |+ hello + + --- apiVersion: v1 kind: ConfigMap diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/helm-4.2.3/pkg/action/release_testing.go new/helm-4.2.4/pkg/action/release_testing.go --- old/helm-4.2.3/pkg/action/release_testing.go 2026-07-09 22:15:44.000000000 +0200 +++ new/helm-4.2.4/pkg/action/release_testing.go 2026-08-13 16:46:03.000000000 +0200 @@ -18,6 +18,7 @@ import ( "context" + "errors" "fmt" "io" "slices" @@ -25,6 +26,8 @@ "time" v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" chartutil "helm.sh/helm/v4/pkg/chart/v2/util" "helm.sh/helm/v4/pkg/kube" @@ -124,9 +127,9 @@ return fmt.Errorf("unable to get kubernetes client to fetch pod logs: %w", err) } - hooksByWight := append([]*release.Hook{}, rel.Hooks...) - sort.Stable(hookByWeight(hooksByWight)) - for _, h := range hooksByWight { + hooksByWeight := append([]*release.Hook{}, rel.Hooks...) + sort.Stable(hookByWeight(hooksByWeight)) + for _, h := range hooksByWeight { for _, e := range h.Events { if e == release.HookTest { if slices.Contains(r.Filters[ExcludeNameFilter], h.Name) { @@ -135,20 +138,43 @@ if len(r.Filters[IncludeNameFilter]) > 0 && !slices.Contains(r.Filters[IncludeNameFilter], h.Name) { continue } - req := client.CoreV1().Pods(r.Namespace).GetLogs(h.Name, &v1.PodLogOptions{}) - logReader, err := req.Stream(context.Background()) - if err != nil { - return fmt.Errorf("unable to get pod logs for %s: %w", h.Name, err) - } - - fmt.Fprintf(out, "POD LOGS: %s\n", h.Name) - _, err = io.Copy(out, logReader) - fmt.Fprintln(out) - if err != nil { - return fmt.Errorf("unable to write pod logs for %s: %w", h.Name, err) + if err := r.getContainerLogs(out, client, h.Name); err != nil { + return err } } } } return nil } + +// getContainerLogs fetches logs from all containers (init and regular) in the +// named pod and writes them to out. It continues on per-container errors and +// returns all of them joined at the end. +func (r *ReleaseTesting) getContainerLogs(out io.Writer, client kubernetes.Interface, podName string) error { + pod, err := client.CoreV1().Pods(r.Namespace).Get(context.Background(), podName, metav1.GetOptions{}) + if err != nil { + return fmt.Errorf("unable to get pod %s: %w", podName, err) + } + + allContainers := append(pod.Spec.InitContainers, pod.Spec.Containers...) + + var errs []error + for _, c := range allContainers { + opts := &v1.PodLogOptions{Container: c.Name} + req := client.CoreV1().Pods(r.Namespace).GetLogs(podName, opts) + logReader, err := req.Stream(context.Background()) + if err != nil { + errs = append(errs, fmt.Errorf("unable to get logs for pod %s, container %s: %w", podName, c.Name, err)) + continue + } + + fmt.Fprintf(out, "POD LOGS: %s (%s)\n", podName, c.Name) + _, err = io.Copy(out, logReader) + logReader.Close() + fmt.Fprintln(out) + if err != nil { + errs = append(errs, fmt.Errorf("unable to write logs for pod %s, container %s: %w", podName, c.Name, err)) + } + } + return errors.Join(errs...) +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/helm-4.2.3/pkg/action/release_testing_test.go new/helm-4.2.4/pkg/action/release_testing_test.go --- old/helm-4.2.3/pkg/action/release_testing_test.go 2026-07-09 22:15:44.000000000 +0200 +++ new/helm-4.2.4/pkg/action/release_testing_test.go 2026-08-13 16:46:03.000000000 +0200 @@ -26,6 +26,9 @@ "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + fakeclientset "k8s.io/client-go/kubernetes/fake" "helm.sh/helm/v4/pkg/cli" "helm.sh/helm/v4/pkg/kube" @@ -89,7 +92,7 @@ }, } - require.ErrorContains(t, client.GetPodLogs(&bytes.Buffer{}, &release.Release{Hooks: hooks}), "unable to get pod logs") + require.ErrorContains(t, client.GetPodLogs(&bytes.Buffer{}, &release.Release{Hooks: hooks}), "unable to get pod") } func TestReleaseTesting_WaitOptionsPassedDownstream(t *testing.T) { @@ -117,3 +120,91 @@ // Verify that WaitOptions were passed to GetWaiter is.NotEmpty(failer.RecordedWaitOptions, "WaitOptions should be passed to GetWaiter") } + +func TestGetContainerLogs_MultipleContainers(t *testing.T) { + pod := &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-pod", + Namespace: "default", + }, + Spec: v1.PodSpec{ + Containers: []v1.Container{ + {Name: "main"}, + {Name: "sidecar"}, + }, + }, + } + + client := fakeclientset.NewClientset(pod) + rt := &ReleaseTesting{Namespace: "default"} + + var buf bytes.Buffer + err := rt.getContainerLogs(&buf, client, "test-pod") + require.NoError(t, err) + output := buf.String() + assert.Contains(t, output, "POD LOGS: test-pod (main)") + assert.Contains(t, output, "POD LOGS: test-pod (sidecar)") +} + +func TestGetContainerLogs_WithInitContainers(t *testing.T) { + pod := &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-pod", + Namespace: "default", + }, + Spec: v1.PodSpec{ + InitContainers: []v1.Container{ + {Name: "init-setup"}, + }, + Containers: []v1.Container{ + {Name: "main"}, + }, + }, + } + + client := fakeclientset.NewClientset(pod) + rt := &ReleaseTesting{Namespace: "default"} + + var buf bytes.Buffer + err := rt.getContainerLogs(&buf, client, "test-pod") + require.NoError(t, err) + output := buf.String() + // Init containers should appear before regular containers + assert.Contains(t, output, "POD LOGS: test-pod (init-setup)") + assert.Contains(t, output, "POD LOGS: test-pod (main)") +} + +func TestGetContainerLogs_PodNotFound(t *testing.T) { + client := fakeclientset.NewClientset() + rt := &ReleaseTesting{Namespace: "default"} + + var buf bytes.Buffer + err := rt.getContainerLogs(&buf, client, "nonexistent-pod") + require.Error(t, err) + assert.Contains(t, err.Error(), "unable to get pod nonexistent-pod") +} + +func TestGetContainerLogs_OutputHeaderFormat(t *testing.T) { + pod := &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "multi-test", + Namespace: "default", + }, + Spec: v1.PodSpec{ + Containers: []v1.Container{ + {Name: "container-a"}, + {Name: "container-b"}, + }, + }, + } + + client := fakeclientset.NewClientset(pod) + rt := &ReleaseTesting{Namespace: "default"} + + var buf bytes.Buffer + err := rt.getContainerLogs(&buf, client, "multi-test") + require.NoError(t, err) + output := buf.String() + assert.Contains(t, output, "POD LOGS: multi-test (container-a)") + assert.Contains(t, output, "POD LOGS: multi-test (container-b)") +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/helm-4.2.3/pkg/action/testdata/rbac.txt new/helm-4.2.4/pkg/action/testdata/rbac.txt --- old/helm-4.2.3/pkg/action/testdata/rbac.txt 2026-07-09 22:15:44.000000000 +0200 +++ new/helm-4.2.4/pkg/action/testdata/rbac.txt 2026-08-13 16:46:03.000000000 +0200 @@ -8,6 +8,8 @@ - apiGroups: [""] resources: ["pods", "pods/exec", "pods/log"] verbs: ["*"] + + --- # Source: hello/templates/rbac apiVersion: rbac.authorization.k8s.io/v1 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/helm-4.2.3/pkg/chart/v2/util/save.go new/helm-4.2.4/pkg/chart/v2/util/save.go --- old/helm-4.2.3/pkg/chart/v2/util/save.go 2026-07-09 22:15:44.000000000 +0200 +++ new/helm-4.2.4/pkg/chart/v2/util/save.go 2026-08-13 16:46:03.000000000 +0200 @@ -33,7 +33,11 @@ chart "helm.sh/helm/v4/pkg/chart/v2" ) -var headerBytes = []byte("+aHR0cHM6Ly95b3V0dS5iZS96OVV6MWljandyTQo=") +// RFC 1952 subfield header: +// +---+---+---+---+==================================+ +// |SI1|SI2| LEN |... LEN bytes of subfield data ...| +// +---+---+---+---+==================================+ +var headerBytes = []byte("rr\x28\x00aHR0cHM6Ly95b3V0dS5iZS96OVV6MWljandyTQo=") // SaveDir saves a chart as files in a directory. // diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/helm-4.2.3/pkg/chart/v2/util/save_test.go new/helm-4.2.4/pkg/chart/v2/util/save_test.go --- old/helm-4.2.3/pkg/chart/v2/util/save_test.go 2026-07-09 22:15:44.000000000 +0200 +++ new/helm-4.2.4/pkg/chart/v2/util/save_test.go 2026-08-13 16:46:03.000000000 +0200 @@ -21,6 +21,7 @@ "bytes" "compress/gzip" "crypto/sha256" + "encoding/binary" "encoding/hex" "errors" "io" @@ -35,6 +36,8 @@ "helm.sh/helm/v4/pkg/chart/common" chart "helm.sh/helm/v4/pkg/chart/v2" "helm.sh/helm/v4/pkg/chart/v2/loader" + + "github.com/stretchr/testify/require" ) func TestSave(t *testing.T) { @@ -130,6 +133,48 @@ } } +// https://github.com/helm/helm/issues/31844 +func TestSavedGzipExtraFieldIsValid(t *testing.T) { + tmp := t.TempDir() + c := &chart.Chart{ + Metadata: &chart.Metadata{ + APIVersion: chart.APIVersionV2, + Name: "ahab", + Version: "1.2.3", + }, + } + + where, err := Save(c, tmp) + if err != nil { + t.Fatalf("Failed to save: %s", err) + } + + f, err := os.Open(where) + if err != nil { + t.Fatalf("Failed to open saved file: %s", err) + } + defer f.Close() + + r, err := gzip.NewReader(f) + if err != nil { + t.Fatalf("Failed to create gzip reader: %s", err) + } + defer r.Close() + + // RFC 1952 §2.3.1.1: + // Each subfield consists of SI1, SI2 (1 byte each), + // a 2-byte little-endian LEN, and LEN bytes of data. + // https://www.rfc-editor.org/rfc/rfc1952.html#page-8 + extra := r.Extra + + require.NotEmpty(t, extra) + require.GreaterOrEqual(t, len(extra), 4) + + dataLen := int(binary.LittleEndian.Uint16(extra[2:4])) + // Assume a single subfield. + require.Lenf(t, extra, 4+dataLen, "gzip extra field has malformed subfield: LEN=%d but %d data byte(s) follow the subfield header", dataLen, len(extra)-4) +} + // Creates a copy with a different schema; does not modify anything. func withSchema(chart chart.Chart, schema []byte) chart.Chart { chart.Schema = schema @@ -299,7 +344,7 @@ Schema: []byte("{\n \"title\": \"Values\"\n}"), SchemaModTime: modTime, }, - want: "fea2662522317b65c2788ff9e5fc446a9264830038dac618d4449493d99b3257", + want: "63358874b93ea095c857cd66bcf5d0a4464840cf84a07547db744d81d6c5af59", }, { name: "Package 2 files", @@ -321,7 +366,7 @@ Schema: []byte("{\n \"title\": \"Values\"\n}"), SchemaModTime: modTime, }, - want: "7ae92b2f274bb51ea3f1969e4187d78cc52b5f6f663b44b8fb3b40bcb8ee46f3", + want: "c2a43990053da788ad4e260d3b00d52a0b103ccc67ab9f48278a7b6dcfb2a4bd", }, } for _, test := range tests { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/helm-4.2.3/pkg/cmd/template.go new/helm-4.2.4/pkg/cmd/template.go --- old/helm-4.2.3/pkg/cmd/template.go 2026-07-09 22:15:44.000000000 +0200 +++ new/helm-4.2.4/pkg/cmd/template.go 2026-08-13 16:46:03.000000000 +0200 @@ -113,6 +113,7 @@ } return err } + installErr := err // We ignore a potential error here because, when the --debug flag was specified, // we always want to print the YAML, even if it is not valid. The error is still returned afterwards. @@ -187,6 +188,10 @@ missing = false } if missing { + if installErr != nil && settings.Debug { + // assume the manifest itself is too malformed to be rendered + return installErr + } return fmt.Errorf("could not find template %s in chart", f) } } @@ -198,7 +203,7 @@ } } - return err + return installErr }, } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/helm-4.2.3/pkg/cmd/template_test.go new/helm-4.2.4/pkg/cmd/template_test.go --- old/helm-4.2.3/pkg/cmd/template_test.go 2026-07-09 22:15:44.000000000 +0200 +++ new/helm-4.2.4/pkg/cmd/template_test.go 2026-08-13 16:46:03.000000000 +0200 @@ -134,6 +134,12 @@ golden: "output/template-with-invalid-yaml-debug.txt", }, { + name: "chart with template with invalid template expression (--debug, --show-only)", + cmd: fmt.Sprintf("template '%s' --debug --show-only %s", "testdata/testcharts/chart-with-template-with-invalid-template-expr", "templates/alpine-pod.yaml"), + wantError: true, + golden: "output/template-with-invalid-template-expr-debug-show-only.txt", + }, + { name: "template skip-tests", cmd: fmt.Sprintf(`template '%s' --skip-tests`, chartPath), golden: "output/template-skip-tests.txt", diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/helm-4.2.3/pkg/cmd/testdata/output/object-order.txt new/helm-4.2.4/pkg/cmd/testdata/output/object-order.txt --- old/helm-4.2.3/pkg/cmd/testdata/output/object-order.txt 2026-07-09 22:15:44.000000000 +0200 +++ new/helm-4.2.4/pkg/cmd/testdata/output/object-order.txt 2026-08-13 16:46:03.000000000 +0200 @@ -10,6 +10,8 @@ policyTypes: - Egress - Ingress + + --- # Source: object-order/templates/01-a.yml # 2 @@ -22,6 +24,8 @@ policyTypes: - Egress - Ingress + + --- # Source: object-order/templates/01-a.yml # 3 @@ -34,6 +38,8 @@ policyTypes: - Egress - Ingress + + --- # Source: object-order/templates/02-b.yml # 5 @@ -46,6 +52,8 @@ policyTypes: - Egress - Ingress + + --- # Source: object-order/templates/02-b.yml # 7 @@ -58,6 +66,8 @@ policyTypes: - Egress - Ingress + + --- # Source: object-order/templates/02-b.yml # 8 @@ -70,6 +80,8 @@ policyTypes: - Egress - Ingress + + --- # Source: object-order/templates/02-b.yml # 9 @@ -82,6 +94,8 @@ policyTypes: - Egress - Ingress + + --- # Source: object-order/templates/02-b.yml # 10 @@ -94,6 +108,8 @@ policyTypes: - Egress - Ingress + + --- # Source: object-order/templates/02-b.yml # 11 @@ -106,6 +122,8 @@ policyTypes: - Egress - Ingress + + --- # Source: object-order/templates/02-b.yml # 12 @@ -118,6 +136,8 @@ policyTypes: - Egress - Ingress + + --- # Source: object-order/templates/02-b.yml # 13 @@ -130,6 +150,8 @@ policyTypes: - Egress - Ingress + + --- # Source: object-order/templates/02-b.yml # 14 @@ -142,6 +164,8 @@ policyTypes: - Egress - Ingress + + --- # Source: object-order/templates/02-b.yml # 15 (11th object within 02-b.yml, in order to test `SplitManifests` which assigns `manifest-10` @@ -190,3 +214,5 @@ policyTypes: - Egress - Ingress + + diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/helm-4.2.3/pkg/cmd/testdata/output/template-show-only-glob.txt new/helm-4.2.4/pkg/cmd/testdata/output/template-show-only-glob.txt --- old/helm-4.2.3/pkg/cmd/testdata/output/template-show-only-glob.txt 2026-07-09 22:15:44.000000000 +0200 +++ new/helm-4.2.4/pkg/cmd/testdata/output/template-show-only-glob.txt 2026-08-13 16:46:03.000000000 +0200 @@ -8,6 +8,8 @@ - apiGroups: [""] resources: ["pods"] verbs: ["get","list","watch"] + + --- # Source: subchart/templates/subdir/rolebinding.yaml apiVersion: rbac.authorization.k8s.io/v1 @@ -22,3 +24,5 @@ - kind: ServiceAccount name: subchart-sa namespace: default + + diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/helm-4.2.3/pkg/cmd/testdata/output/template-show-only-multiple.txt new/helm-4.2.4/pkg/cmd/testdata/output/template-show-only-multiple.txt --- old/helm-4.2.3/pkg/cmd/testdata/output/template-show-only-multiple.txt 2026-07-09 22:15:44.000000000 +0200 +++ new/helm-4.2.4/pkg/cmd/testdata/output/template-show-only-multiple.txt 2026-08-13 16:46:03.000000000 +0200 @@ -19,6 +19,7 @@ name: nginx selector: app.kubernetes.io/name: subchart + --- # Source: subchart/charts/subcharta/templates/service.yaml apiVersion: v1 @@ -36,3 +37,5 @@ name: apache selector: app.kubernetes.io/name: subcharta + + diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/helm-4.2.3/pkg/cmd/testdata/output/template-show-only-one.txt new/helm-4.2.4/pkg/cmd/testdata/output/template-show-only-one.txt --- old/helm-4.2.3/pkg/cmd/testdata/output/template-show-only-one.txt 2026-07-09 22:15:44.000000000 +0200 +++ new/helm-4.2.4/pkg/cmd/testdata/output/template-show-only-one.txt 2026-08-13 16:46:03.000000000 +0200 @@ -19,3 +19,4 @@ name: nginx selector: app.kubernetes.io/name: subchart + diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/helm-4.2.3/pkg/cmd/testdata/output/template-with-invalid-template-expr-debug-show-only.txt new/helm-4.2.4/pkg/cmd/testdata/output/template-with-invalid-template-expr-debug-show-only.txt --- old/helm-4.2.3/pkg/cmd/testdata/output/template-with-invalid-template-expr-debug-show-only.txt 1970-01-01 01:00:00.000000000 +0100 +++ new/helm-4.2.4/pkg/cmd/testdata/output/template-with-invalid-template-expr-debug-show-only.txt 2026-08-13 16:46:03.000000000 +0200 @@ -0,0 +1,3 @@ +Error: chart-with-template-with-invalid-template-expr/templates/alpine-pod.yaml:7:38 + executing "chart-with-template-with-invalid-template-expr/templates/alpine-pod.yaml" at <b64enc>: + invalid value; expected string diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/helm-4.2.3/pkg/cmd/testdata/testcharts/chart-with-template-with-invalid-template-expr/Chart.yaml new/helm-4.2.4/pkg/cmd/testdata/testcharts/chart-with-template-with-invalid-template-expr/Chart.yaml --- old/helm-4.2.3/pkg/cmd/testdata/testcharts/chart-with-template-with-invalid-template-expr/Chart.yaml 1970-01-01 01:00:00.000000000 +0100 +++ new/helm-4.2.4/pkg/cmd/testdata/testcharts/chart-with-template-with-invalid-template-expr/Chart.yaml 2026-08-13 16:46:03.000000000 +0200 @@ -0,0 +1,8 @@ +apiVersion: v1 +description: Deploy a basic Alpine Linux pod +home: https://helm.sh/helm +name: chart-with-template-with-invalid-template-expr +sources: + - https://github.com/helm/helm +version: 0.1.0 +type: application diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/helm-4.2.3/pkg/cmd/testdata/testcharts/chart-with-template-with-invalid-template-expr/README.md new/helm-4.2.4/pkg/cmd/testdata/testcharts/chart-with-template-with-invalid-template-expr/README.md --- old/helm-4.2.3/pkg/cmd/testdata/testcharts/chart-with-template-with-invalid-template-expr/README.md 1970-01-01 01:00:00.000000000 +0100 +++ new/helm-4.2.4/pkg/cmd/testdata/testcharts/chart-with-template-with-invalid-template-expr/README.md 2026-08-13 16:46:03.000000000 +0200 @@ -0,0 +1,13 @@ +#Alpine: A simple Helm chart + +Run a single pod of Alpine Linux. + +This example was generated using the command `helm create alpine`. + +The `templates/` directory contains a very simple pod resource with a +couple of parameters. + +The `values.yaml` file contains the default values for the +`alpine-pod.yaml` template. + +You can install this example using `helm install ./alpine`. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/helm-4.2.3/pkg/cmd/testdata/testcharts/chart-with-template-with-invalid-template-expr/templates/alpine-pod.yaml new/helm-4.2.4/pkg/cmd/testdata/testcharts/chart-with-template-with-invalid-template-expr/templates/alpine-pod.yaml --- old/helm-4.2.3/pkg/cmd/testdata/testcharts/chart-with-template-with-invalid-template-expr/templates/alpine-pod.yaml 1970-01-01 01:00:00.000000000 +0100 +++ new/helm-4.2.4/pkg/cmd/testdata/testcharts/chart-with-template-with-invalid-template-expr/templates/alpine-pod.yaml 2026-08-13 16:46:03.000000000 +0200 @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: Pod +metadata: + name: "{{.Release.Name}}-{{.Values.Name}}" +spec: + containers: + - name: {{ .Values.nonExistentKey | b64enc }} + image: "alpine:3.9" + command: ["/bin/sleep","9000"] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/helm-4.2.3/pkg/cmd/testdata/testcharts/chart-with-template-with-invalid-template-expr/values.yaml new/helm-4.2.4/pkg/cmd/testdata/testcharts/chart-with-template-with-invalid-template-expr/values.yaml --- old/helm-4.2.3/pkg/cmd/testdata/testcharts/chart-with-template-with-invalid-template-expr/values.yaml 1970-01-01 01:00:00.000000000 +0100 +++ new/helm-4.2.4/pkg/cmd/testdata/testcharts/chart-with-template-with-invalid-template-expr/values.yaml 2026-08-13 16:46:03.000000000 +0200 @@ -0,0 +1 @@ +Name: my-alpine diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/helm-4.2.3/pkg/cmd/upgrade.go new/helm-4.2.4/pkg/cmd/upgrade.go --- old/helm-4.2.3/pkg/cmd/upgrade.go 2026-07-09 22:15:44.000000000 +0200 +++ new/helm-4.2.4/pkg/cmd/upgrade.go 2026-08-13 16:46:03.000000000 +0200 @@ -217,6 +217,7 @@ RepositoryCache: settings.RepositoryCache, ContentCache: settings.ContentCache, Debug: settings.Debug, + RegistryClient: registryClient, } if err := man.Update(); err != nil { return err diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/helm-4.2.3/pkg/cmd/upgrade_test.go new/helm-4.2.4/pkg/cmd/upgrade_test.go --- old/helm-4.2.3/pkg/cmd/upgrade_test.go 2026-07-09 22:15:44.000000000 +0200 +++ new/helm-4.2.4/pkg/cmd/upgrade_test.go 2026-08-13 16:46:03.000000000 +0200 @@ -18,6 +18,8 @@ import ( "fmt" + "net/http" + "net/http/httptest" "os" "path/filepath" "reflect" @@ -194,6 +196,64 @@ runTestCmd(t, tests) } +// TestUpgradeDependencyUpdateOCINoPanic is a regression test for a nil-pointer +// panic in `helm upgrade --dependency-update` when a chart declares an OCI +// dependency. The upgrade command built its downloader.Manager without a +// RegistryClient (unlike install, dependency update, and dependency build), so +// resolving an OCI dependency dereferenced a nil *registry.Client. The command +// must now return a graceful error instead of panicking. +func TestUpgradeDependencyUpdateOCINoPanic(t *testing.T) { + defer resetEnv()() + + // A stub registry that answers the API-version ping but rejects the tag + // lookup, so OCI dependency resolution fails fast and hermetically instead + // of reaching a real registry. + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Docker-Distribution-API-Version", "registry/2.0") + if r.URL.Path == "/v2/" { + w.WriteHeader(http.StatusOK) + return + } + w.WriteHeader(http.StatusNotFound) + })) + defer srv.Close() + + // A chart with an unresolved OCI dependency forces --dependency-update into + // the tag-lookup path that previously panicked: the version is a range (an + // explicit version would skip the lookup) and the dependency is not present + // under charts/. + tmp := t.TempDir() + parent := &chart.Chart{ + Metadata: &chart.Metadata{ + APIVersion: chart.APIVersionV2, + Name: "oci-parent", + Version: "0.1.0", + Dependencies: []*chart.Dependency{{ + Name: "subchart", + Repository: fmt.Sprintf("oci://%s/charts", srv.Listener.Addr()), + Version: "^1.0.0", + }}, + }, + } + if err := chartutil.SaveDir(parent, tmp); err != nil { + t.Fatalf("Error creating chart: %v", err) + } + chartPath := filepath.Join(tmp, parent.Metadata.Name) + // SaveDir writes only resolved subcharts (Chart.Dependencies()), not the + // declared Metadata.Dependencies, so create the empty charts/ directory + // explicitly to make the "dependency missing from charts/" state concrete. + if err := os.MkdirAll(filepath.Join(chartPath, "charts"), 0o755); err != nil { + t.Fatalf("Error creating charts dir: %v", err) + } + + // The command must return an error (registry rejects the lookup), not panic. + _, _, err := executeActionCommandC(storageFixture(), + fmt.Sprintf("upgrade --dependency-update --plain-http oci-parent '%s'", chartPath)) + if err == nil { + t.Fatal("expected an error resolving the OCI dependency, got nil") + } +} + func TestUpgradeWithValue(t *testing.T) { releaseName := "funny-bunny-v2" relMock, ch, chartPath := prepareMockRelease(t, releaseName) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/helm-4.2.3/pkg/kube/client.go new/helm-4.2.4/pkg/kube/client.go --- old/helm-4.2.3/pkg/kube/client.go 2026-07-09 22:15:44.000000000 +0200 +++ new/helm-4.2.4/pkg/kube/client.go 2026-08-13 16:46:03.000000000 +0200 @@ -235,12 +235,15 @@ // getKubeClient get or create a new KubernetesClientSet func (c *Client) getKubeClient() (kubernetes.Interface, error) { - var err error - if c.kubeClient == nil { - c.kubeClient, err = c.Factory.KubernetesClientSet() + if c.kubeClient != nil { + return c.kubeClient, nil } - - return c.kubeClient, err + kc, err := c.Factory.KubernetesClientSet() + if err != nil { + return nil, err + } + c.kubeClient = kc + return c.kubeClient, nil } // IsReachable tests connectivity to the cluster. @@ -318,20 +321,24 @@ slog.String("fieldValidationDirective", string(fieldValidationDirective))) return func(target *resource.Info) error { - err := patchResourceServerSide(target, dryRun, forceConflicts, fieldValidationDirective) - logger := c.Logger().With( slog.String("namespace", target.Namespace), slog.String("name", target.Name), slog.String("gvk", target.Mapping.GroupVersionKind.String())) - if err != nil { - logger.Debug("Error creating resource via patch", slog.Any("error", err)) - return err - } - logger.Debug("Created resource via patch") + return retry.OnError( + retry.DefaultRetry, + isServerSideRetryable, + func() error { + err := patchResourceServerSide(target, dryRun, forceConflicts, fieldValidationDirective) + if err != nil { + logger.Debug("Error creating resource via patch", slog.Any("error", err)) + return err + } - return nil + logger.Debug("Created resource via patch") + return nil + }) } } @@ -951,6 +958,32 @@ return err.(*apierrors.StatusError).Status().Code == http.StatusUnsupportedMediaType } +// isServerSideRetryable checks if an error encountered during server-side apply +// should be retried. Currently, only ResourceQuota conflicts are considered retryable. +func isServerSideRetryable(err error) bool { + return isResourceQuotaConflict(err) +} + +// isResourceQuotaConflict checks if the error is a conflict error specifically caused by +// a ResourceQuota. This is used to determine if a retry should be attempted, +// since quota conflicts are typically transient and can be resolved by retrying. +func isResourceQuotaConflict(err error) bool { + if !apierrors.IsConflict(err) { + return false + } + + // Check the error message for the specific ResourceQuota conflict pattern. + // The error message from the ResourceQuota admission controller contains: + // "Operation cannot be fulfilled on resourcequotas" and "the object has been modified" + errMsg := err.Error() + if strings.Contains(errMsg, "Operation cannot be fulfilled on resourcequotas") && + strings.Contains(errMsg, "the object has been modified") { + return true + } + + return false +} + // getManagedFieldsManager returns the manager string. If one was set it will be returned. // Otherwise, one is calculated based on the name of the binary. func getManagedFieldsManager() string { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/helm-4.2.3/pkg/kube/client_test.go new/helm-4.2.4/pkg/kube/client_test.go --- old/helm-4.2.3/pkg/kube/client_test.go 2026-07-09 22:15:44.000000000 +0200 +++ new/helm-4.2.4/pkg/kube/client_test.go 2026-08-13 16:46:03.000000000 +0200 @@ -53,6 +53,7 @@ k8sfake "k8s.io/client-go/kubernetes/fake" "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest/fake" + "k8s.io/client-go/util/retry" cmdtesting "k8s.io/kubectl/pkg/cmd/testing" ) @@ -273,8 +274,30 @@ }, ExpectedErrorContains: "Operation cannot be fulfilled on resourcequotas \"quota\": the object has been modified; " + "please apply your changes to the latest version and try again", + ExpectedActions: func() []string { // expect helm to retry on conflict, workaround for: https://github.com/kubernetes/kubernetes/issues/67761 + actions := make([]string, retry.DefaultRetry.Steps) + for i := range actions { + actions[i] = "/namespaces/default/pods/dolphin:PATCH" + } + return actions + }(), + }, + "Create fail: managed fields conflict (server-side apply)": { + Pods: newPodList("seal"), + ServerSideApply: true, + Callback: func(t *testing.T, _ testCase, _ []RequestResponseAction, req *http.Request) (*http.Response, error) { + t.Helper() + + // Return a generic 409 conflict (not quota-related) + // This simulates a managed fields conflict + return &http.Response{ + StatusCode: http.StatusConflict, + Request: req, + }, nil + }, + ExpectedErrorContains: "the server reported a conflict", ExpectedActions: []string{ - "/namespaces/default/pods/dolphin:PATCH", + "/namespaces/default/pods/seal:PATCH", }, }, } @@ -1420,6 +1443,26 @@ } } +func TestIsReachableTwiceAfterClientCreationFailure(t *testing.T) { + refusedErr := errors.New("connection refused") + client := newTestClient(t) + client.Factory = &errorFactory{err: refusedErr} + + assertReachableErr := func(label string, err error) { + t.Helper() + if err == nil { + t.Fatalf("%s: expected error, got nil", label) + } + if !errors.Is(err, refusedErr) { + t.Fatalf("%s: expected error wrapping %v, got %v", label, refusedErr, err) + } + } + + assertReachableErr("first call", client.IsReachable()) + // Second call must return the same underlying error, not panic. + assertReachableErr("second call", client.IsReachable()) +} + func TestIsIncompatibleServerError(t *testing.T) { testCases := map[string]struct { Err error diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/helm-4.2.3/pkg/provenance/sign.go new/helm-4.2.4/pkg/provenance/sign.go --- old/helm-4.2.3/pkg/provenance/sign.go 2026-07-09 22:15:44.000000000 +0200 +++ new/helm-4.2.4/pkg/provenance/sign.go 2026-08-13 16:46:03.000000000 +0200 @@ -388,7 +388,7 @@ func Digest(in io.Reader) (string, error) { hash := crypto.SHA256.New() if _, err := io.Copy(hash, in); err != nil { - return "", nil + return "", err } return hex.EncodeToString(hash.Sum(nil)), nil } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/helm-4.2.3/pkg/registry/client.go new/helm-4.2.4/pkg/registry/client.go --- old/helm-4.2.3/pkg/registry/client.go 2026-07-09 22:15:44.000000000 +0200 +++ new/helm-4.2.4/pkg/registry/client.go 2026-08-13 16:46:03.000000000 +0200 @@ -717,6 +717,8 @@ repository.PlainHTTP = c.plainHTTP repository.Client = c.authorizer + ctx = withScopeHint(ctx, repository, auth.ActionPull, auth.ActionPush) + manifestDescriptor, err = oras.ExtendedCopy(ctx, memoryStore, parsedRef.String(), repository, parsedRef.String(), oras.DefaultExtendedCopyOptions) if err != nil { return nil, err @@ -929,3 +931,14 @@ return oras.TagBytes(ctx, memoryStore, ocispec.MediaTypeImageManifest, manifestData, parsedRef.String()) } + +// add actions when request a registry authentication token(jwt) +// example1. when we want to pull 'testrepo/local-subchart' we can send below url, and 'pull' is the action +// auth?scope=repository%3Atestrepo%2Flocal-subchart%3Apull&service=testservice +// example2. when we want to push 'testrepo/local-subchart' we can send below url, and 'pull%2Cpush' are the actions +// auth?scope=repository%3Atestrepo%2Flocal-subchart%3Apull%2Cpush&service=testservice +// we can set the actions like below +// example) ctx = withScopeHint(ctx, repository, auth.ActionPush, auth.ActionPull) +func withScopeHint(ctx context.Context, repo *remote.Repository, actions ...string) context.Context { + return auth.AppendRepositoryScope(ctx, repo.Reference, actions...) +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/helm-4.2.3/pkg/registry/client_http_test.go new/helm-4.2.4/pkg/registry/client_http_test.go --- old/helm-4.2.3/pkg/registry/client_http_test.go 2026-07-09 22:15:44.000000000 +0200 +++ new/helm-4.2.4/pkg/registry/client_http_test.go 2026-08-13 16:46:03.000000000 +0200 @@ -31,7 +31,7 @@ func (suite *HTTPRegistryClientTestSuite) SetupSuite() { // init test client - setup(&suite.TestRegistry, false, false) + setup(&suite.TestRegistry, false, false, "htpasswd") } func (suite *HTTPRegistryClientTestSuite) TearDownSuite() { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/helm-4.2.3/pkg/registry/client_insecure_tls_test.go new/helm-4.2.4/pkg/registry/client_insecure_tls_test.go --- old/helm-4.2.3/pkg/registry/client_insecure_tls_test.go 2026-07-09 22:15:44.000000000 +0200 +++ new/helm-4.2.4/pkg/registry/client_insecure_tls_test.go 2026-08-13 16:46:03.000000000 +0200 @@ -29,7 +29,7 @@ func (suite *InsecureTLSRegistryClientTestSuite) SetupSuite() { // init test client - setup(&suite.TestRegistry, true, true) + setup(&suite.TestRegistry, true, true, "htpasswd") } func (suite *InsecureTLSRegistryClientTestSuite) TearDownSuite() { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/helm-4.2.3/pkg/registry/client_scope_test.go new/helm-4.2.4/pkg/registry/client_scope_test.go --- old/helm-4.2.3/pkg/registry/client_scope_test.go 1970-01-01 01:00:00.000000000 +0100 +++ new/helm-4.2.4/pkg/registry/client_scope_test.go 2026-08-13 16:46:03.000000000 +0200 @@ -0,0 +1,142 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package registry + +import ( + "fmt" + "net" + "net/http" + "net/http/httptest" + "net/url" + "os" + "testing" + "time" + + "github.com/stretchr/testify/suite" +) + +type RegistryScopeTestSuite struct { + TestRegistry +} + +func (suite *RegistryScopeTestSuite) SetupSuite() { + // Set up a plain-HTTP registry that uses token auth. The token realm is + // served over http (see setup), so the registry must be contacted over + // http as well: oras refuses to send credentials to an http token realm + // when the registry itself was reached over https. + setup(&suite.TestRegistry, false, false, "token") +} +func (suite *RegistryScopeTestSuite) TearDownSuite() { + teardown(&suite.TestRegistry) + os.RemoveAll(suite.WorkspaceDir) +} + +func (suite *RegistryScopeTestSuite) Test_1_Check_Push_Request_Scope() { + requestURL := make(chan string, 1) + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Capture only the first auth request; never block the handler if the + // client happens to retry, so the auth server always responds and the + // push/pull flow can't deadlock waiting on us. + select { + case requestURL <- r.URL.String(): + default: + } + w.WriteHeader(http.StatusOK) + }) + lnCfg := net.ListenConfig{} + listener, err := lnCfg.Listen(suite.T().Context(), "tcp", suite.AuthServerHost) + suite.NoError(err, "no error creating server listener") + + ts := httptest.NewUnstartedServer(handler) + ts.Listener = listener + ts.Start() + defer ts.Close() + + // basic push, good ref + testingChartCreationTime := "1977-09-02T22:04:05Z" + chartData, err := os.ReadFile("../downloader/testdata/local-subchart-0.1.0.tgz") + suite.NoError(err, "no error loading test chart") + meta, err := extractChartMeta(chartData) + suite.NoError(err, "no error extracting chart meta") + ref := fmt.Sprintf("%s/testrepo/%s:%s", suite.DockerRegistryHost, meta.Name, meta.Version) + _, err = suite.RegistryClient.Push(chartData, ref, PushOptCreationTime(testingChartCreationTime)) + suite.Error(err, "error pushing good ref because auth server doesn't give proper token") + + //check the url that authentication server received + select { + case urlStr := <-requestURL: + u, err := url.Parse(urlStr) + suite.NoError(err, "no error parsing requested URL") + + suite.Equal("/auth", u.Path) + suite.Equal("testservice", u.Query().Get("service")) + scope := u.Query().Get("scope") + suite.Contains(scope, "repository:testrepo/local-subchart:pull,push") + case <-time.After(5 * time.Second): + suite.T().Fatal("timeout waiting for auth request") + } +} + +func (suite *RegistryScopeTestSuite) Test_2_Check_Pull_Request_Scope() { + requestURL := make(chan string, 1) + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Capture only the first auth request; never block the handler if the + // client happens to retry, so the auth server always responds and the + // push/pull flow can't deadlock waiting on us. + select { + case requestURL <- r.URL.String(): + default: + } + w.WriteHeader(http.StatusOK) + }) + lnCfg := net.ListenConfig{} + listener, err := lnCfg.Listen(suite.T().Context(), "tcp", suite.AuthServerHost) + suite.NoError(err, "no error creating server listener") + + ts := httptest.NewUnstartedServer(handler) + ts.Listener = listener + ts.Start() + defer ts.Close() + + // Load test chart (to build ref pushed in previous test) + // Simple pull, chart only + chartData, err := os.ReadFile("../downloader/testdata/local-subchart-0.1.0.tgz") + suite.NoError(err, "no error loading test chart") + meta, err := extractChartMeta(chartData) + suite.NoError(err, "no error extracting chart meta") + ref := fmt.Sprintf("%s/testrepo/%s:%s", suite.DockerRegistryHost, meta.Name, meta.Version) + _, err = suite.RegistryClient.Pull(ref) + suite.Error(err, "error pulling a simple chart because auth server doesn't give proper token") + + //check the url that authentication server received + select { + case urlStr := <-requestURL: + u, err := url.Parse(urlStr) + suite.NoError(err, "no error parsing requested URL") + + suite.Equal("/auth", u.Path) + suite.Equal("testservice", u.Query().Get("service")) + scope := u.Query().Get("scope") + suite.Contains(scope, "repository:testrepo/local-subchart:pull") + case <-time.After(5 * time.Second): + suite.T().Fatal("timeout waiting for auth request") + } +} + +func TestRegistryScopeTestSuite(t *testing.T) { + suite.Run(t, new(RegistryScopeTestSuite)) +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/helm-4.2.3/pkg/registry/client_tls_test.go new/helm-4.2.4/pkg/registry/client_tls_test.go --- old/helm-4.2.3/pkg/registry/client_tls_test.go 2026-07-09 22:15:44.000000000 +0200 +++ new/helm-4.2.4/pkg/registry/client_tls_test.go 2026-08-13 16:46:03.000000000 +0200 @@ -31,7 +31,7 @@ func (suite *TLSRegistryClientTestSuite) SetupSuite() { // init test client - setup(&suite.TestRegistry, true, false) + setup(&suite.TestRegistry, true, false, "htpasswd") } func (suite *TLSRegistryClientTestSuite) TearDownSuite() { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/helm-4.2.3/pkg/registry/registry_test.go new/helm-4.2.4/pkg/registry/registry_test.go --- old/helm-4.2.3/pkg/registry/registry_test.go 2026-07-09 22:15:44.000000000 +0200 +++ new/helm-4.2.4/pkg/registry/registry_test.go 2026-08-13 16:46:03.000000000 +0200 @@ -34,6 +34,7 @@ "github.com/distribution/distribution/v3/configuration" "github.com/distribution/distribution/v3/registry" _ "github.com/distribution/distribution/v3/registry/auth/htpasswd" + _ "github.com/distribution/distribution/v3/registry/auth/token" _ "github.com/distribution/distribution/v3/registry/storage/driver/inmemory" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/stretchr/testify/require" @@ -56,6 +57,8 @@ testHtpasswdFileBasename = "authtest.htpasswd" testUsername = "myuser" testPassword = "mypass" + testIssuer = "testissuer" + testService = "testservice" ) type TestRegistry struct { @@ -63,13 +66,14 @@ Out io.Writer FakeRegistryHost string DockerRegistryHost string + AuthServerHost string CompromisedRegistryHost string WorkspaceDir string RegistryClient *Client dockerRegistry *registry.Registry } -func setup(suite *TestRegistry, tlsEnabled, insecure bool) { +func setup(suite *TestRegistry, tlsEnabled, insecure bool, auth string) { suite.WorkspaceDir = testWorkspaceDir err := os.RemoveAll(suite.WorkspaceDir) require.NoError(suite.T(), err, "no error removing test workspace dir") @@ -125,25 +129,47 @@ // Registry config config := &configuration.Configuration{} + lnCfg := net.ListenConfig{} ln, err := net.Listen("tcp", "127.0.0.1:0") suite.Nil(err, "no error finding free port for test registry") defer func() { _ = ln.Close() }() - // Change the registry host to another host which is not localhost. - // This is required because Docker enforces HTTP if the registry - // host is localhost/127.0.0.1. + // Use localhost for HTTP tests and helm-test-registry for TLS tests. + // TLS tests need a different hostname to match the certificate. port := ln.Addr().(*net.TCPAddr).Port - suite.DockerRegistryHost = fmt.Sprintf("helm-test-registry:%d", port) + if tlsEnabled { + suite.DockerRegistryHost = fmt.Sprintf("helm-test-registry:%d", port) + } else { + suite.DockerRegistryHost = fmt.Sprintf("127.0.0.1:%d", port) + } config.HTTP.Addr = ln.Addr().String() config.HTTP.DrainTimeout = time.Duration(10) * time.Second config.Storage = map[string]configuration.Parameters{"inmemory": map[string]any{}} - config.Auth = configuration.Auth{ - "htpasswd": configuration.Parameters{ - "realm": "localhost", - "path": htpasswdPath, - }, + if auth == "token" { + ln, err := lnCfg.Listen(suite.T().Context(), "tcp", "127.0.0.1:0") + suite.Require().NoError(err, "no error finding free port for test auth server") + defer ln.Close() + + //set test auth server host + suite.AuthServerHost = ln.Addr().String() + + config.Auth = configuration.Auth{ + "token": configuration.Parameters{ + "realm": "http://" + suite.AuthServerHost + "/auth", + "service": testService, + "issuer": testIssuer, + "rootcertbundle": tlsServerCert, + }, + } + } else { + config.Auth = configuration.Auth{ + "htpasswd": configuration.Parameters{ + "realm": "localhost", + "path": htpasswdPath, + }, + } } // config tls diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/helm-4.2.3/pkg/release/v1/util/manifest.go new/helm-4.2.4/pkg/release/v1/util/manifest.go --- old/helm-4.2.3/pkg/release/v1/util/manifest.go 2026-07-09 22:15:44.000000000 +0200 +++ new/helm-4.2.4/pkg/release/v1/util/manifest.go 2026-08-13 16:46:03.000000000 +0200 @@ -34,7 +34,7 @@ } `json:"metadata,omitempty"` } -var sep = regexp.MustCompile("(?:^|\\s*\n)---\\s*") +var sep = regexp.MustCompile(`(?m)^---[ \t]*`) // SplitManifests takes a manifest string and returns a map containing individual manifests. // diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/helm-4.2.3/pkg/release/v1/util/manifest_test.go new/helm-4.2.4/pkg/release/v1/util/manifest_test.go --- old/helm-4.2.3/pkg/release/v1/util/manifest_test.go 2026-07-09 22:15:44.000000000 +0200 +++ new/helm-4.2.4/pkg/release/v1/util/manifest_test.go 2026-08-13 16:46:03.000000000 +0200 @@ -73,7 +73,7 @@ name: "whitespace-only doc after separator is skipped", input: "---\napiVersion: v1\nkind: ConfigMap\nmetadata:\n name: cm1\n---\n \n", expected: map[string]string{ - "manifest-0": "apiVersion: v1\nkind: ConfigMap\nmetadata:\n name: cm1", + "manifest-0": "apiVersion: v1\nkind: ConfigMap\nmetadata:\n name: cm1\n", }, }, { @@ -109,7 +109,8 @@ "manifest-0": `apiVersion: v1 kind: ConfigMap metadata: - name: cm1`, + name: cm1 +`, "manifest-1": `apiVersion: v1 kind: ConfigMap metadata: @@ -328,8 +329,8 @@ }, }, - // Multi-doc with block scalars: the regex consumes \s*\n before ---, - // so trailing newlines from non-last docs are stripped. + // Multi-doc with block scalars: the separator regex preserves trailing + // newlines from non-last documents. { name: "multi-doc block scalar clip (|) before separator", input: ` @@ -353,7 +354,8 @@ name: test data: key: | - hello`, + hello +`, "manifest-1": `apiVersion: v1 kind: ConfigMap metadata: @@ -386,7 +388,10 @@ name: test data: key: |+ - hello`, + hello + + +`, "manifest-1": `apiVersion: v1 kind: ConfigMap metadata: @@ -431,7 +436,8 @@ "manifest-0": `apiVersion: v1 kind: ConfigMap metadata: - name: cm1`, + name: cm1 +`, "manifest-1": `apiVersion: v1 kind: ConfigMap metadata: @@ -459,11 +465,13 @@ "manifest-0": `apiVersion: v1 kind: ConfigMap metadata: - name: cm1`, + name: cm1 +`, "manifest-1": `apiVersion: v1 kind: ConfigMap metadata: - name: cm2`, + name: cm2 +`, "manifest-2": `apiVersion: v1 kind: ConfigMap metadata: @@ -492,11 +500,13 @@ "manifest-0": `apiVersion: v1 kind: ConfigMap metadata: - name: cm1`, + name: cm1 +`, "manifest-1": `apiVersion: v1 kind: ConfigMap metadata: - name: cm2`, + name: cm2 +`, "manifest-2": `apiVersion: v1 kind: ConfigMap metadata: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/helm-4.2.3/pkg/storage/driver/util.go new/helm-4.2.4/pkg/storage/driver/util.go --- old/helm-4.2.3/pkg/storage/driver/util.go 2026-07-09 22:15:44.000000000 +0200 +++ new/helm-4.2.4/pkg/storage/driver/util.go 2026-08-13 16:46:03.000000000 +0200 @@ -46,9 +46,12 @@ return "", err } if _, err = w.Write(b); err != nil { + w.Close() + return "", err + } + if err = w.Close(); err != nil { return "", err } - w.Close() return b64.EncodeToString(buf.Bytes()), nil } ++++++ helm.obsinfo ++++++ --- /var/tmp/diff_new_pack.vMvqPH/_old 2026-08-22 21:37:32.500218610 +0200 +++ /var/tmp/diff_new_pack.vMvqPH/_new 2026-08-22 21:37:32.505218789 +0200 @@ -1,5 +1,5 @@ name: helm -version: 4.2.3 -mtime: 1783628144 -commit: 43e8b7feece8beb0fcba47059ec9b522fd929a64 +version: 4.2.4 +mtime: 1786632363 +commit: 3900f434fd3ef2b84065dc04508df48f288dba00
