Merge branch 'helm:main' into feature/rollback-revision-history

pull/31859/head
MrJack 2 weeks ago committed by GitHub
commit dc04059ea3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

2
.github/env vendored

@ -1,2 +1,2 @@
GOLANG_VERSION=1.25
GOLANG_VERSION=1.26
GOLANGCI_LINT_VERSION=v2.11.3

@ -33,6 +33,7 @@ linters:
- revive
- sloglint
- staticcheck
- testifylint
- thelper
- unused
- usestdlibvars
@ -95,6 +96,24 @@ linters:
- helpers
- models
testifylint:
disable:
- empty
- encoded-compare
- equal-values
- error-is-as
- error-nil
- expected-actual
- float-compare
- go-require
- len
- nil-compare
- require-error
- suite-dont-use-pkg
- suite-extra-assert-call
# Intentionally enable all testifylint rules so new checks are adopted automatically.
enable-all: true
run:
timeout: 10m

@ -1,6 +1,6 @@
module helm.sh/helm/v4
go 1.25.0
go 1.26.0
require (
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24
@ -17,7 +17,7 @@ require (
github.com/evanphx/json-patch/v5 v5.9.11
github.com/extism/go-sdk v1.7.1
github.com/fatih/color v1.19.0
github.com/fluxcd/cli-utils v1.0.0
github.com/fluxcd/cli-utils v1.1.0
github.com/foxcpp/go-mockdns v1.2.0
github.com/gobwas/glob v0.2.3
github.com/gofrs/flock v0.13.0
@ -39,14 +39,14 @@ require (
golang.org/x/term v0.42.0
golang.org/x/text v0.36.0
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/api v0.35.4
k8s.io/apiextensions-apiserver v0.35.4
k8s.io/apimachinery v0.35.4
k8s.io/apiserver v0.35.4
k8s.io/cli-runtime v0.35.4
k8s.io/client-go v0.35.4
k8s.io/klog/v2 v2.130.1
k8s.io/kubectl v0.35.4
k8s.io/api v0.36.0
k8s.io/apiextensions-apiserver v0.36.0
k8s.io/apimachinery v0.36.0
k8s.io/apiserver v0.36.0
k8s.io/cli-runtime v0.36.0
k8s.io/client-go v0.36.0
k8s.io/klog/v2 v2.140.0
k8s.io/kubectl v0.36.0
oras.land/oras-go/v2 v2.6.0
sigs.k8s.io/controller-runtime v0.23.3
sigs.k8s.io/kustomize/kyaml v0.21.1
@ -65,7 +65,7 @@ require (
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/chai2010/gettext-go v1.0.2 // indirect
github.com/cloudflare/circl v1.6.3 // indirect
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
github.com/coreos/go-systemd/v22 v22.7.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
@ -74,7 +74,7 @@ require (
github.com/docker/go-events v0.0.0-20250808211157-605354379745 // indirect
github.com/docker/go-metrics v0.0.1 // indirect
github.com/dylibso/observe-sdk/go v0.0.0-20240819160327-2d926c5d788a // indirect
github.com/emicklei/go-restful/v3 v3.12.2 // indirect
github.com/emicklei/go-restful/v3 v3.13.0 // indirect
github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
@ -91,7 +91,6 @@ require (
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/handlers v1.5.2 // indirect
github.com/gorilla/mux v1.8.1 // indirect
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect
github.com/hashicorp/golang-lru/arc/v2 v2.0.5 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.5 // indirect
@ -168,15 +167,15 @@ require (
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/protobuf v1.36.11 // 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
gopkg.in/yaml.v2 v2.4.0 // indirect
k8s.io/component-base v0.35.4 // indirect
k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // indirect
k8s.io/component-base v0.36.0 // indirect
k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a // indirect
k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 // indirect
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
sigs.k8s.io/kustomize/api v0.21.1 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect
sigs.k8s.io/structured-merge-diff/v6 v6.3.2-0.20260122202528-d9cc6641c482 // indirect
sigs.k8s.io/structured-merge-diff/v6 v6.3.2 // indirect
)

@ -53,8 +53,8 @@ github.com/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNS
github.com/chai2010/gettext-go v1.0.2/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA=
github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8=
github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/coreos/go-systemd/v22 v22.7.0 h1:LAEzFkke61DFROc7zNLX/WA2i5J8gYqe0rSj9KI28KA=
github.com/coreos/go-systemd/v22 v22.7.0/go.mod h1:xNUYtjHu2EDXbsxz1i41wouACIwT7Ybq9o0BQhMwD0w=
github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
@ -81,8 +81,8 @@ github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQ
github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw=
github.com/dylibso/observe-sdk/go v0.0.0-20240819160327-2d926c5d788a h1:UwSIFv5g5lIvbGgtf3tVwC7Ky9rmMFBp0RMs+6f6YqE=
github.com/dylibso/observe-sdk/go v0.0.0-20240819160327-2d926c5d788a/go.mod h1:C8DzXehI4zAbrdlbtOByKX6pfivJTBiV9Jjqv56Yd9Q=
github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU=
github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes=
github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU=
github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM=
github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f h1:Wl78ApPPB2Wvf/TIe2xdyJxTlb6obmF18d8QdkxNDu4=
@ -93,8 +93,8 @@ github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w=
github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fluxcd/cli-utils v1.0.0 h1:+luz8igR6dM5f7uHwkkMTECsl+jp0kR69POuV5aOoDs=
github.com/fluxcd/cli-utils v1.0.0/go.mod h1:ANTIXWLLsNmn5bMNxbyoY22rtwRSR/fbu+IFy756fs0=
github.com/fluxcd/cli-utils v1.1.0 h1:P2oULlj4aNSqjBGcWOCQS+TOS5ZSyoJMy1zYCpqsYus=
github.com/fluxcd/cli-utils v1.1.0/go.mod h1:+ipwad8nfETe+VB3SMgrDv6m0mqA/KQSj2wyn8Y7vmo=
github.com/foxcpp/go-mockdns v1.2.0 h1:omK3OrHRD1IWJz1FuFBCFquhXslXoF17OvBS6JPzZF0=
github.com/foxcpp/go-mockdns v1.2.0/go.mod h1:IhLeSFGed3mJIAXPH2aiRQB+kqz7oqu8ld2qVbOu7Wk=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
@ -128,7 +128,6 @@ github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1v
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gofrs/flock v0.13.0 h1:95JolYOvGMqeH31+FC7D2+uULf6mG61mEZ/A8dRYMzw=
github.com/gofrs/flock v0.13.0/go.mod h1:jxeyy9R1auM5S6JYDBhDt+E2TCo7DkratH4Pgi8P+Z0=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
@ -155,8 +154,6 @@ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY=
github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo=
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA=
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c=
github.com/hashicorp/golang-lru/arc/v2 v2.0.5 h1:l2zaLDubNhW4XO3LnliVj0GXO3+/CGNJAg1dcN2Fpfw=
@ -373,8 +370,8 @@ go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
@ -472,8 +469,8 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 h1:
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/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
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=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
@ -488,28 +485,28 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
k8s.io/api v0.35.4 h1:P7nFYKl5vo9AGUp1Z+Pmd3p2tA7bX2wbFWCvDeRv988=
k8s.io/api v0.35.4/go.mod h1:yl4lqySWOgYJJf9RERXKUwE9g2y+CkuwG+xmcOK8wXU=
k8s.io/apiextensions-apiserver v0.35.4 h1:HeP+Upp7ItdvnyGmub0yoix+2z5+ev4M5cE5TCgtOUU=
k8s.io/apiextensions-apiserver v0.35.4/go.mod h1:ogQlk+stIE8mnoRthSYCwlOS12fVqgWFiErMwPaXA7c=
k8s.io/apimachinery v0.35.4 h1:xtdom9RG7e+yDp71uoXoJDWEE2eOiHgeO4GdBzwWpds=
k8s.io/apimachinery v0.35.4/go.mod h1:NNi1taPOpep0jOj+oRha3mBJPqvi0hGdaV8TCqGQ+cc=
k8s.io/apiserver v0.35.4 h1:vtuFqNFmF9bPRdHDL2lpK6qCTPWDreZJL4LRPwVM6ho=
k8s.io/apiserver v0.35.4/go.mod h1:JnBcb+J8kFXKpZkgcbcUnPBBHi4qgBii1I7dLxFY/oo=
k8s.io/cli-runtime v0.35.4 h1:8QRCXSDvopflFNM65Vkkdv42BljPdRSiqf6HFyI1iik=
k8s.io/cli-runtime v0.35.4/go.mod h1:MKLFuZxiJpm87UxjVeQRNy3sCaczHrSOPKN9pinlrM0=
k8s.io/client-go v0.35.4 h1:DN6fyaGuzK64UvnKO5fOA6ymSjvfGAnCAHAR0C66kD8=
k8s.io/client-go v0.35.4/go.mod h1:2Pg9WpsS4NeOpoYTfHHfMxBG8zFMSAUi4O/qoiJC3nY=
k8s.io/component-base v0.35.4 h1:6n1tNJ87johN0Hif0Fs8K2GMthsaUwMqCebUDLYyv7U=
k8s.io/component-base v0.35.4/go.mod h1:qaDJgz5c1KYKla9occFmlJEfPpkuA55s90G509R+PeY=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE=
k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ=
k8s.io/kubectl v0.35.4 h1:IHitney6OUeH29rBQnt6Cas6az8HpFeSAohormITNMc=
k8s.io/kubectl v0.35.4/go.mod h1:CGWAaof9ae4vGDAyhnSf1bSQN/U7jiWQHLVbMbLMjRI=
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck=
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
k8s.io/api v0.36.0 h1:SgqDhZzHdOtMk40xVSvCXkP9ME0H05hPM3p9AB1kL80=
k8s.io/api v0.36.0/go.mod h1:m1LVrGPNYax5NBHdO+QuAedXyuzTt4RryI/qnmNvs34=
k8s.io/apiextensions-apiserver v0.36.0 h1:Wt7E8J+VBCbj4FjiBfDTK/neXDDjyJVJc7xfuOHImZ0=
k8s.io/apiextensions-apiserver v0.36.0/go.mod h1:kGDjH0msuiIB3tgsYRV0kS9GqpMYMUsQ3GHv7TApyug=
k8s.io/apimachinery v0.36.0 h1:jZyPzhd5Z+3h9vJLt0z9XdzW9VzNzWAUw+P1xZ9PXtQ=
k8s.io/apimachinery v0.36.0/go.mod h1:FklypaRJt6n5wUIwWXIP6GJlIpUizTgfo1T/As+Tyxc=
k8s.io/apiserver v0.36.0 h1:Jg5OFAENUACByUCg15CmhZAYrr5ZyJ+jodyA1mHl3YE=
k8s.io/apiserver v0.36.0/go.mod h1:mHvwdHf+qKEm+1/hYm756SV+oREOKSPnsjagOpx6Vho=
k8s.io/cli-runtime v0.36.0 h1:HNxciQpQMMOKS0/GiUXcKDyA6J2FDILJj9NmP2BZrTg=
k8s.io/cli-runtime v0.36.0/go.mod h1:KObkknK9Ro5LYX+1RdiKc7C8CvGg4aX+V/Zv+E8WPHA=
k8s.io/client-go v0.36.0 h1:pOYi7C4RHChYjMiHpZSpSbIM6ZxVbRXBy7CuiIwqA3c=
k8s.io/client-go v0.36.0/go.mod h1:ZKKcpwF0aLYfkHFCjillCKaTK/yBkEDHTDXCFY6AS9Y=
k8s.io/component-base v0.36.0 h1:hFjEktssxiJhrK1zfybkH4kJOi8iZuF+mIDCqS5+jRo=
k8s.io/component-base v0.36.0/go.mod h1:JZvIfcNHk+uck+8LhJzhSBtydWXaZNQwX2OdL+Mnwsk=
k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc=
k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0=
k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a h1:xCeOEAOoGYl2jnJoHkC3hkbPJgdATINPMAxaynU2Ovg=
k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a/go.mod h1:uGBT7iTA6c6MvqUvSXIaYZo9ukscABYi2btjhvgKGZ0=
k8s.io/kubectl v0.36.0 h1:hEGr8NvIm2Wjqs2Xy48Uzmvo6lpHdGKlLyMvau2gTms=
k8s.io/kubectl v0.36.0/go.mod h1:iDe8aV5BEi45W8k+5n71I2pJ/nwE0PHDu+/2cejzYoo=
k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 h1:AZYQSJemyQB5eRxqcPky+/7EdBj0xi3g0ZcxxJ7vbWU=
k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk=
oras.land/oras-go/v2 v2.6.0 h1:X4ELRsiGkrbeox69+9tzTu492FMUu7zJQW6eJU+I2oc=
oras.land/oras-go/v2 v2.6.0/go.mod h1:magiQDfG6H1O9APp+rOsvCPcW1GD2MM7vgnKY0Y+u1o=
sigs.k8s.io/controller-runtime v0.23.3 h1:VjB/vhoPoA9l1kEKZHBMnQF33tdCLQKJtydy4iqwZ80=
@ -522,7 +519,7 @@ sigs.k8s.io/kustomize/kyaml v0.21.1 h1:IVlbmhC076nf6foyL6Taw4BkrLuEsXUXNpsE+ScX7
sigs.k8s.io/kustomize/kyaml v0.21.1/go.mod h1:hmxADesM3yUN2vbA5z1/YTBnzLJ1dajdqpQonwBL1FQ=
sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
sigs.k8s.io/structured-merge-diff/v6 v6.3.2-0.20260122202528-d9cc6641c482 h1:2WOzJpHUBVrrkDjU4KBT8n5LDcj824eX0I5UKcgeRUs=
sigs.k8s.io/structured-merge-diff/v6 v6.3.2-0.20260122202528-d9cc6641c482/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
sigs.k8s.io/structured-merge-diff/v6 v6.3.2 h1:kwVWMx5yS1CrnFWA/2QHyRVJ8jM6dBA80uLmm0wJkk8=
sigs.k8s.io/structured-merge-diff/v6 v6.3.2/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=

@ -124,8 +124,8 @@ func TestIsRoot(t *testing.T) {
is := assert.New(t)
is.Equal(false, chrt1.IsRoot())
is.Equal(true, chrt2.IsRoot())
is.False(chrt1.IsRoot())
is.True(chrt2.IsRoot())
}
func TestChartPath(t *testing.T) {

@ -28,8 +28,8 @@ import (
"slices"
"strings"
"k8s.io/apimachinery/pkg/api/validate/content"
"k8s.io/apimachinery/pkg/api/validation"
apipath "k8s.io/apimachinery/pkg/api/validation/path"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/apimachinery/pkg/util/yaml"
@ -292,7 +292,7 @@ func validateMetadataNameFunc(obj *k8sYamlStruct) validation.ValidateNameFunc {
case "role", "clusterrole", "rolebinding", "clusterrolebinding":
// https://github.com/kubernetes/kubernetes/blob/v1.20.0/pkg/apis/rbac/validation/validation.go#L32-L34
return func(name string, _ bool) []string {
return apipath.IsValidPathSegmentName(name)
return content.IsPathSegmentName(name)
}
default:
return validation.NameIsDNSSubdomain

@ -88,6 +88,33 @@ const (
DryRunServer DryRunStrategy = "server"
)
// PostRenderStrategy determines how hooks and regular templates are passed
// to the configured post-renderer.
type PostRenderStrategy string
const (
// PostRenderStrategyCombined sends hooks and regular templates together
// as a single stream to the post-renderer. This is the default in Helm 4.
PostRenderStrategyCombined PostRenderStrategy = "combined"
// PostRenderStrategySeparate sends hooks and regular templates to the
// post-renderer in independent invocations. This avoids duplicate-resource
// errors from post-renderers that de-duplicate by resource identity
// (for example Kustomize) when the same resource appears in both a hook
// and a regular template. Passing hooks to post-renderers was introduced
// in Helm 4; Helm 3 never did so, which is why the issue only surfaces
// with the Helm 4 combined default.
PostRenderStrategySeparate PostRenderStrategy = "separate"
// PostRenderStrategyNoHooks sends only regular templates to the
// post-renderer and leaves hooks untouched. This matches the Helm 3
// behavior and is useful for post-renderers that declare transforms
// targeting template-only resources (for example Kustomize patches
// against a Deployment that exists in templates but not in hooks),
// which would otherwise fail against the hook stream.
PostRenderStrategyNoHooks PostRenderStrategy = "nohooks"
)
// Configuration injects the dependencies that all actions share.
type Configuration struct {
// RESTClientGetter is an interface that loads Kubernetes clients.
@ -198,7 +225,14 @@ func annotateAndMerge(files map[string]string) (string, error) {
// splitAndDeannotate reconstructs individual files from a merged YAML stream,
// removing filename annotations and grouping documents by their original filenames.
func splitAndDeannotate(postrendered string) (map[string]string, error) {
// Documents without a filename annotation are assigned a synthesized name of the
// form "generated-by-postrender-<fallbackPrefix>-<i>.yaml" (or
// "generated-by-postrender-<i>.yaml" when fallbackPrefix is empty). The prefix
// disambiguates fallback filenames across multiple post-render invocations (for
// example when PostRenderStrategySeparate runs the post-renderer once per
// group), so that merging results from different invocations does not collide
// on the same synthetic key.
func splitAndDeannotate(postrendered, fallbackPrefix string) (map[string]string, error) {
manifests, err := kio.ParseAll(postrendered)
if err != nil {
return nil, fmt.Errorf("error parsing YAML: %w", err)
@ -212,7 +246,11 @@ func splitAndDeannotate(postrendered string) (map[string]string, error) {
}
fname := meta.Annotations[filenameAnnotation]
if fname == "" {
fname = fmt.Sprintf("generated-by-postrender-%d.yaml", i)
if fallbackPrefix == "" {
fname = fmt.Sprintf("generated-by-postrender-%d.yaml", i)
} else {
fname = fmt.Sprintf("generated-by-postrender-%s-%d.yaml", fallbackPrefix, i)
}
}
if err := manifest.PipeE(kyaml.ClearAnnotation(filenameAnnotation)); err != nil {
return nil, fmt.Errorf("clearing filename annotation: %w", err)
@ -237,7 +275,7 @@ func splitAndDeannotate(postrendered string) (map[string]string, error) {
// TODO: As part of the refactor the duplicate code in cmd/helm/template.go should be removed
//
// This code has to do with writing files to disk.
func (cfg *Configuration) renderResources(ch *chart.Chart, values common.Values, releaseName, outputDir string, subNotes, useReleaseName, includeCrds bool, pr postrenderer.PostRenderer, interactWithRemote, enableDNS, hideSecret bool) ([]*release.Hook, *bytes.Buffer, string, error) {
func (cfg *Configuration) renderResources(ch *chart.Chart, values common.Values, releaseName, outputDir string, subNotes, useReleaseName, includeCrds bool, pr postrenderer.PostRenderer, interactWithRemote, enableDNS, hideSecret bool, postRenderStrategy PostRenderStrategy) ([]*release.Hook, *bytes.Buffer, string, error) {
var hs []*release.Hook
b := bytes.NewBuffer(nil)
@ -301,29 +339,122 @@ func (cfg *Configuration) renderResources(ch *chart.Chart, values common.Values,
notes := notesBuffer.String()
if pr != nil {
// We need to send files to the post-renderer before sorting and splitting
// hooks from manifests. The post-renderer interface expects a stream of
// manifests (similar to what tools like Kustomize and kubectl expect), whereas
// the sorter uses filenames.
// Here, we merge the documents into a stream, post-render them, and then split
// them back into a map of filename -> content.
// Merge files as stream of documents for sending to post renderer
merged, err := annotateAndMerge(files)
if err != nil {
return hs, b, notes, fmt.Errorf("error merging manifests: %w", err)
}
switch postRenderStrategy {
case PostRenderStrategySeparate, PostRenderStrategyNoHooks:
// Split hooks from manifests before post-rendering. For "separate",
// hooks and templates are sent to the post-renderer as independent
// streams to avoid duplicate-resource errors when the same resource
// appears in both (e.g. a ServiceAccount used by a pre-install hook
// that is also declared in the chart's regular templates). For
// "nohooks", hooks skip the post-renderer entirely, matching the
// Helm 3 behavior.
sortedHooks, sortedManifests, err := releaseutil.SortManifests(files, nil, releaseutil.InstallOrder)
if err != nil {
for name, content := range files {
if strings.TrimSpace(content) == "" {
continue
}
fmt.Fprintf(b, "---\n# Source: %s\n%s\n", name, content)
}
return hs, b, "", err
}
// Run the post renderer
postRendered, err := pr.Run(bytes.NewBufferString(merged))
if err != nil {
return hs, b, notes, fmt.Errorf("error while running post render on files: %w", err)
}
// Build separate files maps for hooks and manifests.
hookFiles := make(map[string]string)
for _, h := range sortedHooks {
if existing, ok := hookFiles[h.Path]; ok {
hookFiles[h.Path] = existing + "\n---\n" + h.Manifest
} else {
hookFiles[h.Path] = h.Manifest
}
}
manifestFiles := make(map[string]string)
for _, m := range sortedManifests {
if existing, ok := manifestFiles[m.Name]; ok {
manifestFiles[m.Name] = existing + "\n---\n" + m.Content
} else {
manifestFiles[m.Name] = m.Content
}
}
// Use the file list and contents received from the post renderer
files, err = splitAndDeannotate(postRendered.String())
if err != nil {
return hs, b, notes, fmt.Errorf("error while parsing post rendered output: %w", err)
// Decide which groups to post-render. "nohooks" passes hooks
// through untouched and only post-renders manifests.
groups := []struct {
name string
files map[string]string
postRender bool
}{
{"hooks", hookFiles, postRenderStrategy == PostRenderStrategySeparate},
{"manifests", manifestFiles, true},
}
files = make(map[string]string)
for _, group := range groups {
if len(group.files) == 0 {
continue
}
if !group.postRender {
for k, v := range group.files {
if existing, ok := files[k]; ok {
files[k] = existing + "\n---\n" + v
} else {
files[k] = v
}
}
continue
}
merged, err := annotateAndMerge(group.files)
if err != nil {
return hs, b, notes, fmt.Errorf("error merging %s: %w", group.name, err)
}
postRendered, err := pr.Run(bytes.NewBufferString(merged))
if err != nil {
return hs, b, notes, fmt.Errorf("error while running post render on %s: %w", group.name, err)
}
rendered, err := splitAndDeannotate(postRendered.String(), group.name)
if err != nil {
return hs, b, notes, fmt.Errorf("error while parsing post rendered output for %s: %w", group.name, err)
}
for k, v := range rendered {
if existing, ok := files[k]; ok {
files[k] = existing + "\n---\n" + v
} else {
files[k] = v
}
}
}
case PostRenderStrategyCombined, "":
// We need to send files to the post-renderer before sorting and splitting
// hooks from manifests. The post-renderer interface expects a stream of
// manifests (similar to what tools like Kustomize and kubectl expect), whereas
// the sorter uses filenames.
// Here, we merge the documents into a stream, post-render them, and then split
// them back into a map of filename -> content.
// Merge files as stream of documents for sending to post renderer
merged, err := annotateAndMerge(files)
if err != nil {
return hs, b, notes, fmt.Errorf("error merging manifests: %w", err)
}
// Run the post renderer
postRendered, err := pr.Run(bytes.NewBufferString(merged))
if err != nil {
return hs, b, notes, fmt.Errorf("error while running post render on files: %w", err)
}
// Use the file list and contents received from the post renderer
files, err = splitAndDeannotate(postRendered.String(), "")
if err != nil {
return hs, b, notes, fmt.Errorf("error while parsing post rendered output: %w", err)
}
default:
return hs, b, notes, fmt.Errorf("unknown post-render strategy: '%s'", postRenderStrategy)
}
}

@ -1722,7 +1722,7 @@ metadata:
data:
key: value`,
expectedFiles: map[string]string{
"generated-by-postrender-0.yaml": `apiVersion: v1
"generated-by-postrender-test-0.yaml": `apiVersion: v1
kind: ConfigMap
metadata:
name: test-cm
@ -1735,7 +1735,7 @@ data:
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
files, err := splitAndDeannotate(tt.input)
files, err := splitAndDeannotate(tt.input, "test")
if tt.expectedError != "" {
assert.Error(t, err)
@ -1789,7 +1789,7 @@ data:
require.NoError(t, err)
// Split and deannotate
reconstructed, err := splitAndDeannotate(merged)
reconstructed, err := splitAndDeannotate(merged, "test")
require.NoError(t, err)
// Compare the results
@ -1824,7 +1824,7 @@ func TestRenderResources_PostRenderer_Success(t *testing.T) {
hooks, buf, notes, err := cfg.renderResources(
ch, values, "test-release", "", false, false, false,
mockPR, false, false, false,
mockPR, false, false, false, PostRenderStrategyCombined,
)
assert.NoError(t, err)
@ -1871,7 +1871,7 @@ func TestRenderResources_PostRenderer_Error(t *testing.T) {
_, _, _, err := cfg.renderResources(
ch, values, "test-release", "", false, false, false,
mockPR, false, false, false,
mockPR, false, false, false, PostRenderStrategyCombined,
)
assert.Error(t, err)
@ -1899,7 +1899,7 @@ func TestRenderResources_PostRenderer_MergeError(t *testing.T) {
_, _, _, err := cfg.renderResources(
ch, values, "test-release", "", false, false, false,
mockPR, false, false, false,
mockPR, false, false, false, PostRenderStrategyCombined,
)
assert.Error(t, err)
@ -1921,7 +1921,7 @@ func TestRenderResources_PostRenderer_SplitError(t *testing.T) {
_, _, _, err := cfg.renderResources(
ch, values, "test-release", "", false, false, false,
mockPR, false, false, false,
mockPR, false, false, false, PostRenderStrategyCombined,
)
assert.Error(t, err)
@ -1942,7 +1942,7 @@ func TestRenderResources_PostRenderer_Integration(t *testing.T) {
hooks, buf, notes, err := cfg.renderResources(
ch, values, "test-release", "", false, false, false,
mockPR, false, false, false,
mockPR, false, false, false, PostRenderStrategyCombined,
)
assert.NoError(t, err)
@ -1981,7 +1981,7 @@ func TestRenderResources_NoPostRenderer(t *testing.T) {
hooks, buf, notes, err := cfg.renderResources(
ch, values, "test-release", "", false, false, false,
nil, false, false, false,
nil, false, false, false, PostRenderStrategyCombined,
)
assert.NoError(t, err)
@ -1990,6 +1990,305 @@ func TestRenderResources_NoPostRenderer(t *testing.T) {
assert.Equal(t, "", notes)
}
func TestRenderResources_PostRenderer_DuplicateResourceInHookAndTemplate(t *testing.T) {
cfg := actionConfigFixture(t)
// Simulate a chart where the same ServiceAccount appears both as a
// pre-install hook and as a regular template. This is a valid Helm pattern
// but previously caused post-renderers like Kustomize to fail with
// "may not add resource with an already registered id" because hooks and
// templates were merged into a single stream before post-rendering.
saHook := `apiVersion: v1
kind: ServiceAccount
metadata:
name: my-app
annotations:
"helm.sh/hook": pre-install
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded`
saTemplate := `apiVersion: v1
kind: ServiceAccount
metadata:
name: my-app`
deployment := `apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
template:
spec:
serviceAccountName: my-app`
modTime := time.Now()
ch := buildChartWithTemplates([]*common.File{
{Name: "templates/sa-hook.yaml", ModTime: modTime, Data: []byte(saHook)},
{Name: "templates/sa.yaml", ModTime: modTime, Data: []byte(saTemplate)},
{Name: "templates/deployment.yaml", ModTime: modTime, Data: []byte(deployment)},
})
// Use a post-renderer that rejects duplicate resource IDs, similar to
// how Kustomize behaves. We verify that no single post-render call
// receives the ServiceAccount twice.
mockPR := &mockPostRenderer{
transform: func(content string) string {
count := strings.Count(content, "kind: ServiceAccount")
if count > 1 {
t.Errorf("post-renderer received %d ServiceAccount resources in a single stream, expected at most 1", count)
}
return content
},
}
hooks, buf, _, err := cfg.renderResources(
ch, nil, "test-release", "", false, false, false,
mockPR, false, false, false, PostRenderStrategySeparate,
)
assert.NoError(t, err)
assert.Len(t, hooks, 1)
assert.Equal(t, "my-app", hooks[0].Name)
assert.Contains(t, buf.String(), "kind: Deployment")
assert.Contains(t, buf.String(), "kind: ServiceAccount")
}
func TestRenderResources_PostRenderer_CombinedInvokesOnceWithEverything(t *testing.T) {
cfg := actionConfigFixture(t)
hookManifest := `apiVersion: v1
kind: ConfigMap
metadata:
name: hook-cm
annotations:
"helm.sh/hook": pre-install`
templateManifest := `apiVersion: v1
kind: ConfigMap
metadata:
name: template-cm`
modTime := time.Now()
ch := buildChartWithTemplates([]*common.File{
{Name: "templates/hook.yaml", ModTime: modTime, Data: []byte(hookManifest)},
{Name: "templates/cm.yaml", ModTime: modTime, Data: []byte(templateManifest)},
})
var calls int
var lastInput string
mockPR := &mockPostRenderer{
transform: func(content string) string {
calls++
lastInput = content
return content
},
}
_, _, _, err := cfg.renderResources(
ch, nil, "test-release", "", false, false, false,
mockPR, false, false, false, PostRenderStrategyCombined,
)
assert.NoError(t, err)
assert.Equal(t, 1, calls, "combined strategy should invoke the post-renderer exactly once")
assert.Contains(t, lastInput, "hook-cm")
assert.Contains(t, lastInput, "template-cm")
}
func TestRenderResources_PostRenderer_ZeroValueStrategyActsAsCombined(t *testing.T) {
cfg := actionConfigFixture(t)
modTime := time.Now()
ch := buildChartWithTemplates([]*common.File{
{Name: "templates/cm.yaml", ModTime: modTime, Data: []byte(`apiVersion: v1
kind: ConfigMap
metadata:
name: template-cm`)},
{Name: "templates/hook.yaml", ModTime: modTime, Data: []byte(`apiVersion: v1
kind: ConfigMap
metadata:
name: hook-cm
annotations:
"helm.sh/hook": pre-install`)},
})
var calls int
mockPR := &mockPostRenderer{
transform: func(content string) string {
calls++
return content
},
}
_, _, _, err := cfg.renderResources(
ch, nil, "test-release", "", false, false, false,
mockPR, false, false, false, PostRenderStrategy(""),
)
assert.NoError(t, err)
assert.Equal(t, 1, calls, "unset strategy must preserve backwards-compatible combined behavior")
}
func TestRenderResources_PostRenderer_SeparateSplitsHooksAndTemplates(t *testing.T) {
cfg := actionConfigFixture(t)
modTime := time.Now()
ch := buildChartWithTemplates([]*common.File{
{Name: "templates/hook.yaml", ModTime: modTime, Data: []byte(`apiVersion: v1
kind: ConfigMap
metadata:
name: hook-cm
annotations:
"helm.sh/hook": pre-install`)},
{Name: "templates/cm.yaml", ModTime: modTime, Data: []byte(`apiVersion: v1
kind: ConfigMap
metadata:
name: template-cm`)},
})
var inputs []string
mockPR := &mockPostRenderer{
transform: func(content string) string {
inputs = append(inputs, content)
return content
},
}
_, _, _, err := cfg.renderResources(
ch, nil, "test-release", "", false, false, false,
mockPR, false, false, false, PostRenderStrategySeparate,
)
assert.NoError(t, err)
assert.Len(t, inputs, 2, "separate strategy should invoke the post-renderer twice when both hooks and templates exist")
for _, in := range inputs {
hasHook := strings.Contains(in, "hook-cm")
hasTemplate := strings.Contains(in, "template-cm")
assert.False(t, hasHook && hasTemplate, "a single post-render invocation must not contain both hook and template resources")
assert.True(t, hasHook || hasTemplate, "each post-render invocation must contain either a hook or a template")
}
}
func TestRenderResources_PostRenderer_SeparateWithOnlyTemplates(t *testing.T) {
cfg := actionConfigFixture(t)
modTime := time.Now()
ch := buildChartWithTemplates([]*common.File{
{Name: "templates/cm.yaml", ModTime: modTime, Data: []byte(`apiVersion: v1
kind: ConfigMap
metadata:
name: template-cm`)},
})
var calls int
mockPR := &mockPostRenderer{
transform: func(content string) string {
calls++
return content
},
}
_, _, _, err := cfg.renderResources(
ch, nil, "test-release", "", false, false, false,
mockPR, false, false, false, PostRenderStrategySeparate,
)
assert.NoError(t, err)
assert.Equal(t, 1, calls, "separate strategy should skip the empty hook group and invoke the post-renderer only once")
}
func TestRenderResources_PostRenderer_NoHooksSkipsHooks(t *testing.T) {
cfg := actionConfigFixture(t)
modTime := time.Now()
ch := buildChartWithTemplates([]*common.File{
{Name: "templates/hook.yaml", ModTime: modTime, Data: []byte(`apiVersion: v1
kind: ConfigMap
metadata:
name: hook-cm
annotations:
"helm.sh/hook": pre-install`)},
{Name: "templates/cm.yaml", ModTime: modTime, Data: []byte(`apiVersion: v1
kind: ConfigMap
metadata:
name: template-cm`)},
})
var inputs []string
mockPR := &mockPostRenderer{
transform: func(content string) string {
inputs = append(inputs, content)
return content
},
}
hooks, manifestDoc, _, err := cfg.renderResources(
ch, nil, "test-release", "", false, false, false,
mockPR, false, false, false, PostRenderStrategyNoHooks,
)
assert.NoError(t, err)
assert.Len(t, inputs, 1, "nohooks strategy should invoke the post-renderer exactly once (for templates only)")
assert.NotContains(t, inputs[0], "hook-cm", "hooks must not be sent to the post-renderer")
assert.Contains(t, inputs[0], "template-cm", "templates must be sent to the post-renderer")
// Hooks still round-trip through the release so they can execute.
require.Len(t, hooks, 1)
assert.Contains(t, hooks[0].Manifest, "hook-cm")
assert.Contains(t, manifestDoc.String(), "template-cm")
}
func TestRenderResources_PostRenderer_NoHooksWithOnlyHooks(t *testing.T) {
cfg := actionConfigFixture(t)
modTime := time.Now()
ch := buildChartWithTemplates([]*common.File{
{Name: "templates/hook.yaml", ModTime: modTime, Data: []byte(`apiVersion: v1
kind: ConfigMap
metadata:
name: hook-cm
annotations:
"helm.sh/hook": pre-install`)},
})
var calls int
mockPR := &mockPostRenderer{
transform: func(content string) string {
calls++
return content
},
}
_, _, _, err := cfg.renderResources(
ch, nil, "test-release", "", false, false, false,
mockPR, false, false, false, PostRenderStrategyNoHooks,
)
assert.NoError(t, err)
assert.Equal(t, 0, calls, "nohooks strategy should not invoke the post-renderer when the chart only has hooks")
}
func TestRenderResources_PostRenderer_UnknownStrategyErrors(t *testing.T) {
cfg := actionConfigFixture(t)
modTime := time.Now()
ch := buildChartWithTemplates([]*common.File{
{Name: "templates/cm.yaml", ModTime: modTime, Data: []byte(`apiVersion: v1
kind: ConfigMap
metadata:
name: template-cm`)},
})
mockPR := &mockPostRenderer{}
_, _, _, err := cfg.renderResources(
ch, nil, "test-release", "", false, false, false,
mockPR, false, false, false, PostRenderStrategy("bogus"),
)
assert.Error(t, err)
assert.Contains(t, err.Error(), "unknown post-render strategy")
assert.Contains(t, err.Error(), "bogus")
}
func TestDetermineReleaseSSAApplyMethod(t *testing.T) {
assert.Equal(t, release.ApplyMethodClientSideApply, determineReleaseSSApplyMethod(false))
assert.Equal(t, release.ApplyMethodServerSideApply, determineReleaseSSApplyMethod(true))

@ -37,7 +37,7 @@ func TestNewGetValues(t *testing.T) {
assert.NotNil(t, client)
assert.Equal(t, cfg, client.cfg)
assert.Equal(t, 0, client.Version)
assert.Equal(t, false, client.AllValues)
assert.False(t, client.AllValues)
}
func TestGetValues_Run_UserConfigOnly(t *testing.T) {

@ -130,6 +130,10 @@ type Install struct {
// TakeOwnership will ignore the check for helm annotations and take ownership of the resources.
TakeOwnership bool
PostRenderer postrenderer.PostRenderer
// PostRenderStrategy controls how hooks and regular templates are passed
// to the configured post-renderer. See PostRenderStrategy for the
// available modes. Defaults to PostRenderStrategyCombined.
PostRenderStrategy PostRenderStrategy
// Lock to control raceconditions when the process receives a SIGTERM
Lock sync.Mutex
goroutineCount atomic.Int32
@ -158,9 +162,10 @@ type ChartPathOptions struct {
// NewInstall creates a new Install object with the given configuration.
func NewInstall(cfg *Configuration) *Install {
in := &Install{
cfg: cfg,
ServerSideApply: true, // Must always match the CLI default.
DryRunStrategy: DryRunNone,
cfg: cfg,
ServerSideApply: true, // Must always match the CLI default.
DryRunStrategy: DryRunNone,
PostRenderStrategy: PostRenderStrategyCombined,
}
in.registryClient = cfg.RegistryClient
@ -370,7 +375,7 @@ func (i *Install) RunWithContext(ctx context.Context, ch ci.Charter, vals map[st
rel := i.createRelease(chrt, vals, i.Labels)
var manifestDoc *bytes.Buffer
rel.Hooks, manifestDoc, rel.Info.Notes, err = i.cfg.renderResources(chrt, valuesToRender, i.ReleaseName, i.OutputDir, i.SubNotes, i.UseReleaseName, i.IncludeCRDs, i.PostRenderer, interactWithServer(i.DryRunStrategy), i.EnableDNS, i.HideSecret)
rel.Hooks, manifestDoc, rel.Info.Notes, err = i.cfg.renderResources(chrt, valuesToRender, i.ReleaseName, i.OutputDir, i.SubNotes, i.UseReleaseName, i.IncludeCRDs, i.PostRenderer, interactWithServer(i.DryRunStrategy), i.EnableDNS, i.HideSecret, i.PostRenderStrategy)
// Even for errors, attach this if available
if manifestDoc != nil {
rel.Manifest = manifestDoc.String()

@ -47,14 +47,14 @@ func TestNewPushWithInsecureSkipTLSVerify(t *testing.T) {
client := NewPushWithOpts(WithInsecureSkipTLSVerify(true))
assert.NotNil(t, client)
assert.Equal(t, true, client.insecureSkipTLSVerify)
assert.True(t, client.insecureSkipTLSVerify)
}
func TestNewPushWithPlainHTTP(t *testing.T) {
client := NewPushWithOpts(WithPlainHTTP(true))
assert.NotNil(t, client)
assert.Equal(t, true, client.plainHTTP)
assert.True(t, client.plainHTTP)
}
func TestNewPushWithPushOptWriter(t *testing.T) {

@ -48,7 +48,7 @@ func TestWithInsecure(t *testing.T) {
opt := WithInsecure(true)
assert.Nil(t, opt(client))
assert.Equal(t, true, client.insecure)
assert.True(t, client.insecure)
}
func TestWithKeyFile(t *testing.T) {
@ -80,5 +80,5 @@ func TestWithPlainHTTPLogin(t *testing.T) {
opt := WithPlainHTTPLogin(true)
assert.Nil(t, opt(client))
assert.Equal(t, true, client.plainHTTP)
assert.True(t, client.plainHTTP)
}

@ -121,6 +121,10 @@ type Upgrade struct {
// If this is non-nil, then after templates are rendered, they will be sent to the
// post renderer before sending to the Kubernetes API server.
PostRenderer postrenderer.PostRenderer
// PostRenderStrategy controls how hooks and regular templates are passed
// to the configured post-renderer. See PostRenderStrategy for the
// available modes. Defaults to PostRenderStrategyCombined.
PostRenderStrategy PostRenderStrategy
// DisableOpenAPIValidation controls whether OpenAPI validation is enforced.
DisableOpenAPIValidation bool
// Get missing dependencies
@ -141,9 +145,10 @@ type resultMessage struct {
// NewUpgrade creates a new Upgrade object with the given configuration.
func NewUpgrade(cfg *Configuration) *Upgrade {
up := &Upgrade{
cfg: cfg,
ServerSideApply: "auto", // Must always match the CLI default.
DryRunStrategy: DryRunNone,
cfg: cfg,
ServerSideApply: "auto", // Must always match the CLI default.
DryRunStrategy: DryRunNone,
PostRenderStrategy: PostRenderStrategyCombined,
}
up.registryClient = cfg.RegistryClient
@ -296,7 +301,7 @@ func (u *Upgrade) prepareUpgrade(name string, chart *chartv2.Chart, vals map[str
return nil, nil, false, err
}
hooks, manifestDoc, notesTxt, err := u.cfg.renderResources(chart, valuesToRender, "", "", u.SubNotes, false, false, u.PostRenderer, interactWithServer(u.DryRunStrategy), u.EnableDNS, u.HideSecret)
hooks, manifestDoc, notesTxt, err := u.cfg.renderResources(chart, valuesToRender, "", "", u.SubNotes, false, false, u.PostRenderer, interactWithServer(u.DryRunStrategy), u.EnableDNS, u.HideSecret, u.PostRenderStrategy)
if err != nil {
return nil, nil, false, err
}

@ -124,8 +124,8 @@ func TestIsRoot(t *testing.T) {
is := assert.New(t)
is.Equal(false, chrt1.IsRoot())
is.Equal(true, chrt2.IsRoot())
is.False(chrt1.IsRoot())
is.True(chrt2.IsRoot())
}
func TestChartPath(t *testing.T) {

@ -28,8 +28,8 @@ import (
"slices"
"strings"
"k8s.io/apimachinery/pkg/api/validate/content"
"k8s.io/apimachinery/pkg/api/validation"
apipath "k8s.io/apimachinery/pkg/api/validation/path"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/apimachinery/pkg/util/yaml"
@ -323,7 +323,7 @@ func validateMetadataNameFunc(obj *k8sYamlStruct) validation.ValidateNameFunc {
case "role", "clusterrole", "rolebinding", "clusterrolebinding":
// https://github.com/kubernetes/kubernetes/blob/v1.20.0/pkg/apis/rbac/validation/validation.go#L32-L34
return func(name string, _ bool) []string {
return apipath.IsValidPathSegmentName(name)
return content.IsPathSegmentName(name)
}
default:
return validation.NameIsDNSSubdomain

@ -210,6 +210,26 @@ func addInstallFlags(cmd *cobra.Command, f *pflag.FlagSet, client *action.Instal
f.BoolVar(&client.EnableDNS, "enable-dns", false, "enable DNS lookups when rendering templates")
f.BoolVar(&client.HideNotes, "hide-notes", false, "if set, do not show notes in install output. Does not affect presence in chart metadata")
f.BoolVar(&client.TakeOwnership, "take-ownership", false, "if set, install will ignore the check for helm annotations and take ownership of the existing resources")
// For `helm template`, these notes flags are legacy, unused, and should not show in help, but
// must remain accepted for backwards compatibility in Helm 4. Deprecate and hide them for now
// TODO remove these from template command in Helm 5
if cmd.Name() == "template" {
if err := cmd.Flags().MarkDeprecated("hide-notes", "this flag has no effect for 'helm template' and will be removed in Helm 5"); err != nil {
log.Fatal(err)
}
if err := cmd.Flags().MarkHidden("hide-notes"); err != nil {
log.Fatal(err)
}
if err := cmd.Flags().MarkDeprecated("render-subchart-notes", "this flag has no effect for 'helm template' and will be removed in Helm 5"); err != nil {
log.Fatal(err)
}
if err := cmd.Flags().MarkHidden("render-subchart-notes"); err != nil {
log.Fatal(err)
}
}
addValueOptionsFlags(f, valueOpts)
addChartPathOptionsFlags(f, &client.ChartPathOptions)
AddWaitFlag(cmd, &client.WaitStrategy)

@ -84,7 +84,7 @@ func TestLintCmdWithKubeVersionFlag(t *testing.T) {
wantError: false,
}, {
name: "lint chart with deprecated api version with older kube version",
cmd: "lint --kube-version 1.21.0 --strict " + testChart,
cmd: "lint --kube-version 1.20.0 --strict " + testChart,
golden: "output/lint-chart-with-deprecated-api-old-k8s.txt",
wantError: false,
}}

@ -1,5 +1,5 @@
==> Linting testdata/testcharts/chart-with-deprecated-api
[INFO] Chart.yaml: icon is recommended
[WARNING] templates/horizontalpodautoscaler.yaml: autoscaling/v2beta1 HorizontalPodAutoscaler is deprecated in v1.22+, unavailable in v1.25+; use autoscaling/v2 HorizontalPodAutoscaler
[WARNING] templates/poddisruptionbudget.yaml: policy/v1beta1 PodDisruptionBudget is deprecated in v1.21+, unavailable in v1.25+; use policy/v1 PodDisruptionBudget
Error: 1 chart(s) linted, 1 chart(s) failed

@ -1,5 +1,5 @@
==> Linting testdata/testcharts/chart-with-deprecated-api
[INFO] Chart.yaml: icon is recommended
[WARNING] templates/horizontalpodautoscaler.yaml: autoscaling/v2beta1 HorizontalPodAutoscaler is deprecated in v1.22+, unavailable in v1.25+; use autoscaling/v2 HorizontalPodAutoscaler
[WARNING] templates/poddisruptionbudget.yaml: policy/v1beta1 PodDisruptionBudget is deprecated in v1.21+, unavailable in v1.25+; use policy/v1 PodDisruptionBudget
1 chart(s) linted, 0 chart(s) failed

@ -1,9 +0,0 @@
apiVersion: autoscaling/v2beta1
kind: HorizontalPodAutoscaler
metadata:
name: deprecated
spec:
scaleTargetRef:
kind: Pod
name: pod
maxReplicas: 3

@ -0,0 +1,9 @@
apiVersion: policy/v1beta1
kind: PodDisruptionBudget
metadata:
name: deprecated
spec:
maxUnavailable: 1
selector:
matchLabels:
app: deprecated

@ -972,6 +972,7 @@ func TestGetPodList(t *testing.T) {
podList, err := c.GetPodList(namespace, metav1.ListOptions{})
clientAssertions := assert.New(t)
clientAssertions.NoError(err)
podList.ResourceVersion = ""
clientAssertions.Equal(&responsePodList, podList)
}

Loading…
Cancel
Save