Merge branch 'helm:main' into circleci-project-setup

pull/31010/head
BoomchainLabs 3 months ago committed by GitHub
commit bad903e0ce
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,69 @@
name: Bug Report
description: Report a bug encountered in Helm
labels: kind/bug
body:
- type: textarea
id: problem
attributes:
label: What happened?
description: |
Please provide as much info as possible. Not doing so may result in your bug not being addressed in a timely manner.
validations:
required: true
- type: textarea
id: expected
attributes:
label: What did you expect to happen?
validations:
required: true
- type: textarea
id: repro
attributes:
label: How can we reproduce it (as minimally and precisely as possible)?
description: |
Please list steps someone can follow to trigger the issue.
For example:
1. Run `helm install mychart ./path-to-chart -f values.yaml --debug`
2. Observe the following error: ...
You can include:
- a sample `values.yaml` block
- a link to a chart
- specific `helm` commands used
This helps others reproduce and debug your issue more effectively.
validations:
required: true
- type: textarea
id: helmVersion
attributes:
label: Helm version
value: |
<details>
```console
$ helm version
# paste output here
```
</details>
validations:
required: true
- type: textarea
id: kubeVersion
attributes:
label: Kubernetes version
value: |
<details>
```console
$ kubectl version
# paste output here
```
</details>
validations:
required: true

@ -0,0 +1,27 @@
name: Documentation
description: Report any mistakes or missing information from the documentation or the examples
labels: kind/documentation
body:
- type: markdown
attributes:
value: |
⚠️ **Note**: Most documentation lives in [helm/helm-www](https://github.com/helm/helm-www).
If your issue is about Helm website documentation or examples, please [open an issue there](https://github.com/helm/helm-www/issues/new/choose).
- type: textarea
id: feature
attributes:
label: What would you like to be added?
description: |
Link to the issue (please include a link to the specific documentation or example).
Link to the issue raised in [Helm Documentation Improvement Proposal](https://github.com/helm/helm-www)
validations:
required: true
- type: textarea
id: rationale
attributes:
label: Why is this needed?
validations:
required: true

@ -0,0 +1,21 @@
name: Enhancement/feature
description: Provide supporting details for a feature in development
labels: kind/feature
body:
- type: textarea
id: feature
attributes:
label: What would you like to be added?
description: |
Feature requests are unlikely to make progress as issues.
Initial discussion and ideas can happen on an issue.
But significant changes or features must be proposed as a [Helm Improvement Proposal](https://github.com/helm/community/blob/main/hips/hip-0001.md) (HIP)
validations:
required: true
- type: textarea
id: rationale
attributes:
label: Why is this needed?
validations:
required: true

2
.github/env vendored

@ -1,2 +1,2 @@
GOLANG_VERSION=1.24 GOLANG_VERSION=1.24
GOLANGCI_LINT_VERSION=v2.0.2 GOLANGCI_LINT_VERSION=v2.1.0

@ -1,9 +0,0 @@
<!-- If you need help or think you have found a bug, please help us with your issue by entering the following information (otherwise you can delete this text): -->
Output of `helm version`:
Output of `kubectl version`:
Cloud Provider/Platform (AKS, GKE, Minikube etc.):

@ -22,7 +22,7 @@ jobs:
- name: Add variables to environment file - name: Add variables to environment file
run: cat ".github/env" >> "$GITHUB_ENV" run: cat ".github/env" >> "$GITHUB_ENV"
- name: Setup Go - name: Setup Go
uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # pin@5.4.0 uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # pin@5.5.0
with: with:
go-version: '${{ env.GOLANG_VERSION }}' go-version: '${{ env.GOLANG_VERSION }}'
check-latest: true check-latest: true

@ -17,11 +17,11 @@ jobs:
- name: Add variables to environment file - name: Add variables to environment file
run: cat ".github/env" >> "$GITHUB_ENV" run: cat ".github/env" >> "$GITHUB_ENV"
- name: Setup Go - name: Setup Go
uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # pin@5.4.0 uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # pin@5.5.0
with: with:
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@1481404843c368bc19ca9406f87d6e0fc97bdcfd #pin@7.0.0 uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 #pin@8.0.0
with: with:
version: ${{ env.GOLANGCI_LINT_VERSION }} version: ${{ env.GOLANGCI_LINT_VERSION }}

@ -18,7 +18,7 @@ jobs:
- name: Add variables to environment file - name: Add variables to environment file
run: cat ".github/env" >> "$GITHUB_ENV" run: cat ".github/env" >> "$GITHUB_ENV"
- name: Setup Go - name: Setup Go
uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # pin@5.4.0 uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # pin@5.5.0
with: with:
go-version: '${{ env.GOLANG_VERSION }}' go-version: '${{ env.GOLANG_VERSION }}'
check-latest: true check-latest: true

@ -28,7 +28,7 @@ jobs:
run: cat ".github/env" >> "$GITHUB_ENV" run: cat ".github/env" >> "$GITHUB_ENV"
- name: Setup Go - name: Setup Go
uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # pin@5.4.0 uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # pin@5.5.0
with: with:
go-version: '${{ env.GOLANG_VERSION }}' go-version: '${{ env.GOLANG_VERSION }}'
- name: Run unit tests - name: Run unit tests
@ -85,7 +85,7 @@ jobs:
run: cat ".github/env" >> "$GITHUB_ENV" run: cat ".github/env" >> "$GITHUB_ENV"
- name: Setup Go - name: Setup Go
uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # pin@5.4.0 uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # pin@5.5.0
with: with:
go-version: '${{ env.GOLANG_VERSION }}' go-version: '${{ env.GOLANG_VERSION }}'
check-latest: true check-latest: true

@ -33,7 +33,7 @@ jobs:
persist-credentials: false persist-credentials: false
- name: "Run analysis" - name: "Run analysis"
uses: ossf/scorecard-action@f49aabe0b5af0936a0987cfb85d86b75731b0186 # v2.4.1 uses: ossf/scorecard-action@05b42c624433fc40578a4040d5cf5e36ddca8cde # v2.4.2
with: with:
results_file: results.sarif results_file: results.sarif
results_format: sarif results_format: sarif

@ -20,13 +20,17 @@ linters:
enable: enable:
- depguard - depguard
- dupl - dupl
- gomodguard
- govet - govet
- ineffassign - ineffassign
- misspell - misspell
- nakedret - nakedret
- revive - revive
- staticcheck - staticcheck
- thelper
- unused - unused
- usestdlibvars
- usetesting
exclusions: exclusions:
generated: lax generated: lax
@ -54,6 +58,13 @@ linters:
dupl: dupl:
threshold: 400 threshold: 400
gomodguard:
blocked:
modules:
- github.com/evanphx/json-patch:
recommendations:
- github.com/evanphx/json-patch/v5
run: run:
timeout: 10m timeout: 10m

@ -5,6 +5,7 @@
[![GoDoc](https://img.shields.io/static/v1?label=godoc&message=reference&color=blue)](https://pkg.go.dev/helm.sh/helm/v4) [![GoDoc](https://img.shields.io/static/v1?label=godoc&message=reference&color=blue)](https://pkg.go.dev/helm.sh/helm/v4)
[![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/3131/badge)](https://bestpractices.coreinfrastructure.org/projects/3131) [![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/3131/badge)](https://bestpractices.coreinfrastructure.org/projects/3131)
[![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/helm/helm/badge)](https://scorecard.dev/viewer/?uri=github.com/helm/helm) [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/helm/helm/badge)](https://scorecard.dev/viewer/?uri=github.com/helm/helm)
[![LFX Health Score](https://img.shields.io/static/v1?label=Health%20Score&message=Healthy&color=A7F3D0&logo=linuxfoundation&logoColor=white&style=flat)](https://insights.linuxfoundation.org/project/helm)
Helm is a tool for managing Charts. Charts are packages of pre-configured Kubernetes resources. Helm is a tool for managing Charts. Charts are packages of pre-configured Kubernetes resources.
@ -56,7 +57,7 @@ including installing pre-releases.
## Docs ## Docs
Get started with the [Quick Start guide](https://helm.sh/docs/intro/quickstart/) or plunge into the [complete documentation](https://helm.sh/docs) Get started with the [Quick Start guide](https://helm.sh/docs/intro/quickstart/) or plunge into the [complete documentation](https://helm.sh/docs).
## Roadmap ## Roadmap

@ -41,7 +41,6 @@ func main() {
} }
if err := cmd.Execute(); err != nil { if err := cmd.Execute(); err != nil {
slog.Debug("error", slog.Any("error", err))
switch e := err.(type) { switch e := err.(type) {
case helmcmd.PluginError: case helmcmd.PluginError:
os.Exit(e.Code) os.Exit(e.Code)

@ -13,7 +13,7 @@ require (
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2
github.com/cyphar/filepath-securejoin v0.4.1 github.com/cyphar/filepath-securejoin v0.4.1
github.com/distribution/distribution/v3 v3.0.0 github.com/distribution/distribution/v3 v3.0.0
github.com/evanphx/json-patch v5.9.11+incompatible github.com/evanphx/json-patch/v5 v5.9.11
github.com/fluxcd/cli-utils v0.36.0-flux.13 github.com/fluxcd/cli-utils v0.36.0-flux.13
github.com/foxcpp/go-mockdns v1.1.0 github.com/foxcpp/go-mockdns v1.1.0
github.com/gobwas/glob v0.2.3 github.com/gobwas/glob v0.2.3
@ -27,25 +27,25 @@ require (
github.com/opencontainers/image-spec v1.1.1 github.com/opencontainers/image-spec v1.1.1
github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5
github.com/rubenv/sql-migrate v1.8.0 github.com/rubenv/sql-migrate v1.8.0
github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 github.com/santhosh-tekuri/jsonschema/v6 v6.0.2
github.com/spf13/cobra v1.9.1 github.com/spf13/cobra v1.9.1
github.com/spf13/pflag v1.0.6 github.com/spf13/pflag v1.0.6
github.com/stretchr/testify v1.10.0 github.com/stretchr/testify v1.10.0
golang.org/x/crypto v0.37.0 golang.org/x/crypto v0.39.0
golang.org/x/term v0.31.0 golang.org/x/term v0.32.0
golang.org/x/text v0.24.0 golang.org/x/text v0.26.0
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
k8s.io/api v0.33.0 k8s.io/api v0.33.2
k8s.io/apiextensions-apiserver v0.33.0 k8s.io/apiextensions-apiserver v0.33.2
k8s.io/apimachinery v0.33.0 k8s.io/apimachinery v0.33.2
k8s.io/apiserver v0.33.0 k8s.io/apiserver v0.33.2
k8s.io/cli-runtime v0.33.0 k8s.io/cli-runtime v0.33.2
k8s.io/client-go v0.33.0 k8s.io/client-go v0.33.2
k8s.io/klog/v2 v2.130.1 k8s.io/klog/v2 v2.130.1
k8s.io/kubectl v0.33.0 k8s.io/kubectl v0.33.2
oras.land/oras-go/v2 v2.5.0 oras.land/oras-go/v2 v2.6.0
sigs.k8s.io/controller-runtime v0.20.4 sigs.k8s.io/controller-runtime v0.21.0
sigs.k8s.io/yaml v1.4.0 sigs.k8s.io/yaml v1.5.0
) )
require ( require (
@ -68,7 +68,6 @@ require (
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect
github.com/docker/go-metrics v0.0.1 // indirect github.com/docker/go-metrics v0.0.1 // indirect
github.com/emicklei/go-restful/v3 v3.12.1 // indirect github.com/emicklei/go-restful/v3 v3.12.1 // indirect
github.com/evanphx/json-patch/v5 v5.9.11 // indirect
github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect
github.com/fatih/color v1.13.0 // indirect github.com/fatih/color v1.13.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect
@ -155,13 +154,15 @@ require (
go.opentelemetry.io/otel/sdk/metric v1.32.0 // indirect go.opentelemetry.io/otel/sdk/metric v1.32.0 // indirect
go.opentelemetry.io/otel/trace v1.34.0 // indirect go.opentelemetry.io/otel/trace v1.34.0 // indirect
go.opentelemetry.io/proto/otlp v1.4.0 // indirect go.opentelemetry.io/proto/otlp v1.4.0 // indirect
golang.org/x/mod v0.24.0 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect
golang.org/x/net v0.39.0 // indirect go.yaml.in/yaml/v3 v3.0.3 // indirect
golang.org/x/mod v0.25.0 // indirect
golang.org/x/net v0.40.0 // indirect
golang.org/x/oauth2 v0.29.0 // indirect golang.org/x/oauth2 v0.29.0 // indirect
golang.org/x/sync v0.13.0 // indirect golang.org/x/sync v0.15.0 // indirect
golang.org/x/sys v0.32.0 // indirect golang.org/x/sys v0.33.0 // indirect
golang.org/x/time v0.11.0 // indirect golang.org/x/time v0.11.0 // indirect
golang.org/x/tools v0.32.0 // indirect golang.org/x/tools v0.33.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 // indirect
google.golang.org/grpc v1.68.1 // indirect google.golang.org/grpc v1.68.1 // indirect
@ -169,7 +170,7 @@ require (
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.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.33.0 // indirect k8s.io/component-base v0.33.2 // indirect
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect
k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e // indirect k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e // indirect
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect

@ -77,8 +77,6 @@ github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQ
github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw=
github.com/emicklei/go-restful/v3 v3.12.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtzpL63nKAU= github.com/emicklei/go-restful/v3 v3.12.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtzpL63nKAU=
github.com/emicklei/go-restful/v3 v3.12.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/emicklei/go-restful/v3 v3.12.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/evanphx/json-patch v5.9.11+incompatible h1:ixHHqfcGvxhWkniF1tWxBHA0yb4Z+d1UQi45df52xW8=
github.com/evanphx/json-patch v5.9.11+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU= github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU=
github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM= github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM=
github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f h1:Wl78ApPPB2Wvf/TIe2xdyJxTlb6obmF18d8QdkxNDu4= github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f h1:Wl78ApPPB2Wvf/TIe2xdyJxTlb6obmF18d8QdkxNDu4=
@ -292,8 +290,8 @@ github.com/rubenv/sql-migrate v1.8.0 h1:dXnYiJk9k3wetp7GfQbKJcPHjVJL6YK19tKj8t2N
github.com/rubenv/sql-migrate v1.8.0/go.mod h1:F2bGFBwCU+pnmbtNYDeKvSuvL6lBVtXDXUUv5t+u1qw= github.com/rubenv/sql-migrate v1.8.0/go.mod h1:F2bGFBwCU+pnmbtNYDeKvSuvL6lBVtXDXUUv5t+u1qw=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 h1:PKK9DyHxif4LZo+uQSgXNqs0jj5+xZwwfKHgph2lxBw= github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ=
github.com/santhosh-tekuri/jsonschema/v6 v6.0.1/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU=
github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
@ -378,6 +376,10 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
go.yaml.in/yaml/v3 v3.0.3 h1:bXOww4E/J3f66rav3pX3m8w6jDE4knZjGOw8b5Y6iNE=
go.yaml.in/yaml/v3 v3.0.3/go.mod h1:tBHosrYAkRZjRAOREWbDnBXUf08JOwYq++0QNwQiWzI=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@ -386,16 +388,16 @@ 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.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
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.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
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-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
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=
@ -409,8 +411,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.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98= golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98=
golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
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=
@ -423,8 +425,8 @@ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -446,8 +448,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.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
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=
@ -455,8 +457,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.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o= golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw= golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
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=
@ -464,8 +466,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.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
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=
@ -476,8 +478,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.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU= golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s= golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
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=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -504,32 +506,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.33.0 h1:yTgZVn1XEe6opVpP1FylmNrIFWuDqe2H0V8CT5gxfIU= k8s.io/api v0.33.2 h1:YgwIS5jKfA+BZg//OQhkJNIfie/kmRsO0BmNaVSimvY=
k8s.io/api v0.33.0/go.mod h1:CTO61ECK/KU7haa3qq8sarQ0biLq2ju405IZAd9zsiM= k8s.io/api v0.33.2/go.mod h1:fhrbphQJSM2cXzCWgqU29xLDuks4mu7ti9vveEnpSXs=
k8s.io/apiextensions-apiserver v0.33.0 h1:d2qpYL7Mngbsc1taA4IjJPRJ9ilnsXIrndH+r9IimOs= k8s.io/apiextensions-apiserver v0.33.2 h1:6gnkIbngnaUflR3XwE1mCefN3YS8yTD631JXQhsU6M8=
k8s.io/apiextensions-apiserver v0.33.0/go.mod h1:VeJ8u9dEEN+tbETo+lFkwaaZPg6uFKLGj5vyNEwwSzc= k8s.io/apiextensions-apiserver v0.33.2/go.mod h1:IvVanieYsEHJImTKXGP6XCOjTwv2LUMos0YWc9O+QP8=
k8s.io/apimachinery v0.33.0 h1:1a6kHrJxb2hs4t8EE5wuR/WxKDwGN1FKH3JvDtA0CIQ= k8s.io/apimachinery v0.33.2 h1:IHFVhqg59mb8PJWTLi8m1mAoepkUNYmptHsV+Z1m5jY=
k8s.io/apimachinery v0.33.0/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= k8s.io/apimachinery v0.33.2/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM=
k8s.io/apiserver v0.33.0 h1:QqcM6c+qEEjkOODHppFXRiw/cE2zP85704YrQ9YaBbc= k8s.io/apiserver v0.33.2 h1:KGTRbxn2wJagJowo29kKBp4TchpO1DRO3g+dB/KOJN4=
k8s.io/apiserver v0.33.0/go.mod h1:EixYOit0YTxt8zrO2kBU7ixAtxFce9gKGq367nFmqI8= k8s.io/apiserver v0.33.2/go.mod h1:9qday04wEAMLPWWo9AwqCZSiIn3OYSZacDyu/AcoM/M=
k8s.io/cli-runtime v0.33.0 h1:Lbl/pq/1o8BaIuyn+aVLdEPHVN665tBAXUePs8wjX7c= k8s.io/cli-runtime v0.33.2 h1:koNYQKSDdq5AExa/RDudXMhhtFasEg48KLS2KSAU74Y=
k8s.io/cli-runtime v0.33.0/go.mod h1:QcA+r43HeUM9jXFJx7A+yiTPfCooau/iCcP1wQh4NFw= k8s.io/cli-runtime v0.33.2/go.mod h1:gnhsAWpovqf1Zj5YRRBBU7PFsRc6NkEkwYNQE+mXL88=
k8s.io/client-go v0.33.0 h1:UASR0sAYVUzs2kYuKn/ZakZlcs2bEHaizrrHUZg0G98= k8s.io/client-go v0.33.2 h1:z8CIcc0P581x/J1ZYf4CNzRKxRvQAwoAolYPbtQes+E=
k8s.io/client-go v0.33.0/go.mod h1:kGkd+l/gNGg8GYWAPr0xF1rRKvVWvzh9vmZAMXtaKOg= k8s.io/client-go v0.33.2/go.mod h1:9mCgT4wROvL948w6f6ArJNb7yQd7QsvqavDeZHvNmHo=
k8s.io/component-base v0.33.0 h1:Ot4PyJI+0JAD9covDhwLp9UNkUja209OzsJ4FzScBNk= k8s.io/component-base v0.33.2 h1:sCCsn9s/dG3ZrQTX/Us0/Sx2R0G5kwa0wbZFYoVp/+0=
k8s.io/component-base v0.33.0/go.mod h1:aXYZLbw3kihdkOPMDhWbjGCO6sg+luw554KP51t8qCU= k8s.io/component-base v0.33.2/go.mod h1:/41uw9wKzuelhN+u+/C59ixxf4tYQKW7p32ddkYNe2k=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4= k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4=
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8= k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8=
k8s.io/kubectl v0.33.0 h1:HiRb1yqibBSCqic4pRZP+viiOBAnIdwYDpzUFejs07g= k8s.io/kubectl v0.33.2 h1:7XKZ6DYCklu5MZQzJe+CkCjoGZwD1wWl7t/FxzhMz7Y=
k8s.io/kubectl v0.33.0/go.mod h1:gAlGBuS1Jq1fYZ9AjGWbI/5Vk3M/VW2DK4g10Fpyn/0= k8s.io/kubectl v0.33.2/go.mod h1:8rC67FB8tVTYraovAGNi/idWIK90z2CHFNMmGJZJ3KI=
k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e h1:KqK5c/ghOm8xkHYhlodbp6i6+r+ChV2vuAuVRdFbLro= k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e h1:KqK5c/ghOm8xkHYhlodbp6i6+r+ChV2vuAuVRdFbLro=
k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
oras.land/oras-go/v2 v2.5.0 h1:o8Me9kLY74Vp5uw07QXPiitjsw7qNXi8Twd+19Zf02c= oras.land/oras-go/v2 v2.6.0 h1:X4ELRsiGkrbeox69+9tzTu492FMUu7zJQW6eJU+I2oc=
oras.land/oras-go/v2 v2.5.0/go.mod h1:z4eisnLP530vwIOUOJeBIj0aGI0L1C3d53atvCBqZHg= oras.land/oras-go/v2 v2.6.0/go.mod h1:magiQDfG6H1O9APp+rOsvCPcW1GD2MM7vgnKY0Y+u1o=
sigs.k8s.io/controller-runtime v0.20.4 h1:X3c+Odnxz+iPTRobG4tp092+CvBU9UK0t/bRf+n0DGU= sigs.k8s.io/controller-runtime v0.21.0 h1:CYfjpEuicjUecRk+KAeyYh+ouUBn4llGyDYytIGcJS8=
sigs.k8s.io/controller-runtime v0.20.4/go.mod h1:xg2XB0K5ShQzAgsoujxuKN4LNXR2LfwwHsPj7Iaw+XY= sigs.k8s.io/controller-runtime v0.21.0/go.mod h1:OSg14+F65eWqIu4DceX7k/+QRAbTTvxeQSNSOQpukWM=
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE=
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
sigs.k8s.io/kustomize/api v0.19.0 h1:F+2HB2mU1MSiR9Hp1NEgoU2q9ItNOaBJl0I4Dlus5SQ= sigs.k8s.io/kustomize/api v0.19.0 h1:F+2HB2mU1MSiR9Hp1NEgoU2q9ItNOaBJl0I4Dlus5SQ=
@ -541,5 +543,6 @@ sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
sigs.k8s.io/structured-merge-diff/v4 v4.6.0 h1:IUA9nvMmnKWcj5jl84xn+T5MnlZKThmUW1TdblaLVAc= sigs.k8s.io/structured-merge-diff/v4 v4.6.0 h1:IUA9nvMmnKWcj5jl84xn+T5MnlZKThmUW1TdblaLVAc=
sigs.k8s.io/structured-merge-diff/v4 v4.6.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps= sigs.k8s.io/structured-merge-diff/v4 v4.6.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps=
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
sigs.k8s.io/yaml v1.5.0 h1:M10b2U7aEUY6hRtU870n2VTPgR5RZiL/I6Lcc2F4NUQ=
sigs.k8s.io/yaml v1.5.0/go.mod h1:wZs27Rbxoai4C0f8/9urLZtZtF3avA3gKvGyPdDqTO4=

@ -129,7 +129,7 @@ func (c *Client) Search(term string) ([]SearchResult, error) {
} }
defer res.Body.Close() defer res.Body.Close()
if res.StatusCode != 200 { if res.StatusCode != http.StatusOK {
return nil, fmt.Errorf("failed to fetch %s : %s", p.String(), res.Status) return nil, fmt.Errorf("failed to fetch %s : %s", p.String(), res.Status)
} }

@ -76,6 +76,7 @@ func walkTree(n *Node, path string, f func(path string, n *Node)) {
} }
func makeTree(t *testing.T) { func makeTree(t *testing.T) {
t.Helper()
walkTree(tree, tree.name, func(path string, n *Node) { walkTree(tree, tree.name, func(path string, n *Node) {
if n.entries == nil { if n.entries == nil {
if n.symLinkedTo != "" { if n.symLinkedTo != "" {
@ -99,6 +100,7 @@ func makeTree(t *testing.T) {
} }
func checkMarks(t *testing.T, report bool) { func checkMarks(t *testing.T, report bool) {
t.Helper()
walkTree(tree, tree.name, func(path string, n *Node) { walkTree(tree, tree.name, func(path string, n *Node) {
if n.marks != n.expectedMarks && report { if n.marks != n.expectedMarks && report {
t.Errorf("node %s mark = %d; expected %d", path, n.marks, n.expectedMarks) t.Errorf("node %s mark = %d; expected %d", path, n.marks, n.expectedMarks)

@ -29,12 +29,12 @@ import (
func HelmHome(t *testing.T) { func HelmHome(t *testing.T) {
t.Helper() t.Helper()
base := t.TempDir() base := t.TempDir()
os.Setenv(xdg.CacheHomeEnvVar, base) t.Setenv(xdg.CacheHomeEnvVar, base)
os.Setenv(xdg.ConfigHomeEnvVar, base) t.Setenv(xdg.ConfigHomeEnvVar, base)
os.Setenv(xdg.DataHomeEnvVar, base) t.Setenv(xdg.DataHomeEnvVar, base)
os.Setenv(helmpath.CacheHomeEnvVar, "") t.Setenv(helmpath.CacheHomeEnvVar, "")
os.Setenv(helmpath.ConfigHomeEnvVar, "") t.Setenv(helmpath.ConfigHomeEnvVar, "")
os.Setenv(helmpath.DataHomeEnvVar, "") t.Setenv(helmpath.DataHomeEnvVar, "")
} }
// TempFile ensures a temp file for unit testing purposes. // TempFile ensures a temp file for unit testing purposes.
@ -46,9 +46,10 @@ func HelmHome(t *testing.T) {
// tempdir := TempFile(t, "foo", []byte("bar")) // tempdir := TempFile(t, "foo", []byte("bar"))
// filename := filepath.Join(tempdir, "foo") // filename := filepath.Join(tempdir, "foo")
func TempFile(t *testing.T, name string, data []byte) string { func TempFile(t *testing.T, name string, data []byte) string {
t.Helper()
path := t.TempDir() path := t.TempDir()
filename := filepath.Join(path, name) filename := filepath.Join(path, name)
if err := os.WriteFile(filename, data, 0755); err != nil { if err := os.WriteFile(filename, data, 0o755); err != nil {
t.Fatal(err) t.Fatal(err)
} }
return path return path

@ -457,6 +457,7 @@ func TestCopyFileFail(t *testing.T) {
// files this function creates. It is the caller's responsibility to call // files this function creates. It is the caller's responsibility to call
// this function before the test is done running, whether there's an error or not. // this function before the test is done running, whether there's an error or not.
func setupInaccessibleDir(t *testing.T, op func(dir string) error) func() { func setupInaccessibleDir(t *testing.T, op func(dir string) error) func() {
t.Helper()
dir := t.TempDir() dir := t.TempDir()
subdir := filepath.Join(dir, "dir") subdir := filepath.Join(dir, "dir")

@ -30,8 +30,9 @@ const (
) )
func testfile(t *testing.T, file string) (path string) { func testfile(t *testing.T, file string) (path string) {
var err error t.Helper()
if path, err = filepath.Abs(filepath.Join(tlsTestDir, file)); err != nil { path, err := filepath.Abs(filepath.Join(tlsTestDir, file))
if err != nil {
t.Fatalf("error getting absolute path to test file %q: %v", file, err) t.Fatalf("error getting absolute path to test file %q: %v", file, err)
} }
return path return path

@ -26,6 +26,7 @@ import (
"path" "path"
"path/filepath" "path/filepath"
"strings" "strings"
"sync"
"text/template" "text/template"
"k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/api/meta"
@ -86,6 +87,8 @@ type Configuration struct {
// HookOutputFunc called with container name and returns and expects writer that will receive the log output. // HookOutputFunc called with container name and returns and expects writer that will receive the log output.
HookOutputFunc func(namespace, pod, container string) io.Writer HookOutputFunc func(namespace, pod, container string) io.Writer
mutex sync.Mutex
} }
// renderResources renders the templates in a chart // renderResources renders the templates in a chart

@ -40,6 +40,7 @@ import (
var verbose = flag.Bool("test.log", false, "enable test logging (debug by default)") var verbose = flag.Bool("test.log", false, "enable test logging (debug by default)")
func actionConfigFixture(t *testing.T) *Configuration { func actionConfigFixture(t *testing.T) *Configuration {
t.Helper()
return actionConfigFixtureWithDummyResources(t, nil) return actionConfigFixtureWithDummyResources(t, nil)
} }

@ -49,13 +49,7 @@ func (cfg *Configuration) execHook(rl *release.Release, hook release.HookEvent,
for i, h := range executingHooks { for i, h := range executingHooks {
// Set default delete policy to before-hook-creation // Set default delete policy to before-hook-creation
if len(h.DeletePolicies) == 0 { cfg.hookSetDeletePolicy(h)
// TODO(jlegrone): Only apply before-hook-creation delete policy to run to completion
// resources. For all other resource types update in place if a
// resource with the same name already exists and is owned by the
// current release.
h.DeletePolicies = []release.HookDeletePolicy{release.HookBeforeHookCreation}
}
if err := cfg.deleteHookByPolicy(h, release.HookBeforeHookCreation, waitStrategy, timeout); err != nil { if err := cfg.deleteHookByPolicy(h, release.HookBeforeHookCreation, waitStrategy, timeout); err != nil {
return err return err
@ -154,7 +148,7 @@ func (cfg *Configuration) deleteHookByPolicy(h *release.Hook, policy release.Hoo
if h.Kind == "CustomResourceDefinition" { if h.Kind == "CustomResourceDefinition" {
return nil return nil
} }
if hookHasDeletePolicy(h, policy) { if cfg.hookHasDeletePolicy(h, policy) {
resources, err := cfg.KubeClient.Build(bytes.NewBufferString(h.Manifest), false) resources, err := cfg.KubeClient.Build(bytes.NewBufferString(h.Manifest), false)
if err != nil { if err != nil {
return fmt.Errorf("unable to build kubernetes object for deleting hook %s: %w", h.Path, err) return fmt.Errorf("unable to build kubernetes object for deleting hook %s: %w", h.Path, err)
@ -188,13 +182,24 @@ func (cfg *Configuration) deleteHooksByPolicy(hooks []*release.Hook, policy rele
// hookHasDeletePolicy determines whether the defined hook deletion policy matches the hook deletion polices // hookHasDeletePolicy determines whether the defined hook deletion policy matches the hook deletion polices
// supported by helm. If so, mark the hook as one should be deleted. // supported by helm. If so, mark the hook as one should be deleted.
func hookHasDeletePolicy(h *release.Hook, policy release.HookDeletePolicy) bool { func (cfg *Configuration) hookHasDeletePolicy(h *release.Hook, policy release.HookDeletePolicy) bool {
for _, v := range h.DeletePolicies { cfg.mutex.Lock()
if policy == v { defer cfg.mutex.Unlock()
return true return slices.Contains(h.DeletePolicies, policy)
} }
// hookSetDeletePolicy determines whether the defined hook deletion policy matches the hook deletion polices
// supported by helm. If so, mark the hook as one should be deleted.
func (cfg *Configuration) hookSetDeletePolicy(h *release.Hook) {
cfg.mutex.Lock()
defer cfg.mutex.Unlock()
if len(h.DeletePolicies) == 0 {
// TODO(jlegrone): Only apply before-hook-creation delete policy to run to completion
// resources. For all other resource types update in place if a
// resource with the same name already exists and is owned by the
// current release.
h.DeletePolicies = []release.HookDeletePolicy{release.HookBeforeHookCreation}
} }
return false
} }
// outputLogsByPolicy outputs a pods logs if the hook policy instructs it to // outputLogsByPolicy outputs a pods logs if the hook policy instructs it to

@ -167,6 +167,7 @@ func TestInstallRelease_HooksOutputLogsOnSuccessAndFailure(t *testing.T) {
} }
func runInstallForHooksWithSuccess(t *testing.T, manifest, expectedNamespace string, shouldOutput bool) { func runInstallForHooksWithSuccess(t *testing.T, manifest, expectedNamespace string, shouldOutput bool) {
t.Helper()
var expectedOutput string var expectedOutput string
if shouldOutput { if shouldOutput {
expectedOutput = fmt.Sprintf("attempted to output logs for namespace: %s", expectedNamespace) expectedOutput = fmt.Sprintf("attempted to output logs for namespace: %s", expectedNamespace)
@ -190,6 +191,7 @@ func runInstallForHooksWithSuccess(t *testing.T, manifest, expectedNamespace str
} }
func runInstallForHooksWithFailure(t *testing.T, manifest, expectedNamespace string, shouldOutput bool) { func runInstallForHooksWithFailure(t *testing.T, manifest, expectedNamespace string, shouldOutput bool) {
t.Helper()
var expectedOutput string var expectedOutput string
if shouldOutput { if shouldOutput {
expectedOutput = fmt.Sprintf("attempted to output logs for namespace: %s", expectedNamespace) expectedOutput = fmt.Sprintf("attempted to output logs for namespace: %s", expectedNamespace)

@ -116,6 +116,7 @@ func installActionWithConfig(config *Configuration) *Install {
} }
func installAction(t *testing.T) *Install { func installAction(t *testing.T) *Install {
t.Helper()
config := actionConfigFixture(t) config := actionConfigFixture(t)
instAction := NewInstall(config) instAction := NewInstall(config)
instAction.Namespace = "spaced" instAction.Namespace = "spaced"
@ -130,7 +131,7 @@ func TestInstallRelease(t *testing.T) {
instAction := installAction(t) instAction := installAction(t)
vals := map[string]interface{}{} vals := map[string]interface{}{}
ctx, done := context.WithCancel(context.Background()) ctx, done := context.WithCancel(t.Context())
res, err := instAction.RunWithContext(ctx, buildChart(), vals) res, err := instAction.RunWithContext(ctx, buildChart(), vals)
if err != nil { if err != nil {
t.Fatalf("Failed install: %s", err) t.Fatalf("Failed install: %s", err)
@ -446,7 +447,9 @@ func TestInstallReleaseIncorrectTemplate_DryRun(t *testing.T) {
instAction.DryRun = true instAction.DryRun = true
vals := map[string]interface{}{} vals := map[string]interface{}{}
_, err := instAction.Run(buildChart(withSampleIncludingIncorrectTemplates()), vals) _, err := instAction.Run(buildChart(withSampleIncludingIncorrectTemplates()), vals)
expectedErr := "\"hello/templates/incorrect\" at <.Values.bad.doh>: nil pointer evaluating interface {}.doh" expectedErr := `hello/templates/incorrect:1:10
executing "hello/templates/incorrect" at <.Values.bad.doh>:
nil pointer evaluating interface {}.doh`
if err == nil { if err == nil {
t.Fatalf("Install should fail containing error: %s", expectedErr) t.Fatalf("Install should fail containing error: %s", expectedErr)
} }
@ -556,7 +559,7 @@ func TestInstallRelease_Wait_Interrupted(t *testing.T) {
instAction.WaitStrategy = kube.StatusWatcherStrategy instAction.WaitStrategy = kube.StatusWatcherStrategy
vals := map[string]interface{}{} vals := map[string]interface{}{}
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(t.Context())
time.AfterFunc(time.Second, cancel) time.AfterFunc(time.Second, cancel)
goroutines := runtime.NumGoroutine() goroutines := runtime.NumGoroutine()
@ -640,7 +643,7 @@ func TestInstallRelease_Atomic_Interrupted(t *testing.T) {
instAction.Atomic = true instAction.Atomic = true
vals := map[string]interface{}{} vals := map[string]interface{}{}
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(t.Context())
time.AfterFunc(time.Second, cancel) time.AfterFunc(time.Second, cancel)
goroutines := runtime.NumGoroutine() goroutines := runtime.NumGoroutine()

@ -64,13 +64,14 @@ func TestList_Empty(t *testing.T) {
} }
func newListFixture(t *testing.T) *List { func newListFixture(t *testing.T) *List {
t.Helper()
return NewList(actionConfigFixture(t)) return NewList(actionConfigFixture(t))
} }
func TestList_OneNamespace(t *testing.T) { func TestList_OneNamespace(t *testing.T) {
is := assert.New(t) is := assert.New(t)
lister := newListFixture(t) lister := newListFixture(t)
makeMeSomeReleases(lister.cfg.Releases, t) makeMeSomeReleases(t, lister.cfg.Releases)
list, err := lister.Run() list, err := lister.Run()
is.NoError(err) is.NoError(err)
is.Len(list, 3) is.Len(list, 3)
@ -79,7 +80,7 @@ func TestList_OneNamespace(t *testing.T) {
func TestList_AllNamespaces(t *testing.T) { func TestList_AllNamespaces(t *testing.T) {
is := assert.New(t) is := assert.New(t)
lister := newListFixture(t) lister := newListFixture(t)
makeMeSomeReleases(lister.cfg.Releases, t) makeMeSomeReleases(t, lister.cfg.Releases)
lister.AllNamespaces = true lister.AllNamespaces = true
lister.SetStateMask() lister.SetStateMask()
list, err := lister.Run() list, err := lister.Run()
@ -91,7 +92,7 @@ func TestList_Sort(t *testing.T) {
is := assert.New(t) is := assert.New(t)
lister := newListFixture(t) lister := newListFixture(t)
lister.Sort = ByNameDesc // Other sorts are tested elsewhere lister.Sort = ByNameDesc // Other sorts are tested elsewhere
makeMeSomeReleases(lister.cfg.Releases, t) makeMeSomeReleases(t, lister.cfg.Releases)
list, err := lister.Run() list, err := lister.Run()
is.NoError(err) is.NoError(err)
is.Len(list, 3) is.Len(list, 3)
@ -104,7 +105,7 @@ func TestList_Limit(t *testing.T) {
is := assert.New(t) is := assert.New(t)
lister := newListFixture(t) lister := newListFixture(t)
lister.Limit = 2 lister.Limit = 2
makeMeSomeReleases(lister.cfg.Releases, t) makeMeSomeReleases(t, lister.cfg.Releases)
list, err := lister.Run() list, err := lister.Run()
is.NoError(err) is.NoError(err)
is.Len(list, 2) is.Len(list, 2)
@ -117,7 +118,7 @@ func TestList_BigLimit(t *testing.T) {
is := assert.New(t) is := assert.New(t)
lister := newListFixture(t) lister := newListFixture(t)
lister.Limit = 20 lister.Limit = 20
makeMeSomeReleases(lister.cfg.Releases, t) makeMeSomeReleases(t, lister.cfg.Releases)
list, err := lister.Run() list, err := lister.Run()
is.NoError(err) is.NoError(err)
is.Len(list, 3) is.Len(list, 3)
@ -133,7 +134,7 @@ func TestList_LimitOffset(t *testing.T) {
lister := newListFixture(t) lister := newListFixture(t)
lister.Limit = 2 lister.Limit = 2
lister.Offset = 1 lister.Offset = 1
makeMeSomeReleases(lister.cfg.Releases, t) makeMeSomeReleases(t, lister.cfg.Releases)
list, err := lister.Run() list, err := lister.Run()
is.NoError(err) is.NoError(err)
is.Len(list, 2) is.Len(list, 2)
@ -148,7 +149,7 @@ func TestList_LimitOffsetOutOfBounds(t *testing.T) {
lister := newListFixture(t) lister := newListFixture(t)
lister.Limit = 2 lister.Limit = 2
lister.Offset = 3 // Last item is index 2 lister.Offset = 3 // Last item is index 2
makeMeSomeReleases(lister.cfg.Releases, t) makeMeSomeReleases(t, lister.cfg.Releases)
list, err := lister.Run() list, err := lister.Run()
is.NoError(err) is.NoError(err)
is.Len(list, 0) is.Len(list, 0)
@ -163,7 +164,7 @@ func TestList_LimitOffsetOutOfBounds(t *testing.T) {
func TestList_StateMask(t *testing.T) { func TestList_StateMask(t *testing.T) {
is := assert.New(t) is := assert.New(t)
lister := newListFixture(t) lister := newListFixture(t)
makeMeSomeReleases(lister.cfg.Releases, t) makeMeSomeReleases(t, lister.cfg.Releases)
one, err := lister.cfg.Releases.Get("one", 1) one, err := lister.cfg.Releases.Get("one", 1)
is.NoError(err) is.NoError(err)
one.SetStatus(release.StatusUninstalled, "uninstalled") one.SetStatus(release.StatusUninstalled, "uninstalled")
@ -193,7 +194,7 @@ func TestList_StateMaskWithStaleRevisions(t *testing.T) {
lister := newListFixture(t) lister := newListFixture(t)
lister.StateMask = ListFailed lister.StateMask = ListFailed
makeMeSomeReleasesWithStaleFailure(lister.cfg.Releases, t) makeMeSomeReleasesWithStaleFailure(t, lister.cfg.Releases)
res, err := lister.Run() res, err := lister.Run()
@ -205,7 +206,7 @@ func TestList_StateMaskWithStaleRevisions(t *testing.T) {
is.Equal("failed", res[0].Name) is.Equal("failed", res[0].Name)
} }
func makeMeSomeReleasesWithStaleFailure(store *storage.Storage, t *testing.T) { func makeMeSomeReleasesWithStaleFailure(t *testing.T, store *storage.Storage) {
t.Helper() t.Helper()
one := namedReleaseStub("clean", release.StatusDeployed) one := namedReleaseStub("clean", release.StatusDeployed)
one.Namespace = "default" one.Namespace = "default"
@ -242,7 +243,7 @@ func TestList_Filter(t *testing.T) {
is := assert.New(t) is := assert.New(t)
lister := newListFixture(t) lister := newListFixture(t)
lister.Filter = "th." lister.Filter = "th."
makeMeSomeReleases(lister.cfg.Releases, t) makeMeSomeReleases(t, lister.cfg.Releases)
res, err := lister.Run() res, err := lister.Run()
is.NoError(err) is.NoError(err)
@ -254,13 +255,13 @@ func TestList_FilterFailsCompile(t *testing.T) {
is := assert.New(t) is := assert.New(t)
lister := newListFixture(t) lister := newListFixture(t)
lister.Filter = "t[h.{{{" lister.Filter = "t[h.{{{"
makeMeSomeReleases(lister.cfg.Releases, t) makeMeSomeReleases(t, lister.cfg.Releases)
_, err := lister.Run() _, err := lister.Run()
is.Error(err) is.Error(err)
} }
func makeMeSomeReleases(store *storage.Storage, t *testing.T) { func makeMeSomeReleases(t *testing.T, store *storage.Storage) {
t.Helper() t.Helper()
one := releaseStub() one := releaseStub()
one.Name = "one" one.Name = "one"

@ -28,6 +28,7 @@ import (
) )
func uninstallAction(t *testing.T) *Uninstall { func uninstallAction(t *testing.T) *Uninstall {
t.Helper()
config := actionConfigFixture(t) config := actionConfigFixture(t)
unAction := NewUninstall(config) unAction := NewUninstall(config)
return unAction return unAction

@ -36,6 +36,7 @@ import (
) )
func upgradeAction(t *testing.T) *Upgrade { func upgradeAction(t *testing.T) *Upgrade {
t.Helper()
config := actionConfigFixture(t) config := actionConfigFixture(t)
upAction := NewUpgrade(config) upAction := NewUpgrade(config)
upAction.Namespace = "spaced" upAction.Namespace = "spaced"
@ -56,7 +57,7 @@ func TestUpgradeRelease_Success(t *testing.T) {
upAction.WaitStrategy = kube.StatusWatcherStrategy upAction.WaitStrategy = kube.StatusWatcherStrategy
vals := map[string]interface{}{} vals := map[string]interface{}{}
ctx, done := context.WithCancel(context.Background()) ctx, done := context.WithCancel(t.Context())
res, err := upAction.RunWithContext(ctx, rel.Name, buildChart(), vals) res, err := upAction.RunWithContext(ctx, rel.Name, buildChart(), vals)
done() done()
req.NoError(err) req.NoError(err)
@ -383,7 +384,6 @@ func TestUpgradeRelease_Pending(t *testing.T) {
} }
func TestUpgradeRelease_Interrupted_Wait(t *testing.T) { func TestUpgradeRelease_Interrupted_Wait(t *testing.T) {
is := assert.New(t) is := assert.New(t)
req := require.New(t) req := require.New(t)
@ -399,8 +399,7 @@ func TestUpgradeRelease_Interrupted_Wait(t *testing.T) {
upAction.WaitStrategy = kube.StatusWatcherStrategy upAction.WaitStrategy = kube.StatusWatcherStrategy
vals := map[string]interface{}{} vals := map[string]interface{}{}
ctx := context.Background() ctx, cancel := context.WithCancel(t.Context())
ctx, cancel := context.WithCancel(ctx)
time.AfterFunc(time.Second, cancel) time.AfterFunc(time.Second, cancel)
res, err := upAction.RunWithContext(ctx, rel.Name, buildChart(), vals) res, err := upAction.RunWithContext(ctx, rel.Name, buildChart(), vals)
@ -408,11 +407,9 @@ func TestUpgradeRelease_Interrupted_Wait(t *testing.T) {
req.Error(err) req.Error(err)
is.Contains(res.Info.Description, "Upgrade \"interrupted-release\" failed: context canceled") is.Contains(res.Info.Description, "Upgrade \"interrupted-release\" failed: context canceled")
is.Equal(res.Info.Status, release.StatusFailed) is.Equal(res.Info.Status, release.StatusFailed)
} }
func TestUpgradeRelease_Interrupted_Atomic(t *testing.T) { func TestUpgradeRelease_Interrupted_Atomic(t *testing.T) {
is := assert.New(t) is := assert.New(t)
req := require.New(t) req := require.New(t)
@ -428,8 +425,7 @@ func TestUpgradeRelease_Interrupted_Atomic(t *testing.T) {
upAction.Atomic = true upAction.Atomic = true
vals := map[string]interface{}{} vals := map[string]interface{}{}
ctx := context.Background() ctx, cancel := context.WithCancel(t.Context())
ctx, cancel := context.WithCancel(ctx)
time.AfterFunc(time.Second, cancel) time.AfterFunc(time.Second, cancel)
res, err := upAction.RunWithContext(ctx, rel.Name, buildChart(), vals) res, err := upAction.RunWithContext(ctx, rel.Name, buildChart(), vals)
@ -445,7 +441,7 @@ func TestUpgradeRelease_Interrupted_Atomic(t *testing.T) {
} }
func TestMergeCustomLabels(t *testing.T) { func TestMergeCustomLabels(t *testing.T) {
var tests = [][3]map[string]string{ tests := [][3]map[string]string{
{nil, nil, map[string]string{}}, {nil, nil, map[string]string{}},
{map[string]string{}, map[string]string{}, map[string]string{}}, {map[string]string{}, map[string]string{}, map[string]string{}},
{map[string]string{"k1": "v1", "k2": "v2"}, nil, map[string]string{"k1": "v1", "k2": "v2"}}, {map[string]string{"k1": "v1", "k2": "v2"}, nil, map[string]string{"k1": "v1", "k2": "v2"}},
@ -550,7 +546,7 @@ func TestUpgradeRelease_DryRun(t *testing.T) {
upAction.DryRun = true upAction.DryRun = true
vals := map[string]interface{}{} vals := map[string]interface{}{}
ctx, done := context.WithCancel(context.Background()) ctx, done := context.WithCancel(t.Context())
res, err := upAction.RunWithContext(ctx, rel.Name, buildChart(withSampleSecret()), vals) res, err := upAction.RunWithContext(ctx, rel.Name, buildChart(withSampleSecret()), vals)
done() done()
req.NoError(err) req.NoError(err)
@ -566,7 +562,7 @@ func TestUpgradeRelease_DryRun(t *testing.T) {
upAction.HideSecret = true upAction.HideSecret = true
vals = map[string]interface{}{} vals = map[string]interface{}{}
ctx, done = context.WithCancel(context.Background()) ctx, done = context.WithCancel(t.Context())
res, err = upAction.RunWithContext(ctx, rel.Name, buildChart(withSampleSecret()), vals) res, err = upAction.RunWithContext(ctx, rel.Name, buildChart(withSampleSecret()), vals)
done() done()
req.NoError(err) req.NoError(err)
@ -582,7 +578,7 @@ func TestUpgradeRelease_DryRun(t *testing.T) {
upAction.DryRun = false upAction.DryRun = false
vals = map[string]interface{}{} vals = map[string]interface{}{}
ctx, done = context.WithCancel(context.Background()) ctx, done = context.WithCancel(t.Context())
_, err = upAction.RunWithContext(ctx, rel.Name, buildChart(withSampleSecret()), vals) _, err = upAction.RunWithContext(ctx, rel.Name, buildChart(withSampleSecret()), vals)
done() done()
req.Error(err) req.Error(err)

@ -18,6 +18,7 @@ package action
import ( import (
"fmt" "fmt"
"maps"
apierrors "k8s.io/apimachinery/pkg/api/errors" apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/api/meta"
@ -194,11 +195,7 @@ func mergeAnnotations(obj runtime.Object, annotations map[string]string) error {
// merge two maps, always taking the value on the right // merge two maps, always taking the value on the right
func mergeStrStrMaps(current, desired map[string]string) map[string]string { func mergeStrStrMaps(current, desired map[string]string) map[string]string {
result := make(map[string]string) result := make(map[string]string)
for k, v := range current { maps.Copy(result, current)
result[k] = v maps.Copy(result, desired)
}
for k, desiredVal := range desired {
result[k] = desiredVal
}
return result return result
} }

@ -33,6 +33,7 @@ func TestLoadArchiveFiles(t *testing.T) {
name: "empty input should return no files", name: "empty input should return no files",
generate: func(_ *tar.Writer) {}, generate: func(_ *tar.Writer) {},
check: func(t *testing.T, _ []*BufferedFile, err error) { check: func(t *testing.T, _ []*BufferedFile, err error) {
t.Helper()
if err.Error() != "no files in chart archive" { if err.Error() != "no files in chart archive" {
t.Fatalf(`expected "no files in chart archive", got [%#v]`, err) t.Fatalf(`expected "no files in chart archive", got [%#v]`, err)
} }
@ -61,6 +62,7 @@ func TestLoadArchiveFiles(t *testing.T) {
} }
}, },
check: func(t *testing.T, files []*BufferedFile, err error) { check: func(t *testing.T, files []*BufferedFile, err error) {
t.Helper()
if err != nil { if err != nil {
t.Fatalf(`got unwanted error [%#v] for tar file with pax_global_header content`, err) t.Fatalf(`got unwanted error [%#v] for tar file with pax_global_header content`, err)
} }

@ -19,11 +19,11 @@ package loader
import ( import (
"bufio" "bufio"
"bytes" "bytes"
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"io" "io"
"log" "log"
"maps"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
@ -223,10 +223,7 @@ func LoadValues(data io.Reader) (map[string]interface{}, error) {
} }
return nil, fmt.Errorf("error reading yaml document: %w", err) return nil, fmt.Errorf("error reading yaml document: %w", err)
} }
if err := yaml.Unmarshal(raw, &currentMap, func(d *json.Decoder) *json.Decoder { if err := yaml.Unmarshal(raw, &currentMap); err != nil {
d.UseNumber()
return d
}); err != nil {
return nil, fmt.Errorf("cannot unmarshal yaml document: %w", err) return nil, fmt.Errorf("cannot unmarshal yaml document: %w", err)
} }
values = MergeMaps(values, currentMap) values = MergeMaps(values, currentMap)
@ -238,9 +235,7 @@ func LoadValues(data io.Reader) (map[string]interface{}, error) {
// If the value is a map, the maps will be merged recursively. // If the value is a map, the maps will be merged recursively.
func MergeMaps(a, b map[string]interface{}) map[string]interface{} { func MergeMaps(a, b map[string]interface{}) map[string]interface{} {
out := make(map[string]interface{}, len(a)) out := make(map[string]interface{}, len(a))
for k, v := range a { maps.Copy(out, a)
out[k] = v
}
for k, v := range b { for k, v := range b {
if v, ok := v.(map[string]interface{}); ok { if v, ok := v.(map[string]interface{}); ok {
if bv, ok := out[k]; ok { if bv, ok := out[k]; ok {

@ -648,6 +648,7 @@ func verifyChart(t *testing.T, c *chart.Chart) {
} }
func verifyDependencies(t *testing.T, c *chart.Chart) { func verifyDependencies(t *testing.T, c *chart.Chart) {
t.Helper()
if len(c.Metadata.Dependencies) != 2 { if len(c.Metadata.Dependencies) != 2 {
t.Errorf("Expected 2 dependencies, got %d", len(c.Metadata.Dependencies)) t.Errorf("Expected 2 dependencies, got %d", len(c.Metadata.Dependencies))
} }
@ -670,6 +671,7 @@ func verifyDependencies(t *testing.T, c *chart.Chart) {
} }
func verifyDependenciesLock(t *testing.T, c *chart.Chart) { func verifyDependenciesLock(t *testing.T, c *chart.Chart) {
t.Helper()
if len(c.Metadata.Dependencies) != 2 { if len(c.Metadata.Dependencies) != 2 {
t.Errorf("Expected 2 dependencies, got %d", len(c.Metadata.Dependencies)) t.Errorf("Expected 2 dependencies, got %d", len(c.Metadata.Dependencies))
} }
@ -692,10 +694,12 @@ func verifyDependenciesLock(t *testing.T, c *chart.Chart) {
} }
func verifyFrobnitz(t *testing.T, c *chart.Chart) { func verifyFrobnitz(t *testing.T, c *chart.Chart) {
t.Helper()
verifyChartFileAndTemplate(t, c, "frobnitz") verifyChartFileAndTemplate(t, c, "frobnitz")
} }
func verifyChartFileAndTemplate(t *testing.T, c *chart.Chart, name string) { func verifyChartFileAndTemplate(t *testing.T, c *chart.Chart, name string) {
t.Helper()
if c.Metadata == nil { if c.Metadata == nil {
t.Fatal("Metadata is nil") t.Fatal("Metadata is nil")
} }
@ -750,6 +754,7 @@ func verifyChartFileAndTemplate(t *testing.T, c *chart.Chart, name string) {
} }
func verifyBomStripped(t *testing.T, files []*chart.File) { func verifyBomStripped(t *testing.T, files []*chart.File) {
t.Helper()
for _, file := range files { for _, file := range files {
if bytes.HasPrefix(file.Data, utf8bom) { if bytes.HasPrefix(file.Data, utf8bom) {
t.Errorf("Byte Order Mark still present in processed file %s", file.Name) t.Errorf("Byte Order Mark still present in processed file %s", file.Name)

@ -17,6 +17,7 @@ package util
import ( import (
"fmt" "fmt"
"slices"
"strconv" "strconv"
"github.com/Masterminds/semver/v3" "github.com/Masterminds/semver/v3"
@ -102,12 +103,7 @@ type VersionSet []string
// //
// vs.Has("apps/v1") // vs.Has("apps/v1")
func (v VersionSet) Has(apiVersion string) bool { func (v VersionSet) Has(apiVersion string) bool {
for _, x := range v { return slices.Contains(v, apiVersion)
if x == apiVersion {
return true
}
}
return false
} }
func allKnownVersions() VersionSet { func allKnownVersions() VersionSet {

@ -39,7 +39,7 @@ func LoadChartfile(filename string) (*chart.Metadata, error) {
return y, err return y, err
} }
// StrictLoadChartFile loads a Chart.yaml into a *chart.Metadata using a strict unmarshaling // StrictLoadChartfile loads a Chart.yaml into a *chart.Metadata using a strict unmarshaling
func StrictLoadChartfile(filename string) (*chart.Metadata, error) { func StrictLoadChartfile(filename string) (*chart.Metadata, error) {
b, err := os.ReadFile(filename) b, err := os.ReadFile(filename)
if err != nil { if err != nil {

@ -34,7 +34,7 @@ func TestLoadChartfile(t *testing.T) {
} }
func verifyChartfile(t *testing.T, f *chart.Metadata, name string) { func verifyChartfile(t *testing.T, f *chart.Metadata, name string) {
t.Helper()
if f == nil { //nolint:staticcheck if f == nil { //nolint:staticcheck
t.Fatal("Failed verifyChartfile because f is nil") t.Fatal("Failed verifyChartfile because f is nil")
} }

@ -19,6 +19,7 @@ package util
import ( import (
"fmt" "fmt"
"log" "log"
"maps"
"github.com/mitchellh/copystructure" "github.com/mitchellh/copystructure"
@ -182,9 +183,7 @@ func coalesceGlobals(printf printFn, dest, src map[string]interface{}, prefix st
func copyMap(src map[string]interface{}) map[string]interface{} { func copyMap(src map[string]interface{}) map[string]interface{} {
m := make(map[string]interface{}, len(src)) m := make(map[string]interface{}, len(src))
for k, v := range src { maps.Copy(m, src)
m[k] = v
}
return m return m
} }

@ -19,6 +19,7 @@ package util
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"maps"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -144,9 +145,7 @@ func TestCoalesceValues(t *testing.T) {
// to CoalesceValues as argument, so that we can // to CoalesceValues as argument, so that we can
// use it for asserting later // use it for asserting later
valsCopy := make(Values, len(vals)) valsCopy := make(Values, len(vals))
for key, value := range vals { maps.Copy(valsCopy, vals)
valsCopy[key] = value
}
v, err := CoalesceValues(c, vals) v, err := CoalesceValues(c, vals)
if err != nil { if err != nil {
@ -304,9 +303,7 @@ func TestMergeValues(t *testing.T) {
// to MergeValues as argument, so that we can // to MergeValues as argument, so that we can
// use it for asserting later // use it for asserting later
valsCopy := make(Values, len(vals)) valsCopy := make(Values, len(vals))
for key, value := range vals { maps.Copy(valsCopy, vals)
valsCopy[key] = value
}
v, err := MergeValues(c, vals) v, err := MergeValues(c, vals)
if err != nil { if err != nil {

@ -15,7 +15,6 @@ limitations under the License.
package util package util
import ( import (
"encoding/json"
"os" "os"
"path/filepath" "path/filepath"
"sort" "sort"
@ -134,7 +133,7 @@ func TestDependencyEnabled(t *testing.T) {
} }
} }
// extractCharts recursively searches chart dependencies returning all charts found // extractChartNames recursively searches chart dependencies returning all charts found
func extractChartNames(c *chart.Chart) []string { func extractChartNames(c *chart.Chart) []string {
var out []string var out []string
var fn func(c *chart.Chart) var fn func(c *chart.Chart)
@ -238,20 +237,6 @@ func TestProcessDependencyImportValues(t *testing.T) {
if b := strconv.FormatBool(pv); b != vv { if b := strconv.FormatBool(pv); b != vv {
t.Errorf("failed to match imported bool value %v with expected %v for key %q", b, vv, kk) t.Errorf("failed to match imported bool value %v with expected %v for key %q", b, vv, kk)
} }
case json.Number:
if fv, err := pv.Float64(); err == nil {
if sfv := strconv.FormatFloat(fv, 'f', -1, 64); sfv != vv {
t.Errorf("failed to match imported float value %v with expected %v for key %q", sfv, vv, kk)
}
}
if iv, err := pv.Int64(); err == nil {
if siv := strconv.FormatInt(iv, 10); siv != vv {
t.Errorf("failed to match imported int value %v with expected %v for key %q", siv, vv, kk)
}
}
if pv.String() != vv {
t.Errorf("failed to match imported string value %q with expected %q for key %q", pv, vv, kk)
}
default: default:
if pv != vv { if pv != vv {
t.Errorf("failed to match imported string value %q with expected %q for key %q", pv, vv, kk) t.Errorf("failed to match imported string value %q with expected %q for key %q", pv, vv, kk)
@ -356,10 +341,6 @@ func TestProcessDependencyImportValuesMultiLevelPrecedence(t *testing.T) {
if s := strconv.FormatFloat(pv, 'f', -1, 64); s != vv { if s := strconv.FormatFloat(pv, 'f', -1, 64); s != vv {
t.Errorf("failed to match imported float value %v with expected %v", s, vv) t.Errorf("failed to match imported float value %v with expected %v", s, vv)
} }
case json.Number:
if pv.String() != vv {
t.Errorf("failed to match imported string value %q with expected %q", pv, vv)
}
default: default:
if pv != vv { if pv != vv {
t.Errorf("failed to match imported string value %q with expected %q", pv, vv) t.Errorf("failed to match imported string value %q with expected %q", pv, vv)
@ -558,6 +539,7 @@ func TestDependentChartsWithSomeSubchartsSpecifiedInDependency(t *testing.T) {
} }
func validateDependencyTree(t *testing.T, c *chart.Chart) { func validateDependencyTree(t *testing.T, c *chart.Chart) {
t.Helper()
for _, dependency := range c.Dependencies() { for _, dependency := range c.Dependencies() {
if dependency.Parent() != c { if dependency.Parent() != c {
if dependency.Parent() != c { if dependency.Parent() != c {

@ -55,8 +55,8 @@ func TestValidateAgainstInvalidSingleSchema(t *testing.T) {
errString = err.Error() errString = err.Error()
} }
expectedErrString := "unable to validate schema: runtime error: invalid " + expectedErrString := `"file:///values.schema.json#" is not valid against metaschema: jsonschema validation failed with 'https://json-schema.org/draft/2020-12/schema#'
"memory address or nil pointer dereference" - at '': got number, want boolean or object`
if errString != expectedErrString { if errString != expectedErrString {
t.Errorf("Error string :\n`%s`\ndoes not match expected\n`%s`", errString, expectedErrString) t.Errorf("Error string :\n`%s`\ndoes not match expected\n`%s`", errString, expectedErrString)
} }

@ -17,7 +17,6 @@ limitations under the License.
package util package util
import ( import (
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"io" "io"
@ -106,10 +105,7 @@ func tableLookup(v Values, simple string) (Values, error) {
// ReadValues will parse YAML byte data into a Values. // ReadValues will parse YAML byte data into a Values.
func ReadValues(data []byte) (vals Values, err error) { func ReadValues(data []byte) (vals Values, err error) {
err = yaml.Unmarshal(data, &vals, func(d *json.Decoder) *json.Decoder { err = yaml.Unmarshal(data, &vals)
d.UseNumber()
return d
})
if len(vals) == 0 { if len(vals) == 0 {
vals = Values{} vals = Values{}
} }

@ -224,6 +224,7 @@ chapter:
} }
func matchValues(t *testing.T, data map[string]interface{}) { func matchValues(t *testing.T, data map[string]interface{}) {
t.Helper()
if data["poet"] != "Coleridge" { if data["poet"] != "Coleridge" {
t.Errorf("Unexpected poet: %s", data["poet"]) t.Errorf("Unexpected poet: %s", data["poet"])
} }

@ -38,7 +38,6 @@ func TestSetNamespace(t *testing.T) {
if settings.namespace != "testns" { if settings.namespace != "testns" {
t.Errorf("Expected namespace testns, got %s", settings.namespace) t.Errorf("Expected namespace testns, got %s", settings.namespace)
} }
} }
func TestEnvSettings(t *testing.T) { func TestEnvSettings(t *testing.T) {
@ -126,7 +125,7 @@ func TestEnvSettings(t *testing.T) {
defer resetEnv()() defer resetEnv()()
for k, v := range tt.envvars { for k, v := range tt.envvars {
os.Setenv(k, v) t.Setenv(k, v)
} }
flags := pflag.NewFlagSet("testing", pflag.ContinueOnError) flags := pflag.NewFlagSet("testing", pflag.ContinueOnError)
@ -233,10 +232,7 @@ func TestEnvOrBool(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) {
if tt.env != "" { if tt.env != "" {
t.Cleanup(func() { t.Setenv(tt.env, tt.val)
os.Unsetenv(tt.env)
})
os.Setenv(tt.env, tt.val)
} }
actual := envBoolOr(tt.env, tt.def) actual := envBoolOr(tt.env, tt.def)
if actual != tt.expected { if actual != tt.expected {

@ -27,6 +27,7 @@ import (
// Check if file completion should be performed according to parameter 'shouldBePerformed' // Check if file completion should be performed according to parameter 'shouldBePerformed'
func checkFileCompletion(t *testing.T, cmdName string, shouldBePerformed bool) { func checkFileCompletion(t *testing.T, cmdName string, shouldBePerformed bool) {
t.Helper()
storage := storageFixture() storage := storageFixture()
storage.Create(&release.Release{ storage.Create(&release.Release{
Name: "myrelease", Name: "myrelease",
@ -64,6 +65,7 @@ func TestCompletionFileCompletion(t *testing.T) {
} }
func checkReleaseCompletion(t *testing.T, cmdName string, multiReleasesAllowed bool) { func checkReleaseCompletion(t *testing.T, cmdName string, multiReleasesAllowed bool) {
t.Helper()
multiReleaseTestGolden := "output/empty_nofile_comp.txt" multiReleaseTestGolden := "output/empty_nofile_comp.txt"
if multiReleasesAllowed { if multiReleasesAllowed {
multiReleaseTestGolden = "output/release_list_repeat_comp.txt" multiReleaseTestGolden = "output/release_list_repeat_comp.txt"

@ -33,7 +33,7 @@ func TestCreateCmd(t *testing.T) {
ensure.HelmHome(t) ensure.HelmHome(t)
cname := "testchart" cname := "testchart"
dir := t.TempDir() dir := t.TempDir()
defer testChdir(t, dir)() defer t.Chdir(dir)
// Run a create // Run a create
if _, _, err := executeActionCommand("create " + cname); err != nil { if _, _, err := executeActionCommand("create " + cname); err != nil {
@ -64,19 +64,19 @@ func TestCreateStarterCmd(t *testing.T) {
ensure.HelmHome(t) ensure.HelmHome(t)
cname := "testchart" cname := "testchart"
defer resetEnv()() defer resetEnv()()
os.MkdirAll(helmpath.CachePath(), 0755) os.MkdirAll(helmpath.CachePath(), 0o755)
defer testChdir(t, helmpath.CachePath())() defer t.Chdir(helmpath.CachePath())
// Create a starter. // Create a starter.
starterchart := helmpath.DataPath("starters") starterchart := helmpath.DataPath("starters")
os.MkdirAll(starterchart, 0755) os.MkdirAll(starterchart, 0o755)
if dest, err := chartutil.Create("starterchart", starterchart); err != nil { if dest, err := chartutil.Create("starterchart", starterchart); err != nil {
t.Fatalf("Could not create chart: %s", err) t.Fatalf("Could not create chart: %s", err)
} else { } else {
t.Logf("Created %s", dest) t.Logf("Created %s", dest)
} }
tplpath := filepath.Join(starterchart, "starterchart", "templates", "foo.tpl") tplpath := filepath.Join(starterchart, "starterchart", "templates", "foo.tpl")
if err := os.WriteFile(tplpath, []byte("test"), 0644); err != nil { if err := os.WriteFile(tplpath, []byte("test"), 0o644); err != nil {
t.Fatalf("Could not write template: %s", err) t.Fatalf("Could not write template: %s", err)
} }
@ -122,7 +122,6 @@ func TestCreateStarterCmd(t *testing.T) {
if !found { if !found {
t.Error("Did not find foo.tpl") t.Error("Did not find foo.tpl")
} }
} }
func TestCreateStarterAbsoluteCmd(t *testing.T) { func TestCreateStarterAbsoluteCmd(t *testing.T) {
@ -132,19 +131,19 @@ func TestCreateStarterAbsoluteCmd(t *testing.T) {
// Create a starter. // Create a starter.
starterchart := helmpath.DataPath("starters") starterchart := helmpath.DataPath("starters")
os.MkdirAll(starterchart, 0755) os.MkdirAll(starterchart, 0o755)
if dest, err := chartutil.Create("starterchart", starterchart); err != nil { if dest, err := chartutil.Create("starterchart", starterchart); err != nil {
t.Fatalf("Could not create chart: %s", err) t.Fatalf("Could not create chart: %s", err)
} else { } else {
t.Logf("Created %s", dest) t.Logf("Created %s", dest)
} }
tplpath := filepath.Join(starterchart, "starterchart", "templates", "foo.tpl") tplpath := filepath.Join(starterchart, "starterchart", "templates", "foo.tpl")
if err := os.WriteFile(tplpath, []byte("test"), 0644); err != nil { if err := os.WriteFile(tplpath, []byte("test"), 0o644); err != nil {
t.Fatalf("Could not write template: %s", err) t.Fatalf("Could not write template: %s", err)
} }
os.MkdirAll(helmpath.CachePath(), 0755) os.MkdirAll(helmpath.CachePath(), 0o755)
defer testChdir(t, helmpath.CachePath())() defer t.Chdir(helmpath.CachePath())
starterChartPath := filepath.Join(starterchart, "starterchart") starterChartPath := filepath.Join(starterchart, "starterchart")

@ -250,6 +250,7 @@ func TestDependencyUpdateCmd_WithRepoThatWasNotAdded(t *testing.T) {
} }
func setupMockRepoServer(t *testing.T) *repotest.Server { func setupMockRepoServer(t *testing.T) *repotest.Server {
t.Helper()
srv := repotest.NewTempServer( srv := repotest.NewTempServer(
t, t,
repotest.WithChartSourceGlob("testdata/testcharts/*.tgz"), repotest.WithChartSourceGlob("testdata/testcharts/*.tgz"),

@ -29,6 +29,7 @@ import (
) )
func outputFlagCompletionTest(t *testing.T, cmdName string) { func outputFlagCompletionTest(t *testing.T, cmdName string) {
t.Helper()
releasesMockWithStatus := func(info *release.Info, hooks ...*release.Hook) []*release.Release { releasesMockWithStatus := func(info *release.Info, hooks ...*release.Hook) []*release.Release {
info.LastDeployed = helmtime.Unix(1452902400, 0).UTC() info.LastDeployed = helmtime.Unix(1452902400, 0).UTC()
return []*release.Release{{ return []*release.Release{{

@ -149,15 +149,3 @@ func resetEnv() func() {
settings = cli.New() settings = cli.New()
} }
} }
func testChdir(t *testing.T, dir string) func() {
t.Helper()
old, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
if err := os.Chdir(dir); err != nil {
t.Fatal(err)
}
return func() { os.Chdir(old) }
}

@ -75,6 +75,7 @@ func TestHistoryOutputCompletion(t *testing.T) {
} }
func revisionFlagCompletionTest(t *testing.T, cmdName string) { func revisionFlagCompletionTest(t *testing.T, cmdName string) {
t.Helper()
mk := func(name string, vers int, status release.Status) *release.Release { mk := func(name string, vers int, status release.Status) *release.Release {
return release.Mock(&release.MockReleaseOptions{ return release.Mock(&release.MockReleaseOptions{
Name: name, Name: name,

@ -25,6 +25,7 @@ import (
"log/slog" "log/slog"
"os" "os"
"os/signal" "os/signal"
"slices"
"syscall" "syscall"
"time" "time"
@ -350,13 +351,7 @@ func compInstall(args []string, toComplete string, client *action.Install) ([]st
func validateDryRunOptionFlag(dryRunOptionFlagValue string) error { func validateDryRunOptionFlag(dryRunOptionFlagValue string) error {
// Validate dry-run flag value with a set of allowed value // Validate dry-run flag value with a set of allowed value
allowedDryRunValues := []string{"false", "true", "none", "client", "server"} allowedDryRunValues := []string{"false", "true", "none", "client", "server"}
isAllowed := false isAllowed := slices.Contains(allowedDryRunValues, dryRunOptionFlagValue)
for _, v := range allowedDryRunValues {
if dryRunOptionFlagValue == v {
isAllowed = true
break
}
}
if !isAllowed { if !isAllowed {
return errors.New("invalid dry-run flag. Flag must one of the following: false, true, none, client, server") return errors.New("invalid dry-run flag. Flag must one of the following: false, true, none, client, server")
} }

@ -20,6 +20,7 @@ import (
"fmt" "fmt"
"io" "io"
"os" "os"
"slices"
"strconv" "strconv"
"github.com/gosuri/uitable" "github.com/gosuri/uitable"
@ -203,13 +204,7 @@ func filterReleases(releases []*release.Release, ignoredReleaseNames []string) [
var filteredReleases []*release.Release var filteredReleases []*release.Release
for _, rel := range releases { for _, rel := range releases {
found := false found := slices.Contains(ignoredReleaseNames, rel.Name)
for _, ignoredName := range ignoredReleaseNames {
if rel.Name == ignoredName {
found = true
break
}
}
if !found { if !found {
filteredReleases = append(filteredReleases, rel) filteredReleases = append(filteredReleases, rel)
} }

@ -23,6 +23,7 @@ import (
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"slices"
"strconv" "strconv"
"strings" "strings"
"syscall" "syscall"
@ -163,11 +164,9 @@ func manuallyProcessArgs(args []string) ([]string, []string) {
} }
isKnown := func(v string) string { isKnown := func(v string) string {
for _, i := range kvargs { if slices.Contains(kvargs, v) {
if i == v {
return v return v
} }
}
return "" return ""
} }

@ -111,9 +111,9 @@ func TestPackage(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) {
cachePath := t.TempDir() cachePath := t.TempDir()
defer testChdir(t, cachePath)() defer t.Chdir(cachePath)
if err := os.MkdirAll("toot", 0777); err != nil { if err := os.MkdirAll("toot", 0o777); err != nil {
t.Fatal(err) t.Fatal(err)
} }

@ -19,6 +19,7 @@ import (
"fmt" "fmt"
"io" "io"
"log/slog" "log/slog"
"slices"
"github.com/gosuri/uitable" "github.com/gosuri/uitable"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@ -60,13 +61,7 @@ func filterPlugins(plugins []*plugin.Plugin, ignoredPluginNames []string) []*plu
var filteredPlugins []*plugin.Plugin var filteredPlugins []*plugin.Plugin
for _, plugin := range plugins { for _, plugin := range plugins {
found := false found := slices.Contains(ignoredPluginNames, plugin.Metadata.Name)
for _, ignoredName := range ignoredPluginNames {
if plugin.Metadata.Name == ignoredName {
found = true
break
}
}
if !found { if !found {
filteredPlugins = append(filteredPlugins, plugin) filteredPlugins = append(filteredPlugins, plugin)
} }

@ -79,7 +79,6 @@ func TestManuallyProcessArgs(t *testing.T) {
t.Errorf("expected unknown flag %d to be %q, got %q", i, expectUnknown[i], k) t.Errorf("expected unknown flag %d to be %q, got %q", i, expectUnknown[i], k)
} }
} }
} }
func TestLoadPlugins(t *testing.T) { func TestLoadPlugins(t *testing.T) {
@ -276,6 +275,7 @@ func TestLoadPluginsForCompletion(t *testing.T) {
} }
func checkCommand(t *testing.T, plugins []*cobra.Command, tests []staticCompletionDetails) { func checkCommand(t *testing.T, plugins []*cobra.Command, tests []staticCompletionDetails) {
t.Helper()
if len(plugins) != len(tests) { if len(plugins) != len(tests) {
t.Fatalf("Expected commands %v, got %v", tests, plugins) t.Fatalf("Expected commands %v, got %v", tests, plugins)
} }
@ -326,7 +326,6 @@ func checkCommand(t *testing.T, plugins []*cobra.Command, tests []staticCompleti
} }
func TestPluginDynamicCompletion(t *testing.T) { func TestPluginDynamicCompletion(t *testing.T) {
tests := []cmdTestCase{{ tests := []cmdTestCase{{
name: "completion for plugin", name: "completion for plugin",
cmd: "__complete args ''", cmd: "__complete args ''",
@ -363,7 +362,7 @@ func TestLoadPlugins_HelmNoPlugins(t *testing.T) {
settings.PluginsDirectory = "testdata/helmhome/helm/plugins" settings.PluginsDirectory = "testdata/helmhome/helm/plugins"
settings.RepositoryConfig = "testdata/helmhome/helm/repository" settings.RepositoryConfig = "testdata/helmhome/helm/repository"
os.Setenv("HELM_NO_PLUGINS", "1") t.Setenv("HELM_NO_PLUGINS", "1")
out := bytes.NewBuffer(nil) out := bytes.NewBuffer(nil)
cmd := &cobra.Command{} cmd := &cobra.Command{}
@ -376,7 +375,6 @@ func TestLoadPlugins_HelmNoPlugins(t *testing.T) {
} }
func TestPluginCmdsCompletion(t *testing.T) { func TestPluginCmdsCompletion(t *testing.T) {
tests := []cmdTestCase{{ tests := []cmdTestCase{{
name: "completion for plugin update", name: "completion for plugin update",
cmd: "__complete plugin update ''", cmd: "__complete plugin update ''",

@ -34,6 +34,10 @@ import (
const registryLoginDesc = ` const registryLoginDesc = `
Authenticate to a remote registry. Authenticate to a remote registry.
For example for Github Container Registry:
echo "$GITHUB_TOKEN" | helm registry login ghcr.io -u $GITHUB_USER --password-stdin
` `
type registryLoginOptions struct { type registryLoginOptions struct {

@ -17,6 +17,7 @@ limitations under the License.
package cmd package cmd
import ( import (
"errors"
"fmt" "fmt"
"io" "io"
"regexp" "regexp"
@ -85,7 +86,7 @@ func newReleaseTestCmd(cfg *action.Configuration, out io.Writer) *cobra.Command
// Print a newline to stdout to separate the output // Print a newline to stdout to separate the output
fmt.Fprintln(out) fmt.Fprintln(out)
if err := client.GetPodLogs(out, rel); err != nil { if err := client.GetPodLogs(out, rel); err != nil {
return err return errors.Join(runErr, err)
} }
} }

@ -52,6 +52,7 @@ type repoAddOptions struct {
passCredentialsAll bool passCredentialsAll bool
forceUpdate bool forceUpdate bool
allowDeprecatedRepos bool allowDeprecatedRepos bool
timeout time.Duration
certFile string certFile string
keyFile string keyFile string
@ -96,6 +97,7 @@ func newRepoAddCmd(out io.Writer) *cobra.Command {
f.BoolVar(&o.insecureSkipTLSverify, "insecure-skip-tls-verify", false, "skip tls certificate checks for the repository") f.BoolVar(&o.insecureSkipTLSverify, "insecure-skip-tls-verify", false, "skip tls certificate checks for the repository")
f.BoolVar(&o.allowDeprecatedRepos, "allow-deprecated-repos", false, "by default, this command will not allow adding official repos that have been permanently deleted. This disables that behavior") f.BoolVar(&o.allowDeprecatedRepos, "allow-deprecated-repos", false, "by default, this command will not allow adding official repos that have been permanently deleted. This disables that behavior")
f.BoolVar(&o.passCredentialsAll, "pass-credentials", false, "pass credentials to all domains") f.BoolVar(&o.passCredentialsAll, "pass-credentials", false, "pass credentials to all domains")
f.DurationVar(&o.timeout, "timeout", getter.DefaultHTTPTimeout*time.Second, "time to wait for the index file download to complete")
return cmd return cmd
} }
@ -199,7 +201,7 @@ func (o *repoAddOptions) run(out io.Writer) error {
return nil return nil
} }
r, err := repo.NewChartRepository(&c, getter.All(settings)) r, err := repo.NewChartRepository(&c, getter.All(settings, getter.WithTimeout(o.timeout)))
if err != nil { if err != nil {
return err return err
} }

@ -50,7 +50,7 @@ func TestRepoAddCmd(t *testing.T) {
defer srv2.Stop() defer srv2.Stop()
tmpdir := filepath.Join(t.TempDir(), "path-component.yaml/data") tmpdir := filepath.Join(t.TempDir(), "path-component.yaml/data")
if err := os.MkdirAll(tmpdir, 0777); err != nil { if err := os.MkdirAll(tmpdir, 0o777); err != nil {
t.Fatal(err) t.Fatal(err)
} }
repoFile := filepath.Join(tmpdir, "repositories.yaml") repoFile := filepath.Join(tmpdir, "repositories.yaml")
@ -99,7 +99,7 @@ func TestRepoAdd(t *testing.T) {
forceUpdate: false, forceUpdate: false,
repoFile: repoFile, repoFile: repoFile,
} }
os.Setenv(xdg.CacheHomeEnvVar, rootDir) t.Setenv(xdg.CacheHomeEnvVar, rootDir)
if err := o.run(io.Discard); err != nil { if err := o.run(io.Discard); err != nil {
t.Error(err) t.Error(err)
@ -153,7 +153,7 @@ func TestRepoAddCheckLegalName(t *testing.T) {
forceUpdate: false, forceUpdate: false,
repoFile: repoFile, repoFile: repoFile,
} }
os.Setenv(xdg.CacheHomeEnvVar, rootDir) t.Setenv(xdg.CacheHomeEnvVar, rootDir)
wantErrorMsg := fmt.Sprintf("repository name (%s) contains '/', please specify a different name without '/'", testRepoName) wantErrorMsg := fmt.Sprintf("repository name (%s) contains '/', please specify a different name without '/'", testRepoName)
@ -191,6 +191,7 @@ func TestRepoAddConcurrentHiddenFile(t *testing.T) {
} }
func repoAddConcurrent(t *testing.T, testName, repoFile string) { func repoAddConcurrent(t *testing.T, testName, repoFile string) {
t.Helper()
ts := repotest.NewTempServer( ts := repotest.NewTempServer(
t, t,
repotest.WithChartSourceGlob("testdata/testserver/*.*"), repotest.WithChartSourceGlob("testdata/testserver/*.*"),

@ -17,7 +17,6 @@ limitations under the License.
package cmd package cmd
import ( import (
"errors"
"fmt" "fmt"
"io" "io"
@ -37,10 +36,14 @@ func newRepoListCmd(out io.Writer) *cobra.Command {
Short: "list chart repositories", Short: "list chart repositories",
Args: require.NoArgs, Args: require.NoArgs,
ValidArgsFunction: noMoreArgsCompFunc, ValidArgsFunction: noMoreArgsCompFunc,
RunE: func(_ *cobra.Command, _ []string) error { RunE: func(cmd *cobra.Command, _ []string) error {
// The error is silently ignored. If no repository file exists, it cannot be loaded,
// or the file isn't the right format to be parsed the error is ignored. The
// repositories will be 0.
f, _ := repo.LoadFile(settings.RepositoryConfig) f, _ := repo.LoadFile(settings.RepositoryConfig)
if len(f.Repositories) == 0 && outfmt != output.JSON && outfmt != output.YAML { if len(f.Repositories) == 0 && outfmt != output.JSON && outfmt != output.YAML {
return errors.New("no repositories to show") fmt.Fprintln(cmd.ErrOrStderr(), "no repositories to show")
return nil
} }
return outfmt.Write(out, &repoListWriter{f.Repositories}) return outfmt.Write(out, &repoListWriter{f.Repositories})

@ -17,6 +17,8 @@ limitations under the License.
package cmd package cmd
import ( import (
"fmt"
"path/filepath"
"testing" "testing"
) )
@ -27,3 +29,26 @@ func TestRepoListOutputCompletion(t *testing.T) {
func TestRepoListFileCompletion(t *testing.T) { func TestRepoListFileCompletion(t *testing.T) {
checkFileCompletion(t, "repo list", false) checkFileCompletion(t, "repo list", false)
} }
func TestRepoList(t *testing.T) {
rootDir := t.TempDir()
repoFile := filepath.Join(rootDir, "repositories.yaml")
repoFile2 := "testdata/repositories.yaml"
tests := []cmdTestCase{
{
name: "list with no repos",
cmd: fmt.Sprintf("repo list --repository-config %s --repository-cache %s", repoFile, rootDir),
golden: "output/repo-list-empty.txt",
wantError: false,
},
{
name: "list with repos",
cmd: fmt.Sprintf("repo list --repository-config %s --repository-cache %s", repoFile2, rootDir),
golden: "output/repo-list.txt",
wantError: false,
},
}
runTestCmd(t, tests)
}

@ -153,6 +153,7 @@ func createCacheFiles(rootDir string, repoName string) (cacheIndexFile string, c
} }
func testCacheFiles(t *testing.T, cacheIndexFile string, cacheChartsFile string, repoName string) { func testCacheFiles(t *testing.T, cacheIndexFile string, cacheChartsFile string, repoName string) {
t.Helper()
if _, err := os.Stat(cacheIndexFile); err == nil { if _, err := os.Stat(cacheIndexFile); err == nil {
t.Errorf("Error cache index file was not removed for repository %s", repoName) t.Errorf("Error cache index file was not removed for repository %s", repoName)
} }

@ -22,6 +22,7 @@ import (
"io" "io"
"slices" "slices"
"sync" "sync"
"time"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@ -46,6 +47,7 @@ type repoUpdateOptions struct {
repoFile string repoFile string
repoCache string repoCache string
names []string names []string
timeout time.Duration
} }
func newRepoUpdateCmd(out io.Writer) *cobra.Command { func newRepoUpdateCmd(out io.Writer) *cobra.Command {
@ -68,6 +70,9 @@ func newRepoUpdateCmd(out io.Writer) *cobra.Command {
}, },
} }
f := cmd.Flags()
f.DurationVar(&o.timeout, "timeout", getter.DefaultHTTPTimeout*time.Second, "time to wait for the index file download to complete")
return cmd return cmd
} }
@ -94,7 +99,7 @@ func (o *repoUpdateOptions) run(out io.Writer) error {
for _, cfg := range f.Repositories { for _, cfg := range f.Repositories {
if updateAllRepos || isRepoRequested(cfg.Name, o.names) { if updateAllRepos || isRepoRequested(cfg.Name, o.names) {
r, err := repo.NewChartRepository(cfg, getter.All(settings)) r, err := repo.NewChartRepository(cfg, getter.All(settings, getter.WithTimeout(o.timeout)))
if err != nil { if err != nil {
return err return err
} }
@ -113,14 +118,19 @@ func updateCharts(repos []*repo.ChartRepository, out io.Writer) error {
var wg sync.WaitGroup var wg sync.WaitGroup
failRepoURLChan := make(chan string, len(repos)) failRepoURLChan := make(chan string, len(repos))
writeMutex := sync.Mutex{}
for _, re := range repos { for _, re := range repos {
wg.Add(1) wg.Add(1)
go func(re *repo.ChartRepository) { go func(re *repo.ChartRepository) {
defer wg.Done() defer wg.Done()
if _, err := re.DownloadIndexFile(); err != nil { if _, err := re.DownloadIndexFile(); err != nil {
writeMutex.Lock()
defer writeMutex.Unlock()
fmt.Fprintf(out, "...Unable to get an update from the %q chart repository (%s):\n\t%s\n", re.Config.Name, re.Config.URL, err) fmt.Fprintf(out, "...Unable to get an update from the %q chart repository (%s):\n\t%s\n", re.Config.Name, re.Config.URL, err)
failRepoURLChan <- re.Config.URL failRepoURLChan <- re.Config.URL
} else { } else {
writeMutex.Lock()
defer writeMutex.Unlock()
fmt.Fprintf(out, "...Successfully got an update from the %q chart repository\n", re.Config.Name) fmt.Fprintf(out, "...Successfully got an update from the %q chart repository\n", re.Config.Name)
} }
}(re) }(re)

@ -63,6 +63,7 @@ type testCase struct {
} }
func runTestCases(t *testing.T, testCases []testCase) { func runTestCases(t *testing.T, testCases []testCase) {
t.Helper()
for i, tc := range testCases { for i, tc := range testCases {
t.Run(fmt.Sprint(i), func(t *testing.T) { t.Run(fmt.Sprint(i), func(t *testing.T) {
cmd := &cobra.Command{ cmd := &cobra.Command{

@ -80,7 +80,7 @@ func TestRootCmd(t *testing.T) {
ensure.HelmHome(t) ensure.HelmHome(t)
for k, v := range tt.envvars { for k, v := range tt.envvars {
os.Setenv(k, v) t.Setenv(k, v)
} }
if _, _, err := executeActionCommand(tt.args); err != nil { if _, _, err := executeActionCommand(tt.args); err != nil {

@ -22,18 +22,6 @@ import (
"testing" "testing"
) )
func TestTemplateCmdWithToml(t *testing.T) {
tests := []cmdTestCase{
{
name: "check toToml function rendering",
cmd: fmt.Sprintf("template '%s'", "testdata/testcharts/issue-totoml"),
golden: "output/issue-totoml.txt",
},
}
runTestCmd(t, tests)
}
var chartPath = "testdata/testcharts/subchart" var chartPath = "testdata/testcharts/subchart"
func TestTemplateCmd(t *testing.T) { func TestTemplateCmd(t *testing.T) {

@ -1,8 +0,0 @@
---
# Source: issue-totoml/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: issue-totoml
data: |
key = 13

@ -0,0 +1 @@
no repositories to show

@ -0,0 +1,4 @@
NAME URL
charts https://charts.helm.sh/stable
firstexample http://firstexample.com
secondexample http://secondexample.com

@ -1,3 +0,0 @@
apiVersion: v2
name: issue-totoml
version: 0.1.0

@ -1,6 +0,0 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: issue-totoml
data: |
{{ .Values.global | toToml }}

@ -193,7 +193,7 @@ func TestUpgradeCmd(t *testing.T) {
func TestUpgradeWithValue(t *testing.T) { func TestUpgradeWithValue(t *testing.T) {
releaseName := "funny-bunny-v2" releaseName := "funny-bunny-v2"
relMock, ch, chartPath := prepareMockRelease(releaseName, t) relMock, ch, chartPath := prepareMockRelease(t, releaseName)
defer resetEnv()() defer resetEnv()()
@ -220,7 +220,7 @@ func TestUpgradeWithValue(t *testing.T) {
func TestUpgradeWithStringValue(t *testing.T) { func TestUpgradeWithStringValue(t *testing.T) {
releaseName := "funny-bunny-v3" releaseName := "funny-bunny-v3"
relMock, ch, chartPath := prepareMockRelease(releaseName, t) relMock, ch, chartPath := prepareMockRelease(t, releaseName)
defer resetEnv()() defer resetEnv()()
@ -248,7 +248,7 @@ func TestUpgradeWithStringValue(t *testing.T) {
func TestUpgradeInstallWithSubchartNotes(t *testing.T) { func TestUpgradeInstallWithSubchartNotes(t *testing.T) {
releaseName := "wacky-bunny-v1" releaseName := "wacky-bunny-v1"
relMock, ch, _ := prepareMockRelease(releaseName, t) relMock, ch, _ := prepareMockRelease(t, releaseName)
defer resetEnv()() defer resetEnv()()
@ -280,7 +280,7 @@ func TestUpgradeInstallWithSubchartNotes(t *testing.T) {
func TestUpgradeWithValuesFile(t *testing.T) { func TestUpgradeWithValuesFile(t *testing.T) {
releaseName := "funny-bunny-v4" releaseName := "funny-bunny-v4"
relMock, ch, chartPath := prepareMockRelease(releaseName, t) relMock, ch, chartPath := prepareMockRelease(t, releaseName)
defer resetEnv()() defer resetEnv()()
@ -308,7 +308,7 @@ func TestUpgradeWithValuesFile(t *testing.T) {
func TestUpgradeWithValuesFromStdin(t *testing.T) { func TestUpgradeWithValuesFromStdin(t *testing.T) {
releaseName := "funny-bunny-v5" releaseName := "funny-bunny-v5"
relMock, ch, chartPath := prepareMockRelease(releaseName, t) relMock, ch, chartPath := prepareMockRelease(t, releaseName)
defer resetEnv()() defer resetEnv()()
@ -340,7 +340,7 @@ 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(releaseName, t) _, _, chartPath := prepareMockRelease(t, releaseName)
defer resetEnv()() defer resetEnv()()
@ -368,7 +368,8 @@ func TestUpgradeInstallWithValuesFromStdin(t *testing.T) {
} }
func prepareMockRelease(releaseName string, t *testing.T) (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) {
t.Helper()
tmpChart := t.TempDir() tmpChart := t.TempDir()
configmapData, err := os.ReadFile("testdata/testcharts/upgradetest/templates/configmap.yaml") configmapData, err := os.ReadFile("testdata/testcharts/upgradetest/templates/configmap.yaml")
if err != nil { if err != nil {
@ -445,7 +446,7 @@ func TestUpgradeFileCompletion(t *testing.T) {
func TestUpgradeInstallWithLabels(t *testing.T) { func TestUpgradeInstallWithLabels(t *testing.T) {
releaseName := "funny-bunny-labels" releaseName := "funny-bunny-labels"
_, _, chartPath := prepareMockRelease(releaseName, t) _, _, chartPath := prepareMockRelease(t, releaseName)
defer resetEnv()() defer resetEnv()()
@ -471,7 +472,8 @@ func TestUpgradeInstallWithLabels(t *testing.T) {
} }
} }
func prepareMockReleaseWithSecret(releaseName string, t *testing.T) (func(n string, v int, ch *chart.Chart) *release.Release, *chart.Chart, string) { func prepareMockReleaseWithSecret(t *testing.T, releaseName string) (func(n string, v int, ch *chart.Chart) *release.Release, *chart.Chart, string) {
t.Helper()
tmpChart := t.TempDir() tmpChart := t.TempDir()
configmapData, err := os.ReadFile("testdata/testcharts/chart-with-secret/templates/configmap.yaml") configmapData, err := os.ReadFile("testdata/testcharts/chart-with-secret/templates/configmap.yaml")
if err != nil { if err != nil {
@ -512,7 +514,7 @@ func prepareMockReleaseWithSecret(releaseName string, t *testing.T) (func(n stri
func TestUpgradeWithDryRun(t *testing.T) { func TestUpgradeWithDryRun(t *testing.T) {
releaseName := "funny-bunny-labels" releaseName := "funny-bunny-labels"
_, _, chartPath := prepareMockReleaseWithSecret(releaseName, t) _, _, chartPath := prepareMockReleaseWithSecret(t, releaseName)
defer resetEnv()() defer resetEnv()()

@ -46,6 +46,7 @@ func TestResolveChartRef(t *testing.T) {
{name: "reference, querystring repo", ref: "testing-querystring/alpine", expect: "http://example.com/alpine-1.2.3.tgz?key=value"}, {name: "reference, querystring repo", ref: "testing-querystring/alpine", expect: "http://example.com/alpine-1.2.3.tgz?key=value"},
{name: "reference, testing-relative repo", ref: "testing-relative/foo", expect: "http://example.com/helm/charts/foo-1.2.3.tgz"}, {name: "reference, testing-relative repo", ref: "testing-relative/foo", expect: "http://example.com/helm/charts/foo-1.2.3.tgz"},
{name: "reference, testing-relative repo", ref: "testing-relative/bar", expect: "http://example.com/helm/bar-1.2.3.tgz"}, {name: "reference, testing-relative repo", ref: "testing-relative/bar", expect: "http://example.com/helm/bar-1.2.3.tgz"},
{name: "reference, testing-relative repo", ref: "testing-relative/baz", expect: "http://example.com/path/to/baz-1.2.3.tgz"},
{name: "reference, testing-relative-trailing-slash repo", ref: "testing-relative-trailing-slash/foo", expect: "http://example.com/helm/charts/foo-1.2.3.tgz"}, {name: "reference, testing-relative-trailing-slash repo", ref: "testing-relative-trailing-slash/foo", expect: "http://example.com/helm/charts/foo-1.2.3.tgz"},
{name: "reference, testing-relative-trailing-slash repo", ref: "testing-relative-trailing-slash/bar", expect: "http://example.com/helm/bar-1.2.3.tgz"}, {name: "reference, testing-relative-trailing-slash repo", ref: "testing-relative-trailing-slash/bar", expect: "http://example.com/helm/bar-1.2.3.tgz"},
{name: "encoded URL", ref: "encoded-url/foobar", expect: "http://example.com/with%2Fslash/charts/foobar-4.2.1.tgz"}, {name: "encoded URL", ref: "encoded-url/foobar", expect: "http://example.com/with%2Fslash/charts/foobar-4.2.1.tgz"},

@ -25,7 +25,6 @@ import (
"log" "log"
"net/url" "net/url"
"os" "os"
"path"
"path/filepath" "path/filepath"
"regexp" "regexp"
"strings" "strings"
@ -728,7 +727,6 @@ func (m *Manager) findChartURL(name, version, repoURL string, repos map[string]*
} }
for _, cr := range repos { for _, cr := range repos {
if urlutil.Equal(repoURL, cr.Config.URL) { if urlutil.Equal(repoURL, cr.Config.URL) {
var entry repo.ChartVersions var entry repo.ChartVersions
entry, err = findEntryByName(name, cr) entry, err = findEntryByName(name, cr)
@ -745,7 +743,7 @@ func (m *Manager) findChartURL(name, version, repoURL string, repos map[string]*
//nolint:nakedret //nolint:nakedret
return return
} }
url, err = normalizeURL(repoURL, ve.URLs[0]) url, err = repo.ResolveReferenceURL(repoURL, ve.URLs[0])
if err != nil { if err != nil {
//nolint:nakedret //nolint:nakedret
return return
@ -811,24 +809,6 @@ func versionEquals(v1, v2 string) bool {
return sv1.Equal(sv2) return sv1.Equal(sv2)
} }
func normalizeURL(baseURL, urlOrPath string) (string, error) {
u, err := url.Parse(urlOrPath)
if err != nil {
return urlOrPath, err
}
if u.IsAbs() {
return u.String(), nil
}
u2, err := url.Parse(baseURL)
if err != nil {
return urlOrPath, fmt.Errorf("base URL failed to parse: %w", err)
}
u2.RawPath = path.Join(u2.RawPath, urlOrPath)
u2.Path = path.Join(u2.Path, urlOrPath)
return u2.String(), nil
}
// loadChartRepositories reads the repositories.yaml, and then builds a map of // loadChartRepositories reads the repositories.yaml, and then builds a map of
// ChartRepositories. // ChartRepositories.
// //

@ -53,26 +53,6 @@ func TestVersionEquals(t *testing.T) {
} }
} }
func TestNormalizeURL(t *testing.T) {
tests := []struct {
name, base, path, expect string
}{
{name: "basic URL", base: "https://example.com", path: "http://helm.sh/foo", expect: "http://helm.sh/foo"},
{name: "relative path", base: "https://helm.sh/charts", path: "foo", expect: "https://helm.sh/charts/foo"},
{name: "Encoded path", base: "https://helm.sh/a%2Fb/charts", path: "foo", expect: "https://helm.sh/a%2Fb/charts/foo"},
}
for _, tt := range tests {
got, err := normalizeURL(tt.base, tt.path)
if err != nil {
t.Errorf("%s: error %s", tt.name, err)
continue
} else if got != tt.expect {
t.Errorf("%s: expected %q, got %q", tt.name, tt.expect, got)
}
}
}
func TestFindChartURL(t *testing.T) { func TestFindChartURL(t *testing.T) {
var b bytes.Buffer var b bytes.Buffer
m := &Manager{ m := &Manager{
@ -134,6 +114,31 @@ func TestFindChartURL(t *testing.T) {
if passcredentialsall != false { if passcredentialsall != false {
t.Errorf("Unexpected passcredentialsall %t", passcredentialsall) t.Errorf("Unexpected passcredentialsall %t", passcredentialsall)
} }
name = "foo"
version = "1.2.3"
repoURL = "http://example.com/helm"
churl, username, password, insecureSkipTLSVerify, passcredentialsall, _, _, _, err = m.findChartURL(name, version, repoURL, repos)
if err != nil {
t.Fatal(err)
}
if churl != "http://example.com/helm/charts/foo-1.2.3.tgz" {
t.Errorf("Unexpected URL %q", churl)
}
if username != "" {
t.Errorf("Unexpected username %q", username)
}
if password != "" {
t.Errorf("Unexpected password %q", password)
}
if passcredentialsall != false {
t.Errorf("Unexpected passcredentialsall %t", passcredentialsall)
}
if insecureSkipTLSVerify {
t.Errorf("Unexpected insecureSkipTLSVerify %t", insecureSkipTLSVerify)
}
} }
func TestGetRepoNames(t *testing.T) { func TestGetRepoNames(t *testing.T) {
@ -437,6 +442,7 @@ func TestUpdateWithNoRepo(t *testing.T) {
// Parent chart includes local-subchart 0.1.0 subchart from a fake repository, by default. // Parent chart includes local-subchart 0.1.0 subchart from a fake repository, by default.
// If each of these main fields (name, version, repository) is not supplied by dep param, default value will be used. // If each of these main fields (name, version, repository) is not supplied by dep param, default value will be used.
func checkBuildWithOptionalFields(t *testing.T, chartName string, dep chart.Dependency) { func checkBuildWithOptionalFields(t *testing.T, chartName string, dep chart.Dependency) {
t.Helper()
// Set up a fake repo // Set up a fake repo
srv := repotest.NewTempServer( srv := repotest.NewTempServer(
t, t,

@ -26,3 +26,16 @@ entries:
version: 1.2.3 version: 1.2.3
checksum: 0e6661f193211d7a5206918d42f5c2a9470b737d checksum: 0e6661f193211d7a5206918d42f5c2a9470b737d
apiVersion: v2 apiVersion: v2
baz:
- name: baz
description: Baz Chart With Absolute Path
home: https://helm.sh/helm
keywords: []
maintainers: []
sources:
- https://github.com/helm/charts
urls:
- /path/to/baz-1.2.3.tgz
version: 1.2.3
checksum: 0e6661f193211d7a5206918d42f5c2a9470b737d
apiVersion: v2

@ -20,6 +20,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"log/slog" "log/slog"
"maps"
"path" "path"
"path/filepath" "path/filepath"
"regexp" "regexp"
@ -33,6 +34,18 @@ import (
chartutil "helm.sh/helm/v4/pkg/chart/v2/util" chartutil "helm.sh/helm/v4/pkg/chart/v2/util"
) )
// taken from https://cs.opensource.google/go/go/+/refs/tags/go1.23.6:src/text/template/exec.go;l=141
// > "template: %s: executing %q at <%s>: %s"
var execErrFmt = regexp.MustCompile(`^template: (?P<templateName>(?U).+): executing (?P<functionName>(?U).+) at (?P<location>(?U).+): (?P<errMsg>(?U).+)(?P<nextErr>( template:.*)?)$`)
// taken from https://cs.opensource.google/go/go/+/refs/tags/go1.23.6:src/text/template/exec.go;l=138
// > "template: %s: %s"
var execErrFmtWithoutTemplate = regexp.MustCompile(`^template: (?P<templateName>(?U).+): (?P<errMsg>.*)(?P<nextErr>( template:.*)?)$`)
// taken from https://cs.opensource.google/go/go/+/refs/tags/go1.23.6:src/text/template/exec.go;l=191
// > "template: no template %q associated with template %q"
var execErrNoTemplateAssociated = regexp.MustCompile(`^template: no template (?P<location>.*) associated with template (?P<functionName>(.*)?)$`)
// Engine is an implementation of the Helm rendering implementation for templates. // Engine is an implementation of the Helm rendering implementation for templates.
type Engine struct { type Engine struct {
// If strict is enabled, template rendering will fail if a template references // If strict is enabled, template rendering will fail if a template references
@ -249,9 +262,7 @@ func (e Engine) initFunMap(t *template.Template) {
} }
// Set custom template funcs // Set custom template funcs
for k, v := range e.CustomTemplateFuncs { maps.Copy(funcMap, e.CustomTemplateFuncs)
funcMap[k] = v
}
t.Funcs(funcMap) t.Funcs(funcMap)
} }
@ -304,7 +315,7 @@ func (e Engine) render(tpls map[string]renderable) (rendered map[string]string,
vals["Template"] = chartutil.Values{"Name": filename, "BasePath": tpls[filename].basePath} vals["Template"] = chartutil.Values{"Name": filename, "BasePath": tpls[filename].basePath}
var buf strings.Builder var buf strings.Builder
if err := t.ExecuteTemplate(&buf, filename, vals); err != nil { if err := t.ExecuteTemplate(&buf, filename, vals); err != nil {
return map[string]string{}, cleanupExecError(filename, err) return map[string]string{}, reformatExecErrorMsg(filename, err)
} }
// Work around the issue where Go will emit "<no value>" even if Options(missing=zero) // Work around the issue where Go will emit "<no value>" even if Options(missing=zero)
@ -330,7 +341,33 @@ func cleanupParseError(filename string, err error) error {
return fmt.Errorf("parse error at (%s): %s", string(location), errMsg) return fmt.Errorf("parse error at (%s): %s", string(location), errMsg)
} }
func cleanupExecError(filename string, err error) error { type TraceableError struct {
location string
message string
executedFunction string
}
func (t TraceableError) String() string {
var errorString strings.Builder
if t.location != "" {
fmt.Fprintf(&errorString, "%s\n ", t.location)
}
if t.executedFunction != "" {
fmt.Fprintf(&errorString, "%s\n ", t.executedFunction)
}
if t.message != "" {
fmt.Fprintf(&errorString, "%s\n", t.message)
}
return errorString.String()
}
// reformatExecErrorMsg takes an error message for template rendering and formats it into a formatted
// multi-line error string
func reformatExecErrorMsg(filename string, err error) error {
// This function matches the error message against regex's for the text/template package.
// If the regex's can parse out details from that error message such as the line number, template it failed on,
// and error description, then it will construct a new error that displays these details in a structured way.
// If there are issues with parsing the error message, the err passed into the function should return instead.
if _, isExecError := err.(template.ExecError); !isExecError { if _, isExecError := err.(template.ExecError); !isExecError {
return err return err
} }
@ -349,8 +386,46 @@ func cleanupExecError(filename string, err error) error {
if len(parts) >= 2 { if len(parts) >= 2 {
return fmt.Errorf("execution error at (%s): %s", string(location), parts[1]) return fmt.Errorf("execution error at (%s): %s", string(location), parts[1])
} }
current := err
fileLocations := []TraceableError{}
for current != nil {
var traceable TraceableError
if matches := execErrFmt.FindStringSubmatch(current.Error()); matches != nil {
templateName := matches[execErrFmt.SubexpIndex("templateName")]
functionName := matches[execErrFmt.SubexpIndex("functionName")]
locationName := matches[execErrFmt.SubexpIndex("location")]
errMsg := matches[execErrFmt.SubexpIndex("errMsg")]
traceable = TraceableError{
location: templateName,
message: errMsg,
executedFunction: "executing " + functionName + " at " + locationName + ":",
}
} else if matches := execErrFmtWithoutTemplate.FindStringSubmatch(current.Error()); matches != nil {
templateName := matches[execErrFmt.SubexpIndex("templateName")]
errMsg := matches[execErrFmt.SubexpIndex("errMsg")]
traceable = TraceableError{
location: templateName,
message: errMsg,
}
} else if matches := execErrNoTemplateAssociated.FindStringSubmatch(current.Error()); matches != nil {
traceable = TraceableError{
message: current.Error(),
}
} else {
return err return err
}
if len(fileLocations) == 0 || fileLocations[len(fileLocations)-1] != traceable {
fileLocations = append(fileLocations, traceable)
}
current = errors.Unwrap(current)
}
var finalErrorString strings.Builder
for _, fileLocation := range fileLocations {
fmt.Fprintf(&finalErrorString, "%s", fileLocation.String())
}
return errors.New(strings.TrimSpace(finalErrorString.String()))
} }
func sortTemplates(tpls map[string]renderable) []string { func sortTemplates(tpls map[string]renderable) []string {

@ -24,6 +24,8 @@ import (
"testing" "testing"
"text/template" "text/template"
"github.com/stretchr/testify/assert"
"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"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
@ -1289,16 +1291,82 @@ func TestRenderTplMissingKeyString(t *testing.T) {
t.Errorf("Expected error, got %v", out) t.Errorf("Expected error, got %v", out)
return return
} }
switch err.(type) {
case (template.ExecError):
errTxt := fmt.Sprint(err) errTxt := fmt.Sprint(err)
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)
} }
default:
// Some unexpected error. }
t.Fatal(err)
func TestNestedHelpersProducesMultilineStacktrace(t *testing.T) {
c := &chart.Chart{
Metadata: &chart.Metadata{Name: "NestedHelperFunctions"},
Templates: []*chart.File{
{Name: "templates/svc.yaml", Data: []byte(
`name: {{ include "nested_helper.name" . }}`,
)},
{Name: "templates/_helpers_1.tpl", Data: []byte(
`{{- define "nested_helper.name" -}}{{- include "common.names.get_name" . -}}{{- end -}}`,
)},
{Name: "charts/common/templates/_helpers_2.tpl", Data: []byte(
`{{- define "common.names.get_name" -}}{{- .Values.nonexistant.key | trunc 63 | trimSuffix "-" -}}{{- end -}}`,
)},
},
}
expectedErrorMessage := `NestedHelperFunctions/templates/svc.yaml:1:9
executing "NestedHelperFunctions/templates/svc.yaml" at <include "nested_helper.name" .>:
error calling include:
NestedHelperFunctions/templates/_helpers_1.tpl:1:39
executing "nested_helper.name" at <include "common.names.get_name" .>:
error calling include:
NestedHelperFunctions/charts/common/templates/_helpers_2.tpl:1:49
executing "common.names.get_name" at <.Values.nonexistant.key>:
nil pointer evaluating interface {}.key`
v := chartutil.Values{}
val, _ := chartutil.CoalesceValues(c, v)
vals := map[string]interface{}{
"Values": val.AsMap(),
} }
_, err := Render(c, vals)
assert.NotNil(t, err)
assert.Equal(t, expectedErrorMessage, err.Error())
}
func TestMultilineNoTemplateAssociatedError(t *testing.T) {
c := &chart.Chart{
Metadata: &chart.Metadata{Name: "multiline"},
Templates: []*chart.File{
{Name: "templates/svc.yaml", Data: []byte(
`name: {{ include "nested_helper.name" . }}`,
)},
{Name: "templates/test.yaml", Data: []byte(
`{{ toYaml .Values }}`,
)},
{Name: "charts/common/templates/_helpers_2.tpl", Data: []byte(
`{{ toYaml .Values }}`,
)},
},
}
expectedErrorMessage := `multiline/templates/svc.yaml:1:9
executing "multiline/templates/svc.yaml" at <include "nested_helper.name" .>:
error calling include:
template: no template "nested_helper.name" associated with template "gotpl"`
v := chartutil.Values{}
val, _ := chartutil.CoalesceValues(c, v)
vals := map[string]interface{}{
"Values": val.AsMap(),
}
_, err := Render(c, vals)
assert.NotNil(t, err)
assert.Equal(t, expectedErrorMessage, err.Error())
} }
func TestRenderCustomTemplateFuncs(t *testing.T) { func TestRenderCustomTemplateFuncs(t *testing.T) {

@ -19,6 +19,7 @@ package engine
import ( import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"maps"
"strings" "strings"
"text/template" "text/template"
@ -51,10 +52,12 @@ func funcMap() template.FuncMap {
"toToml": toTOML, "toToml": toTOML,
"fromToml": fromTOML, "fromToml": fromTOML,
"toYaml": toYAML, "toYaml": toYAML,
"mustToYaml": mustToYAML,
"toYamlPretty": toYAMLPretty, "toYamlPretty": toYAMLPretty,
"fromYaml": fromYAML, "fromYaml": fromYAML,
"fromYamlArray": fromYAMLArray, "fromYamlArray": fromYAMLArray,
"toJson": toJSON, "toJson": toJSON,
"mustToJson": mustToJSON,
"fromJson": fromJSON, "fromJson": fromJSON,
"fromJsonArray": fromJSONArray, "fromJsonArray": fromJSONArray,
@ -71,9 +74,7 @@ func funcMap() template.FuncMap {
}, },
} }
for k, v := range extra { maps.Copy(f, extra)
f[k] = v
}
return f return f
} }
@ -91,6 +92,19 @@ func toYAML(v interface{}) string {
return strings.TrimSuffix(string(data), "\n") return strings.TrimSuffix(string(data), "\n")
} }
// mustToYAML takes an interface, marshals it to yaml, and returns a string.
// It will panic if there is an error.
//
// This is designed to be called from a template when need to ensure that the
// output YAML is valid.
func mustToYAML(v interface{}) string {
data, err := yaml.Marshal(v)
if err != nil {
panic(err)
}
return strings.TrimSuffix(string(data), "\n")
}
func toYAMLPretty(v interface{}) string { func toYAMLPretty(v interface{}) string {
var data bytes.Buffer var data bytes.Buffer
encoder := goYaml.NewEncoder(&data) encoder := goYaml.NewEncoder(&data)
@ -176,6 +190,19 @@ func toJSON(v interface{}) string {
return string(data) return string(data)
} }
// mustToJSON takes an interface, marshals it to json, and returns a string.
// It will panic if there is an error.
//
// This is designed to be called from a template when need to ensure that the
// output JSON is valid.
func mustToJSON(v interface{}) string {
data, err := json.Marshal(v)
if err != nil {
panic(err)
}
return string(data)
}
// fromJSON converts a JSON document into a map[string]interface{}. // fromJSON converts a JSON document into a map[string]interface{}.
// //
// This is not a general-purpose JSON parser, and will not parse all valid // This is not a general-purpose JSON parser, and will not parse all valid

@ -135,6 +135,43 @@ keyInElement1 = "valueInElement1"`,
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, tt.expect, b.String(), tt.tpl) assert.Equal(t, tt.expect, b.String(), tt.tpl)
} }
loopMap := map[string]interface{}{
"foo": "bar",
}
loopMap["loop"] = []interface{}{loopMap}
mustFuncsTests := []struct {
tpl string
expect interface{}
vars interface{}
}{{
tpl: `{{ mustToYaml . }}`,
vars: loopMap,
}, {
tpl: `{{ mustToJson . }}`,
vars: loopMap,
}, {
tpl: `{{ toYaml . }}`,
expect: "", // should return empty string and swallow error
vars: loopMap,
}, {
tpl: `{{ toJson . }}`,
expect: "", // should return empty string and swallow error
vars: loopMap,
},
}
for _, tt := range mustFuncsTests {
var b strings.Builder
err := template.Must(template.New("test").Funcs(funcMap()).Parse(tt.tpl)).Execute(&b, tt.vars)
if tt.expect != nil {
assert.NoError(t, err)
assert.Equal(t, tt.expect, b.String(), tt.tpl)
} else {
assert.Error(t, err)
}
}
} }
// 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

@ -23,14 +23,13 @@ import (
const name string = "HELM_EXPERIMENTAL_FEATURE" const name string = "HELM_EXPERIMENTAL_FEATURE"
func TestIsEnabled(t *testing.T) { func TestIsEnabled(t *testing.T) {
os.Unsetenv(name)
g := Gate(name) g := Gate(name)
if g.IsEnabled() { if g.IsEnabled() {
t.Errorf("feature gate shows as available, but the environment variable %s was not set", name) t.Errorf("feature gate shows as available, but the environment variable %s was not set", name)
} }
os.Setenv(name, "1") t.Setenv(name, "1")
if !g.IsEnabled() { if !g.IsEnabled() {
t.Errorf("feature gate shows as disabled, but the environment variable %s was set", name) t.Errorf("feature gate shows as disabled, but the environment variable %s was set", name)

@ -20,6 +20,7 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"net/http" "net/http"
"slices"
"time" "time"
"helm.sh/helm/v4/pkg/cli" "helm.sh/helm/v4/pkg/cli"
@ -163,12 +164,7 @@ type Provider struct {
// Provides returns true if the given scheme is supported by this Provider. // Provides returns true if the given scheme is supported by this Provider.
func (p Provider) Provides(scheme string) bool { func (p Provider) Provides(scheme string) bool {
for _, i := range p.Schemes { return slices.Contains(p.Schemes, scheme)
if i == scheme {
return true
}
}
return false
} }
// Providers is a collection of Provider objects. // Providers is a collection of Provider objects.
@ -195,24 +191,32 @@ const (
var defaultOptions = []Option{WithTimeout(time.Second * DefaultHTTPTimeout)} var defaultOptions = []Option{WithTimeout(time.Second * DefaultHTTPTimeout)}
var httpProvider = Provider{ func Getters(extraOpts ...Option) Providers {
return Providers{
Provider{
Schemes: []string{"http", "https"}, Schemes: []string{"http", "https"},
New: func(options ...Option) (Getter, error) { New: func(options ...Option) (Getter, error) {
options = append(options, defaultOptions...) options = append(options, defaultOptions...)
options = append(options, extraOpts...)
return NewHTTPGetter(options...) return NewHTTPGetter(options...)
}, },
} },
Provider{
var ociProvider = Provider{
Schemes: []string{registry.OCIScheme}, Schemes: []string{registry.OCIScheme},
New: NewOCIGetter, New: func(options ...Option) (Getter, error) {
options = append(options, defaultOptions...)
options = append(options, extraOpts...)
return NewOCIGetter(options...)
},
},
}
} }
// All finds all of the registered getters as a list of Provider instances. // All finds all of the registered getters as a list of Provider instances.
// Currently, the built-in getters and the discovered plugins with downloader // Currently, the built-in getters and the discovered plugins with downloader
// notations are collected. // notations are collected.
func All(settings *cli.EnvSettings) Providers { func All(settings *cli.EnvSettings, opts ...Option) Providers {
result := Providers{httpProvider, ociProvider} result := Getters(opts...)
pluginDownloaders, _ := collectPlugins(settings) pluginDownloaders, _ := collectPlugins(settings)
result = append(result, pluginDownloaders...) result = append(result, pluginDownloaders...)
return result return result

@ -17,6 +17,7 @@ package getter
import ( import (
"testing" "testing"
"time"
"helm.sh/helm/v4/pkg/cli" "helm.sh/helm/v4/pkg/cli"
) )
@ -52,6 +53,23 @@ func TestProviders(t *testing.T) {
} }
} }
func TestProvidersWithTimeout(t *testing.T) {
want := time.Hour
getters := Getters(WithTimeout(want))
getter, err := getters.ByScheme("http")
if err != nil {
t.Error(err)
}
client, err := getter.(*HTTPGetter).httpClient()
if err != nil {
t.Error(err)
}
got := client.Timeout
if got != want {
t.Errorf("Expected %q, got %q", want, got)
}
}
func TestAll(t *testing.T) { func TestAll(t *testing.T) {
env := cli.New() env := cli.New()
env.PluginsDirectory = pluginDir env.PluginsDirectory = pluginDir

@ -576,6 +576,7 @@ func TestHttpClientInsecureSkipVerify(t *testing.T) {
} }
func verifyInsecureSkipVerify(t *testing.T, g *HTTPGetter, caseName string, expectedValue bool) *http.Transport { func verifyInsecureSkipVerify(t *testing.T, g *HTTPGetter, caseName string, expectedValue bool) *http.Transport {
t.Helper()
returnVal, err := g.httpClient() returnVal, err := g.httpClient()
if err != nil { if err != nil {

@ -16,7 +16,6 @@
package helmpath package helmpath
import ( import (
"os"
"runtime" "runtime"
"testing" "testing"
@ -24,9 +23,9 @@ import (
) )
func TestHelmHome(t *testing.T) { func TestHelmHome(t *testing.T) {
os.Setenv(xdg.CacheHomeEnvVar, "/cache") t.Setenv(xdg.CacheHomeEnvVar, "/cache")
os.Setenv(xdg.ConfigHomeEnvVar, "/config") t.Setenv(xdg.ConfigHomeEnvVar, "/config")
os.Setenv(xdg.DataHomeEnvVar, "/data") t.Setenv(xdg.DataHomeEnvVar, "/data")
isEq := func(t *testing.T, got, expected string) { isEq := func(t *testing.T, got, expected string) {
t.Helper() t.Helper()
if expected != got { if expected != got {
@ -40,7 +39,7 @@ func TestHelmHome(t *testing.T) {
isEq(t, DataPath(), "/data/helm") isEq(t, DataPath(), "/data/helm")
// test to see if lazy-loading environment variables at runtime works // test to see if lazy-loading environment variables at runtime works
os.Setenv(xdg.CacheHomeEnvVar, "/cache2") t.Setenv(xdg.CacheHomeEnvVar, "/cache2")
isEq(t, CachePath(), "/cache2/helm") isEq(t, CachePath(), "/cache2/helm")
} }

@ -40,7 +40,7 @@ func TestDataPath(t *testing.T) {
t.Errorf("expected '%s', got '%s'", expected, lazy.dataPath(testFile)) t.Errorf("expected '%s', got '%s'", expected, lazy.dataPath(testFile))
} }
os.Setenv(xdg.DataHomeEnvVar, "/tmp") t.Setenv(xdg.DataHomeEnvVar, "/tmp")
expected = filepath.Join("/tmp", appName, testFile) expected = filepath.Join("/tmp", appName, testFile)
@ -58,7 +58,7 @@ func TestConfigPath(t *testing.T) {
t.Errorf("expected '%s', got '%s'", expected, lazy.configPath(testFile)) t.Errorf("expected '%s', got '%s'", expected, lazy.configPath(testFile))
} }
os.Setenv(xdg.ConfigHomeEnvVar, "/tmp") t.Setenv(xdg.ConfigHomeEnvVar, "/tmp")
expected = filepath.Join("/tmp", appName, testFile) expected = filepath.Join("/tmp", appName, testFile)
@ -76,7 +76,7 @@ func TestCachePath(t *testing.T) {
t.Errorf("expected '%s', got '%s'", expected, lazy.cachePath(testFile)) t.Errorf("expected '%s', got '%s'", expected, lazy.cachePath(testFile))
} }
os.Setenv(xdg.CacheHomeEnvVar, "/tmp") t.Setenv(xdg.CacheHomeEnvVar, "/tmp")
expected = filepath.Join("/tmp", appName, testFile) expected = filepath.Join("/tmp", appName, testFile)

@ -16,7 +16,6 @@
package helmpath package helmpath
import ( import (
"os"
"path/filepath" "path/filepath"
"testing" "testing"
@ -32,15 +31,13 @@ const (
) )
func TestDataPath(t *testing.T) { func TestDataPath(t *testing.T) {
os.Unsetenv(xdg.DataHomeEnvVar)
expected := filepath.Join(homedir.HomeDir(), ".local", "share", appName, testFile) expected := filepath.Join(homedir.HomeDir(), ".local", "share", appName, testFile)
if lazy.dataPath(testFile) != expected { if lazy.dataPath(testFile) != expected {
t.Errorf("expected '%s', got '%s'", expected, lazy.dataPath(testFile)) t.Errorf("expected '%s', got '%s'", expected, lazy.dataPath(testFile))
} }
os.Setenv(xdg.DataHomeEnvVar, "/tmp") t.Setenv(xdg.DataHomeEnvVar, "/tmp")
expected = filepath.Join("/tmp", appName, testFile) expected = filepath.Join("/tmp", appName, testFile)
@ -50,15 +47,13 @@ func TestDataPath(t *testing.T) {
} }
func TestConfigPath(t *testing.T) { func TestConfigPath(t *testing.T) {
os.Unsetenv(xdg.ConfigHomeEnvVar)
expected := filepath.Join(homedir.HomeDir(), ".config", appName, testFile) expected := filepath.Join(homedir.HomeDir(), ".config", appName, testFile)
if lazy.configPath(testFile) != expected { if lazy.configPath(testFile) != expected {
t.Errorf("expected '%s', got '%s'", expected, lazy.configPath(testFile)) t.Errorf("expected '%s', got '%s'", expected, lazy.configPath(testFile))
} }
os.Setenv(xdg.ConfigHomeEnvVar, "/tmp") t.Setenv(xdg.ConfigHomeEnvVar, "/tmp")
expected = filepath.Join("/tmp", appName, testFile) expected = filepath.Join("/tmp", appName, testFile)
@ -68,15 +63,13 @@ func TestConfigPath(t *testing.T) {
} }
func TestCachePath(t *testing.T) { func TestCachePath(t *testing.T) {
os.Unsetenv(xdg.CacheHomeEnvVar)
expected := filepath.Join(homedir.HomeDir(), ".cache", appName, testFile) expected := filepath.Join(homedir.HomeDir(), ".cache", appName, testFile)
if lazy.cachePath(testFile) != expected { if lazy.cachePath(testFile) != expected {
t.Errorf("expected '%s', got '%s'", expected, lazy.cachePath(testFile)) t.Errorf("expected '%s', got '%s'", expected, lazy.cachePath(testFile))
} }
os.Setenv(xdg.CacheHomeEnvVar, "/tmp") t.Setenv(xdg.CacheHomeEnvVar, "/tmp")
expected = filepath.Join("/tmp", appName, testFile) expected = filepath.Join("/tmp", appName, testFile)

@ -170,10 +170,10 @@ func (r *Rules) parseRule(rule string) error {
rule = strings.TrimSuffix(rule, "/") rule = strings.TrimSuffix(rule, "/")
} }
if strings.HasPrefix(rule, "/") { if after, ok := strings.CutPrefix(rule, "/"); ok {
// Require path matches the root path. // Require path matches the root path.
p.match = func(n string, _ os.FileInfo) bool { p.match = func(n string, _ os.FileInfo) bool {
rule = strings.TrimPrefix(rule, "/") rule = after
ok, err := filepath.Match(rule, n) ok, err := filepath.Match(rule, n)
if err != nil { if err != nil {
slog.Error("failed to compile", "rule", rule, slog.Any("error", err)) slog.Error("failed to compile", "rule", rule, slog.Any("error", err))

@ -30,7 +30,7 @@ import (
"strings" "strings"
"sync" "sync"
jsonpatch "github.com/evanphx/json-patch" jsonpatch "github.com/evanphx/json-patch/v5"
v1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
apiextv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" apiextv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
@ -585,10 +585,14 @@ func batchPerform(infos ResourceList, fn func(*resource.Info) error, errs chan<-
} }
} }
var createMutex sync.Mutex
func createResource(info *resource.Info) error { func createResource(info *resource.Info) error {
return retry.RetryOnConflict( return retry.RetryOnConflict(
retry.DefaultRetry, retry.DefaultRetry,
func() error { func() error {
createMutex.Lock()
defer createMutex.Unlock()
obj, err := resource.NewHelper(info.Client, info.Mapping).WithFieldManager(getManagedFieldsManager()).Create(info.Namespace, true, info.Object) obj, err := resource.NewHelper(info.Client, info.Mapping).WithFieldManager(getManagedFieldsManager()).Create(info.Namespace, true, info.Object)
if err != nil { if err != nil {
return err return err

@ -41,8 +41,10 @@ import (
cmdtesting "k8s.io/kubectl/pkg/cmd/testing" cmdtesting "k8s.io/kubectl/pkg/cmd/testing"
) )
var unstructuredSerializer = resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer var (
var codec = scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...) unstructuredSerializer = resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer
codec = scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
)
func objBody(obj runtime.Object) io.ReadCloser { func objBody(obj runtime.Object) io.ReadCloser {
return io.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(codec, obj)))) return io.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(codec, obj))))
@ -107,6 +109,7 @@ func newResponseJSON(code int, json []byte) (*http.Response, error) {
} }
func newTestClient(t *testing.T) *Client { func newTestClient(t *testing.T) *Client {
t.Helper()
testFactory := cmdtesting.NewTestFactory() testFactory := cmdtesting.NewTestFactory()
t.Cleanup(testFactory.Cleanup) t.Cleanup(testFactory.Cleanup)
@ -138,15 +141,15 @@ func TestCreate(t *testing.T) {
actions = append(actions, path+":"+method) actions = append(actions, path+":"+method)
t.Logf("got request %s %s", path, method) t.Logf("got request %s %s", path, method)
switch { switch {
case path == "/namespaces/default/pods" && method == "POST": case path == "/namespaces/default/pods" && method == http.MethodPost:
if strings.Contains(body, "starfish") { if strings.Contains(body, "starfish") {
if iterationCounter < 2 { if iterationCounter < 2 {
iterationCounter++ iterationCounter++
return newResponseJSON(409, resourceQuotaConflict) return newResponseJSON(http.StatusConflict, resourceQuotaConflict)
} }
return newResponse(200, &listA.Items[0]) return newResponse(http.StatusOK, &listA.Items[0])
} }
return newResponseJSON(409, resourceQuotaConflict) return newResponseJSON(http.StatusConflict, resourceQuotaConflict)
default: default:
t.Fatalf("unexpected request: %s %s", method, path) t.Fatalf("unexpected request: %s %s", method, path)
return nil, nil return nil, nil
@ -213,6 +216,7 @@ func TestCreate(t *testing.T) {
} }
func testUpdate(t *testing.T, threeWayMerge bool) { func testUpdate(t *testing.T, threeWayMerge bool) {
t.Helper()
listA := newPodList("starfish", "otter", "squid") listA := newPodList("starfish", "otter", "squid")
listB := newPodList("starfish", "otter", "dolphin") listB := newPodList("starfish", "otter", "dolphin")
listC := newPodList("starfish", "otter", "dolphin") listC := newPodList("starfish", "otter", "dolphin")
@ -230,11 +234,11 @@ func testUpdate(t *testing.T, threeWayMerge bool) {
actions = append(actions, p+":"+m) actions = append(actions, p+":"+m)
t.Logf("got request %s %s", p, m) t.Logf("got request %s %s", p, m)
switch { switch {
case p == "/namespaces/default/pods/starfish" && m == "GET": case p == "/namespaces/default/pods/starfish" && m == http.MethodGet:
return newResponse(200, &listA.Items[0]) return newResponse(http.StatusOK, &listA.Items[0])
case p == "/namespaces/default/pods/otter" && m == "GET": case p == "/namespaces/default/pods/otter" && m == http.MethodGet:
return newResponse(200, &listA.Items[1]) return newResponse(http.StatusOK, &listA.Items[1])
case p == "/namespaces/default/pods/otter" && m == "PATCH": case p == "/namespaces/default/pods/otter" && m == http.MethodPatch:
data, err := io.ReadAll(req.Body) data, err := io.ReadAll(req.Body)
if err != nil { if err != nil {
t.Fatalf("could not dump request: %s", err) t.Fatalf("could not dump request: %s", err)
@ -244,10 +248,10 @@ func testUpdate(t *testing.T, threeWayMerge bool) {
if string(data) != expected { if string(data) != expected {
t.Errorf("expected patch\n%s\ngot\n%s", expected, string(data)) t.Errorf("expected patch\n%s\ngot\n%s", expected, string(data))
} }
return newResponse(200, &listB.Items[0]) return newResponse(http.StatusOK, &listB.Items[0])
case p == "/namespaces/default/pods/dolphin" && m == "GET": case p == "/namespaces/default/pods/dolphin" && m == http.MethodGet:
return newResponse(404, notFoundBody()) return newResponse(http.StatusNotFound, notFoundBody())
case p == "/namespaces/default/pods/starfish" && m == "PATCH": case p == "/namespaces/default/pods/starfish" && m == http.MethodPatch:
data, err := io.ReadAll(req.Body) data, err := io.ReadAll(req.Body)
if err != nil { if err != nil {
t.Fatalf("could not dump request: %s", err) t.Fatalf("could not dump request: %s", err)
@ -257,17 +261,17 @@ func testUpdate(t *testing.T, threeWayMerge bool) {
if string(data) != expected { if string(data) != expected {
t.Errorf("expected patch\n%s\ngot\n%s", expected, string(data)) t.Errorf("expected patch\n%s\ngot\n%s", expected, string(data))
} }
return newResponse(200, &listB.Items[0]) return newResponse(http.StatusOK, &listB.Items[0])
case p == "/namespaces/default/pods" && m == "POST": case p == "/namespaces/default/pods" && m == http.MethodPost:
if iterationCounter < 2 { if iterationCounter < 2 {
iterationCounter++ iterationCounter++
return newResponseJSON(409, resourceQuotaConflict) return newResponseJSON(http.StatusConflict, resourceQuotaConflict)
} }
return newResponse(200, &listB.Items[1]) return newResponse(http.StatusOK, &listB.Items[1])
case p == "/namespaces/default/pods/squid" && m == "DELETE": case p == "/namespaces/default/pods/squid" && m == http.MethodDelete:
return newResponse(200, &listB.Items[1]) return newResponse(http.StatusOK, &listB.Items[1])
case p == "/namespaces/default/pods/squid" && m == "GET": case p == "/namespaces/default/pods/squid" && m == http.MethodGet:
return newResponse(200, &listB.Items[2]) return newResponse(http.StatusOK, &listB.Items[2])
default: default:
t.Fatalf("unexpected request: %s %s", req.Method, req.URL.Path) t.Fatalf("unexpected request: %s %s", req.Method, req.URL.Path)
return nil, nil return nil, nil
@ -485,7 +489,7 @@ func TestWait(t *testing.T) {
p, m := req.URL.Path, req.Method p, m := req.URL.Path, req.Method
t.Logf("got request %s %s", p, m) t.Logf("got request %s %s", p, m)
switch { switch {
case p == "/api/v1/namespaces/default/pods/starfish" && m == "GET": case p == "/api/v1/namespaces/default/pods/starfish" && m == http.MethodGet:
pod := &podList.Items[0] pod := &podList.Items[0]
if created != nil && time.Since(*created) >= time.Second*5 { if created != nil && time.Since(*created) >= time.Second*5 {
pod.Status.Conditions = []v1.PodCondition{ pod.Status.Conditions = []v1.PodCondition{
@ -495,8 +499,8 @@ func TestWait(t *testing.T) {
}, },
} }
} }
return newResponse(200, pod) return newResponse(http.StatusOK, pod)
case p == "/api/v1/namespaces/default/pods/otter" && m == "GET": case p == "/api/v1/namespaces/default/pods/otter" && m == http.MethodGet:
pod := &podList.Items[1] pod := &podList.Items[1]
if created != nil && time.Since(*created) >= time.Second*5 { if created != nil && time.Since(*created) >= time.Second*5 {
pod.Status.Conditions = []v1.PodCondition{ pod.Status.Conditions = []v1.PodCondition{
@ -506,8 +510,8 @@ func TestWait(t *testing.T) {
}, },
} }
} }
return newResponse(200, pod) return newResponse(http.StatusOK, pod)
case p == "/api/v1/namespaces/default/pods/squid" && m == "GET": case p == "/api/v1/namespaces/default/pods/squid" && m == http.MethodGet:
pod := &podList.Items[2] pod := &podList.Items[2]
if created != nil && time.Since(*created) >= time.Second*5 { if created != nil && time.Since(*created) >= time.Second*5 {
pod.Status.Conditions = []v1.PodCondition{ pod.Status.Conditions = []v1.PodCondition{
@ -517,15 +521,15 @@ func TestWait(t *testing.T) {
}, },
} }
} }
return newResponse(200, pod) return newResponse(http.StatusOK, pod)
case p == "/namespaces/default/pods" && m == "POST": case p == "/namespaces/default/pods" && m == http.MethodPost:
resources, err := c.Build(req.Body, false) resources, err := c.Build(req.Body, false)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
now := time.Now() now := time.Now()
created = &now created = &now
return newResponse(200, resources[0].Object) return newResponse(http.StatusOK, resources[0].Object)
default: default:
t.Fatalf("unexpected request: %s %s", req.Method, req.URL.Path) t.Fatalf("unexpected request: %s %s", req.Method, req.URL.Path)
return nil, nil return nil, nil
@ -570,19 +574,19 @@ func TestWaitJob(t *testing.T) {
p, m := req.URL.Path, req.Method p, m := req.URL.Path, req.Method
t.Logf("got request %s %s", p, m) t.Logf("got request %s %s", p, m)
switch { switch {
case p == "/apis/batch/v1/namespaces/default/jobs/starfish" && m == "GET": case p == "/apis/batch/v1/namespaces/default/jobs/starfish" && m == http.MethodGet:
if created != nil && time.Since(*created) >= time.Second*5 { if created != nil && time.Since(*created) >= time.Second*5 {
job.Status.Succeeded = 1 job.Status.Succeeded = 1
} }
return newResponse(200, job) return newResponse(http.StatusOK, job)
case p == "/namespaces/default/jobs" && m == "POST": case p == "/namespaces/default/jobs" && m == http.MethodPost:
resources, err := c.Build(req.Body, false) resources, err := c.Build(req.Body, false)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
now := time.Now() now := time.Now()
created = &now created = &now
return newResponse(200, resources[0].Object) return newResponse(http.StatusOK, resources[0].Object)
default: default:
t.Fatalf("unexpected request: %s %s", req.Method, req.URL.Path) t.Fatalf("unexpected request: %s %s", req.Method, req.URL.Path)
return nil, nil return nil, nil
@ -627,21 +631,21 @@ func TestWaitDelete(t *testing.T) {
p, m := req.URL.Path, req.Method p, m := req.URL.Path, req.Method
t.Logf("got request %s %s", p, m) t.Logf("got request %s %s", p, m)
switch { switch {
case p == "/namespaces/default/pods/starfish" && m == "GET": case p == "/namespaces/default/pods/starfish" && m == http.MethodGet:
if deleted != nil && time.Since(*deleted) >= time.Second*5 { if deleted != nil && time.Since(*deleted) >= time.Second*5 {
return newResponse(404, notFoundBody()) return newResponse(http.StatusNotFound, notFoundBody())
} }
return newResponse(200, &pod) return newResponse(http.StatusOK, &pod)
case p == "/namespaces/default/pods/starfish" && m == "DELETE": case p == "/namespaces/default/pods/starfish" && m == http.MethodDelete:
now := time.Now() now := time.Now()
deleted = &now deleted = &now
return newResponse(200, &pod) return newResponse(http.StatusOK, &pod)
case p == "/namespaces/default/pods" && m == "POST": case p == "/namespaces/default/pods" && m == http.MethodPost:
resources, err := c.Build(req.Body, false) resources, err := c.Build(req.Body, false)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
return newResponse(200, resources[0].Object) return newResponse(http.StatusOK, resources[0].Object)
default: default:
t.Fatalf("unexpected request: %s %s", req.Method, req.URL.Path) t.Fatalf("unexpected request: %s %s", req.Method, req.URL.Path)
return nil, nil return nil, nil
@ -718,7 +722,6 @@ func TestReal(t *testing.T) {
} }
func TestGetPodList(t *testing.T) { func TestGetPodList(t *testing.T) {
namespace := "some-namespace" namespace := "some-namespace"
names := []string{"dave", "jimmy"} names := []string{"dave", "jimmy"}
var responsePodList v1.PodList var responsePodList v1.PodList
@ -733,7 +736,6 @@ func TestGetPodList(t *testing.T) {
clientAssertions := assert.New(t) clientAssertions := assert.New(t)
clientAssertions.NoError(err) clientAssertions.NoError(err)
clientAssertions.Equal(&responsePodList, podList) clientAssertions.Equal(&responsePodList, podList)
} }
func TestOutputContainerLogsForPodList(t *testing.T) { func TestOutputContainerLogsForPodList(t *testing.T) {
@ -820,11 +822,11 @@ spec:
apiVersion: v1 apiVersion: v1
kind: Service kind: Service
metadata: metadata:
name: redis-slave name: redis-replica
labels: labels:
app: redis app: redis
tier: backend tier: backend
role: slave role: replica
spec: spec:
ports: ports:
# the port that this service should serve on # the port that this service should serve on
@ -832,24 +834,24 @@ spec:
selector: selector:
app: redis app: redis
tier: backend tier: backend
role: slave role: replica
--- ---
apiVersion: extensions/v1beta1 apiVersion: extensions/v1beta1
kind: Deployment kind: Deployment
metadata: metadata:
name: redis-slave name: redis-replica
spec: spec:
replicas: 2 replicas: 2
template: template:
metadata: metadata:
labels: labels:
app: redis app: redis
role: slave role: replica
tier: backend tier: backend
spec: spec:
containers: containers:
- name: slave - name: replica
image: gcr.io/google_samples/gb-redisslave:v1 image: gcr.io/google_samples/gb-redisreplica:v1
resources: resources:
requests: requests:
cpu: 100m cpu: 100m
@ -964,7 +966,7 @@ func (c createPatchTestCase) run(t *testing.T) {
restClient := &fake.RESTClient{ restClient := &fake.RESTClient{
NegotiatedSerializer: unstructuredSerializer, NegotiatedSerializer: unstructuredSerializer,
Resp: &http.Response{ Resp: &http.Response{
StatusCode: 200, StatusCode: http.StatusOK,
Body: objBody(c.actual), Body: objBody(c.actual),
Header: header, Header: header,
}, },

@ -60,7 +60,7 @@ func Test_ReadyChecker_IsReady_Pod(t *testing.T) {
pausedAsReady: false, pausedAsReady: false,
}, },
args: args{ args: args{
ctx: context.TODO(), ctx: t.Context(),
resource: &resource.Info{Object: &corev1.Pod{}, Name: "foo", Namespace: defaultNamespace}, resource: &resource.Info{Object: &corev1.Pod{}, Name: "foo", Namespace: defaultNamespace},
}, },
pod: newPodWithCondition("foo", corev1.ConditionTrue), pod: newPodWithCondition("foo", corev1.ConditionTrue),
@ -75,7 +75,7 @@ func Test_ReadyChecker_IsReady_Pod(t *testing.T) {
pausedAsReady: false, pausedAsReady: false,
}, },
args: args{ args: args{
ctx: context.TODO(), ctx: t.Context(),
resource: &resource.Info{Object: &corev1.Pod{}, Name: "foo", Namespace: defaultNamespace}, resource: &resource.Info{Object: &corev1.Pod{}, Name: "foo", Namespace: defaultNamespace},
}, },
pod: newPodWithCondition("bar", corev1.ConditionTrue), pod: newPodWithCondition("bar", corev1.ConditionTrue),
@ -90,7 +90,7 @@ func Test_ReadyChecker_IsReady_Pod(t *testing.T) {
checkJobs: tt.fields.checkJobs, checkJobs: tt.fields.checkJobs,
pausedAsReady: tt.fields.pausedAsReady, pausedAsReady: tt.fields.pausedAsReady,
} }
if _, err := c.client.CoreV1().Pods(defaultNamespace).Create(context.TODO(), tt.pod, metav1.CreateOptions{}); err != nil { if _, err := c.client.CoreV1().Pods(defaultNamespace).Create(t.Context(), tt.pod, metav1.CreateOptions{}); err != nil {
t.Errorf("Failed to create Pod error: %v", err) t.Errorf("Failed to create Pod error: %v", err)
return return
} }
@ -132,7 +132,7 @@ func Test_ReadyChecker_IsReady_Job(t *testing.T) {
pausedAsReady: false, pausedAsReady: false,
}, },
args: args{ args: args{
ctx: context.TODO(), ctx: t.Context(),
resource: &resource.Info{Object: &batchv1.Job{}, Name: "foo", Namespace: defaultNamespace}, resource: &resource.Info{Object: &batchv1.Job{}, Name: "foo", Namespace: defaultNamespace},
}, },
job: newJob("bar", 1, intToInt32(1), 1, 0), job: newJob("bar", 1, intToInt32(1), 1, 0),
@ -147,7 +147,7 @@ func Test_ReadyChecker_IsReady_Job(t *testing.T) {
pausedAsReady: false, pausedAsReady: false,
}, },
args: args{ args: args{
ctx: context.TODO(), ctx: t.Context(),
resource: &resource.Info{Object: &batchv1.Job{}, Name: "foo", Namespace: defaultNamespace}, resource: &resource.Info{Object: &batchv1.Job{}, Name: "foo", Namespace: defaultNamespace},
}, },
job: newJob("foo", 1, intToInt32(1), 1, 0), job: newJob("foo", 1, intToInt32(1), 1, 0),
@ -162,7 +162,7 @@ func Test_ReadyChecker_IsReady_Job(t *testing.T) {
checkJobs: tt.fields.checkJobs, checkJobs: tt.fields.checkJobs,
pausedAsReady: tt.fields.pausedAsReady, pausedAsReady: tt.fields.pausedAsReady,
} }
if _, err := c.client.BatchV1().Jobs(defaultNamespace).Create(context.TODO(), tt.job, metav1.CreateOptions{}); err != nil { if _, err := c.client.BatchV1().Jobs(defaultNamespace).Create(t.Context(), tt.job, metav1.CreateOptions{}); err != nil {
t.Errorf("Failed to create Job error: %v", err) t.Errorf("Failed to create Job error: %v", err)
return return
} }
@ -204,7 +204,7 @@ func Test_ReadyChecker_IsReady_Deployment(t *testing.T) {
pausedAsReady: false, pausedAsReady: false,
}, },
args: args{ args: args{
ctx: context.TODO(), ctx: t.Context(),
resource: &resource.Info{Object: &appsv1.Deployment{}, Name: "foo", Namespace: defaultNamespace}, resource: &resource.Info{Object: &appsv1.Deployment{}, Name: "foo", Namespace: defaultNamespace},
}, },
replicaSet: newReplicaSet("foo", 0, 0, true), replicaSet: newReplicaSet("foo", 0, 0, true),
@ -220,7 +220,7 @@ func Test_ReadyChecker_IsReady_Deployment(t *testing.T) {
pausedAsReady: false, pausedAsReady: false,
}, },
args: args{ args: args{
ctx: context.TODO(), ctx: t.Context(),
resource: &resource.Info{Object: &appsv1.Deployment{}, Name: "foo", Namespace: defaultNamespace}, resource: &resource.Info{Object: &appsv1.Deployment{}, Name: "foo", Namespace: defaultNamespace},
}, },
replicaSet: newReplicaSet("foo", 0, 0, true), replicaSet: newReplicaSet("foo", 0, 0, true),
@ -236,11 +236,11 @@ func Test_ReadyChecker_IsReady_Deployment(t *testing.T) {
checkJobs: tt.fields.checkJobs, checkJobs: tt.fields.checkJobs,
pausedAsReady: tt.fields.pausedAsReady, pausedAsReady: tt.fields.pausedAsReady,
} }
if _, err := c.client.AppsV1().Deployments(defaultNamespace).Create(context.TODO(), tt.deployment, metav1.CreateOptions{}); err != nil { if _, err := c.client.AppsV1().Deployments(defaultNamespace).Create(t.Context(), tt.deployment, metav1.CreateOptions{}); err != nil {
t.Errorf("Failed to create Deployment error: %v", err) t.Errorf("Failed to create Deployment error: %v", err)
return return
} }
if _, err := c.client.AppsV1().ReplicaSets(defaultNamespace).Create(context.TODO(), tt.replicaSet, metav1.CreateOptions{}); err != nil { if _, err := c.client.AppsV1().ReplicaSets(defaultNamespace).Create(t.Context(), tt.replicaSet, metav1.CreateOptions{}); err != nil {
t.Errorf("Failed to create ReplicaSet error: %v", err) t.Errorf("Failed to create ReplicaSet error: %v", err)
return return
} }
@ -281,7 +281,7 @@ func Test_ReadyChecker_IsReady_PersistentVolumeClaim(t *testing.T) {
pausedAsReady: false, pausedAsReady: false,
}, },
args: args{ args: args{
ctx: context.TODO(), ctx: t.Context(),
resource: &resource.Info{Object: &corev1.PersistentVolumeClaim{}, Name: "foo", Namespace: defaultNamespace}, resource: &resource.Info{Object: &corev1.PersistentVolumeClaim{}, Name: "foo", Namespace: defaultNamespace},
}, },
pvc: newPersistentVolumeClaim("foo", corev1.ClaimPending), pvc: newPersistentVolumeClaim("foo", corev1.ClaimPending),
@ -296,7 +296,7 @@ func Test_ReadyChecker_IsReady_PersistentVolumeClaim(t *testing.T) {
pausedAsReady: false, pausedAsReady: false,
}, },
args: args{ args: args{
ctx: context.TODO(), ctx: t.Context(),
resource: &resource.Info{Object: &corev1.PersistentVolumeClaim{}, Name: "foo", Namespace: defaultNamespace}, resource: &resource.Info{Object: &corev1.PersistentVolumeClaim{}, Name: "foo", Namespace: defaultNamespace},
}, },
pvc: newPersistentVolumeClaim("bar", corev1.ClaimPending), pvc: newPersistentVolumeClaim("bar", corev1.ClaimPending),
@ -311,7 +311,7 @@ func Test_ReadyChecker_IsReady_PersistentVolumeClaim(t *testing.T) {
checkJobs: tt.fields.checkJobs, checkJobs: tt.fields.checkJobs,
pausedAsReady: tt.fields.pausedAsReady, pausedAsReady: tt.fields.pausedAsReady,
} }
if _, err := c.client.CoreV1().PersistentVolumeClaims(defaultNamespace).Create(context.TODO(), tt.pvc, metav1.CreateOptions{}); err != nil { if _, err := c.client.CoreV1().PersistentVolumeClaims(defaultNamespace).Create(t.Context(), tt.pvc, metav1.CreateOptions{}); err != nil {
t.Errorf("Failed to create PersistentVolumeClaim error: %v", err) t.Errorf("Failed to create PersistentVolumeClaim error: %v", err)
return return
} }
@ -352,7 +352,7 @@ func Test_ReadyChecker_IsReady_Service(t *testing.T) {
pausedAsReady: false, pausedAsReady: false,
}, },
args: args{ args: args{
ctx: context.TODO(), ctx: t.Context(),
resource: &resource.Info{Object: &corev1.Service{}, Name: "foo", Namespace: defaultNamespace}, resource: &resource.Info{Object: &corev1.Service{}, Name: "foo", Namespace: defaultNamespace},
}, },
svc: newService("foo", corev1.ServiceSpec{Type: corev1.ServiceTypeLoadBalancer, ClusterIP: ""}), svc: newService("foo", corev1.ServiceSpec{Type: corev1.ServiceTypeLoadBalancer, ClusterIP: ""}),
@ -367,7 +367,7 @@ func Test_ReadyChecker_IsReady_Service(t *testing.T) {
pausedAsReady: false, pausedAsReady: false,
}, },
args: args{ args: args{
ctx: context.TODO(), ctx: t.Context(),
resource: &resource.Info{Object: &corev1.Service{}, Name: "foo", Namespace: defaultNamespace}, resource: &resource.Info{Object: &corev1.Service{}, Name: "foo", Namespace: defaultNamespace},
}, },
svc: newService("bar", corev1.ServiceSpec{Type: corev1.ServiceTypeExternalName, ClusterIP: ""}), svc: newService("bar", corev1.ServiceSpec{Type: corev1.ServiceTypeExternalName, ClusterIP: ""}),
@ -382,7 +382,7 @@ func Test_ReadyChecker_IsReady_Service(t *testing.T) {
checkJobs: tt.fields.checkJobs, checkJobs: tt.fields.checkJobs,
pausedAsReady: tt.fields.pausedAsReady, pausedAsReady: tt.fields.pausedAsReady,
} }
if _, err := c.client.CoreV1().Services(defaultNamespace).Create(context.TODO(), tt.svc, metav1.CreateOptions{}); err != nil { if _, err := c.client.CoreV1().Services(defaultNamespace).Create(t.Context(), tt.svc, metav1.CreateOptions{}); err != nil {
t.Errorf("Failed to create Service error: %v", err) t.Errorf("Failed to create Service error: %v", err)
return return
} }
@ -423,7 +423,7 @@ func Test_ReadyChecker_IsReady_DaemonSet(t *testing.T) {
pausedAsReady: false, pausedAsReady: false,
}, },
args: args{ args: args{
ctx: context.TODO(), ctx: t.Context(),
resource: &resource.Info{Object: &appsv1.DaemonSet{}, Name: "foo", Namespace: defaultNamespace}, resource: &resource.Info{Object: &appsv1.DaemonSet{}, Name: "foo", Namespace: defaultNamespace},
}, },
ds: newDaemonSet("foo", 0, 0, 1, 0, true), ds: newDaemonSet("foo", 0, 0, 1, 0, true),
@ -438,7 +438,7 @@ func Test_ReadyChecker_IsReady_DaemonSet(t *testing.T) {
pausedAsReady: false, pausedAsReady: false,
}, },
args: args{ args: args{
ctx: context.TODO(), ctx: t.Context(),
resource: &resource.Info{Object: &appsv1.DaemonSet{}, Name: "foo", Namespace: defaultNamespace}, resource: &resource.Info{Object: &appsv1.DaemonSet{}, Name: "foo", Namespace: defaultNamespace},
}, },
ds: newDaemonSet("bar", 0, 1, 1, 1, true), ds: newDaemonSet("bar", 0, 1, 1, 1, true),
@ -453,7 +453,7 @@ func Test_ReadyChecker_IsReady_DaemonSet(t *testing.T) {
checkJobs: tt.fields.checkJobs, checkJobs: tt.fields.checkJobs,
pausedAsReady: tt.fields.pausedAsReady, pausedAsReady: tt.fields.pausedAsReady,
} }
if _, err := c.client.AppsV1().DaemonSets(defaultNamespace).Create(context.TODO(), tt.ds, metav1.CreateOptions{}); err != nil { if _, err := c.client.AppsV1().DaemonSets(defaultNamespace).Create(t.Context(), tt.ds, metav1.CreateOptions{}); err != nil {
t.Errorf("Failed to create DaemonSet error: %v", err) t.Errorf("Failed to create DaemonSet error: %v", err)
return return
} }
@ -494,7 +494,7 @@ func Test_ReadyChecker_IsReady_StatefulSet(t *testing.T) {
pausedAsReady: false, pausedAsReady: false,
}, },
args: args{ args: args{
ctx: context.TODO(), ctx: t.Context(),
resource: &resource.Info{Object: &appsv1.StatefulSet{}, Name: "foo", Namespace: defaultNamespace}, resource: &resource.Info{Object: &appsv1.StatefulSet{}, Name: "foo", Namespace: defaultNamespace},
}, },
ss: newStatefulSet("foo", 1, 0, 0, 1, true), ss: newStatefulSet("foo", 1, 0, 0, 1, true),
@ -509,7 +509,7 @@ func Test_ReadyChecker_IsReady_StatefulSet(t *testing.T) {
pausedAsReady: false, pausedAsReady: false,
}, },
args: args{ args: args{
ctx: context.TODO(), ctx: t.Context(),
resource: &resource.Info{Object: &appsv1.StatefulSet{}, Name: "foo", Namespace: defaultNamespace}, resource: &resource.Info{Object: &appsv1.StatefulSet{}, Name: "foo", Namespace: defaultNamespace},
}, },
ss: newStatefulSet("bar", 1, 0, 1, 1, true), ss: newStatefulSet("bar", 1, 0, 1, 1, true),
@ -524,7 +524,7 @@ func Test_ReadyChecker_IsReady_StatefulSet(t *testing.T) {
checkJobs: tt.fields.checkJobs, checkJobs: tt.fields.checkJobs,
pausedAsReady: tt.fields.pausedAsReady, pausedAsReady: tt.fields.pausedAsReady,
} }
if _, err := c.client.AppsV1().StatefulSets(defaultNamespace).Create(context.TODO(), tt.ss, metav1.CreateOptions{}); err != nil { if _, err := c.client.AppsV1().StatefulSets(defaultNamespace).Create(t.Context(), tt.ss, metav1.CreateOptions{}); err != nil {
t.Errorf("Failed to create StatefulSet error: %v", err) t.Errorf("Failed to create StatefulSet error: %v", err)
return return
} }
@ -565,7 +565,7 @@ func Test_ReadyChecker_IsReady_ReplicationController(t *testing.T) {
pausedAsReady: false, pausedAsReady: false,
}, },
args: args{ args: args{
ctx: context.TODO(), ctx: t.Context(),
resource: &resource.Info{Object: &corev1.ReplicationController{}, Name: "foo", Namespace: defaultNamespace}, resource: &resource.Info{Object: &corev1.ReplicationController{}, Name: "foo", Namespace: defaultNamespace},
}, },
rc: newReplicationController("foo", false), rc: newReplicationController("foo", false),
@ -580,7 +580,7 @@ func Test_ReadyChecker_IsReady_ReplicationController(t *testing.T) {
pausedAsReady: false, pausedAsReady: false,
}, },
args: args{ args: args{
ctx: context.TODO(), ctx: t.Context(),
resource: &resource.Info{Object: &corev1.ReplicationController{}, Name: "foo", Namespace: defaultNamespace}, resource: &resource.Info{Object: &corev1.ReplicationController{}, Name: "foo", Namespace: defaultNamespace},
}, },
rc: newReplicationController("bar", false), rc: newReplicationController("bar", false),
@ -595,7 +595,7 @@ func Test_ReadyChecker_IsReady_ReplicationController(t *testing.T) {
pausedAsReady: false, pausedAsReady: false,
}, },
args: args{ args: args{
ctx: context.TODO(), ctx: t.Context(),
resource: &resource.Info{Object: &corev1.ReplicationController{}, Name: "foo", Namespace: defaultNamespace}, resource: &resource.Info{Object: &corev1.ReplicationController{}, Name: "foo", Namespace: defaultNamespace},
}, },
rc: newReplicationController("foo", true), rc: newReplicationController("foo", true),
@ -610,7 +610,7 @@ func Test_ReadyChecker_IsReady_ReplicationController(t *testing.T) {
checkJobs: tt.fields.checkJobs, checkJobs: tt.fields.checkJobs,
pausedAsReady: tt.fields.pausedAsReady, pausedAsReady: tt.fields.pausedAsReady,
} }
if _, err := c.client.CoreV1().ReplicationControllers(defaultNamespace).Create(context.TODO(), tt.rc, metav1.CreateOptions{}); err != nil { if _, err := c.client.CoreV1().ReplicationControllers(defaultNamespace).Create(t.Context(), tt.rc, metav1.CreateOptions{}); err != nil {
t.Errorf("Failed to create ReplicationController error: %v", err) t.Errorf("Failed to create ReplicationController error: %v", err)
return return
} }
@ -651,7 +651,7 @@ func Test_ReadyChecker_IsReady_ReplicaSet(t *testing.T) {
pausedAsReady: false, pausedAsReady: false,
}, },
args: args{ args: args{
ctx: context.TODO(), ctx: t.Context(),
resource: &resource.Info{Object: &appsv1.ReplicaSet{}, Name: "foo", Namespace: defaultNamespace}, resource: &resource.Info{Object: &appsv1.ReplicaSet{}, Name: "foo", Namespace: defaultNamespace},
}, },
rs: newReplicaSet("foo", 1, 1, true), rs: newReplicaSet("foo", 1, 1, true),
@ -666,7 +666,7 @@ func Test_ReadyChecker_IsReady_ReplicaSet(t *testing.T) {
pausedAsReady: false, pausedAsReady: false,
}, },
args: args{ args: args{
ctx: context.TODO(), ctx: t.Context(),
resource: &resource.Info{Object: &appsv1.ReplicaSet{}, Name: "foo", Namespace: defaultNamespace}, resource: &resource.Info{Object: &appsv1.ReplicaSet{}, Name: "foo", Namespace: defaultNamespace},
}, },
rs: newReplicaSet("bar", 1, 1, false), rs: newReplicaSet("bar", 1, 1, false),
@ -1014,12 +1014,12 @@ func Test_ReadyChecker_podsReadyForObject(t *testing.T) {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
c := NewReadyChecker(fake.NewClientset()) c := NewReadyChecker(fake.NewClientset())
for _, pod := range tt.existPods { for _, pod := range tt.existPods {
if _, err := c.client.CoreV1().Pods(defaultNamespace).Create(context.TODO(), &pod, metav1.CreateOptions{}); err != nil { if _, err := c.client.CoreV1().Pods(defaultNamespace).Create(t.Context(), &pod, metav1.CreateOptions{}); err != nil {
t.Errorf("Failed to create Pod error: %v", err) t.Errorf("Failed to create Pod error: %v", err)
return return
} }
} }
got, err := c.podsReadyForObject(context.TODO(), tt.args.namespace, tt.args.obj) got, err := c.podsReadyForObject(t.Context(), tt.args.namespace, tt.args.obj)
if (err != nil) != tt.wantErr { if (err != nil) != tt.wantErr {
t.Errorf("podsReadyForObject() error = %v, wantErr %v", err, tt.wantErr) t.Errorf("podsReadyForObject() error = %v, wantErr %v", err, tt.wantErr)
return return

@ -81,5 +81,5 @@ func (r ResourceList) Intersect(rs ResourceList) ResourceList {
// isMatchingInfo returns true if infos match on Name and GroupVersionKind. // isMatchingInfo returns true if infos match on Name and GroupVersionKind.
func isMatchingInfo(a, b *resource.Info) bool { func isMatchingInfo(a, b *resource.Info) bool {
return a.Name == b.Name && a.Namespace == b.Namespace && a.Mapping.GroupVersionKind.Kind == b.Mapping.GroupVersionKind.Kind && a.Mapping.GroupVersionKind.Group == b.Mapping.GroupVersionKind.Group return a.Name == b.Name && a.Namespace == b.Namespace && a.Mapping.GroupVersionKind == b.Mapping.GroupVersionKind
} }

@ -59,3 +59,42 @@ func TestResourceList(t *testing.T) {
t.Error("expected intersect to return bar") t.Error("expected intersect to return bar")
} }
} }
func TestIsMatchingInfo(t *testing.T) {
gvk := schema.GroupVersionKind{Group: "group1", Version: "version1", Kind: "pod"}
resourceInfo := resource.Info{Name: "name1", Namespace: "namespace1", Mapping: &meta.RESTMapping{GroupVersionKind: gvk}}
gvkDiffGroup := schema.GroupVersionKind{Group: "diff", Version: "version1", Kind: "pod"}
resourceInfoDiffGroup := resource.Info{Name: "name1", Namespace: "namespace1", Mapping: &meta.RESTMapping{GroupVersionKind: gvkDiffGroup}}
if isMatchingInfo(&resourceInfo, &resourceInfoDiffGroup) {
t.Error("expected resources not equal")
}
gvkDiffVersion := schema.GroupVersionKind{Group: "group1", Version: "diff", Kind: "pod"}
resourceInfoDiffVersion := resource.Info{Name: "name1", Namespace: "namespace1", Mapping: &meta.RESTMapping{GroupVersionKind: gvkDiffVersion}}
if isMatchingInfo(&resourceInfo, &resourceInfoDiffVersion) {
t.Error("expected resources not equal")
}
gvkDiffKind := schema.GroupVersionKind{Group: "group1", Version: "version1", Kind: "deployment"}
resourceInfoDiffKind := resource.Info{Name: "name1", Namespace: "namespace1", Mapping: &meta.RESTMapping{GroupVersionKind: gvkDiffKind}}
if isMatchingInfo(&resourceInfo, &resourceInfoDiffKind) {
t.Error("expected resources not equal")
}
resourceInfoDiffName := resource.Info{Name: "diff", Namespace: "namespace1", Mapping: &meta.RESTMapping{GroupVersionKind: gvk}}
if isMatchingInfo(&resourceInfo, &resourceInfoDiffName) {
t.Error("expected resources not equal")
}
resourceInfoDiffNamespace := resource.Info{Name: "name1", Namespace: "diff", Mapping: &meta.RESTMapping{GroupVersionKind: gvk}}
if isMatchingInfo(&resourceInfo, &resourceInfoDiffNamespace) {
t.Error("expected resources not equal")
}
gvkEqual := schema.GroupVersionKind{Group: "group1", Version: "version1", Kind: "pod"}
resourceInfoEqual := resource.Info{Name: "name1", Namespace: "namespace1", Mapping: &meta.RESTMapping{GroupVersionKind: gvkEqual}}
if !isMatchingInfo(&resourceInfo, &resourceInfoEqual) {
t.Error("expected resources to be equal")
}
}

@ -154,6 +154,7 @@ spec:
` `
func getGVR(t *testing.T, mapper meta.RESTMapper, obj *unstructured.Unstructured) schema.GroupVersionResource { func getGVR(t *testing.T, mapper meta.RESTMapper, obj *unstructured.Unstructured) schema.GroupVersionResource {
t.Helper()
gvk := obj.GroupVersionKind() gvk := obj.GroupVersionKind()
mapping, err := mapper.RESTMapping(gvk.GroupKind(), gvk.Version) mapping, err := mapper.RESTMapping(gvk.GroupKind(), gvk.Version)
require.NoError(t, err) require.NoError(t, err)
@ -161,6 +162,7 @@ func getGVR(t *testing.T, mapper meta.RESTMapper, obj *unstructured.Unstructured
} }
func getRuntimeObjFromManifests(t *testing.T, manifests []string) []runtime.Object { func getRuntimeObjFromManifests(t *testing.T, manifests []string) []runtime.Object {
t.Helper()
objects := []runtime.Object{} objects := []runtime.Object{}
for _, manifest := range manifests { for _, manifest := range manifests {
m := make(map[string]interface{}) m := make(map[string]interface{})
@ -173,6 +175,7 @@ func getRuntimeObjFromManifests(t *testing.T, manifests []string) []runtime.Obje
} }
func getResourceListFromRuntimeObjs(t *testing.T, c *Client, objs []runtime.Object) ResourceList { func getResourceListFromRuntimeObjs(t *testing.T, c *Client, objs []runtime.Object) ResourceList {
t.Helper()
resourceList := ResourceList{} resourceList := ResourceList{}
for _, obj := range objs { for _, obj := range objs {
list, err := c.Build(objBody(obj), false) list, err := c.Build(objBody(obj), false)

@ -117,7 +117,7 @@ func (hw *legacyWaiter) isRetryableHTTPStatusCode(httpStatusCode int32) bool {
return httpStatusCode == 0 || httpStatusCode == http.StatusTooManyRequests || (httpStatusCode >= 500 && httpStatusCode != http.StatusNotImplemented) return httpStatusCode == 0 || httpStatusCode == http.StatusTooManyRequests || (httpStatusCode >= 500 && httpStatusCode != http.StatusNotImplemented)
} }
// waitForDeletedResources polls to check if all the resources are deleted or a timeout is reached // WaitForDelete polls to check if all the resources are deleted or a timeout is reached
func (hw *legacyWaiter) WaitForDelete(deleted ResourceList, timeout time.Duration) error { func (hw *legacyWaiter) WaitForDelete(deleted ResourceList, timeout time.Duration) error {
slog.Debug("beginning wait for resources to be deleted", "count", len(deleted), "timeout", timeout) slog.Debug("beginning wait for resources to be deleted", "count", len(deleted), "timeout", timeout)

@ -25,6 +25,7 @@ import (
"os" "os"
"path" "path"
"path/filepath" "path/filepath"
"slices"
"strings" "strings"
"k8s.io/apimachinery/pkg/api/validation" "k8s.io/apimachinery/pkg/api/validation"
@ -206,11 +207,9 @@ func validateAllowedExtension(fileName string) error {
ext := filepath.Ext(fileName) ext := filepath.Ext(fileName)
validExtensions := []string{".yaml", ".yml", ".tpl", ".txt"} validExtensions := []string{".yaml", ".yml", ".tpl", ".txt"}
for _, b := range validExtensions { if slices.Contains(validExtensions, ext) {
if b == ext {
return nil return nil
} }
}
return fmt.Errorf("file extension '%s' not valid. Valid extensions are .yaml, .yml, .tpl, or .txt", ext) return fmt.Errorf("file extension '%s' not valid. Valid extensions are .yaml, .yml, .tpl, or .txt", ext)
} }

@ -14,7 +14,6 @@ limitations under the License.
package installer // import "helm.sh/helm/v4/pkg/plugin/installer" package installer // import "helm.sh/helm/v4/pkg/plugin/installer"
import ( import (
"os"
"testing" "testing"
) )
@ -37,12 +36,11 @@ func TestPath(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
os.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()
if baseInsPath != tt.expectPath { if baseInsPath != tt.expectPath {
t.Errorf("expected name %s, got %s", tt.expectPath, baseInsPath) t.Errorf("expected name %s, got %s", tt.expectPath, baseInsPath)
} }
os.Unsetenv("HELM_PLUGINS")
} }
} }

@ -27,6 +27,7 @@ import (
"path" "path"
"path/filepath" "path/filepath"
"regexp" "regexp"
"slices"
"strings" "strings"
securejoin "github.com/cyphar/filepath-securejoin" securejoin "github.com/cyphar/filepath-securejoin"
@ -196,11 +197,9 @@ func cleanJoin(root, dest string) (string, error) {
// We want to alert the user that something bad was attempted. Cleaning it // We want to alert the user that something bad was attempted. Cleaning it
// is not a good practice. // is not a good practice.
for _, part := range strings.Split(dest, "/") { if slices.Contains(strings.Split(dest, "/"), "..") {
if part == ".." {
return "", errors.New("path contains '..', which is illegal") return "", errors.New("path contains '..', which is illegal")
} }
}
// If a path is absolute, the creator of the TAR is doing something shady. // If a path is absolute, the creator of the TAR is doing something shady.
if path.IsAbs(dest) { if path.IsAbs(dest) {

@ -20,12 +20,14 @@ import (
"path/filepath" "path/filepath"
"testing" "testing"
"helm.sh/helm/v4/internal/test/ensure"
"helm.sh/helm/v4/pkg/helmpath" "helm.sh/helm/v4/pkg/helmpath"
) )
var _ Installer = new(LocalInstaller) var _ Installer = new(LocalInstaller)
func TestLocalInstaller(t *testing.T) { func TestLocalInstaller(t *testing.T) {
ensure.HelmHome(t)
// Make a temp dir // Make a temp dir
tdir := t.TempDir() tdir := t.TempDir()
if err := os.WriteFile(filepath.Join(tdir, "plugin.yaml"), []byte{}, 0644); err != nil { if err := os.WriteFile(filepath.Join(tdir, "plugin.yaml"), []byte{}, 0644); err != nil {

@ -19,6 +19,7 @@ import (
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
"strings"
"testing" "testing"
"github.com/Masterminds/vcs" "github.com/Masterminds/vcs"
@ -119,6 +120,8 @@ func TestVCSInstallerNonExistentVersion(t *testing.T) {
if err := Install(i); err == nil { if err := Install(i); err == nil {
t.Fatalf("expected error for version does not exists, got none") t.Fatalf("expected error for version does not exists, got none")
} else if strings.Contains(err.Error(), "Could not resolve host: github.com") {
t.Skip("Unable to run test without Internet access")
} else if err.Error() != fmt.Sprintf("requested version %q does not exist for plugin %q", version, source) { } else if err.Error() != fmt.Sprintf("requested version %q does not exist for plugin %q", version, source) {
t.Fatalf("expected error for version does not exists, got (%v)", err) t.Fatalf("expected error for version does not exists, got (%v)", err)
} }
@ -146,8 +149,12 @@ func TestVCSInstallerUpdate(t *testing.T) {
// Install plugin before update // Install plugin before update
if err := Install(i); err != nil { if err := Install(i); err != nil {
if strings.Contains(err.Error(), "Could not resolve host: github.com") {
t.Skip("Unable to run test without Internet access")
} else {
t.Fatal(err) t.Fatal(err)
} }
}
// Test FindSource method for positive result // Test FindSource method for positive result
pluginInfo, err := FindSource(i.Path()) pluginInfo, err := FindSource(i.Path())

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

Loading…
Cancel
Save