Merge branch 'main' into em/check-ownership-before-delete

Signed-off-by: Evans Mungai <mbuevans@gmail.com>
pull/31584/head
Evans Mungai 1 month ago committed by GitHub
commit a6179d0dd1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -48,7 +48,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@e46ed2cbd01164d986452f91f178727624ae40d7 # pinv4.35.3
uses: github/codeql-action/init@87557b9c84dde89fdd9b10e88954ac2f4248e463 # pinv4.36.1
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@ -59,7 +59,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@e46ed2cbd01164d986452f91f178727624ae40d7 # pinv4.35.3
uses: github/codeql-action/autobuild@87557b9c84dde89fdd9b10e88954ac2f4248e463 # pinv4.36.1
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
@ -73,4 +73,4 @@ jobs:
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@e46ed2cbd01164d986452f91f178727624ae40d7 # pinv4.35.3
uses: github/codeql-action/analyze@87557b9c84dde89fdd9b10e88954ac2f4248e463 # pinv4.36.1

@ -22,6 +22,6 @@ jobs:
go-version: '${{ env.GOLANG_VERSION }}'
check-latest: true
- name: golangci-lint
uses: golangci/golangci-lint-action@1e7e51e771db61008b38414a730f564565cf7c20 #pin@9.2.0
uses: golangci/golangci-lint-action@82606bf257cbaff209d206a39f5134f0cfbfd2ee #pin@9.2.1
with:
version: ${{ env.GOLANGCI_LINT_VERSION }}

@ -64,6 +64,6 @@ jobs:
# Upload the results to GitHub's code scanning dashboard (optional).
# Commenting out will disable upload of results to your repo's Code Scanning dashboard
- name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@e46ed2cbd01164d986452f91f178727624ae40d7 # v4.35.3
uses: github/codeql-action/upload-sarif@87557b9c84dde89fdd9b10e88954ac2f4248e463 # v4.36.1
with:
sarif_file: results.sarif

@ -12,7 +12,7 @@ jobs:
issues: write
pull-requests: write
steps:
- uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f # v10.2.0
- uses: actions/stale@eb5cf3af3ac0a1aa4c9c45633dd1ae542a27a899 # v10.3.0
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
stale-issue-message: 'This issue has been marked as stale because it has been open for 90 days with no activity. This thread will be automatically closed in 30 days if no further activity occurs.'

@ -19,6 +19,7 @@ linters:
# Keep sorted alphabetically
enable:
- bidichk
- depguard
- dupl
- exhaustive
@ -38,6 +39,7 @@ linters:
- unused
- usestdlibvars
- usetesting
- whitespace
exclusions:
@ -101,8 +103,6 @@ linters:
- empty
- encoded-compare
- equal-values
- error-is-as
- error-nil
- expected-actual
- float-compare
- go-require

@ -32,8 +32,8 @@ Think of it like apt/yum/homebrew for Kubernetes.
## Helm Development and Stable Versions
Helm v4 is currently under development on the `main` branch. This is unstable and the APIs within the Go SDK and at the command line are changing.
Helm v3 (current stable) is maintained on the `dev-v3` branch. APIs there follow semantic versioning.
Helm v4 is the current stable release, developed on the `main` branch.
Helm v3 is in support mode on the `dev-v3` branch: bug fixes until July 8th 2026, security fixes until November 11th 2026.
## Install
@ -64,7 +64,7 @@ Get started with the [Quick Start guide](https://helm.sh/docs/intro/quickstart/)
The [Helm roadmap uses GitHub milestones](https://github.com/helm/helm/milestones) to track the progress of the project.
The development of Helm v4 is currently happening on the `main` branch while the development of Helm v3, the stable branch, is happening on the `dev-v3` branch. Changes should be made to the `main` branch prior to being added to the `dev-v3` branch so that all changes are carried along to Helm v4.
Helm v4 development happens on the `main` branch. Helm v3 is in support mode on the `dev-v3` branch and receives only bug and security fixes.
## Community, discussion, contribution, and support

@ -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.2.0
github.com/fluxcd/cli-utils v1.2.1
github.com/foxcpp/go-mockdns v1.2.0
github.com/gobwas/glob v0.2.3
github.com/gofrs/flock v0.13.0
@ -33,22 +33,22 @@ require (
github.com/spf13/cobra v1.10.2
github.com/spf13/pflag v1.0.10
github.com/stretchr/testify v1.11.1
github.com/tetratelabs/wazero v1.11.0
github.com/tetratelabs/wazero v1.12.0
go.yaml.in/yaml/v3 v3.0.4
golang.org/x/crypto v0.50.0
golang.org/x/term v0.42.0
golang.org/x/text v0.36.0
golang.org/x/crypto v0.52.0
golang.org/x/term v0.43.0
golang.org/x/text v0.37.0
gopkg.in/yaml.v3 v3.0.1 // indirect
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/api v0.36.1
k8s.io/apiextensions-apiserver v0.36.1
k8s.io/apimachinery v0.36.1
k8s.io/apiserver v0.36.1
k8s.io/cli-runtime v0.36.1
k8s.io/client-go v0.36.1
k8s.io/klog/v2 v2.140.0
k8s.io/kubectl v0.36.0
k8s.io/kubectl v0.36.1
oras.land/oras-go/v2 v2.6.0
sigs.k8s.io/controller-runtime v0.24.0
sigs.k8s.io/controller-runtime v0.24.1
sigs.k8s.io/kustomize/kyaml v0.21.1
sigs.k8s.io/yaml v1.6.0
)
@ -157,13 +157,13 @@ require (
go.opentelemetry.io/otel/trace v1.43.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.34.0 // indirect
golang.org/x/net v0.52.0 // indirect
golang.org/x/mod v0.35.0 // indirect
golang.org/x/net v0.55.0 // indirect
golang.org/x/oauth2 v0.36.0 // indirect
golang.org/x/sync v0.20.0 // indirect
golang.org/x/sys v0.43.0 // indirect
golang.org/x/sys v0.45.0 // indirect
golang.org/x/time v0.15.0 // indirect
golang.org/x/tools v0.43.0 // indirect
golang.org/x/tools v0.44.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
@ -171,7 +171,7 @@ require (
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.36.0 // indirect
k8s.io/component-base v0.36.1 // 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

@ -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.2.0 h1:1o07pXTMxJ/XJ1GpAbLtjdXwfCUMq4Ku1OcnvJHLohI=
github.com/fluxcd/cli-utils v1.2.0/go.mod h1:d5HdTDdR5sCbsIbgtOQ7x7srKYwYeZORU6CD2yn4j/M=
github.com/fluxcd/cli-utils v1.2.1 h1:ug9CicKW7H9QXnvNDapTSKuryZvWcu4Nw7pRvQa6jDY=
github.com/fluxcd/cli-utils v1.2.1/go.mod h1:cky6M6eHvTQkoPtsuFYLIgAMYdpTCSLoor4IA6vueSw=
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=
@ -309,8 +309,8 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/tetratelabs/wabin v0.0.0-20230304001439-f6f874872834 h1:ZF+QBjOI+tILZjBaFj3HgFonKXUcwgJ4djLb6i42S3Q=
github.com/tetratelabs/wabin v0.0.0-20230304001439-f6f874872834/go.mod h1:m9ymHTgNSEjuxvw8E7WWe4Pl4hZQHXONY8wE6dMLaRk=
github.com/tetratelabs/wazero v1.11.0 h1:+gKemEuKCTevU4d7ZTzlsvgd1uaToIDtlQlmNbwqYhA=
github.com/tetratelabs/wazero v1.11.0/go.mod h1:eV28rsN8Q+xwjogd7f4/Pp4xFxO7uOGbLcD/LzB1wiU=
github.com/tetratelabs/wazero v1.12.0 h1:DuWcpNu/FzgEXgGBDp8J1Spc+CWOvvtvVyjKlaZopYU=
github.com/tetratelabs/wazero v1.12.0/go.mod h1:LvKtzl2RqO4gyF27BiXU+nKAjcV8f38U+kP/q2vgxh0=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ=
@ -382,14 +382,14 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q=
golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988=
golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI=
golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY=
golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM=
golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@ -400,8 +400,8 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ=
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -430,8 +430,8 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
@ -439,8 +439,8 @@ golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww=
golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY=
golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY=
golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4=
golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
@ -448,8 +448,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@ -458,8 +458,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk=
golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s=
golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0=
golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c=
golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI=
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=
@ -485,32 +485,32 @@ 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.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/api v0.36.1 h1:XbL/EMj8K2aJpJtePmqUyQMsM0D4QI2pvl7YKJ20FTY=
k8s.io/api v0.36.1/go.mod h1:KOWo4ey3TINlXjeHVuwB3i+tXXnu+UcwFBHlI/9dvEo=
k8s.io/apiextensions-apiserver v0.36.1 h1:6JfYmPUsuUIHuN+3QxutXYWj492RqF5fBSx67GYK5Ks=
k8s.io/apiextensions-apiserver v0.36.1/go.mod h1:pLzZin90riwisdzKwv/GoTwENooytoIx5zWJb4Hkby8=
k8s.io/apimachinery v0.36.1 h1:G63Gjx2W+q0YD+72Vo8oY0nDnePVwnuzTmmy5ENrVSA=
k8s.io/apimachinery v0.36.1/go.mod h1:ibYOR00vW/I1kzvi5SF0dRuJ52BvKtfvRdOn35GPQ+8=
k8s.io/apiserver v0.36.1 h1:iMS5V+rPUertv5P9RaqJgmHHTuh4quWpoxchvMUY+JY=
k8s.io/apiserver v0.36.1/go.mod h1:Cby1PbLWztu0GDOxoO6iFOyyqIsziHNEW+w9zVQ22Kw=
k8s.io/cli-runtime v0.36.1 h1:yuC/BGnnj1YYPh6D1P+pZnzinCs6DvMq86yAeNqoqzM=
k8s.io/cli-runtime v0.36.1/go.mod h1:ZQWHGt8xAF7KnviB79vX0lYNyUUqKIpU+LQg7exuFAw=
k8s.io/client-go v0.36.1 h1:FN/K8QIT2CEDt+2WB2HnWrUANZ50AP5GII43/SP2JR0=
k8s.io/client-go v0.36.1/go.mod h1:s6rAnCtTGYDQnpNjEhSaISV+2O8jwruZ6m3QOYBFbtU=
k8s.io/component-base v0.36.1 h1:iG6GsELftXqTNG9HG6kiVjatSgAw1sf5pJ6R5a6N0kA=
k8s.io/component-base v0.36.1/go.mod h1:nf9XPlntRdqO6WMeEWAA5F93Y4ICZQdeT9GeqLDB3JI=
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/kubectl v0.36.1 h1:96HqS9twIdHM0MlJLTwbo14b9kUKPkOzZ4tlRDLv4qI=
k8s.io/kubectl v0.36.1/go.mod h1:/DGPAIewKsFWF9VFgGvkPhao2Ev4SNuE3BioZo8yPbk=
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.24.0 h1:Ck6N2LdS8Lovy1o25BB4r1xjvLEKUl1s2o9kU+KWDE4=
sigs.k8s.io/controller-runtime v0.24.0/go.mod h1:vFkfY5fGt5xAC/sKb8IBFKgWPNKG9OUG29dR8Y2wImw=
sigs.k8s.io/controller-runtime v0.24.1 h1:miPEwrmirImAvgME1L9qebGHrOnGJoVmVdtOU9fRfo4=
sigs.k8s.io/controller-runtime v0.24.1/go.mod h1:vFkfY5fGt5xAC/sKb8IBFKgWPNKG9OUG29dR8Y2wImw=
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg=
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
sigs.k8s.io/kustomize/api v0.21.1 h1:lzqbzvz2CSvsjIUZUBNFKtIMsEw7hVLJp0JeSIVmuJs=

@ -104,7 +104,7 @@ func TestMetadata(t *testing.T) {
is.Equal("foo.yaml", chrt.Name())
is.Equal("1.0.0", chrt.AppVersion())
is.Equal(nil, chrt.Validate())
is.NoError(chrt.Validate())
}
func TestIsRoot(t *testing.T) {

@ -44,7 +44,6 @@ func WithSkipSchemaValidation(skipSchemaValidation bool) LinterOption {
}
func RunAll(baseDir string, values map[string]any, namespace string, options ...LinterOption) support.Linter {
chartDir, _ := filepath.Abs(baseDir)
lo := linterOptions{}

@ -28,7 +28,7 @@ import (
kscheme "k8s.io/client-go/kubernetes/scheme"
)
// deprecatedAPIError indicates than an API is deprecated in Kubernetes
// deprecatedAPIError indicates that an API is deprecated in Kubernetes
type deprecatedAPIError struct {
Deprecated string
Message string

@ -242,7 +242,6 @@ data:
//
// See https://github.com/helm/helm/issues/7483
func TestStrictTemplateParsingMapError(t *testing.T) {
ch := chart.Chart{
Metadata: &chart.Metadata{
Name: "regression7483",
@ -371,7 +370,6 @@ func TestValidateTopIndentLevel(t *testing.T) {
t.Errorf("Expected %t for %q", shouldFail, doc)
}
}
}
// TestEmptyWithCommentsManifests checks the lint is not failing against empty manifests that contains only comments
@ -463,5 +461,4 @@ func TestIsYamlFileExtension(t *testing.T) {
t.Errorf("isYamlFileExtension(%s) = %v; want %v", test.filename, result, test.expected)
}
}
}

@ -337,7 +337,6 @@ icon: https://example.com/64x64.png
if text.String() != "" {
t.Errorf("Expected no message to Stderr, got %s", text.String())
}
}
// Packaging the chart on a Windows machine will produce an
@ -607,7 +606,6 @@ func verifyChart(t *testing.T, c *chart.Chart) {
t.Errorf("Expected %s version %s, got %s", dep.Name(), exp["version"], dep.Metadata.Version)
}
}
}
func verifyDependencies(t *testing.T, c *chart.Chart) {

@ -702,7 +702,6 @@ func CreateFrom(chartfile *chart.Metadata, dest, src string) error {
// error. In such a case, this will attempt to clean up by removing the
// new chart directory.
func Create(name, dir string) (string, error) {
// Sanity-check the name of a chart so user doesn't create one that causes problems.
if err := validateChartName(name); err != nil {
return "", err

@ -459,7 +459,6 @@ func TestDependentChartAliases(t *testing.T) {
if aliasChart := getAliasDependency(c.Dependencies(), req[2]); aliasChart != nil {
t.Fatalf("expected no chart but got %s", aliasChart.Name())
}
}
func TestDependentChartWithSubChartsAbsentInDependency(t *testing.T) {

@ -79,7 +79,6 @@ func ValidateReleaseName(name string) error {
// This case is preserved for backwards compatibility
if name == "" {
return errMissingName
}
if len(name) > maxReleaseNameLen || !validName.MatchString(name) {
return errInvalidName

@ -24,7 +24,6 @@ import (
)
func TestColorizeStatus(t *testing.T) {
tests := []struct {
name string
status common.Status
@ -107,7 +106,6 @@ func TestColorizeStatus(t *testing.T) {
}
func TestColorizeHeader(t *testing.T) {
tests := []struct {
name string
header string
@ -149,7 +147,6 @@ func TestColorizeHeader(t *testing.T) {
}
func TestColorizeNamespace(t *testing.T) {
tests := []struct {
name string
namespace string

@ -33,7 +33,6 @@ type Client struct {
// New creates a new client
func New(u string) (*Client, error) {
// Validate we have a URL
if err := validate(u); err != nil {
return nil, err
@ -46,7 +45,6 @@ func New(u string) (*Client, error) {
// Validate if the base URL for monocular is valid.
func validate(u string) error {
// Check if it is parsable
p, err := url.Parse(u)
if err != nil {

@ -99,7 +99,6 @@ type ChartVersion struct {
// Search performs a search against the monocular search API
func (c *Client) Search(term string) ([]SearchResult, error) {
// Create the URL to the search endpoint
// Note, this is currently an internal API for the Hub. This should be
// formatted without showing how monocular operates.

@ -27,7 +27,6 @@ import (
var searchResult = `{"data":[{"id":"stable/phpmyadmin","type":"chart","attributes":{"name":"phpmyadmin","repo":{"name":"stable","url":"https://charts.helm.sh/stable"},"description":"phpMyAdmin is an mysql administration frontend","home":"https://www.phpmyadmin.net/","keywords":["mariadb","mysql","phpmyadmin"],"maintainers":[{"name":"Bitnami","email":"containers@bitnami.com"}],"sources":["https://github.com/bitnami/bitnami-docker-phpmyadmin"],"icon":""},"links":{"self":"/v1/charts/stable/phpmyadmin"},"relationships":{"latestChartVersion":{"data":{"version":"3.0.0","app_version":"4.9.0-1","created":"2019-08-08T17:57:31.38Z","digest":"119c499251bffd4b06ff0cd5ac98c2ce32231f84899fb4825be6c2d90971c742","urls":["https://charts.helm.sh/stable/phpmyadmin-3.0.0.tgz"],"readme":"/v1/assets/stable/phpmyadmin/versions/3.0.0/README.md","values":"/v1/assets/stable/phpmyadmin/versions/3.0.0/values.yaml"},"links":{"self":"/v1/charts/stable/phpmyadmin/versions/3.0.0"}}}},{"id":"bitnami/phpmyadmin","type":"chart","attributes":{"name":"phpmyadmin","repo":{"name":"bitnami","url":"https://charts.bitnami.com"},"description":"phpMyAdmin is an mysql administration frontend","home":"https://www.phpmyadmin.net/","keywords":["mariadb","mysql","phpmyadmin"],"maintainers":[{"name":"Bitnami","email":"containers@bitnami.com"}],"sources":["https://github.com/bitnami/bitnami-docker-phpmyadmin"],"icon":""},"links":{"self":"/v1/charts/bitnami/phpmyadmin"},"relationships":{"latestChartVersion":{"data":{"version":"3.0.0","app_version":"4.9.0-1","created":"2019-08-08T18:34:13.341Z","digest":"66d77cf6d8c2b52c488d0a294cd4996bd5bad8dc41d3829c394498fb401c008a","urls":["https://charts.bitnami.com/bitnami/phpmyadmin-3.0.0.tgz"],"readme":"/v1/assets/bitnami/phpmyadmin/versions/3.0.0/README.md","values":"/v1/assets/bitnami/phpmyadmin/versions/3.0.0/values.yaml"},"links":{"self":"/v1/charts/bitnami/phpmyadmin/versions/3.0.0"}}}}]}`
func TestSearch(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
fmt.Fprintln(w, searchResult)
}))

@ -35,7 +35,6 @@ func TestPath(t *testing.T) {
}
for _, tt := range tests {
t.Setenv("HELM_PLUGINS", tt.helmPluginsDir)
baseIns := newBase(tt.source)
baseInsPath := baseIns.Path()

@ -90,7 +90,6 @@ func NewExtractor(source string) (Extractor, error) {
// - Beginning a path with a path separator is illegal
// - Rudimentary symlink protections are offered by SecureJoin.
func cleanJoin(root, dest string) (string, error) {
// On Windows, this is a drive separator. On UNIX-like, this is the path list separator.
// In neither case do we want to trust a TAR that contains these.
if strings.Contains(dest, ":") {

@ -124,7 +124,6 @@ func TestHTTPInstaller(t *testing.T) {
} else if err.Error() != "plugin already exists" {
t.Fatalf("expected error for plugin exists, got (%v)", err)
}
}
func TestHTTPInstallerNonExistentVersion(t *testing.T) {
@ -157,7 +156,6 @@ func TestHTTPInstallerNonExistentVersion(t *testing.T) {
if err := Install(i); err == nil {
t.Fatal("expected error from http client")
}
}
func TestHTTPInstallerUpdate(t *testing.T) {
@ -297,7 +295,6 @@ func TestExtract(t *testing.T) {
t.Fatalf("Expected %s to have %o mode but has %o (umask: %o)",
readmeFullPath, expectedReadmePerm, info.Mode().Perm(), currentUmask)
}
}
func TestCleanJoin(t *testing.T) {
@ -327,11 +324,9 @@ func TestCleanJoin(t *testing.T) {
t.Errorf("Test %d: Expected %q but got %q", i, fixture.expect, out)
}
}
}
func TestMediaTypeToExtension(t *testing.T) {
for mt, shouldPass := range map[string]bool{
"": false,
"application/gzip": true,

@ -72,7 +72,6 @@ type VerificationResult struct {
// InstallWithOptions installs a plugin with options.
func InstallWithOptions(i Installer, opts Options) (*VerificationResult, error) {
if err := os.MkdirAll(filepath.Dir(i.Path()), 0755); err != nil {
return nil, err
}

@ -185,5 +185,4 @@ func TestVCSInstallerUpdate(t *testing.T) {
} else if err.Error() != "plugin repo was modified" {
t.Fatalf("expected error for plugin modified, got (%v)", err)
}
}

@ -45,7 +45,6 @@ func peekAPIVersion(r io.Reader) (string, error) {
}
func loadMetadataLegacy(metadataData []byte) (*Metadata, error) {
var ml MetadataLegacy
d := yaml.NewDecoder(bytes.NewReader(metadataData))
// NOTE: No strict unmarshalling for legacy plugins - maintain backwards compatibility
@ -65,7 +64,6 @@ func loadMetadataLegacy(metadataData []byte) (*Metadata, error) {
}
func loadMetadataV1(metadataData []byte) (*Metadata, error) {
var mv1 MetadataV1
d := yaml.NewDecoder(bytes.NewReader(metadataData))
d.KnownFields(true)
@ -109,7 +107,6 @@ type prototypePluginManager struct {
}
func newPrototypePluginManager() (*prototypePluginManager, error) {
cc, err := wazero.NewCompilationCacheWithDir(helmpath.CachePath("wazero-build"))
if err != nil {
return nil, fmt.Errorf("failed to create wazero compilation cache: %w", err)
@ -229,7 +226,6 @@ func findPlugins(pluginsDirs []string, findFn findFunc, filterFn filterFunc) ([]
found = append(found, p)
}
}
}
return found, nil
@ -242,7 +238,6 @@ func makeDescriptorFilter(descriptor Descriptor) filterFunc {
// If name is specified, it must match
if descriptor.Name != "" && p.Metadata().Name != descriptor.Name {
return false
}
// If type is specified, it must match
if descriptor.Type != "" && p.Metadata().Type != descriptor.Type {

@ -62,7 +62,6 @@ name: "test-plugin"
}
func TestLoadDir(t *testing.T) {
makeMetadata := func(apiVersion string) Metadata {
usage := "hello [params]..."
if apiVersion == "legacy" {

@ -23,7 +23,6 @@ import (
)
func TestValidatePluginData(t *testing.T) {
// A mock plugin with no commands
mockNoCommand := mockSubprocessCLIPlugin(t, "foo")
mockNoCommand.metadata.RuntimeConfig = &RuntimeConfigSubprocess{

@ -77,5 +77,5 @@ type Output struct {
// validPluginName is a regular expression that validates plugin names.
//
// Plugin names can only contain the ASCII characters a-z, A-Z, 0-9, _ and -.
// Plugin names can only contain the ASCII characters a-z, A-Z, 0-9, _ and -.
var validPluginName = regexp.MustCompile("^[A-Za-z0-9_-]+$")

@ -28,7 +28,6 @@ func TestMakeOutputMessage(t *testing.T) {
ptm := pluginTypesIndex["getter/v1"]
outputType := reflect.Zero(ptm.outputType).Interface()
assert.IsType(t, schema.OutputMessageGetterV1{}, outputType)
}
func TestMakeConfig(t *testing.T) {

@ -99,7 +99,6 @@ type RuntimeExtismV1 struct {
var _ Runtime = (*RuntimeExtismV1)(nil)
func (r *RuntimeExtismV1) CreatePlugin(pluginDir string, metadata *Metadata) (Plugin, error) {
rc, ok := metadata.RuntimeConfig.(*RuntimeConfigExtismV1)
if !ok {
return nil, fmt.Errorf("invalid extism/v1 plugin runtime config type: %T", metadata.RuntimeConfig)
@ -139,7 +138,6 @@ func (p *ExtismV1PluginRuntime) Dir() string {
}
func (p *ExtismV1PluginRuntime) Invoke(ctx context.Context, input *Input) (*Output, error) {
var tmpDir string
if p.rc.FileSystem.CreateTempDir {
tmpDirInner, err := os.MkdirTemp(os.TempDir(), "helm-plugin-*")

@ -79,7 +79,7 @@ func TestRuntimeExtismV1InvokePlugin(t *testing.T) {
Name: "Phippy",
},
})
require.Nil(t, err)
require.NoError(t, err)
msg := output.Message.(schema.OutputMessageTestV1)
assert.Equal(t, "Hello, Phippy! (6)", msg.Greeting)

@ -48,7 +48,6 @@ func TestPrepareCommand(t *testing.T) {
}
func TestPrepareCommandExtraArgs(t *testing.T) {
cmdMain := "sh"
cmdArgs := []string{"-c", "echo \"test\""}
platformCommand := []PlatformCommand{

@ -26,7 +26,6 @@ import (
)
func TestSortManifests(t *testing.T) {
data := []struct {
name []string
path string
@ -183,7 +182,6 @@ metadata:
if !reflect.DeepEqual(expectedHooks, out.Events) {
t.Errorf("expected events: %v but got: %v", expectedHooks, out.Events)
}
}
}
if !found {

@ -54,7 +54,6 @@ func New(chartpath, cachepath string, registryClient *registry.Client) *Resolver
// Resolve resolves dependencies and returns a lock file with the resolution.
func (r *Resolver) Resolve(reqs []*chart.Dependency, repoNames map[string]string) (*chart.Lock, error) {
// Now we clone the dependencies, locking as we go.
locked := make([]*chart.Dependency, len(reqs))
missing := []string{}
@ -146,7 +145,6 @@ func (r *Resolver) Resolve(reqs []*chart.Dependency, repoNames map[string]string
Version: version,
},
}}
} else {
// Retrieve list of tags for repository
ref := fmt.Sprintf("%s/%s", strings.TrimPrefix(d.Repository, registry.OCIScheme+"://"), d.Name)

@ -238,7 +238,6 @@ func TestCopyDirFail_SrcIsNotDir(t *testing.T) {
if !errors.Is(err, errSrcNotDir) {
t.Fatalf("expected %v error for CopyDir(%s, %s), got %s", errSrcNotDir, srcdir, dstdir, err)
}
}
func TestCopyDirFail_DstExists(t *testing.T) {
@ -491,7 +490,6 @@ func setupInaccessibleDir(t *testing.T, op func(dir string) error) func() {
}
func TestIsDir(t *testing.T) {
var currentUID = os.Getuid()
if currentUID == 0 {
@ -544,7 +542,6 @@ func TestIsDir(t *testing.T) {
}
func TestIsSymlink(t *testing.T) {
var currentUID = os.Getuid()
if currentUID == 0 {

@ -34,7 +34,7 @@ var (
//
// Increment major number for new feature additions and behavioral changes.
// Increment minor number for bug fixes and performance enhancements.
version = "v4.1"
version = "v4.2"
// metadata is extra build time data
metadata = ""
@ -77,7 +77,6 @@ func GetUserAgent() string {
// Get returns build info
func Get() BuildInfo {
makeKubeClientVersionString := func() string {
// Test builds don't include debug info / module info
// (And even if they did, we probably want a stable version during tests anyway)

@ -630,7 +630,6 @@ func GetVersionSet(client discovery.ServerResourcesInterface) (common.VersionSet
var ok bool
for _, r := range resources {
for _, rl := range r.APIResources {
// A Kind at a GroupVersion can show up more than once. We only want
// it displayed once in the final output.
id = path.Join(r.GroupVersion, rl.Kind)

@ -120,7 +120,6 @@ func (d *Dependency) dependencyStatus(chartpath string, dep *chart.Dependency, p
if r := statArchiveForStatus(archive, dep); r != "" {
return r
}
}
// End unnecessary code.

@ -119,7 +119,6 @@ func (m *Metadata) FormattedDepNames() string {
continue
}
depsNames = append(depsNames, ac.Name())
}
sort.StringSlice(depsNames).Sort()

@ -35,7 +35,6 @@ import (
func (cfg *Configuration) execHook(rl *release.Release, hook release.HookEvent,
waitStrategy kube.WaitStrategy, waitOptions []kube.WaitOption,
timeout time.Duration, serverSideApply bool) error {
shutdown, err := cfg.execHookWithDelayedShutdown(rl, hook, waitStrategy, waitOptions, timeout, serverSideApply)
if shutdown == nil {
return err
@ -59,7 +58,6 @@ func shutdownNoOp() error {
func (cfg *Configuration) execHookWithDelayedShutdown(rl *release.Release, hook release.HookEvent,
waitStrategy kube.WaitStrategy, waitOptions []kube.WaitOption, timeout time.Duration,
serverSideApply bool) (ExecuteShutdownFunc, error) {
executingHooks := []*release.Hook{}
for _, h := range rl.Hooks {
@ -179,7 +177,6 @@ func (x hookByWeight) Less(i, j int) bool {
// deleteHookByPolicy deletes a hook if the hook policy instructs it to
func (cfg *Configuration) deleteHookByPolicy(h *release.Hook, policy release.HookDeletePolicy,
waitStrategy kube.WaitStrategy, waitOptions []kube.WaitOption, timeout time.Duration) error {
// Never delete CustomResourceDefinitions; this could cause lots of
// cascading garbage collection.
if h.Kind == "CustomResourceDefinition" {
@ -214,7 +211,6 @@ func (cfg *Configuration) deleteHookByPolicy(h *release.Hook, policy release.Hoo
// deleteHooksByPolicy deletes all hooks if the hook policy instructs it to
func (cfg *Configuration) deleteHooksByPolicy(hooks []*release.Hook, policy release.HookDeletePolicy,
waitStrategy kube.WaitStrategy, waitOptions []kube.WaitOption, timeout time.Duration) error {
for _, h := range hooks {
if err := cfg.deleteHookByPolicy(h, policy, waitStrategy, waitOptions, timeout); err != nil {
return err

@ -737,7 +737,6 @@ func TestInstallRelease_RollbackOnFailure(t *testing.T) {
})
}
func TestInstallRelease_RollbackOnFailure_Interrupted(t *testing.T) {
is := assert.New(t)
instAction := installAction(t)
instAction.ReleaseName = "interrupted-release"
@ -767,7 +766,6 @@ func TestInstallRelease_RollbackOnFailure_Interrupted(t *testing.T) {
is.Equal(goroutines+1, instAction.getGoroutineCount()) // installation goroutine still is in background
time.Sleep(10 * time.Second) // wait for goroutine to finish
is.Equal(goroutines, instAction.getGoroutineCount())
}
func TestNameTemplate(t *testing.T) {
testCases := []nameTemplateTestCase{
@ -804,7 +802,6 @@ func TestNameTemplate(t *testing.T) {
}
for _, tc := range testCases {
n, err := TemplateName(tc.tpl)
if err != nil {
if tc.expectedErrorStr == "" {
@ -867,7 +864,7 @@ func TestInstallReleaseOutputDir(t *testing.T) {
test.AssertGoldenFile(t, filepath.Join(dir, "hello/templates/rbac"), "rbac.txt")
_, err = os.Stat(filepath.Join(dir, "hello/templates/empty"))
is.True(errors.Is(err, fs.ErrNotExist))
is.ErrorIs(err, fs.ErrNotExist)
}
func TestInstallOutputDirWithReleaseName(t *testing.T) {
@ -903,7 +900,7 @@ func TestInstallOutputDirWithReleaseName(t *testing.T) {
test.AssertGoldenFile(t, filepath.Join(newDir, "hello/templates/rbac"), "rbac.txt")
_, err = os.Stat(filepath.Join(newDir, "hello/templates/empty"))
is.True(errors.Is(err, fs.ErrNotExist))
is.ErrorIs(err, fs.ErrNotExist)
}
func TestNameAndChart(t *testing.T) {
@ -1168,7 +1165,7 @@ func TestInstallCRDs_AlreadyExist(t *testing.T) {
mockChart := buildChart(withFile(mockFile))
crdsToInstall := mockChart.CRDObjects()
assert.Nil(t, instAction.installCRDs(crdsToInstall))
assert.NoError(t, instAction.installCRDs(crdsToInstall))
}
func TestInstallCRDs_KubeClient_BuildError(t *testing.T) {
@ -1227,7 +1224,7 @@ func TestCheckDependencies(t *testing.T) {
dependency := chart.Dependency{Name: "hello"}
mockChart := buildChart(withDependency())
assert.Nil(t, CheckDependencies(mockChart, []ci.Dependency{&dependency}))
assert.NoError(t, CheckDependencies(mockChart, []ci.Dependency{&dependency}))
}
func TestCheckDependencies_MissingDependency(t *testing.T) {

@ -150,7 +150,6 @@ func TestValidateVersion(t *testing.T) {
if !errors.Is(err, tt.wantErr) {
t.Errorf("Expected {%v}, got {%v}", tt.wantErr, err)
}
}
})
}

@ -37,7 +37,7 @@ func TestWithCertFile(t *testing.T) {
certFile := "testdata/cert.pem"
opt := WithCertFile(certFile)
assert.Nil(t, opt(client))
assert.NoError(t, opt(client))
assert.Equal(t, certFile, client.certFile)
}
@ -47,7 +47,7 @@ func TestWithInsecure(t *testing.T) {
opt := WithInsecure(true)
assert.Nil(t, opt(client))
assert.NoError(t, opt(client))
assert.True(t, client.insecure)
}
@ -58,7 +58,7 @@ func TestWithKeyFile(t *testing.T) {
keyFile := "testdata/key.pem"
opt := WithKeyFile(keyFile)
assert.Nil(t, opt(client))
assert.NoError(t, opt(client))
assert.Equal(t, keyFile, client.keyFile)
}
@ -69,7 +69,7 @@ func TestWithCAFile(t *testing.T) {
caFile := "testdata/ca.pem"
opt := WithCAFile(caFile)
assert.Nil(t, opt(client))
assert.NoError(t, opt(client))
assert.Equal(t, caFile, client.caFile)
}
@ -79,6 +79,6 @@ func TestWithPlainHTTPLogin(t *testing.T) {
opt := WithPlainHTTPLogin(true)
assert.Nil(t, opt(client))
assert.NoError(t, opt(client))
assert.True(t, client.plainHTTP)
}

@ -18,6 +18,7 @@ package action
import (
"context"
"errors"
"fmt"
"io"
"slices"
@ -25,6 +26,8 @@ import (
"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 @@ func (r *ReleaseTesting) GetPodLogs(out io.Writer, rel *release.Release) error {
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 @@ func (r *ReleaseTesting) GetPodLogs(out io.Writer, rel *release.Release) error {
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...)
}

@ -26,6 +26,9 @@ import (
"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 @@ func TestReleaseTestingGetPodLogs_PodRetrievalError(t *testing.T) {
},
}
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 @@ func TestReleaseTesting_WaitOptionsPassedDownstream(t *testing.T) {
// 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)")
}

@ -40,7 +40,6 @@ func filterManifestsToKeep(manifests []releaseutil.Manifest) (keep, remaining []
if resourcePolicyType == kube.KeepPolicy {
keep = append(keep, m)
}
}
return keep, remaining
}

@ -268,7 +268,6 @@ func (u *Upgrade) prepareUpgrade(name string, chart *chartv2.Chart, vals map[str
return nil, nil, false, err
}
}
}
// determine if values will be reused

@ -446,7 +446,6 @@ func TestUpgradeRelease_Interrupted_Wait(t *testing.T) {
}
func TestUpgradeRelease_Interrupted_RollbackOnFailure(t *testing.T) {
is := assert.New(t)
req := require.New(t)
@ -716,7 +715,7 @@ func TestGetUpgradeServerSideValue(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
serverSideApply, err := getUpgradeServerSideValue(tt.actionServerSideOption, tt.releaseApplyMethod)
assert.Nil(t, err)
assert.NoError(t, err)
assert.Equal(t, tt.expectedServerSideApply, serverSideApply)
})
}
@ -741,7 +740,6 @@ func TestGetUpgradeServerSideValue(t *testing.T) {
assert.ErrorContains(t, err, tt.expectedErrorMsg)
})
}
}
func TestUpgradeRun_UnreachableKubeClient(t *testing.T) {

@ -365,7 +365,6 @@ func TestValidateNameAndGenerateName(t *testing.T) {
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
skip, err := validateNameAndGenerateName(tc.info)
if tc.wantErr {

@ -167,7 +167,6 @@ func makeDefaultCapabilities() (*Capabilities, error) {
}
func newCapabilities(kubeVersionMajor, kubeVersionMinor uint64) (*Capabilities, error) {
version := fmt.Sprintf("v%d.%d.0", kubeVersionMajor, kubeVersionMinor)
return &Capabilities{
KubeVersion: KubeVersion{

@ -60,8 +60,8 @@ func TestDefaultCapabilities(t *testing.T) {
}
hv := caps.HelmVersion
if hv.Version != "v4.1" {
t.Errorf("Expected default HelmVersion to be v4.1, got %q", hv.Version)
if hv.Version != "v4.2" {
t.Errorf("Expected default HelmVersion to be v4.2, got %q", hv.Version)
}
}

@ -666,7 +666,6 @@ func TestMergeTables(t *testing.T) {
}
func TestCoalesceValuesWarnings(t *testing.T) {
c := withDeps(&chart.Chart{
Metadata: &chart.Metadata{Name: "level1"},
Values: map[string]any{
@ -724,7 +723,6 @@ func TestCoalesceValuesWarnings(t *testing.T) {
assert.Contains(t, warnings, "warning: skipped value for level1.level2.level3.boat: Not a table.")
assert.Contains(t, warnings, "warning: destination for level1.level2.level3.spear.tip is a table. Ignoring non-table value (true)")
assert.Contains(t, warnings, "warning: cannot overwrite table with non table for level1.level2.level3.spear.sail (map[cotton:true])")
}
func TestConcatPrefix(t *testing.T) {

@ -25,7 +25,6 @@ import (
)
func TestToRenderValues(t *testing.T) {
chartValues := map[string]any{
"name": "al Rashid",
"where": map[string]any{

@ -100,7 +100,6 @@ func LoadDir(dir string) (chart.Charter, error) {
default:
return nil, errors.New("unsupported chart version")
}
}
// FileLoader loads a chart from a file

@ -104,7 +104,7 @@ func TestMetadata(t *testing.T) {
is.Equal("foo.yaml", chrt.Name())
is.Equal("1.0.0", chrt.AppVersion())
is.Equal(nil, chrt.Validate())
is.NoError(chrt.Validate())
}
func TestIsRoot(t *testing.T) {

@ -44,7 +44,6 @@ func WithSkipSchemaValidation(skipSchemaValidation bool) LinterOption {
}
func RunAll(baseDir string, values map[string]any, namespace string, options ...LinterOption) support.Linter {
chartDir, _ := filepath.Abs(baseDir)
lo := linterOptions{}

@ -28,7 +28,7 @@ import (
kscheme "k8s.io/client-go/kubernetes/scheme"
)
// deprecatedAPIError indicates than an API is deprecated in Kubernetes
// deprecatedAPIError indicates that an API is deprecated in Kubernetes
type deprecatedAPIError struct {
Deprecated string
Message string

@ -62,7 +62,6 @@ func TemplateLinterSkipSchemaValidation(skipSchemaValidation bool) TemplateLinte
}
func newTemplateLinter(linter *support.Linter, namespace string, values map[string]any, options ...TemplateLinterOption) templateLinter {
result := templateLinter{
linter: linter,
values: values,

@ -257,7 +257,6 @@ data:
//
// See https://github.com/helm/helm/issues/7483
func TestStrictTemplateParsingMapError(t *testing.T) {
ch := chart.Chart{
Metadata: &chart.Metadata{
Name: "regression7483",
@ -390,7 +389,6 @@ func TestValidateTopIndentLevel(t *testing.T) {
t.Errorf("Expected %t for %q", shouldFail, doc)
}
}
}
// TestEmptyWithCommentsManifests checks the lint is not failing against empty manifests that contains only comments
@ -486,5 +484,4 @@ func TestIsYamlFileExtension(t *testing.T) {
t.Errorf("isYamlFileExtension(%s) = %v; want %v", test.filename, result, test.expected)
}
}
}

@ -390,7 +390,6 @@ icon: https://example.com/64x64.png
if text.String() != "" {
t.Errorf("Expected no message to Stderr, got %s", text.String())
}
}
// Packaging the chart on a Windows machine will produce an
@ -660,7 +659,6 @@ func verifyChart(t *testing.T, c *chart.Chart) {
t.Errorf("Expected %s version %s, got %s", dep.Name(), exp["version"], dep.Metadata.Version)
}
}
}
func verifyDependencies(t *testing.T, c *chart.Chart) {

@ -701,7 +701,6 @@ func CreateFrom(chartfile *chart.Metadata, dest, src string) error {
// error. In such a case, this will attempt to clean up by removing the
// new chart directory.
func Create(name, dir string) (string, error) {
// Sanity-check the name of a chart so user doesn't create one that causes problems.
if err := validateChartName(name); err != nil {
return "", err

@ -459,7 +459,6 @@ func TestDependentChartAliases(t *testing.T) {
if aliasChart := getAliasDependency(c.Dependencies(), req[2]); aliasChart != nil {
t.Fatalf("expected no chart but got %s", aliasChart.Name())
}
}
func TestDependentChartWithSubChartsAbsentInDependency(t *testing.T) {

@ -79,7 +79,6 @@ func ValidateReleaseName(name string) error {
// This case is preserved for backwards compatibility
if name == "" {
return errMissingName
}
if len(name) > maxReleaseNameLen || !validName.MatchString(name) {
return errInvalidName

@ -51,7 +51,6 @@ func checkFileCompletion(t *testing.T, cmdName string, shouldBePerformed bool) {
if shouldBePerformed {
t.Errorf("Unexpected directive ShellCompDirectiveNoFileComp when completing '%s'", cmdName)
} else {
t.Errorf("Did not receive directive ShellCompDirectiveNoFileComp when completing '%s'", cmdName)
}
t.Log(out)

@ -220,7 +220,6 @@ func (p *postRendererArgsSlice) Type() string {
}
func (p *postRendererArgsSlice) Set(val string) error {
// a post-renderer defined by a user may accept empty arguments
p.options.args = append(p.options.args, val)

@ -77,7 +77,6 @@ func newGetMetadataCmd(cfg *action.Configuration, out io.Writer) *cobra.Command
}
func (w metadataWriter) WriteTable(out io.Writer) error {
formatApplyMethod := func(applyMethod string) string {
switch applyMethod {
case "":

@ -43,7 +43,6 @@ func addDryRunFlag(cmd *cobra.Command) {
// Determine the `action.DryRunStrategy` given -dry-run=<value>` flag (or absence of)
// Legacy usage of the flag: boolean values, and `--dry-run` (without value) are supported, and log warnings emitted
func cmdGetDryRunFlagStrategy(cmd *cobra.Command, isTemplate bool) (action.DryRunStrategy, error) {
f := cmd.Flag("dry-run")
v := f.Value.String()

@ -157,7 +157,6 @@ func resetEnv() func() {
}
func TestCmdGetDryRunFlagStrategy(t *testing.T) {
type testCaseExpectedLog struct {
Level string
Msg string
@ -274,7 +273,6 @@ func TestCmdGetDryRunFlagStrategy(t *testing.T) {
}
for name, tc := range testCases {
logBuf := new(bytes.Buffer)
logger := slog.New(slog.NewJSONHandler(logBuf, nil))
slog.SetDefault(logger)
@ -290,14 +288,14 @@ func TestCmdGetDryRunFlagStrategy(t *testing.T) {
if tc.ExpectedError {
assert.Error(t, err)
} else {
assert.Nil(t, err)
assert.NoError(t, err)
assert.Equal(t, tc.ExpectedStrategy, dryRunStrategy)
}
if tc.ExpectedLog != nil {
logResult := map[string]string{}
err = json.Unmarshal(logBuf.Bytes(), &logResult)
require.Nil(t, err)
require.NoError(t, err)
assert.Equal(t, tc.ExpectedLog.Level, logResult["level"])
assert.Equal(t, tc.ExpectedLog.Msg, logResult["msg"])

@ -225,7 +225,6 @@ func getReleaseHistory(rls []*release.Release) (history releaseHistory) {
}
if !r.Info.LastDeployed.IsZero() {
rInfo.Updated = r.Info.LastDeployed
}
history = append(history, rInfo)
}

@ -60,7 +60,6 @@ func TestLintCmdWithQuietFlag(t *testing.T) {
wantError: true,
}}
runTestCmd(t, tests)
}
func TestLintCmdWithKubeVersionFlag(t *testing.T) {

@ -333,7 +333,6 @@ func loadFile(path string) (*pluginCommand, error) {
// to obtain the dynamic completion choices. It must pass all the flags and sub-commands
// specified in the command-line to the plugin.complete executable (except helm's global flags)
func pluginDynamicComp(plug plugin.Plugin, cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
subprocessPlug, ok := plug.(*plugin.SubprocessPluginRuntime)
if !ok {
// Completion only supported for subprocess plugins (TODO: fix this)

@ -260,7 +260,6 @@ func TestPullCmd(t *testing.T) {
if out != outString {
t.Errorf("%q: expected verification output %q, got %q", tt.name, outString, out)
}
}
ef := filepath.Join(outdir, tt.expectFile)

@ -28,7 +28,6 @@ import (
)
func TestRepoIndexCmd(t *testing.T) {
dir := t.TempDir()
comp := filepath.Join(dir, "compressedchart-0.1.0.tgz")

@ -103,7 +103,6 @@ func TestRepoRemove(t *testing.T) {
cacheIndex, cacheChart := createCacheFiles(rootDir, repoName)
cacheFiles[repoName] = []string{cacheIndex, cacheChart}
}
// Create repo remove command

@ -355,7 +355,6 @@ func hookOutputWriter(_, _, _ string) io.Writer {
}
func checkForExpiredRepos(repofile string) {
expiredRepos := []struct {
name string
old string
@ -399,7 +398,6 @@ func checkForExpiredRepos(repofile string) {
)
}
}
}
func newRegistryClient(

@ -29,7 +29,6 @@ Use search subcommands to search different locations for charts.
`
func newSearchCmd(out io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "search [keyword]",
Short: "search for a keyword in charts",

@ -123,7 +123,6 @@ func (i *Index) Search(term string, threshold int, regexp bool) ([]*Result, erro
// calcScore calculates a score for a match.
func (i *Index) calcScore(index int, matchline string) int {
// This is currently tied to the fact that sep is a single char.
splits := []int{}
s := rune(sep[0])

@ -149,7 +149,6 @@ func TestAddRepo_Sort(t *testing.T) {
}
func TestSearchByName(t *testing.T) {
tests := []struct {
name string
query string
@ -245,7 +244,6 @@ func TestSearchByName(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
charts, err := i.Search(tt.query, 100, tt.regexp)
if err != nil {
if tt.fail {
@ -274,7 +272,6 @@ func TestSearchByName(t *testing.T) {
t.Errorf("[%d]: Expected name %q, got %q", i, ex.Name, got.Name)
}
}
})
}
}

@ -196,5 +196,4 @@ func (h *hubSearchWriter) encodeByFormat(out io.Writer, format output.Format) er
// WriteJSON and WriteYAML, we shouldn't get invalid types
return nil
}
}

@ -24,7 +24,6 @@ import (
)
func TestSearchHubCmd(t *testing.T) {
// Setup a mock search service
var searchResult = `{"data":[{"id":"stable/phpmyadmin","type":"chart","attributes":{"name":"phpmyadmin","repo":{"name":"stable","url":"https://charts.helm.sh/stable"},"description":"phpMyAdmin is an mysql administration frontend","home":"https://www.phpmyadmin.net/","keywords":["mariadb","mysql","phpmyadmin"],"maintainers":[{"name":"Bitnami","email":"containers@bitnami.com"}],"sources":["https://github.com/bitnami/bitnami-docker-phpmyadmin"],"icon":""},"links":{"self":"/v1/charts/stable/phpmyadmin"},"relationships":{"latestChartVersion":{"data":{"version":"3.0.0","app_version":"4.9.0-1","created":"2019-08-08T17:57:31.38Z","digest":"119c499251bffd4b06ff0cd5ac98c2ce32231f84899fb4825be6c2d90971c742","urls":["https://charts.helm.sh/stable/phpmyadmin-3.0.0.tgz"],"readme":"/v1/assets/stable/phpmyadmin/versions/3.0.0/README.md","values":"/v1/assets/stable/phpmyadmin/versions/3.0.0/values.yaml"},"links":{"self":"/v1/charts/stable/phpmyadmin/versions/3.0.0"}}}},{"id":"bitnami/phpmyadmin","type":"chart","attributes":{"name":"phpmyadmin","repo":{"name":"bitnami","url":"https://charts.bitnami.com"},"description":"phpMyAdmin is an mysql administration frontend","home":"https://www.phpmyadmin.net/","keywords":["mariadb","mysql","phpmyadmin"],"maintainers":[{"name":"Bitnami","email":"containers@bitnami.com"}],"sources":["https://github.com/bitnami/bitnami-docker-phpmyadmin"],"icon":""},"links":{"self":"/v1/charts/bitnami/phpmyadmin"},"relationships":{"latestChartVersion":{"data":{"version":"3.0.0","app_version":"4.9.0-1","created":"2019-08-08T18:34:13.341Z","digest":"66d77cf6d8c2b52c488d0a294cd4996bd5bad8dc41d3829c394498fb401c008a","urls":["https://charts.bitnami.com/bitnami/phpmyadmin-3.0.0.tgz"],"readme":"/v1/assets/bitnami/phpmyadmin/versions/3.0.0/README.md","values":"/v1/assets/bitnami/phpmyadmin/versions/3.0.0/values.yaml"},"links":{"self":"/v1/charts/bitnami/phpmyadmin/versions/3.0.0"}}}}]}`
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
@ -54,7 +53,6 @@ func TestSearchHubCmd(t *testing.T) {
}
func TestSearchHubListRepoCmd(t *testing.T) {
// Setup a mock search service
var searchResult = `{"data":[{"id":"stable/phpmyadmin","type":"chart","attributes":{"name":"phpmyadmin","repo":{"name":"stable","url":"https://charts.helm.sh/stable"},"description":"phpMyAdmin is an mysql administration frontend","home":"https://www.phpmyadmin.net/","keywords":["mariadb","mysql","phpmyadmin"],"maintainers":[{"name":"Bitnami","email":"containers@bitnami.com"}],"sources":["https://github.com/bitnami/bitnami-docker-phpmyadmin"],"icon":""},"links":{"self":"/v1/charts/stable/phpmyadmin"},"relationships":{"latestChartVersion":{"data":{"version":"3.0.0","app_version":"4.9.0-1","created":"2019-08-08T17:57:31.38Z","digest":"119c499251bffd4b06ff0cd5ac98c2ce32231f84899fb4825be6c2d90971c742","urls":["https://charts.helm.sh/stable/phpmyadmin-3.0.0.tgz"],"readme":"/v1/assets/stable/phpmyadmin/versions/3.0.0/README.md","values":"/v1/assets/stable/phpmyadmin/versions/3.0.0/values.yaml"},"links":{"self":"/v1/charts/stable/phpmyadmin/versions/3.0.0"}}}},{"id":"bitnami/phpmyadmin","type":"chart","attributes":{"name":"phpmyadmin","repo":{"name":"bitnami","url":"https://charts.bitnami.com"},"description":"phpMyAdmin is an mysql administration frontend","home":"https://www.phpmyadmin.net/","keywords":["mariadb","mysql","phpmyadmin"],"maintainers":[{"name":"Bitnami","email":"containers@bitnami.com"}],"sources":["https://github.com/bitnami/bitnami-docker-phpmyadmin"],"icon":""},"links":{"self":"/v1/charts/bitnami/phpmyadmin"},"relationships":{"latestChartVersion":{"data":{"version":"3.0.0","app_version":"4.9.0-1","created":"2019-08-08T18:34:13.341Z","digest":"66d77cf6d8c2b52c488d0a294cd4996bd5bad8dc41d3829c394498fb401c008a","urls":["https://charts.bitnami.com/bitnami/phpmyadmin-3.0.0.tgz"],"readme":"/v1/assets/bitnami/phpmyadmin/versions/3.0.0/README.md","values":"/v1/assets/bitnami/phpmyadmin/versions/3.0.0/values.yaml"},"links":{"self":"/v1/charts/bitnami/phpmyadmin/versions/3.0.0"}}}}]}`
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {

@ -142,7 +142,6 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
return err
}
}
}
}

@ -1 +1 @@
Version: v4.1
Version: v4.2

@ -1 +1 @@
version.BuildInfo{Version:"v4.1", GitCommit:"", GitTreeState:"", GoVersion:"", KubeClientVersion:"v1.20"}
version.BuildInfo{Version:"v4.2", GitCommit:"", GitTreeState:"", GoVersion:"", KubeClientVersion:"v1.20"}

@ -59,7 +59,6 @@ func newUninstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
return validationErr
}
for i := range args {
res, err := client.Run(args[i])
if err != nil {
return err

@ -34,7 +34,6 @@ import (
)
func TestUpgradeCmd(t *testing.T) {
tmpChart := t.TempDir()
cfile := &chart.Chart{
Metadata: &chart.Metadata{
@ -222,7 +221,6 @@ func TestUpgradeWithValue(t *testing.T) {
if !strings.Contains(updatedRel.Manifest, "drink: tea") {
t.Errorf("The value is not set correctly. manifest: %s", updatedRel.Manifest)
}
}
func TestUpgradeWithStringValue(t *testing.T) {
@ -253,11 +251,9 @@ func TestUpgradeWithStringValue(t *testing.T) {
if !strings.Contains(updatedRel.Manifest, "drink: coffee") {
t.Errorf("The value is not set correctly. manifest: %s", updatedRel.Manifest)
}
}
func TestUpgradeInstallWithSubchartNotes(t *testing.T) {
releaseName := "wacky-bunny-v1"
relMock, ch, _ := prepareMockRelease(t, releaseName)
@ -289,11 +285,9 @@ func TestUpgradeInstallWithSubchartNotes(t *testing.T) {
if !strings.Contains(upgradedRel.Info.Notes, "SUBCHART NOTES") {
t.Errorf("The subchart notes are not set correctly. NOTES: %s", upgradedRel.Info.Notes)
}
}
func TestUpgradeWithValuesFile(t *testing.T) {
releaseName := "funny-bunny-v4"
relMock, ch, chartPath := prepareMockRelease(t, releaseName)
@ -321,11 +315,9 @@ func TestUpgradeWithValuesFile(t *testing.T) {
if !strings.Contains(updatedRel.Manifest, "drink: beer") {
t.Errorf("The value is not set correctly. manifest: %s", updatedRel.Manifest)
}
}
func TestUpgradeWithValuesFromStdin(t *testing.T) {
releaseName := "funny-bunny-v5"
relMock, ch, chartPath := prepareMockRelease(t, releaseName)
@ -361,7 +353,6 @@ func TestUpgradeWithValuesFromStdin(t *testing.T) {
}
func TestUpgradeInstallWithValuesFromStdin(t *testing.T) {
releaseName := "funny-bunny-v6"
_, _, chartPath := prepareMockRelease(t, releaseName)
@ -392,7 +383,6 @@ func TestUpgradeInstallWithValuesFromStdin(t *testing.T) {
if !strings.Contains(updatedRel.Manifest, "drink: beer") {
t.Errorf("The value is not set correctly. manifest: %s", updatedRel.Manifest)
}
}
func prepareMockRelease(t *testing.T, releaseName string) (func(n string, v int, ch *chart.Chart) *release.Release, *chart.Chart, string) {

@ -22,7 +22,6 @@ import (
)
func TestVerifyCmd(t *testing.T) {
statExe := "stat"
statPathMsg := "no such file or directory"
statFileMsg := statPathMsg

@ -59,15 +59,17 @@ func (c *DiskCache) Get(key [sha256.Size]byte, cacheType string) (string, error)
if err != nil {
return "", err
}
// Empty files treated as not exist because there is no content.
if fi.Size() == 0 {
return p, os.ErrNotExist
}
// directories should never happen unless something outside helm is operating
// on this content.
if fi.IsDir() {
return p, errors.New("is a directory")
}
// Empty files are treated as non-existent because there is no content.
// IsDir must be checked first: some filesystems (e.g. overlayfs) report
// directory size as 0.
if fi.Size() == 0 {
return p, os.ErrNotExist
}
return p, nil
}

@ -282,7 +282,6 @@ func (c *ChartDownloader) DownloadToCache(ref, version string) (string, *provena
// If provenance is requested, verify it.
ver := &provenance.Verification{}
if c.Verify > VerifyNever {
ppth, err := c.Cache.Get(digest32, CacheProv)
if err == nil {
slog.Debug("found provenance in cache", "id", digestString)
@ -308,7 +307,6 @@ func (c *ChartDownloader) DownloadToCache(ref, version string) (string, *provena
}
if c.Verify != VerifyLater {
// provenance files pin to a specific name so this needs to be accounted for
// when verifying.
// Note, this does make an assumption that the name/version is unique to a

@ -502,11 +502,9 @@ Loop:
// in a known repo and attempt to ensure the data is present for steps like
// version resolution.
func (m *Manager) ensureMissingRepos(repoNames map[string]string, deps []*chart.Dependency) (map[string]string, error) {
var ru []*repo.Entry
for _, dd := range deps {
// If the chart is in the local charts directory no repository needs
// to be specified.
if dd.Repository == "" {
@ -679,7 +677,6 @@ func dedupeRepos(repos []*repo.Entry) []*repo.Entry {
}
func (m *Manager) parallelRepoUpdate(repos []*repo.Entry) error {
var wg sync.WaitGroup
localRepos := dedupeRepos(repos)

@ -27,6 +27,7 @@ import (
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
@ -657,7 +658,6 @@ func TestRenderDependency(t *testing.T) {
if out["outerchart/templates/outer"] != expect {
t.Errorf("Expected %q, got %q", expect, out["outer"])
}
}
func TestRenderNestedValues(t *testing.T) {
@ -813,7 +813,6 @@ func TestRenderBuiltinValues(t *testing.T) {
t.Errorf("Expected %q, got %q", expect, out[file])
}
}
}
func TestAlterFuncMap_include(t *testing.T) {
@ -1009,7 +1008,6 @@ func TestAlterFuncMap_tplinclude(t *testing.T) {
if got := out["TplFunction/templates/base"]; got != expect {
t.Errorf("Expected %q, got %q (%v)", expect, got, out)
}
}
func TestRenderRecursionLimit(t *testing.T) {
@ -1068,7 +1066,6 @@ func TestRenderRecursionLimit(t *testing.T) {
if got := out["overlook/templates/quote"]; got != expect {
t.Errorf("Expected %q, got %q (%v)", expect, got, out)
}
}
func TestRenderLoadTemplateForTplFromFile(t *testing.T) {
@ -1319,7 +1316,6 @@ func TestRenderTplMissingKeyString(t *testing.T) {
if !strings.Contains(errTxt, "noSuchKey") {
t.Errorf("Expected error to contain 'noSuchKey', got %s", errTxt)
}
}
func TestNestedHelpersProducesMultilineStacktrace(t *testing.T) {
@ -1357,7 +1353,7 @@ NestedHelperFunctions/charts/common/templates/_helpers_2.tpl:1:49
}
_, err := Render(c, vals)
assert.NotNil(t, err)
require.Error(t, err)
assert.Equal(t, expectedErrorMessage, err.Error())
}
@ -1391,7 +1387,7 @@ template: no template "nested_helper.name" associated with template "gotpl"`
}
_, err := Render(c, vals)
assert.NotNil(t, err)
require.Error(t, err)
assert.Equal(t, expectedErrorMessage, err.Error())
}

@ -19,9 +19,15 @@ package engine
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"maps"
"math"
"reflect"
"strconv"
"strings"
"text/template"
"time"
"github.com/BurntSushi/toml"
"github.com/Masterminds/sprig/v3"
@ -62,6 +68,19 @@ func funcMap() template.FuncMap {
"fromJson": fromJSON,
"fromJsonArray": fromJSONArray,
// Duration helpers
"mustToDuration": mustToDuration,
"durationSeconds": durationSeconds,
"durationMilliseconds": durationMilliseconds,
"durationMicroseconds": durationMicroseconds,
"durationNanoseconds": durationNanoseconds,
"durationMinutes": durationMinutes,
"durationHours": durationHours,
"durationDays": durationDays,
"durationWeeks": durationWeeks,
"durationRoundTo": durationRoundTo,
"durationTruncateTo": durationTruncateTo,
// This is a placeholder for the "include" function, which is
// late-bound to a template. By declaring it here, we preserve the
// integrity of the linter.
@ -249,3 +268,210 @@ func fromJSONArray(str string) []any {
}
return a
}
// -----------------------------------------------------------------------------
// Duration helpers (numeric and time.Duration returns)
// -----------------------------------------------------------------------------
const (
maxDurationSeconds = int64(math.MaxInt64 / int64(time.Second))
minDurationSeconds = int64(math.MinInt64 / int64(time.Second))
maxDurationSecondsFloat = float64(math.MaxInt64) / float64(time.Second)
minDurationSecondsFloat = float64(math.MinInt64) / float64(time.Second)
)
func durationFromSecondsInt(seconds int64) (time.Duration, error) {
if seconds > maxDurationSeconds || seconds < minDurationSeconds {
return 0, fmt.Errorf("duration seconds overflow: %d", seconds)
}
return time.Duration(seconds) * time.Second, nil
}
func durationFromSecondsUint(seconds uint64) (time.Duration, error) {
if seconds > uint64(maxDurationSeconds) {
return 0, fmt.Errorf("duration seconds overflow: %d", seconds)
}
return time.Duration(int64(seconds)) * time.Second, nil
}
func durationFromSecondsFloat(seconds float64) (time.Duration, error) {
if math.IsNaN(seconds) || math.IsInf(seconds, 0) {
return 0, fmt.Errorf("invalid duration seconds: %v", seconds)
}
if seconds > maxDurationSecondsFloat || seconds < minDurationSecondsFloat {
return 0, fmt.Errorf("duration seconds overflow: %v", seconds)
}
nanos := seconds * float64(time.Second)
if nanos > float64(math.MaxInt64) || nanos < float64(math.MinInt64) {
return 0, fmt.Errorf("duration nanoseconds overflow: %v", nanos)
}
return time.Duration(nanos), nil
}
// asDuration converts common template values into a time.Duration.
//
// Supported inputs:
// - time.Duration
// - string duration values parsed by time.ParseDuration (e.g. "1h2m3s")
// - numeric strings treated as seconds (e.g. "2.5")
// - ints and uints treated as seconds
// - floats treated as seconds
func asDuration(v any) (time.Duration, error) {
switch x := v.(type) {
case time.Duration:
return x, nil
case string:
s := strings.TrimSpace(x)
if s == "" {
return 0, errors.New("empty duration")
}
if d, err := time.ParseDuration(s); err == nil {
return d, nil
}
if f, err := strconv.ParseFloat(s, 64); err == nil {
return durationFromSecondsFloat(f)
}
return 0, fmt.Errorf("could not parse duration %q", x)
case nil:
return 0, errors.New("invalid duration")
}
rv := reflect.ValueOf(v)
switch rv.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return durationFromSecondsInt(rv.Int())
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return durationFromSecondsUint(rv.Uint())
case reflect.Float32, reflect.Float64:
return durationFromSecondsFloat(rv.Float())
default:
return 0, fmt.Errorf("unsupported duration type %T", v)
}
}
// mustToDuration takes anything and attempts to parse as a duration returning a time.Duration.
//
// This is designed to be called from a template when need to ensure that a
// duration is valid.
func mustToDuration(v any) time.Duration {
d, err := asDuration(v)
if err != nil {
panic(err)
}
return d
}
// durationSeconds converts a duration to seconds (float64).
// On error it returns 0.
func durationSeconds(v any) float64 {
d, err := asDuration(v)
if err != nil {
return 0
}
return d.Seconds()
}
// durationMilliseconds converts a duration to milliseconds (int64).
// On error it returns 0.
func durationMilliseconds(v any) int64 {
d, err := asDuration(v)
if err != nil {
return 0
}
return d.Milliseconds()
}
// durationMicroseconds converts a duration to microseconds (int64).
// On error it returns 0.
func durationMicroseconds(v any) int64 {
d, err := asDuration(v)
if err != nil {
return 0
}
return d.Microseconds()
}
// durationNanoseconds converts a duration to nanoseconds (int64).
// On error it returns 0.
func durationNanoseconds(v any) int64 {
d, err := asDuration(v)
if err != nil {
return 0
}
return d.Nanoseconds()
}
// durationMinutes converts a duration to minutes (float64).
// On error it returns 0.
func durationMinutes(v any) float64 {
d, err := asDuration(v)
if err != nil {
return 0
}
return d.Minutes()
}
// durationHours converts a duration to hours (float64).
// On error it returns 0.
func durationHours(v any) float64 {
d, err := asDuration(v)
if err != nil {
return 0
}
return d.Hours()
}
// durationDays converts a duration to days (float64). (Not in Go's stdlib; handy in templates.)
// On error it returns 0.
func durationDays(v any) float64 {
d, err := asDuration(v)
if err != nil {
return 0
}
return d.Hours() / 24.0
}
// durationWeeks converts a duration to weeks (float64). (Not in Go's stdlib; handy in templates.)
// On error it returns 0.
func durationWeeks(v any) float64 {
d, err := asDuration(v)
if err != nil {
return 0
}
return d.Hours() / 24.0 / 7.0
}
// durationRoundTo rounds v to the nearest multiple of m.
// Returns a time.Duration.
//
// v and m accept the same forms as asDuration (e.g. "2h13m", "30s").
// On error, it returns time.Duration(0). If m is invalid, it returns v.
func durationRoundTo(v any, m any) time.Duration {
d, err := asDuration(v)
if err != nil {
return 0
}
mul, err := asDuration(m)
if err != nil {
return d
}
return d.Round(mul)
}
// durationTruncateTo truncates v toward zero to a multiple of m.
// Returns a time.Duration.
//
// On error, it returns time.Duration(0). If m is invalid, it returns v.
func durationTruncateTo(v any, m any) time.Duration {
d, err := asDuration(v)
if err != nil {
return 0
}
mul, err := asDuration(m)
if err != nil {
return d
}
return d.Truncate(mul)
}

@ -17,11 +17,14 @@ limitations under the License.
package engine
import (
"math"
"strings"
"testing"
"text/template"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestFuncs(t *testing.T) {
@ -151,6 +154,17 @@ keyInElement1 = "valueInElement1"`,
}, {
tpl: `{{ mustToJson . }}`,
vars: loopMap,
}, {
tpl: `{{ mustToDuration 30 }}`,
expect: `30s`,
vars: nil,
}, {
tpl: `{{ mustToDuration "1m30s" }}`,
expect: `1m30s`,
vars: nil,
}, {
tpl: `{{ mustToDuration "foo" }}`,
vars: nil,
}, {
tpl: `{{ toYaml . }}`,
expect: "", // should return empty string and swallow error
@ -181,6 +195,239 @@ keyInElement1 = "valueInElement1"`,
}
}
func TestDurationHelpers(t *testing.T) {
tests := []struct {
name string
tpl string
vars any
expect string
}{{
name: "durationSeconds parses duration string",
tpl: `{{ durationSeconds "1m30s" }}`,
expect: `90`,
}, {
name: "durationSeconds parses numeric string as seconds",
tpl: `{{ durationSeconds "2.5" }}`,
expect: `2.5`,
}, {
name: "durationSeconds trims whitespace around numeric string",
tpl: `{{ durationSeconds " 2.5 " }}`,
expect: `2.5`,
}, {
name: "durationSeconds int treated as seconds",
tpl: `{{ durationSeconds 2 }}`,
expect: `2`,
}, {
name: "durationSeconds float treated as seconds",
tpl: `{{ durationSeconds 2.5 }}`,
expect: `2.5`,
}, {
name: "durationSeconds uint treated as seconds",
tpl: `{{ durationSeconds . }}`,
vars: uint(2),
expect: `2`,
}, {
name: "durationSeconds time.Duration passthrough",
tpl: `{{ durationSeconds . }}`,
vars: 1500 * time.Millisecond,
expect: `1.5`,
}, {
name: "invalid duration string returns 0",
tpl: `{{ durationSeconds "nope" }}`,
expect: `0`,
}, {
name: "empty duration string returns 0",
tpl: `{{ durationSeconds "" }}`,
expect: `0`,
}, {
name: "whitespace-only duration string returns 0",
tpl: `{{ durationSeconds " " }}`,
expect: `0`,
}, {
name: "nil returns 0",
tpl: `{{ durationSeconds . }}`,
vars: nil,
expect: `0`,
}, {
name: "durationSeconds uint overflow returns 0",
tpl: `{{ durationSeconds . }}`,
vars: uint64(math.MaxInt64) + 1,
expect: `0`,
}, {
name: "durationSeconds int overflow returns 0",
tpl: `{{ durationSeconds . }}`,
vars: maxDurationSeconds + 1,
expect: `0`,
}, {
name: "durationSeconds int underflow returns 0",
tpl: `{{ durationSeconds . }}`,
vars: minDurationSeconds - 1,
expect: `0`,
}, {
name: "durationSeconds float overflow returns 0",
tpl: `{{ durationSeconds . }}`,
vars: maxDurationSecondsFloat + 0.5,
expect: `0`,
}, {
name: "durationSeconds float underflow returns 0",
tpl: `{{ durationSeconds . }}`,
vars: minDurationSecondsFloat - 0.5,
expect: `0`,
}, {
name: "durationSeconds NaN returns 0",
tpl: `{{ durationSeconds . }}`,
vars: math.NaN(),
expect: `0`,
}, {
name: "durationSeconds Inf returns 0",
tpl: `{{ durationSeconds . }}`,
vars: math.Inf(1),
expect: `0`,
}, {
name: "durationMilliseconds int seconds",
tpl: `{{ durationMilliseconds 2 }}`,
expect: `2000`,
}, {
name: "durationMilliseconds float seconds",
tpl: `{{ durationMilliseconds 1.5 }}`,
expect: `1500`,
}, {
name: "durationMicroseconds int seconds",
tpl: `{{ durationMicroseconds 2 }}`,
expect: `2000000`,
}, {
name: "durationNanoseconds int seconds",
tpl: `{{ durationNanoseconds 2 }}`,
expect: `2000000000`,
}, {
name: "durationMinutes parses duration string",
tpl: `{{ durationMinutes "90s" }}`,
expect: `1.5`,
}, {
name: "durationHours parses duration string",
tpl: `{{ durationHours "90m" }}`,
expect: `1.5`,
}, {
name: "durationDays parses duration string",
tpl: `{{ durationDays "36h" }}`,
expect: `1.5`,
}, {
name: "durationDays numeric seconds",
tpl: `{{ durationDays 86400 }}`,
expect: `1`,
}, {
name: "durationWeeks parses duration string",
tpl: `{{ durationWeeks "168h" }}`,
expect: `1`,
}, {
name: "durationWeeks parses fractional weeks",
tpl: `{{ durationWeeks "252h" }}`,
expect: `1.5`,
}, {
name: "durationRoundTo numeric seconds",
tpl: `{{ durationRoundTo 93 60 }}`, // 93s rounded to 60s = 120s
expect: `2m0s`,
}, {
name: "durationTruncateTo numeric seconds",
tpl: `{{ durationTruncateTo 93 60 }}`, // 93s truncated to 60s = 60s
expect: `1m0s`,
}, {
name: "durationRoundTo accepts duration-string multiplier",
tpl: `{{ durationRoundTo "93s" "1m" }}`,
expect: `2m0s`,
}, {
name: "durationTruncateTo accepts duration-string multiplier",
tpl: `{{ durationTruncateTo "93s" "1m" }}`,
expect: `1m0s`,
}, {
name: "durationRoundTo invalid m returns v unchanged",
tpl: `{{ durationRoundTo "93s" "nope" }}`,
expect: `1m33s`,
}, {
name: "durationTruncateTo invalid m returns v unchanged",
tpl: `{{ durationTruncateTo "93s" "nope" }}`,
expect: `1m33s`,
}, {
name: "durationRoundTo zero m returns v unchanged",
tpl: `{{ durationRoundTo "93s" 0 }}`,
expect: `1m33s`,
}, {
name: "durationTruncateTo negative m returns v unchanged",
tpl: `{{ durationTruncateTo "93s" -1 }}`,
expect: `1m33s`,
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var b strings.Builder
err := template.Must(template.New("test").Funcs(funcMap()).Parse(tt.tpl)).Execute(&b, tt.vars)
require.NoError(t, err, tt.tpl)
assert.Equal(t, tt.expect, b.String(), tt.tpl)
})
}
mustErrTests := []struct {
name string
tpl string
vars any
}{{
name: "mustToDuration invalid string",
tpl: `{{ mustToDuration "nope" }}`,
}, {
name: "mustToDuration empty string",
tpl: `{{ mustToDuration "" }}`,
}, {
name: "mustToDuration whitespace string",
tpl: `{{ mustToDuration " " }}`,
}, {
name: "mustToDuration unsupported type",
tpl: `{{ mustToDuration . }}`,
vars: []int{1, 2, 3},
}, {
name: "mustToDuration uint overflow",
tpl: `{{ mustToDuration . }}`,
vars: uint64(math.MaxInt64) + 1,
}, {
name: "mustToDuration int overflow",
tpl: `{{ mustToDuration . }}`,
vars: maxDurationSeconds + 1,
}, {
name: "mustToDuration int underflow",
tpl: `{{ mustToDuration . }}`,
vars: minDurationSeconds - 1,
}, {
name: "mustToDuration float overflow",
tpl: `{{ mustToDuration . }}`,
vars: maxDurationSecondsFloat + 0.5,
}, {
name: "mustToDuration float underflow",
tpl: `{{ mustToDuration . }}`,
vars: minDurationSecondsFloat - 0.5,
}, {
name: "mustToDuration NaN",
tpl: `{{ mustToDuration . }}`,
vars: math.NaN(),
}, {
name: "mustToDuration Inf",
tpl: `{{ mustToDuration . }}`,
vars: math.Inf(-1),
},
}
for _, tt := range mustErrTests {
t.Run(tt.name, func(t *testing.T) {
var b strings.Builder
tmpl := template.Must(
template.New("test").
Funcs(funcMap()).
Parse(tt.tpl),
)
err := tmpl.Execute(&b, tt.vars)
require.Error(t, err, tt.tpl)
})
}
}
// This test to check a function provided by sprig is due to a change in a
// dependency of sprig. mergo in v0.3.9 changed the way it merges and only does
// public fields (i.e. those starting with a capital letter). This test, from

@ -510,7 +510,6 @@ func TestDownloadInsecureSkipTLSVerify(t *testing.T) {
if _, err = g.Get(u.String()); err != nil {
t.Error(err)
}
}
func TestHTTPGetterTarDownload(t *testing.T) {

@ -41,7 +41,6 @@ func collectGetterPlugins(settings *cli.EnvSettings) (Providers, error) {
env := plugin.FormatEnv(settings.EnvVars())
pluginConstructorBuilder := func(plg plugin.Plugin) Constructor {
return func(option ...Option) (Getter, error) {
return &getterPlugin{
options: append([]Option{}, option...),
plg: plg,

@ -38,7 +38,6 @@ const (
type lazypath string
func (l lazypath) path(helmEnvVar, xdgEnvVar string, defaultFn func() string, elem ...string) string {
// There is an order to checking for a path.
// 1. See if a Helm specific environment variable has been set.
// 2. Check if an XDG environment variable is set

@ -521,7 +521,6 @@ func determineFieldValidationDirective(validate bool) FieldValidationDirective {
}
func buildResourceList(f Factory, namespace string, validationDirective FieldValidationDirective, reader io.Reader, transformRequest resource.RequestTransform) (ResourceList, error) {
schema, err := f.Validator(string(validationDirective))
if err != nil {
return nil, err
@ -859,7 +858,6 @@ func (c *Client) Update(originals, targets ResourceList, options ...ClientUpdate
slog.String("fieldValidationDirective", string(updateOptions.fieldValidationDirective)),
slog.Bool("upgradeClientSideFieldManager", updateOptions.upgradeClientSideFieldManager))
return func(original, target *resource.Info) error {
logger := c.Logger().With(
slog.String("namespace", target.Namespace),
slog.String("name", target.Name),
@ -954,7 +952,6 @@ func isIncompatibleServerError(err error) bool {
// 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 {
// When a manager is explicitly set use it
if ManagedFieldsManager != "" {
return ManagedFieldsManager
@ -1102,7 +1099,6 @@ func createPatch(original runtime.Object, target *resource.Info, threeWayMergeFo
}
func replaceResource(target *resource.Info, fieldValidationDirective FieldValidationDirective) error {
helper := resource.NewHelper(target.Client, target.Mapping).
WithFieldValidation(string(fieldValidationDirective)).
WithFieldManager(getManagedFieldsManager())
@ -1117,11 +1113,9 @@ func replaceResource(target *resource.Info, fieldValidationDirective FieldValida
}
return nil
}
func patchResourceClientSide(original runtime.Object, target *resource.Info, threeWayMergeForUnstructured bool) error {
patch, patchType, err := createPatch(original, target, threeWayMergeForUnstructured)
if err != nil {
return fmt.Errorf("failed to create patch: %w", err)
@ -1155,14 +1149,12 @@ func patchResourceClientSide(original runtime.Object, target *resource.Info, thr
// that upgrade CSA managed fields to SSA apply
// see: https://github.com/kubernetes/kubernetes/pull/112905
func upgradeClientSideFieldManager(info *resource.Info, dryRun bool, fieldValidationDirective FieldValidationDirective) (bool, error) {
fieldManagerName := getManagedFieldsManager()
patched := false
err := retry.RetryOnConflict(
retry.DefaultRetry,
func() error {
if err := info.Get(); err != nil {
return fmt.Errorf("failed to get object %s/%s %s: %w", info.Namespace, info.Name, info.Mapping.GroupVersionKind.String(), err)
}

@ -282,7 +282,6 @@ func TestCreate(t *testing.T) {
c := newTestClient(t)
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
client := NewRequestResponseLogClient(t, func(previous []RequestResponseAction, req *http.Request) (*http.Response, error) {
return tc.Callback(t, tc, previous, req)
})
@ -317,7 +316,6 @@ func TestCreate(t *testing.T) {
}
assert.Equal(t, tc.ExpectedActions, actions)
})
}
}
@ -444,7 +442,6 @@ func TestUpdate(t *testing.T) {
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
listOriginal := tc.OriginalPods
listTarget := tc.TargetPods
@ -1410,7 +1407,6 @@ func TestIsReachable(t *testing.T) {
if !strings.Contains(err.Error(), tt.errorContains) {
t.Errorf("expected error message to contain '%s', got: %v", tt.errorContains, err)
}
} else {
if err != nil {
t.Errorf("expected no error but got: %v", err)
@ -1488,7 +1484,6 @@ func TestReplaceResource(t *testing.T) {
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
testFactory := cmdtesting.NewTestFactory()
t.Cleanup(testFactory.Cleanup)
@ -1611,7 +1606,6 @@ func TestPatchResourceClientSide(t *testing.T) {
t.Fail()
return nil, nil
},
ExpectedErrorContains: "cannot patch \"whale\" with kind Pod: the server reported a conflict",
},
@ -1633,14 +1627,12 @@ func TestPatchResourceClientSide(t *testing.T) {
t.Fail()
return nil, nil // newResponse(http.StatusOK, &tc.TargetPods.Items[0])
},
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
testFactory := cmdtesting.NewTestFactory()
t.Cleanup(testFactory.Cleanup)
@ -1824,7 +1816,6 @@ func TestPatchResourceServerSide(t *testing.T) {
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
testFactory := cmdtesting.NewTestFactory()
t.Cleanup(testFactory.Cleanup)
@ -1855,7 +1846,6 @@ func TestPatchResourceServerSide(t *testing.T) {
}
func TestDetermineFieldValidationDirective(t *testing.T) {
assert.Equal(t, FieldValidationDirectiveIgnore, determineFieldValidationDirective(false))
assert.Equal(t, FieldValidationDirectiveStrict, determineFieldValidationDirective(true))
}

@ -68,6 +68,12 @@ func alwaysReady(_ *unstructured.Unstructured) (*status.Result, error) {
}, nil
}
func getStatusWatcher(dynamicClient dynamic.Interface, mapper meta.RESTMapper) *watcher.DefaultStatusWatcher {
sw := watcher.NewDefaultStatusWatcher(dynamicClient, mapper)
sw.ResyncPeriod = 3 * time.Minute
return sw
}
func (w *statusWaiter) WatchUntilReady(resourceList ResourceList, timeout time.Duration) error {
if timeout == 0 {
timeout = DefaultStatusWatcherTimeout
@ -75,7 +81,7 @@ func (w *statusWaiter) WatchUntilReady(resourceList ResourceList, timeout time.D
ctx, cancel := w.contextWithTimeout(w.watchUntilReadyCtx, timeout)
defer cancel()
w.Logger().Debug("waiting for resources", "count", len(resourceList), "timeout", timeout)
sw := watcher.NewDefaultStatusWatcher(w.client, w.restMapper)
sw := getStatusWatcher(w.client, w.restMapper)
jobSR := helmStatusReaders.NewCustomJobStatusReader(w.restMapper)
podSR := helmStatusReaders.NewCustomPodStatusReader(w.restMapper)
// We don't want to wait on any other resources as watchUntilReady is only for Helm hooks.
@ -97,7 +103,7 @@ func (w *statusWaiter) Wait(resourceList ResourceList, timeout time.Duration) er
ctx, cancel := w.contextWithTimeout(w.waitCtx, timeout)
defer cancel()
w.Logger().Debug("waiting for resources", "count", len(resourceList), "timeout", timeout)
sw := watcher.NewDefaultStatusWatcher(w.client, w.restMapper)
sw := getStatusWatcher(w.client, w.restMapper)
sw.StatusReader = statusreaders.NewStatusReader(w.restMapper, w.readers...)
return w.wait(ctx, resourceList, sw)
}
@ -109,7 +115,7 @@ func (w *statusWaiter) WaitWithJobs(resourceList ResourceList, timeout time.Dura
ctx, cancel := w.contextWithTimeout(w.waitWithJobsCtx, timeout)
defer cancel()
w.Logger().Debug("waiting for resources", "count", len(resourceList), "timeout", timeout)
sw := watcher.NewDefaultStatusWatcher(w.client, w.restMapper)
sw := getStatusWatcher(w.client, w.restMapper)
newCustomJobStatusReader := helmStatusReaders.NewCustomJobStatusReader(w.restMapper)
readers := append([]engine.StatusReader(nil), w.readers...)
readers = append(readers, newCustomJobStatusReader)
@ -125,7 +131,7 @@ func (w *statusWaiter) WaitForDelete(resourceList ResourceList, timeout time.Dur
ctx, cancel := w.contextWithTimeout(w.waitForDeleteCtx, timeout)
defer cancel()
w.Logger().Debug("waiting for resources to be deleted", "count", len(resourceList), "timeout", timeout)
sw := watcher.NewDefaultStatusWatcher(w.client, w.restMapper)
sw := getStatusWatcher(w.client, w.restMapper)
return w.waitForDelete(ctx, resourceList, sw)
}
@ -161,7 +167,15 @@ func (w *statusWaiter) waitForDelete(ctx context.Context, resourceList ResourceL
rs.Identifier.GroupKind.Kind, rs.Identifier.Namespace, rs.Identifier.Name, rs.Status, rs.Message))
}
if err := ctx.Err(); err != nil {
errs = append(errs, err)
// context.Canceled and other non-deadline errors always propagate: they signal an
// external interruption regardless of resource state.
// context.DeadlineExceeded is only added when there are resource-specific errors;
// if all resources are Unknown or NotFound the timeout is not itself a failure for
// a delete wait (e.g. resources deleted before the watch started stay Unknown in
// the fake client but are effectively gone).
if !errors.Is(err, context.DeadlineExceeded) || len(errs) > 0 {
errs = append(errs, err)
}
}
if len(errs) > 0 {
return errors.Join(errs...)
@ -234,6 +248,7 @@ func statusObserver(cancel context.CancelFunc, desired status.Status, logger *sl
return func(statusCollector *collector.ResourceStatusCollector, _ event.Event) {
var rss []*event.ResourceStatus
var nonDesiredResources []*event.ResourceStatus
var unknownSkipped int
for _, rs := range statusCollector.ResourceStatuses {
if rs == nil {
continue
@ -241,6 +256,7 @@ func statusObserver(cancel context.CancelFunc, desired status.Status, logger *sl
// If a resource is already deleted before waiting has started, it will show as unknown.
// This check ensures we don't wait forever for a resource that is already deleted.
if rs.Status == status.UnknownStatus && desired == status.NotFoundStatus {
unknownSkipped++
continue
}
// Failed is a terminal state. This check ensures we don't wait forever for a resource
@ -254,6 +270,14 @@ func statusObserver(cancel context.CancelFunc, desired status.Status, logger *sl
}
}
// During informer initialization there is a brief window where existing resources
// appear as Unknown before their real status is delivered. If every resource was
// skipped as Unknown, we cannot yet distinguish "all deleted" from "not yet synced",
// so hold off on the early-cancel to avoid a spurious success or premature exit.
if unknownSkipped > 0 && len(rss) == 0 {
return
}
if aggregator.AggregateStatus(rss, desired) == desired {
logger.Debug("all resources achieved desired status", "desiredStatus", desired, "resourceCount", len(rss))
cancel()

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save