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

Signed-off-by: Evans Mungai <mbuevans@gmail.com>
pull/31584/head
Evans Mungai 3 months 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. # Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@e46ed2cbd01164d986452f91f178727624ae40d7 # pinv4.35.3 uses: github/codeql-action/init@87557b9c84dde89fdd9b10e88954ac2f4248e463 # pinv4.36.1
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file. # 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). # 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) # If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild - 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. # Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl # 📚 https://git.io/JvXDl
@ -73,4 +73,4 @@ jobs:
# make release # make release
- name: Perform CodeQL Analysis - 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 }}' go-version: '${{ env.GOLANG_VERSION }}'
check-latest: true check-latest: true
- name: golangci-lint - name: golangci-lint
uses: golangci/golangci-lint-action@1e7e51e771db61008b38414a730f564565cf7c20 #pin@9.2.0 uses: golangci/golangci-lint-action@82606bf257cbaff209d206a39f5134f0cfbfd2ee #pin@9.2.1
with: with:
version: ${{ env.GOLANGCI_LINT_VERSION }} version: ${{ env.GOLANGCI_LINT_VERSION }}

@ -64,6 +64,6 @@ jobs:
# Upload the results to GitHub's code scanning dashboard (optional). # Upload the results to GitHub's code scanning dashboard (optional).
# Commenting out will disable upload of results to your repo's Code Scanning dashboard # Commenting out will disable upload of results to your repo's Code Scanning dashboard
- name: "Upload to code-scanning" - 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: with:
sarif_file: results.sarif sarif_file: results.sarif

@ -12,7 +12,7 @@ jobs:
issues: write issues: write
pull-requests: write pull-requests: write
steps: steps:
- uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f # v10.2.0 - uses: actions/stale@eb5cf3af3ac0a1aa4c9c45633dd1ae542a27a899 # v10.3.0
with: with:
repo-token: ${{ secrets.GITHUB_TOKEN }} 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.' 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 # Keep sorted alphabetically
enable: enable:
- bidichk
- depguard - depguard
- dupl - dupl
- exhaustive - exhaustive
@ -38,6 +39,7 @@ linters:
- unused - unused
- usestdlibvars - usestdlibvars
- usetesting - usetesting
- whitespace
exclusions: exclusions:
@ -101,8 +103,6 @@ linters:
- empty - empty
- encoded-compare - encoded-compare
- equal-values - equal-values
- error-is-as
- error-nil
- expected-actual - expected-actual
- float-compare - float-compare
- go-require - go-require

@ -32,8 +32,8 @@ Think of it like apt/yum/homebrew for Kubernetes.
## Helm Development and Stable Versions ## 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 v4 is the current stable release, developed on the `main` branch.
Helm v3 (current stable) is maintained on the `dev-v3` branch. APIs there follow semantic versioning. Helm v3 is in support mode on the `dev-v3` branch: bug fixes until July 8th 2026, security fixes until November 11th 2026.
## Install ## 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 [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 ## Community, discussion, contribution, and support

@ -17,7 +17,7 @@ require (
github.com/evanphx/json-patch/v5 v5.9.11 github.com/evanphx/json-patch/v5 v5.9.11
github.com/extism/go-sdk v1.7.1 github.com/extism/go-sdk v1.7.1
github.com/fatih/color v1.19.0 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/foxcpp/go-mockdns v1.2.0
github.com/gobwas/glob v0.2.3 github.com/gobwas/glob v0.2.3
github.com/gofrs/flock v0.13.0 github.com/gofrs/flock v0.13.0
@ -33,22 +33,22 @@ require (
github.com/spf13/cobra v1.10.2 github.com/spf13/cobra v1.10.2
github.com/spf13/pflag v1.0.10 github.com/spf13/pflag v1.0.10
github.com/stretchr/testify v1.11.1 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 go.yaml.in/yaml/v3 v3.0.4
golang.org/x/crypto v0.50.0 golang.org/x/crypto v0.52.0
golang.org/x/term v0.42.0 golang.org/x/term v0.43.0
golang.org/x/text v0.36.0 golang.org/x/text v0.37.0
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/api v0.36.0 k8s.io/api v0.36.1
k8s.io/apiextensions-apiserver v0.36.0 k8s.io/apiextensions-apiserver v0.36.1
k8s.io/apimachinery v0.36.0 k8s.io/apimachinery v0.36.1
k8s.io/apiserver v0.36.0 k8s.io/apiserver v0.36.1
k8s.io/cli-runtime v0.36.0 k8s.io/cli-runtime v0.36.1
k8s.io/client-go v0.36.0 k8s.io/client-go v0.36.1
k8s.io/klog/v2 v2.140.0 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 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/kustomize/kyaml v0.21.1
sigs.k8s.io/yaml v1.6.0 sigs.k8s.io/yaml v1.6.0
) )
@ -157,13 +157,13 @@ require (
go.opentelemetry.io/otel/trace v1.43.0 // indirect go.opentelemetry.io/otel/trace v1.43.0 // indirect
go.opentelemetry.io/proto/otlp v1.10.0 // indirect go.opentelemetry.io/proto/otlp v1.10.0 // indirect
go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect
golang.org/x/mod v0.34.0 // indirect golang.org/x/mod v0.35.0 // indirect
golang.org/x/net v0.52.0 // indirect golang.org/x/net v0.55.0 // indirect
golang.org/x/oauth2 v0.36.0 // indirect golang.org/x/oauth2 v0.36.0 // indirect
golang.org/x/sync v0.20.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/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/api v0.0.0-20260401024825-9d38bb4040a9 // indirect
google.golang.org/genproto/googleapis/rpc 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/grpc v1.80.0 // indirect
@ -171,7 +171,7 @@ require (
gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // 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/kube-openapi v0.0.0-20260317180543-43fb72c5454a // indirect
k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 // indirect k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 // indirect
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // 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/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 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= 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.1 h1:ug9CicKW7H9QXnvNDapTSKuryZvWcu4Nw7pRvQa6jDY=
github.com/fluxcd/cli-utils v1.2.0/go.mod h1:d5HdTDdR5sCbsIbgtOQ7x7srKYwYeZORU6CD2yn4j/M= 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 h1:omK3OrHRD1IWJz1FuFBCFquhXslXoF17OvBS6JPzZF0=
github.com/foxcpp/go-mockdns v1.2.0/go.mod h1:IhLeSFGed3mJIAXPH2aiRQB+kqz7oqu8ld2qVbOu7Wk= github.com/foxcpp/go-mockdns v1.2.0/go.mod h1:IhLeSFGed3mJIAXPH2aiRQB+kqz7oqu8ld2qVbOu7Wk=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= 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/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 h1:ZF+QBjOI+tILZjBaFj3HgFonKXUcwgJ4djLb6i42S3Q=
github.com/tetratelabs/wabin v0.0.0-20230304001439-f6f874872834/go.mod h1:m9ymHTgNSEjuxvw8E7WWe4Pl4hZQHXONY8wE6dMLaRk= 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.12.0 h1:DuWcpNu/FzgEXgGBDp8J1Spc+CWOvvtvVyjKlaZopYU=
github.com/tetratelabs/wazero v1.11.0/go.mod h1:eV28rsN8Q+xwjogd7f4/Pp4xFxO7uOGbLcD/LzB1wiU= 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 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= 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.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= 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.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI= golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988=
golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q= 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.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.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.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.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI= golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM=
golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY= 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-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-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= 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.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= 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.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ=
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= 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 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= 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= 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.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.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.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= 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-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.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 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.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= 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.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww=
golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY= golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4=
golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY= 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.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.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 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.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.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.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= 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 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= 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= 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.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= 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.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.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c=
golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0= 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= 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 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= 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.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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 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.1 h1:XbL/EMj8K2aJpJtePmqUyQMsM0D4QI2pvl7YKJ20FTY=
k8s.io/api v0.36.0/go.mod h1:m1LVrGPNYax5NBHdO+QuAedXyuzTt4RryI/qnmNvs34= k8s.io/api v0.36.1/go.mod h1:KOWo4ey3TINlXjeHVuwB3i+tXXnu+UcwFBHlI/9dvEo=
k8s.io/apiextensions-apiserver v0.36.0 h1:Wt7E8J+VBCbj4FjiBfDTK/neXDDjyJVJc7xfuOHImZ0= k8s.io/apiextensions-apiserver v0.36.1 h1:6JfYmPUsuUIHuN+3QxutXYWj492RqF5fBSx67GYK5Ks=
k8s.io/apiextensions-apiserver v0.36.0/go.mod h1:kGDjH0msuiIB3tgsYRV0kS9GqpMYMUsQ3GHv7TApyug= k8s.io/apiextensions-apiserver v0.36.1/go.mod h1:pLzZin90riwisdzKwv/GoTwENooytoIx5zWJb4Hkby8=
k8s.io/apimachinery v0.36.0 h1:jZyPzhd5Z+3h9vJLt0z9XdzW9VzNzWAUw+P1xZ9PXtQ= k8s.io/apimachinery v0.36.1 h1:G63Gjx2W+q0YD+72Vo8oY0nDnePVwnuzTmmy5ENrVSA=
k8s.io/apimachinery v0.36.0/go.mod h1:FklypaRJt6n5wUIwWXIP6GJlIpUizTgfo1T/As+Tyxc= k8s.io/apimachinery v0.36.1/go.mod h1:ibYOR00vW/I1kzvi5SF0dRuJ52BvKtfvRdOn35GPQ+8=
k8s.io/apiserver v0.36.0 h1:Jg5OFAENUACByUCg15CmhZAYrr5ZyJ+jodyA1mHl3YE= k8s.io/apiserver v0.36.1 h1:iMS5V+rPUertv5P9RaqJgmHHTuh4quWpoxchvMUY+JY=
k8s.io/apiserver v0.36.0/go.mod h1:mHvwdHf+qKEm+1/hYm756SV+oREOKSPnsjagOpx6Vho= k8s.io/apiserver v0.36.1/go.mod h1:Cby1PbLWztu0GDOxoO6iFOyyqIsziHNEW+w9zVQ22Kw=
k8s.io/cli-runtime v0.36.0 h1:HNxciQpQMMOKS0/GiUXcKDyA6J2FDILJj9NmP2BZrTg= k8s.io/cli-runtime v0.36.1 h1:yuC/BGnnj1YYPh6D1P+pZnzinCs6DvMq86yAeNqoqzM=
k8s.io/cli-runtime v0.36.0/go.mod h1:KObkknK9Ro5LYX+1RdiKc7C8CvGg4aX+V/Zv+E8WPHA= k8s.io/cli-runtime v0.36.1/go.mod h1:ZQWHGt8xAF7KnviB79vX0lYNyUUqKIpU+LQg7exuFAw=
k8s.io/client-go v0.36.0 h1:pOYi7C4RHChYjMiHpZSpSbIM6ZxVbRXBy7CuiIwqA3c= k8s.io/client-go v0.36.1 h1:FN/K8QIT2CEDt+2WB2HnWrUANZ50AP5GII43/SP2JR0=
k8s.io/client-go v0.36.0/go.mod h1:ZKKcpwF0aLYfkHFCjillCKaTK/yBkEDHTDXCFY6AS9Y= k8s.io/client-go v0.36.1/go.mod h1:s6rAnCtTGYDQnpNjEhSaISV+2O8jwruZ6m3QOYBFbtU=
k8s.io/component-base v0.36.0 h1:hFjEktssxiJhrK1zfybkH4kJOi8iZuF+mIDCqS5+jRo= k8s.io/component-base v0.36.1 h1:iG6GsELftXqTNG9HG6kiVjatSgAw1sf5pJ6R5a6N0kA=
k8s.io/component-base v0.36.0/go.mod h1:JZvIfcNHk+uck+8LhJzhSBtydWXaZNQwX2OdL+Mnwsk= 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 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc=
k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0= 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 h1:xCeOEAOoGYl2jnJoHkC3hkbPJgdATINPMAxaynU2Ovg=
k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a/go.mod h1:uGBT7iTA6c6MvqUvSXIaYZo9ukscABYi2btjhvgKGZ0= 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.1 h1:96HqS9twIdHM0MlJLTwbo14b9kUKPkOzZ4tlRDLv4qI=
k8s.io/kubectl v0.36.0/go.mod h1:iDe8aV5BEi45W8k+5n71I2pJ/nwE0PHDu+/2cejzYoo= 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 h1:AZYQSJemyQB5eRxqcPky+/7EdBj0xi3g0ZcxxJ7vbWU=
k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= 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 h1:X4ELRsiGkrbeox69+9tzTu492FMUu7zJQW6eJU+I2oc=
oras.land/oras-go/v2 v2.6.0/go.mod h1:magiQDfG6H1O9APp+rOsvCPcW1GD2MM7vgnKY0Y+u1o= 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.1 h1:miPEwrmirImAvgME1L9qebGHrOnGJoVmVdtOU9fRfo4=
sigs.k8s.io/controller-runtime v0.24.0/go.mod h1:vFkfY5fGt5xAC/sKb8IBFKgWPNKG9OUG29dR8Y2wImw= 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 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg=
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
sigs.k8s.io/kustomize/api v0.21.1 h1:lzqbzvz2CSvsjIUZUBNFKtIMsEw7hVLJp0JeSIVmuJs= 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("foo.yaml", chrt.Name())
is.Equal("1.0.0", chrt.AppVersion()) is.Equal("1.0.0", chrt.AppVersion())
is.Equal(nil, chrt.Validate()) is.NoError(chrt.Validate())
} }
func TestIsRoot(t *testing.T) { 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 { func RunAll(baseDir string, values map[string]any, namespace string, options ...LinterOption) support.Linter {
chartDir, _ := filepath.Abs(baseDir) chartDir, _ := filepath.Abs(baseDir)
lo := linterOptions{} lo := linterOptions{}

@ -28,7 +28,7 @@ import (
kscheme "k8s.io/client-go/kubernetes/scheme" 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 { type deprecatedAPIError struct {
Deprecated string Deprecated string
Message string Message string

@ -242,7 +242,6 @@ data:
// //
// See https://github.com/helm/helm/issues/7483 // See https://github.com/helm/helm/issues/7483
func TestStrictTemplateParsingMapError(t *testing.T) { func TestStrictTemplateParsingMapError(t *testing.T) {
ch := chart.Chart{ ch := chart.Chart{
Metadata: &chart.Metadata{ Metadata: &chart.Metadata{
Name: "regression7483", Name: "regression7483",
@ -371,7 +370,6 @@ func TestValidateTopIndentLevel(t *testing.T) {
t.Errorf("Expected %t for %q", shouldFail, doc) t.Errorf("Expected %t for %q", shouldFail, doc)
} }
} }
} }
// TestEmptyWithCommentsManifests checks the lint is not failing against empty manifests that contains only comments // 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) 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() != "" { if text.String() != "" {
t.Errorf("Expected no message to Stderr, got %s", text.String()) t.Errorf("Expected no message to Stderr, got %s", text.String())
} }
} }
// Packaging the chart on a Windows machine will produce an // 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) t.Errorf("Expected %s version %s, got %s", dep.Name(), exp["version"], dep.Metadata.Version)
} }
} }
} }
func verifyDependencies(t *testing.T, c *chart.Chart) { 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 // error. In such a case, this will attempt to clean up by removing the
// new chart directory. // new chart directory.
func Create(name, dir string) (string, error) { func Create(name, dir string) (string, error) {
// Sanity-check the name of a chart so user doesn't create one that causes problems. // Sanity-check the name of a chart so user doesn't create one that causes problems.
if err := validateChartName(name); err != nil { if err := validateChartName(name); err != nil {
return "", err return "", err

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

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

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

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

@ -99,7 +99,6 @@ type ChartVersion struct {
// Search performs a search against the monocular search API // Search performs a search against the monocular search API
func (c *Client) Search(term string) ([]SearchResult, error) { func (c *Client) Search(term string) ([]SearchResult, error) {
// Create the URL to the search endpoint // Create the URL to the search endpoint
// Note, this is currently an internal API for the Hub. This should be // Note, this is currently an internal API for the Hub. This should be
// formatted without showing how monocular operates. // 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"}}}}]}` 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) { func TestSearch(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
fmt.Fprintln(w, searchResult) fmt.Fprintln(w, searchResult)
})) }))

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

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

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

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

@ -185,5 +185,4 @@ func TestVCSInstallerUpdate(t *testing.T) {
} else if err.Error() != "plugin repo was modified" { } else if err.Error() != "plugin repo was modified" {
t.Fatalf("expected error for plugin modified, got (%v)", err) 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) { func loadMetadataLegacy(metadataData []byte) (*Metadata, error) {
var ml MetadataLegacy var ml MetadataLegacy
d := yaml.NewDecoder(bytes.NewReader(metadataData)) d := yaml.NewDecoder(bytes.NewReader(metadataData))
// NOTE: No strict unmarshalling for legacy plugins - maintain backwards compatibility // 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) { func loadMetadataV1(metadataData []byte) (*Metadata, error) {
var mv1 MetadataV1 var mv1 MetadataV1
d := yaml.NewDecoder(bytes.NewReader(metadataData)) d := yaml.NewDecoder(bytes.NewReader(metadataData))
d.KnownFields(true) d.KnownFields(true)
@ -109,7 +107,6 @@ type prototypePluginManager struct {
} }
func newPrototypePluginManager() (*prototypePluginManager, error) { func newPrototypePluginManager() (*prototypePluginManager, error) {
cc, err := wazero.NewCompilationCacheWithDir(helmpath.CachePath("wazero-build")) cc, err := wazero.NewCompilationCacheWithDir(helmpath.CachePath("wazero-build"))
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to create wazero compilation cache: %w", err) 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) found = append(found, p)
} }
} }
} }
return found, nil return found, nil
@ -242,7 +238,6 @@ func makeDescriptorFilter(descriptor Descriptor) filterFunc {
// If name is specified, it must match // If name is specified, it must match
if descriptor.Name != "" && p.Metadata().Name != descriptor.Name { if descriptor.Name != "" && p.Metadata().Name != descriptor.Name {
return false return false
} }
// If type is specified, it must match // If type is specified, it must match
if descriptor.Type != "" && p.Metadata().Type != descriptor.Type { if descriptor.Type != "" && p.Metadata().Type != descriptor.Type {

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

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

@ -77,5 +77,5 @@ type Output struct {
// validPluginName is a regular expression that validates plugin names. // 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_-]+$") var validPluginName = regexp.MustCompile("^[A-Za-z0-9_-]+$")

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

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

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

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

@ -26,7 +26,6 @@ import (
) )
func TestSortManifests(t *testing.T) { func TestSortManifests(t *testing.T) {
data := []struct { data := []struct {
name []string name []string
path string path string
@ -183,7 +182,6 @@ metadata:
if !reflect.DeepEqual(expectedHooks, out.Events) { if !reflect.DeepEqual(expectedHooks, out.Events) {
t.Errorf("expected events: %v but got: %v", expectedHooks, out.Events) t.Errorf("expected events: %v but got: %v", expectedHooks, out.Events)
} }
} }
} }
if !found { 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. // 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) { func (r *Resolver) Resolve(reqs []*chart.Dependency, repoNames map[string]string) (*chart.Lock, error) {
// Now we clone the dependencies, locking as we go. // Now we clone the dependencies, locking as we go.
locked := make([]*chart.Dependency, len(reqs)) locked := make([]*chart.Dependency, len(reqs))
missing := []string{} missing := []string{}
@ -146,7 +145,6 @@ func (r *Resolver) Resolve(reqs []*chart.Dependency, repoNames map[string]string
Version: version, Version: version,
}, },
}} }}
} else { } else {
// Retrieve list of tags for repository // Retrieve list of tags for repository
ref := fmt.Sprintf("%s/%s", strings.TrimPrefix(d.Repository, registry.OCIScheme+"://"), d.Name) 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) { if !errors.Is(err, errSrcNotDir) {
t.Fatalf("expected %v error for CopyDir(%s, %s), got %s", errSrcNotDir, srcdir, dstdir, err) t.Fatalf("expected %v error for CopyDir(%s, %s), got %s", errSrcNotDir, srcdir, dstdir, err)
} }
} }
func TestCopyDirFail_DstExists(t *testing.T) { 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) { func TestIsDir(t *testing.T) {
var currentUID = os.Getuid() var currentUID = os.Getuid()
if currentUID == 0 { if currentUID == 0 {
@ -544,7 +542,6 @@ func TestIsDir(t *testing.T) {
} }
func TestIsSymlink(t *testing.T) { func TestIsSymlink(t *testing.T) {
var currentUID = os.Getuid() var currentUID = os.Getuid()
if currentUID == 0 { if currentUID == 0 {

@ -34,7 +34,7 @@ var (
// //
// Increment major number for new feature additions and behavioral changes. // Increment major number for new feature additions and behavioral changes.
// Increment minor number for bug fixes and performance enhancements. // Increment minor number for bug fixes and performance enhancements.
version = "v4.1" version = "v4.2"
// metadata is extra build time data // metadata is extra build time data
metadata = "" metadata = ""
@ -77,7 +77,6 @@ func GetUserAgent() string {
// Get returns build info // Get returns build info
func Get() BuildInfo { func Get() BuildInfo {
makeKubeClientVersionString := func() string { makeKubeClientVersionString := func() string {
// Test builds don't include debug info / module info // Test builds don't include debug info / module info
// (And even if they did, we probably want a stable version during tests anyway) // (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 var ok bool
for _, r := range resources { for _, r := range resources {
for _, rl := range r.APIResources { for _, rl := range r.APIResources {
// A Kind at a GroupVersion can show up more than once. We only want // A Kind at a GroupVersion can show up more than once. We only want
// it displayed once in the final output. // it displayed once in the final output.
id = path.Join(r.GroupVersion, rl.Kind) 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 != "" { if r := statArchiveForStatus(archive, dep); r != "" {
return r return r
} }
} }
// End unnecessary code. // End unnecessary code.

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

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

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

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

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

@ -18,6 +18,7 @@ package action
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"io" "io"
"slices" "slices"
@ -25,6 +26,8 @@ import (
"time" "time"
v1 "k8s.io/api/core/v1" 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" chartutil "helm.sh/helm/v4/pkg/chart/v2/util"
"helm.sh/helm/v4/pkg/kube" "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) return fmt.Errorf("unable to get kubernetes client to fetch pod logs: %w", err)
} }
hooksByWight := append([]*release.Hook{}, rel.Hooks...) hooksByWeight := append([]*release.Hook{}, rel.Hooks...)
sort.Stable(hookByWeight(hooksByWight)) sort.Stable(hookByWeight(hooksByWeight))
for _, h := range hooksByWight { for _, h := range hooksByWeight {
for _, e := range h.Events { for _, e := range h.Events {
if e == release.HookTest { if e == release.HookTest {
if slices.Contains(r.Filters[ExcludeNameFilter], h.Name) { 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) { if len(r.Filters[IncludeNameFilter]) > 0 && !slices.Contains(r.Filters[IncludeNameFilter], h.Name) {
continue continue
} }
req := client.CoreV1().Pods(r.Namespace).GetLogs(h.Name, &v1.PodLogOptions{}) if err := r.getContainerLogs(out, client, h.Name); err != nil {
logReader, err := req.Stream(context.Background()) return err
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)
} }
} }
} }
} }
return nil 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/assert"
"github.com/stretchr/testify/require" "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/cli"
"helm.sh/helm/v4/pkg/kube" "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) { func TestReleaseTesting_WaitOptionsPassedDownstream(t *testing.T) {
@ -117,3 +120,91 @@ func TestReleaseTesting_WaitOptionsPassedDownstream(t *testing.T) {
// Verify that WaitOptions were passed to GetWaiter // Verify that WaitOptions were passed to GetWaiter
is.NotEmpty(failer.RecordedWaitOptions, "WaitOptions should be 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 { if resourcePolicyType == kube.KeepPolicy {
keep = append(keep, m) keep = append(keep, m)
} }
} }
return keep, remaining return keep, remaining
} }

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

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

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

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

@ -60,8 +60,8 @@ func TestDefaultCapabilities(t *testing.T) {
} }
hv := caps.HelmVersion hv := caps.HelmVersion
if hv.Version != "v4.1" { if hv.Version != "v4.2" {
t.Errorf("Expected default HelmVersion to be v4.1, got %q", hv.Version) 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) { func TestCoalesceValuesWarnings(t *testing.T) {
c := withDeps(&chart.Chart{ c := withDeps(&chart.Chart{
Metadata: &chart.Metadata{Name: "level1"}, Metadata: &chart.Metadata{Name: "level1"},
Values: map[string]any{ 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: 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: 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])") 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) { func TestConcatPrefix(t *testing.T) {

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

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

@ -104,7 +104,7 @@ func TestMetadata(t *testing.T) {
is.Equal("foo.yaml", chrt.Name()) is.Equal("foo.yaml", chrt.Name())
is.Equal("1.0.0", chrt.AppVersion()) is.Equal("1.0.0", chrt.AppVersion())
is.Equal(nil, chrt.Validate()) is.NoError(chrt.Validate())
} }
func TestIsRoot(t *testing.T) { 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 { func RunAll(baseDir string, values map[string]any, namespace string, options ...LinterOption) support.Linter {
chartDir, _ := filepath.Abs(baseDir) chartDir, _ := filepath.Abs(baseDir)
lo := linterOptions{} lo := linterOptions{}

@ -28,7 +28,7 @@ import (
kscheme "k8s.io/client-go/kubernetes/scheme" 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 { type deprecatedAPIError struct {
Deprecated string Deprecated string
Message 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 { func newTemplateLinter(linter *support.Linter, namespace string, values map[string]any, options ...TemplateLinterOption) templateLinter {
result := templateLinter{ result := templateLinter{
linter: linter, linter: linter,
values: values, values: values,

@ -257,7 +257,6 @@ data:
// //
// See https://github.com/helm/helm/issues/7483 // See https://github.com/helm/helm/issues/7483
func TestStrictTemplateParsingMapError(t *testing.T) { func TestStrictTemplateParsingMapError(t *testing.T) {
ch := chart.Chart{ ch := chart.Chart{
Metadata: &chart.Metadata{ Metadata: &chart.Metadata{
Name: "regression7483", Name: "regression7483",
@ -390,7 +389,6 @@ func TestValidateTopIndentLevel(t *testing.T) {
t.Errorf("Expected %t for %q", shouldFail, doc) t.Errorf("Expected %t for %q", shouldFail, doc)
} }
} }
} }
// TestEmptyWithCommentsManifests checks the lint is not failing against empty manifests that contains only comments // 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) 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() != "" { if text.String() != "" {
t.Errorf("Expected no message to Stderr, got %s", text.String()) t.Errorf("Expected no message to Stderr, got %s", text.String())
} }
} }
// Packaging the chart on a Windows machine will produce an // 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) t.Errorf("Expected %s version %s, got %s", dep.Name(), exp["version"], dep.Metadata.Version)
} }
} }
} }
func verifyDependencies(t *testing.T, c *chart.Chart) { 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 // error. In such a case, this will attempt to clean up by removing the
// new chart directory. // new chart directory.
func Create(name, dir string) (string, error) { func Create(name, dir string) (string, error) {
// Sanity-check the name of a chart so user doesn't create one that causes problems. // Sanity-check the name of a chart so user doesn't create one that causes problems.
if err := validateChartName(name); err != nil { if err := validateChartName(name); err != nil {
return "", err return "", err

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

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

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

@ -220,7 +220,6 @@ func (p *postRendererArgsSlice) Type() string {
} }
func (p *postRendererArgsSlice) Set(val string) error { func (p *postRendererArgsSlice) Set(val string) error {
// a post-renderer defined by a user may accept empty arguments // a post-renderer defined by a user may accept empty arguments
p.options.args = append(p.options.args, val) 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 { func (w metadataWriter) WriteTable(out io.Writer) error {
formatApplyMethod := func(applyMethod string) string { formatApplyMethod := func(applyMethod string) string {
switch applyMethod { switch applyMethod {
case "": case "":

@ -43,7 +43,6 @@ func addDryRunFlag(cmd *cobra.Command) {
// Determine the `action.DryRunStrategy` given -dry-run=<value>` flag (or absence of) // 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 // 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) { func cmdGetDryRunFlagStrategy(cmd *cobra.Command, isTemplate bool) (action.DryRunStrategy, error) {
f := cmd.Flag("dry-run") f := cmd.Flag("dry-run")
v := f.Value.String() v := f.Value.String()

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

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

@ -60,7 +60,6 @@ func TestLintCmdWithQuietFlag(t *testing.T) {
wantError: true, wantError: true,
}} }}
runTestCmd(t, tests) runTestCmd(t, tests)
} }
func TestLintCmdWithKubeVersionFlag(t *testing.T) { 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 // 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) // 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) { func pluginDynamicComp(plug plugin.Plugin, cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
subprocessPlug, ok := plug.(*plugin.SubprocessPluginRuntime) subprocessPlug, ok := plug.(*plugin.SubprocessPluginRuntime)
if !ok { if !ok {
// Completion only supported for subprocess plugins (TODO: fix this) // Completion only supported for subprocess plugins (TODO: fix this)

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

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

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

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

@ -29,7 +29,6 @@ Use search subcommands to search different locations for charts.
` `
func newSearchCmd(out io.Writer) *cobra.Command { func newSearchCmd(out io.Writer) *cobra.Command {
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "search [keyword]", Use: "search [keyword]",
Short: "search for a keyword in charts", 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. // calcScore calculates a score for a match.
func (i *Index) calcScore(index int, matchline string) int { func (i *Index) calcScore(index int, matchline string) int {
// This is currently tied to the fact that sep is a single char. // This is currently tied to the fact that sep is a single char.
splits := []int{} splits := []int{}
s := rune(sep[0]) s := rune(sep[0])

@ -149,7 +149,6 @@ func TestAddRepo_Sort(t *testing.T) {
} }
func TestSearchByName(t *testing.T) { func TestSearchByName(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
query string query string
@ -245,7 +244,6 @@ func TestSearchByName(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
charts, err := i.Search(tt.query, 100, tt.regexp) charts, err := i.Search(tt.query, 100, tt.regexp)
if err != nil { if err != nil {
if tt.fail { 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) 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 // WriteJSON and WriteYAML, we shouldn't get invalid types
return nil return nil
} }
} }

@ -24,7 +24,6 @@ import (
) )
func TestSearchHubCmd(t *testing.T) { func TestSearchHubCmd(t *testing.T) {
// Setup a mock search service // 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"}}}}]}` 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) { 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) { func TestSearchHubListRepoCmd(t *testing.T) {
// Setup a mock search service // 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"}}}}]}` 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) { 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 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 return validationErr
} }
for i := range args { for i := range args {
res, err := client.Run(args[i]) res, err := client.Run(args[i])
if err != nil { if err != nil {
return err return err

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

@ -59,15 +59,17 @@ func (c *DiskCache) Get(key [sha256.Size]byte, cacheType string) (string, error)
if err != nil { if err != nil {
return "", err 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 // directories should never happen unless something outside helm is operating
// on this content. // on this content.
if fi.IsDir() { if fi.IsDir() {
return p, errors.New("is a directory") 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 return p, nil
} }

@ -282,7 +282,6 @@ func (c *ChartDownloader) DownloadToCache(ref, version string) (string, *provena
// If provenance is requested, verify it. // If provenance is requested, verify it.
ver := &provenance.Verification{} ver := &provenance.Verification{}
if c.Verify > VerifyNever { if c.Verify > VerifyNever {
ppth, err := c.Cache.Get(digest32, CacheProv) ppth, err := c.Cache.Get(digest32, CacheProv)
if err == nil { if err == nil {
slog.Debug("found provenance in cache", "id", digestString) 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 { if c.Verify != VerifyLater {
// provenance files pin to a specific name so this needs to be accounted for // provenance files pin to a specific name so this needs to be accounted for
// when verifying. // when verifying.
// Note, this does make an assumption that the name/version is unique to a // 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 // in a known repo and attempt to ensure the data is present for steps like
// version resolution. // version resolution.
func (m *Manager) ensureMissingRepos(repoNames map[string]string, deps []*chart.Dependency) (map[string]string, error) { func (m *Manager) ensureMissingRepos(repoNames map[string]string, deps []*chart.Dependency) (map[string]string, error) {
var ru []*repo.Entry var ru []*repo.Entry
for _, dd := range deps { for _, dd := range deps {
// If the chart is in the local charts directory no repository needs // If the chart is in the local charts directory no repository needs
// to be specified. // to be specified.
if dd.Repository == "" { if dd.Repository == "" {
@ -679,7 +677,6 @@ func dedupeRepos(repos []*repo.Entry) []*repo.Entry {
} }
func (m *Manager) parallelRepoUpdate(repos []*repo.Entry) error { func (m *Manager) parallelRepoUpdate(repos []*repo.Entry) error {
var wg sync.WaitGroup var wg sync.WaitGroup
localRepos := dedupeRepos(repos) localRepos := dedupeRepos(repos)

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

@ -19,9 +19,15 @@ package engine
import ( import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"errors"
"fmt"
"maps" "maps"
"math"
"reflect"
"strconv"
"strings" "strings"
"text/template" "text/template"
"time"
"github.com/BurntSushi/toml" "github.com/BurntSushi/toml"
"github.com/Masterminds/sprig/v3" "github.com/Masterminds/sprig/v3"
@ -62,6 +68,19 @@ func funcMap() template.FuncMap {
"fromJson": fromJSON, "fromJson": fromJSON,
"fromJsonArray": fromJSONArray, "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 // This is a placeholder for the "include" function, which is
// late-bound to a template. By declaring it here, we preserve the // late-bound to a template. By declaring it here, we preserve the
// integrity of the linter. // integrity of the linter.
@ -249,3 +268,210 @@ func fromJSONArray(str string) []any {
} }
return a 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 package engine
import ( import (
"math"
"strings" "strings"
"testing" "testing"
"text/template" "text/template"
"time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func TestFuncs(t *testing.T) { func TestFuncs(t *testing.T) {
@ -151,6 +154,17 @@ keyInElement1 = "valueInElement1"`,
}, { }, {
tpl: `{{ mustToJson . }}`, tpl: `{{ mustToJson . }}`,
vars: loopMap, vars: loopMap,
}, {
tpl: `{{ mustToDuration 30 }}`,
expect: `30s`,
vars: nil,
}, {
tpl: `{{ mustToDuration "1m30s" }}`,
expect: `1m30s`,
vars: nil,
}, {
tpl: `{{ mustToDuration "foo" }}`,
vars: nil,
}, { }, {
tpl: `{{ toYaml . }}`, tpl: `{{ toYaml . }}`,
expect: "", // should return empty string and swallow error 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 // 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 // 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 // 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 { if _, err = g.Get(u.String()); err != nil {
t.Error(err) t.Error(err)
} }
} }
func TestHTTPGetterTarDownload(t *testing.T) { func TestHTTPGetterTarDownload(t *testing.T) {

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

@ -38,7 +38,6 @@ const (
type lazypath string type lazypath string
func (l lazypath) path(helmEnvVar, xdgEnvVar string, defaultFn func() string, elem ...string) string { func (l lazypath) path(helmEnvVar, xdgEnvVar string, defaultFn func() string, elem ...string) string {
// There is an order to checking for a path. // There is an order to checking for a path.
// 1. See if a Helm specific environment variable has been set. // 1. See if a Helm specific environment variable has been set.
// 2. Check if an XDG environment variable is 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) { func buildResourceList(f Factory, namespace string, validationDirective FieldValidationDirective, reader io.Reader, transformRequest resource.RequestTransform) (ResourceList, error) {
schema, err := f.Validator(string(validationDirective)) schema, err := f.Validator(string(validationDirective))
if err != nil { if err != nil {
return nil, err return nil, err
@ -859,7 +858,6 @@ func (c *Client) Update(originals, targets ResourceList, options ...ClientUpdate
slog.String("fieldValidationDirective", string(updateOptions.fieldValidationDirective)), slog.String("fieldValidationDirective", string(updateOptions.fieldValidationDirective)),
slog.Bool("upgradeClientSideFieldManager", updateOptions.upgradeClientSideFieldManager)) slog.Bool("upgradeClientSideFieldManager", updateOptions.upgradeClientSideFieldManager))
return func(original, target *resource.Info) error { return func(original, target *resource.Info) error {
logger := c.Logger().With( logger := c.Logger().With(
slog.String("namespace", target.Namespace), slog.String("namespace", target.Namespace),
slog.String("name", target.Name), 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. // getManagedFieldsManager returns the manager string. If one was set it will be returned.
// Otherwise, one is calculated based on the name of the binary. // Otherwise, one is calculated based on the name of the binary.
func getManagedFieldsManager() string { func getManagedFieldsManager() string {
// When a manager is explicitly set use it // When a manager is explicitly set use it
if ManagedFieldsManager != "" { if ManagedFieldsManager != "" {
return ManagedFieldsManager return ManagedFieldsManager
@ -1102,7 +1099,6 @@ func createPatch(original runtime.Object, target *resource.Info, threeWayMergeFo
} }
func replaceResource(target *resource.Info, fieldValidationDirective FieldValidationDirective) error { func replaceResource(target *resource.Info, fieldValidationDirective FieldValidationDirective) error {
helper := resource.NewHelper(target.Client, target.Mapping). helper := resource.NewHelper(target.Client, target.Mapping).
WithFieldValidation(string(fieldValidationDirective)). WithFieldValidation(string(fieldValidationDirective)).
WithFieldManager(getManagedFieldsManager()) WithFieldManager(getManagedFieldsManager())
@ -1117,11 +1113,9 @@ func replaceResource(target *resource.Info, fieldValidationDirective FieldValida
} }
return nil return nil
} }
func patchResourceClientSide(original runtime.Object, target *resource.Info, threeWayMergeForUnstructured bool) error { func patchResourceClientSide(original runtime.Object, target *resource.Info, threeWayMergeForUnstructured bool) error {
patch, patchType, err := createPatch(original, target, threeWayMergeForUnstructured) patch, patchType, err := createPatch(original, target, threeWayMergeForUnstructured)
if err != nil { if err != nil {
return fmt.Errorf("failed to create patch: %w", err) 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 // that upgrade CSA managed fields to SSA apply
// see: https://github.com/kubernetes/kubernetes/pull/112905 // see: https://github.com/kubernetes/kubernetes/pull/112905
func upgradeClientSideFieldManager(info *resource.Info, dryRun bool, fieldValidationDirective FieldValidationDirective) (bool, error) { func upgradeClientSideFieldManager(info *resource.Info, dryRun bool, fieldValidationDirective FieldValidationDirective) (bool, error) {
fieldManagerName := getManagedFieldsManager() fieldManagerName := getManagedFieldsManager()
patched := false patched := false
err := retry.RetryOnConflict( err := retry.RetryOnConflict(
retry.DefaultRetry, retry.DefaultRetry,
func() error { func() error {
if err := info.Get(); err != nil { 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) 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) c := newTestClient(t)
for name, tc := range testCases { for name, tc := range testCases {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
client := NewRequestResponseLogClient(t, func(previous []RequestResponseAction, req *http.Request) (*http.Response, error) { client := NewRequestResponseLogClient(t, func(previous []RequestResponseAction, req *http.Request) (*http.Response, error) {
return tc.Callback(t, tc, previous, req) return tc.Callback(t, tc, previous, req)
}) })
@ -317,7 +316,6 @@ func TestCreate(t *testing.T) {
} }
assert.Equal(t, tc.ExpectedActions, actions) assert.Equal(t, tc.ExpectedActions, actions)
}) })
} }
} }
@ -444,7 +442,6 @@ func TestUpdate(t *testing.T) {
for name, tc := range testCases { for name, tc := range testCases {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
listOriginal := tc.OriginalPods listOriginal := tc.OriginalPods
listTarget := tc.TargetPods listTarget := tc.TargetPods
@ -1410,7 +1407,6 @@ func TestIsReachable(t *testing.T) {
if !strings.Contains(err.Error(), tt.errorContains) { if !strings.Contains(err.Error(), tt.errorContains) {
t.Errorf("expected error message to contain '%s', got: %v", tt.errorContains, err) t.Errorf("expected error message to contain '%s', got: %v", tt.errorContains, err)
} }
} else { } else {
if err != nil { if err != nil {
t.Errorf("expected no error but got: %v", err) t.Errorf("expected no error but got: %v", err)
@ -1488,7 +1484,6 @@ func TestReplaceResource(t *testing.T) {
for name, tc := range testCases { for name, tc := range testCases {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
testFactory := cmdtesting.NewTestFactory() testFactory := cmdtesting.NewTestFactory()
t.Cleanup(testFactory.Cleanup) t.Cleanup(testFactory.Cleanup)
@ -1611,7 +1606,6 @@ func TestPatchResourceClientSide(t *testing.T) {
t.Fail() t.Fail()
return nil, nil return nil, nil
}, },
ExpectedErrorContains: "cannot patch \"whale\" with kind Pod: the server reported a conflict", ExpectedErrorContains: "cannot patch \"whale\" with kind Pod: the server reported a conflict",
}, },
@ -1633,14 +1627,12 @@ func TestPatchResourceClientSide(t *testing.T) {
t.Fail() t.Fail()
return nil, nil // newResponse(http.StatusOK, &tc.TargetPods.Items[0]) return nil, nil // newResponse(http.StatusOK, &tc.TargetPods.Items[0])
}, },
}, },
} }
for name, tc := range testCases { for name, tc := range testCases {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
testFactory := cmdtesting.NewTestFactory() testFactory := cmdtesting.NewTestFactory()
t.Cleanup(testFactory.Cleanup) t.Cleanup(testFactory.Cleanup)
@ -1824,7 +1816,6 @@ func TestPatchResourceServerSide(t *testing.T) {
for name, tc := range testCases { for name, tc := range testCases {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
testFactory := cmdtesting.NewTestFactory() testFactory := cmdtesting.NewTestFactory()
t.Cleanup(testFactory.Cleanup) t.Cleanup(testFactory.Cleanup)
@ -1855,7 +1846,6 @@ func TestPatchResourceServerSide(t *testing.T) {
} }
func TestDetermineFieldValidationDirective(t *testing.T) { func TestDetermineFieldValidationDirective(t *testing.T) {
assert.Equal(t, FieldValidationDirectiveIgnore, determineFieldValidationDirective(false)) assert.Equal(t, FieldValidationDirectiveIgnore, determineFieldValidationDirective(false))
assert.Equal(t, FieldValidationDirectiveStrict, determineFieldValidationDirective(true)) assert.Equal(t, FieldValidationDirectiveStrict, determineFieldValidationDirective(true))
} }

@ -68,6 +68,12 @@ func alwaysReady(_ *unstructured.Unstructured) (*status.Result, error) {
}, nil }, 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 { func (w *statusWaiter) WatchUntilReady(resourceList ResourceList, timeout time.Duration) error {
if timeout == 0 { if timeout == 0 {
timeout = DefaultStatusWatcherTimeout timeout = DefaultStatusWatcherTimeout
@ -75,7 +81,7 @@ func (w *statusWaiter) WatchUntilReady(resourceList ResourceList, timeout time.D
ctx, cancel := w.contextWithTimeout(w.watchUntilReadyCtx, timeout) ctx, cancel := w.contextWithTimeout(w.watchUntilReadyCtx, timeout)
defer cancel() defer cancel()
w.Logger().Debug("waiting for resources", "count", len(resourceList), "timeout", timeout) 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) jobSR := helmStatusReaders.NewCustomJobStatusReader(w.restMapper)
podSR := helmStatusReaders.NewCustomPodStatusReader(w.restMapper) podSR := helmStatusReaders.NewCustomPodStatusReader(w.restMapper)
// We don't want to wait on any other resources as watchUntilReady is only for Helm hooks. // 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) ctx, cancel := w.contextWithTimeout(w.waitCtx, timeout)
defer cancel() defer cancel()
w.Logger().Debug("waiting for resources", "count", len(resourceList), "timeout", timeout) 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...) sw.StatusReader = statusreaders.NewStatusReader(w.restMapper, w.readers...)
return w.wait(ctx, resourceList, sw) 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) ctx, cancel := w.contextWithTimeout(w.waitWithJobsCtx, timeout)
defer cancel() defer cancel()
w.Logger().Debug("waiting for resources", "count", len(resourceList), "timeout", timeout) 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) newCustomJobStatusReader := helmStatusReaders.NewCustomJobStatusReader(w.restMapper)
readers := append([]engine.StatusReader(nil), w.readers...) readers := append([]engine.StatusReader(nil), w.readers...)
readers = append(readers, newCustomJobStatusReader) readers = append(readers, newCustomJobStatusReader)
@ -125,7 +131,7 @@ func (w *statusWaiter) WaitForDelete(resourceList ResourceList, timeout time.Dur
ctx, cancel := w.contextWithTimeout(w.waitForDeleteCtx, timeout) ctx, cancel := w.contextWithTimeout(w.waitForDeleteCtx, timeout)
defer cancel() defer cancel()
w.Logger().Debug("waiting for resources to be deleted", "count", len(resourceList), "timeout", timeout) 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) 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)) rs.Identifier.GroupKind.Kind, rs.Identifier.Namespace, rs.Identifier.Name, rs.Status, rs.Message))
} }
if err := ctx.Err(); err != nil { 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 { if len(errs) > 0 {
return errors.Join(errs...) 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) { return func(statusCollector *collector.ResourceStatusCollector, _ event.Event) {
var rss []*event.ResourceStatus var rss []*event.ResourceStatus
var nonDesiredResources []*event.ResourceStatus var nonDesiredResources []*event.ResourceStatus
var unknownSkipped int
for _, rs := range statusCollector.ResourceStatuses { for _, rs := range statusCollector.ResourceStatuses {
if rs == nil { if rs == nil {
continue 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. // 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. // This check ensures we don't wait forever for a resource that is already deleted.
if rs.Status == status.UnknownStatus && desired == status.NotFoundStatus { if rs.Status == status.UnknownStatus && desired == status.NotFoundStatus {
unknownSkipped++
continue continue
} }
// Failed is a terminal state. This check ensures we don't wait forever for a resource // 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 { if aggregator.AggregateStatus(rss, desired) == desired {
logger.Debug("all resources achieved desired status", "desiredStatus", desired, "resourceCount", len(rss)) logger.Debug("all resources achieved desired status", "desiredStatus", desired, "resourceCount", len(rss))
cancel() cancel()

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

Loading…
Cancel
Save