diff --git a/.github/dependabot.yml b/.github/dependabot.yml index c9702f2cd..0133fd8f4 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,7 +1,24 @@ version: 2 updates: + - # Keep dev-v3 branch dependencies up to date, while Helm v3 is within support + package-ecosystem: "gomod" + target-branch: "dev-v3" + directory: "/" + schedule: + interval: "daily" + groups: + k8s.io: + patterns: + - "k8s.io/api" + - "k8s.io/apiextensions-apiserver" + - "k8s.io/apimachinery" + - "k8s.io/apiserver" + - "k8s.io/cli-runtime" + - "k8s.io/client-go" + - "k8s.io/kubectl" - package-ecosystem: "gomod" + target-branch: "main" directory: "/" schedule: interval: "daily" @@ -16,6 +33,7 @@ updates: - "k8s.io/client-go" - "k8s.io/kubectl" - package-ecosystem: "github-actions" + target-branch: "main" directory: "/" schedule: interval: "daily" diff --git a/.github/env b/.github/env new file mode 100644 index 000000000..b321f6ef7 --- /dev/null +++ b/.github/env @@ -0,0 +1,2 @@ +GOLANG_VERSION=1.24 +GOLANGCI_LINT_VERSION=v2.0.2 diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 19ada6df6..6ed7092dc 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -7,9 +7,10 @@ on: - "release-**" pull_request: branches: - - main + - "main" + - "dev-v3" -permissions: +permissions: contents: read jobs: @@ -18,10 +19,12 @@ jobs: steps: - name: Checkout source code uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # pin@v4.2.2 + - name: Add variables to environment file + run: cat ".github/env" >> "$GITHUB_ENV" - name: Setup Go - uses: actions/setup-go@41dfa10bad2bb2ae585af6ee5bb4d7d973ad74ed # pin@5.1.0 + uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # pin@5.4.0 with: - go-version: '1.22' + go-version: '${{ env.GOLANG_VERSION }}' check-latest: true - name: Test source headers are present run: make test-source-headers diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index 746f7c306..7ecbcb95d 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -14,13 +14,14 @@ jobs: steps: - name: Checkout uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # pin@v4.2.2 - + - name: Add variables to environment file + run: cat ".github/env" >> "$GITHUB_ENV" - name: Setup Go - uses: actions/setup-go@41dfa10bad2bb2ae585af6ee5bb4d7d973ad74ed # pin@5.1.0 + uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # pin@5.4.0 with: - go-version: '1.22' + go-version: '${{ env.GOLANG_VERSION }}' check-latest: true - name: golangci-lint - uses: golangci/golangci-lint-action@971e284b6050e8a5849b72094c50ab08da042db8 #pin@6.1.1 + uses: golangci/golangci-lint-action@1481404843c368bc19ca9406f87d6e0fc97bdcfd #pin@7.0.0 with: - version: v1.58 + version: ${{ env.GOLANGCI_LINT_VERSION }} diff --git a/.github/workflows/govulncheck.yml b/.github/workflows/govulncheck.yml index ea9d09526..8d183e244 100644 --- a/.github/workflows/govulncheck.yml +++ b/.github/workflows/govulncheck.yml @@ -13,10 +13,12 @@ jobs: name: govulncheck runs-on: ubuntu-latest steps: + - name: Add variables to environment file + run: cat ".github/env" >> "$GITHUB_ENV" - name: Setup Go - uses: actions/setup-go@41dfa10bad2bb2ae585af6ee5bb4d7d973ad74ed # pin@5.1.0 + uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # pin@5.4.0 with: - go-version: '1.22' + go-version: '${{ env.GOLANG_VERSION }}' check-latest: true - name: govulncheck uses: golang/govulncheck-action@b625fbe08f3bccbe446d94fbf87fcc875a4f50ee # pin@1.0.4 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9d51acef7..38d13a175 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -24,14 +24,15 @@ jobs: with: fetch-depth: 0 + - name: Add variables to environment file + run: cat ".github/env" >> "$GITHUB_ENV" + - name: Setup Go - uses: actions/setup-go@41dfa10bad2bb2ae585af6ee5bb4d7d973ad74ed # pin@5.1.0 + uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # pin@5.4.0 with: - go-version: '1.22.7' - + go-version: '${{ env.GOLANG_VERSION }}' - name: Run unit tests run: make test-coverage - - name: Build Helm Binaries run: | set -eu -o pipefail @@ -80,10 +81,13 @@ jobs: - name: Checkout source code uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # pin@v4.2.2 + - name: Add variables to environment file + run: cat ".github/env" >> "$GITHUB_ENV" + - name: Setup Go - uses: actions/setup-go@41dfa10bad2bb2ae585af6ee5bb4d7d973ad74ed # pin@5.1.0 + uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # pin@5.4.0 with: - go-version: '1.22' + go-version: '${{ env.GOLANG_VERSION }}' check-latest: true - name: Run unit tests diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index a4edf1a1e..a8c2e8a15 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -33,7 +33,7 @@ jobs: persist-credentials: false - name: "Run analysis" - uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # v2.4.0 + uses: ossf/scorecard-action@f49aabe0b5af0936a0987cfb85d86b75731b0186 # v2.4.1 with: results_file: results.sarif results_format: sarif @@ -55,7 +55,7 @@ jobs: # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF # format to the repository Actions tab. - name: "Upload artifact" - uses: actions/upload-artifact@97a0fba1372883ab732affbe8f94b823f91727db # v3.pre.node20 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: SARIF file path: results.sarif diff --git a/.github/workflows/stale-issue-bot.yaml b/.github/workflows/stale-issue-bot.yaml index b31e08b6e..613d2900c 100644 --- a/.github/workflows/stale-issue-bot.yaml +++ b/.github/workflows/stale-issue-bot.yaml @@ -9,7 +9,7 @@ jobs: stale: runs-on: ubuntu-latest steps: - - uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e # v9.0.0 + - uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9.1.0 with: repo-token: ${{ secrets.GITHUB_TOKEN }} stale-issue-message: 'This issue has been marked as stale because it has been open for 90 days with no activity. This thread will be automatically closed in 30 days if no further activity occurs.' diff --git a/.gitignore b/.gitignore index cdb68ceb5..7ea0717ed 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,5 @@ bin/ vendor/ # Ignores charts pulled for dependency build tests cmd/helm/testdata/testcharts/issue-7233/charts/* +pkg/cmd/testdata/testcharts/issue-7233/charts/* +.pre-commit-config.yaml diff --git a/.golangci.yml b/.golangci.yml index 3cf50a0d4..f0d45e5ea 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,25 +1,63 @@ +version: "2" run: timeout: 10m - linters: - disable-all: true + default: none enable: - dupl - - gofmt - - goimports - - gosimple - govet - ineffassign - misspell - nakedret - revive - - unused - staticcheck - -linters-settings: - gofmt: - simplify: true - goimports: - local-prefixes: helm.sh/helm/v3 - dupl: - threshold: 400 + - unused + settings: + dupl: + threshold: 400 + exclusions: + # Helm, and the Go source code itself, sometimes uses these names outside their built-in + # functions. As the Go source code has re-used these names it's ok for Helm to do the same. + # Linting will look for redefinition of built-in id's but we opt-in to the ones we choose to use. + generated: lax + presets: + - comments + - common-false-positives + - legacy + - std-error-handling + rules: + - linters: + - revive + text: 'redefines-builtin-id: redefinition of the built-in function append' + - linters: + - revive + text: 'redefines-builtin-id: redefinition of the built-in function clear' + - linters: + - revive + text: 'redefines-builtin-id: redefinition of the built-in function max' + - linters: + - revive + text: 'redefines-builtin-id: redefinition of the built-in function min' + - linters: + - revive + text: 'redefines-builtin-id: redefinition of the built-in function new' + paths: + - third_party$ + - builtin$ + - examples$ +formatters: + enable: + - gofmt + - goimports + settings: + gofmt: + simplify: true + goimports: + local-prefixes: + - helm.sh/helm/v4 + exclusions: + generated: lax + paths: + - third_party$ + - builtin$ + - examples$ diff --git a/ADOPTERS.md b/ADOPTERS.md index 5d477589e..a83519fea 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -5,15 +5,21 @@ # Organizations Using Helm -- [IBM](https://www.ibm.com) -- [Microsoft](https://microsoft.com) -- [Octopus Deploy](https://octopus.com/) -- [New Relic](https://www.newrelic.com) -- [Qovery](https://www.qovery.com/) -- [Samsung SDS](https://www.samsungsds.com/) -- [Softonic](https://hello.softonic.com/) -- [Syself](https://syself.com) -- [Ville de Montreal](https://montreal.ca) -- [Intercept](https://Intercept.cloud) +- [IBM](https://www.ibm.com) +- [InfoCert](https://www.infocert.it/) +- [Intercept](https://Intercept.cloud) +- [Microsoft](https://microsoft.com) +- [New Relic](https://www.newrelic.com) +- [Octopus Deploy](https://octopus.com/) +- [Omnistrate](https://omnistrate.com) +- [Oracle](www.oracle.com) +- [Percona](https://www.percona.com) +- [Qovery](https://www.qovery.com/) +- [Samsung SDS](https://www.samsungsds.com/) +- [Softonic](https://hello.softonic.com/) +- [SyncTune](https://mb-consulting.dev) +- [Syself](https://syself.com) +- [Ville de Montreal](https://montreal.ca) + _This file is part of the CNCF official documentation for projects._ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0c2f88453..8ab93403d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -276,12 +276,26 @@ Like any good open source project, we use Pull Requests (PRs) to track code chan or explicitly request another OWNER do that for them. - If the owner of a PR is _not_ listed in `OWNERS`, any core maintainer may merge the PR. -#### Documentation PRs +### Documentation PRs Documentation PRs should be made on the docs repo: . Keeping Helm's documentation up to date is highly desirable, and is recommended for all user facing changes. Accurate and helpful documentation is critical for effectively communicating Helm's behavior to a wide audience. Small, ad-hoc changes/PRs to Helm which introduce user facing changes, which would benefit from documentation changes, should apply the `docs needed` label. Larger changes associated with a HIP should track docs via that HIP. The `docs needed` label doesn't block PRs, and maintainers/PR reviewers should apply discretion judging in whether the `docs needed` label should be applied. +### Profiling PRs + +If your contribution requires profiling to check memory and/or CPU usage, you can set `HELM_PPROF_CPU_PROFILE=/path/to/cpu.prof` and/or `HELM_PPROF_MEM_PROFILE=/path/to/mem.prof` environment variables to collect runtime profiling data for analysis. You can use Golang's [pprof](https://github.com/google/pprof/blob/main/doc/README.md) tool to inspect the results. + +Example analysing collected profiling data +``` +HELM_PPROF_CPU_PROFILE=cpu.prof HELM_PPROF_MEM_PROFILE=mem.prof helm show all bitnami/nginx + +# Visualize graphs. You need to have installed graphviz package in your system +go tool pprof -http=":8000" cpu.prof + +go tool pprof -http=":8001" mem.prof +``` + ## The Triager Each week, one of the core maintainers will serve as the designated "triager" starting after the diff --git a/KEYS b/KEYS index f14c596c7..e772fff40 100644 --- a/KEYS +++ b/KEYS @@ -1037,3 +1037,24 @@ nk38BkgHg3LHjCbCNEVkSK2TMT69A58iwpY9WUQlphsiz4WBpafSPbv/jSlsm7uK TNWtbFGBRpJyEg== =w141 -----END PGP PUBLIC KEY BLOCK----- +pub ed25519 2024-07-09 [SC] + 7FEC81FACC7FFB2A010ADD13C2D40F4D8196E874 +uid [ultimate] Robert Sirchia (I like turtles.) +sig 3 C2D40F4D8196E874 2024-07-09 [self-signature] +sub cv25519 2024-07-09 [E] +sig C2D40F4D8196E874 2024-07-09 [self-signature] + +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mDMEZo2C6xYJKwYBBAHaRw8BAQdA8kCWaI+FlCabcTw8EVeiMkokyWDalgl/Inbn +ACcGN1e0N1JvYmVydCBTaXJjaGlhIChJIGxpa2UgdHVydGxlcy4pIDxyc2lyY2hp +YUBvdXRsb29rLmNvbT6IkwQTFgoAOxYhBH/sgfrMf/sqAQrdE8LUD02Bluh0BQJm +jYLrAhsDBQsJCAcCAiICBhUKCQgLAgQWAgMBAh4HAheAAAoJEMLUD02Bluh0dyYA +/i7RB6m3MXNA8ei7GD8uQVpLfCRgEFsqSS/AzAOu8NGhAQCbw1kWL3AUll7KKtiQ +UE96nhCk+HnkQeVkWYS+MZ1tALg4BGaNgusSCisGAQQBl1UBBQEBB0CCA6Au4krL +YinQq9aAs29fFeRu/ye3PqQuz5jZ2r1ScAMBCAeIeAQYFgoAIBYhBH/sgfrMf/sq +AQrdE8LUD02Bluh0BQJmjYLrAhsMAAoJEMLUD02Bluh0KH4BAMSwEIGkoQl10LN3 +K6V08VpFmniENmCDHshXYq0gGiTDAP9FsXl2UtmFU5xuYxH4fRKIxgmxJRAFMWI8 +u3Rdu/s+DQ== +=smBO +-----END PGP PUBLIC KEY BLOCK----- diff --git a/Makefile b/Makefile index 2a264916a..0785fdb2e 100644 --- a/Makefile +++ b/Makefile @@ -44,7 +44,7 @@ BINARY_VERSION ?= ${GIT_TAG} # Only set Version if building a tag or VERSION is set ifneq ($(BINARY_VERSION),) - LDFLAGS += -X helm.sh/helm/v3/internal/version.version=${BINARY_VERSION} + LDFLAGS += -X helm.sh/helm/v4/internal/version.version=${BINARY_VERSION} endif VERSION_METADATA = unreleased @@ -53,9 +53,9 @@ ifneq ($(GIT_TAG),) VERSION_METADATA = endif -LDFLAGS += -X helm.sh/helm/v3/internal/version.metadata=${VERSION_METADATA} -LDFLAGS += -X helm.sh/helm/v3/internal/version.gitCommit=${GIT_COMMIT} -LDFLAGS += -X helm.sh/helm/v3/internal/version.gitTreeState=${GIT_DIRTY} +LDFLAGS += -X helm.sh/helm/v4/internal/version.metadata=${VERSION_METADATA} +LDFLAGS += -X helm.sh/helm/v4/internal/version.gitCommit=${GIT_COMMIT} +LDFLAGS += -X helm.sh/helm/v4/internal/version.gitTreeState=${GIT_DIRTY} LDFLAGS += $(EXT_LDFLAGS) # Define constants based on the client-go version @@ -63,10 +63,10 @@ K8S_MODULES_VER=$(subst ., ,$(subst v,,$(shell go list -f '{{.Version}}' -m k8s. K8S_MODULES_MAJOR_VER=$(shell echo $$(($(firstword $(K8S_MODULES_VER)) + 1))) K8S_MODULES_MINOR_VER=$(word 2,$(K8S_MODULES_VER)) -LDFLAGS += -X helm.sh/helm/v3/pkg/lint/rules.k8sVersionMajor=$(K8S_MODULES_MAJOR_VER) -LDFLAGS += -X helm.sh/helm/v3/pkg/lint/rules.k8sVersionMinor=$(K8S_MODULES_MINOR_VER) -LDFLAGS += -X helm.sh/helm/v3/pkg/chartutil.k8sVersionMajor=$(K8S_MODULES_MAJOR_VER) -LDFLAGS += -X helm.sh/helm/v3/pkg/chartutil.k8sVersionMinor=$(K8S_MODULES_MINOR_VER) +LDFLAGS += -X helm.sh/helm/v4/pkg/lint/rules.k8sVersionMajor=$(K8S_MODULES_MAJOR_VER) +LDFLAGS += -X helm.sh/helm/v4/pkg/lint/rules.k8sVersionMinor=$(K8S_MODULES_MINOR_VER) +LDFLAGS += -X helm.sh/helm/v4/pkg/chart/v2/util.k8sVersionMajor=$(K8S_MODULES_MAJOR_VER) +LDFLAGS += -X helm.sh/helm/v4/pkg/chart/v2/util.k8sVersionMinor=$(K8S_MODULES_MINOR_VER) .PHONY: all all: build @@ -156,7 +156,7 @@ format: $(GOIMPORTS) # Generate golden files used in unit tests .PHONY: gen-test-golden gen-test-golden: -gen-test-golden: PKG = ./cmd/helm ./pkg/action +gen-test-golden: PKG = ./pkg/cmd ./pkg/action gen-test-golden: TESTFLAGS = -update gen-test-golden: test-unit diff --git a/README.md b/README.md index 2488147c7..5f4d71d4c 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # Helm [![Build Status](https://github.com/helm/helm/workflows/release/badge.svg)](https://github.com/helm/helm/actions?workflow=release) -[![Go Report Card](https://goreportcard.com/badge/github.com/helm/helm)](https://goreportcard.com/report/github.com/helm/helm) -[![GoDoc](https://img.shields.io/static/v1?label=godoc&message=reference&color=blue)](https://pkg.go.dev/helm.sh/helm/v3) +[![Go Report Card](https://goreportcard.com/badge/helm.sh/helm/v4)](https://goreportcard.com/report/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) [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/helm/helm/badge)](https://scorecard.dev/viewer/?uri=github.com/helm/helm) @@ -29,6 +29,11 @@ Think of it like apt/yum/homebrew for Kubernetes. - Charts can be stored on disk, or fetched from remote chart repositories (like Debian or RedHat packages) +## Helm Development and Stable Versions + +Helm v4 is currently under development on the `main` branch. This is unstable and the APIs within the Go SDK and at the command line are changing. +Helm v3 (current stable) is maintained on the `dev-v3` branch. APIs there follow semantic versioning. + ## Install Binary downloads of the Helm client can be found on [the Releases page](https://github.com/helm/helm/releases/latest). @@ -39,6 +44,7 @@ If you want to use a package manager: - [Homebrew](https://brew.sh/) users can use `brew install helm`. - [Chocolatey](https://chocolatey.org/) users can use `choco install kubernetes-helm`. +- [Winget](https://learn.microsoft.com/en-us/windows/package-manager/) users can use `winget install Helm.Helm`. - [Scoop](https://scoop.sh/) users can use `scoop install helm`. - [Snapcraft](https://snapcraft.io/) users can use `snap install helm --classic`. - [Flox](https://flox.dev) users can use `flox install kubernetes-helm`. diff --git a/cmd/helm/helm.go b/cmd/helm/helm.go index 2fae7cddb..eefce5158 100644 --- a/cmd/helm/helm.go +++ b/cmd/helm/helm.go @@ -14,49 +14,19 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main // import "helm.sh/helm/v3/cmd/helm" +package main // import "helm.sh/helm/v4/cmd/helm" import ( - "fmt" - "io" - "log" + "log/slog" "os" - "strings" - "time" - - "github.com/spf13/cobra" - "sigs.k8s.io/yaml" // Import to initialize client auth plugins. _ "k8s.io/client-go/plugin/pkg/client/auth" - "helm.sh/helm/v3/pkg/action" - "helm.sh/helm/v3/pkg/cli" - "helm.sh/helm/v3/pkg/kube" - kubefake "helm.sh/helm/v3/pkg/kube/fake" - "helm.sh/helm/v3/pkg/release" - "helm.sh/helm/v3/pkg/storage/driver" + helmcmd "helm.sh/helm/v4/pkg/cmd" + "helm.sh/helm/v4/pkg/kube" ) -var settings = cli.New() - -func init() { - log.SetFlags(log.Lshortfile) -} - -func debug(format string, v ...interface{}) { - if settings.Debug { - timeNow := time.Now().String() - format = fmt.Sprintf("%s [debug] %s\n", timeNow, format) - log.Output(2, fmt.Sprintf(format, v...)) - } -} - -func warning(format string, v ...interface{}) { - format = fmt.Sprintf("WARNING: %s\n", format) - fmt.Fprintf(os.Stderr, format, v...) -} - func main() { // Setting the name of the app for managedFields in the Kubernetes client. // It is set here to the full name of "helm" so that renaming of helm to @@ -64,69 +34,19 @@ func main() { // manager as picked up by the automated name detection. kube.ManagedFieldsManager = "helm" - actionConfig := new(action.Configuration) - cmd, err := newRootCmd(actionConfig, os.Stdout, os.Args[1:]) + cmd, err := helmcmd.NewRootCmd(os.Stdout, os.Args[1:]) if err != nil { - warning("%+v", err) + slog.Warn("command failed", slog.Any("error", err)) os.Exit(1) } - // run when each command's execute method is called - cobra.OnInitialize(func() { - helmDriver := os.Getenv("HELM_DRIVER") - if err := actionConfig.Init(settings.RESTClientGetter(), settings.Namespace(), helmDriver, debug); err != nil { - log.Fatal(err) - } - if helmDriver == "memory" { - loadReleasesInMemory(actionConfig) - } - }) - if err := cmd.Execute(); err != nil { - debug("%+v", err) + slog.Debug("error", slog.Any("error", err)) switch e := err.(type) { - case pluginError: - os.Exit(e.code) + case helmcmd.PluginError: + os.Exit(e.Code) default: os.Exit(1) } } } - -// This function loads releases into the memory storage if the -// environment variable is properly set. -func loadReleasesInMemory(actionConfig *action.Configuration) { - filePaths := strings.Split(os.Getenv("HELM_MEMORY_DRIVER_DATA"), ":") - if len(filePaths) == 0 { - return - } - - store := actionConfig.Releases - mem, ok := store.Driver.(*driver.Memory) - if !ok { - // For an unexpected reason we are not dealing with the memory storage driver. - return - } - - actionConfig.KubeClient = &kubefake.PrintingKubeClient{Out: io.Discard} - - for _, path := range filePaths { - b, err := os.ReadFile(path) - if err != nil { - log.Fatal("Unable to read memory driver data", err) - } - - releases := []*release.Release{} - if err := yaml.Unmarshal(b, &releases); err != nil { - log.Fatal("Unable to unmarshal memory driver data: ", err) - } - - for _, rel := range releases { - if err := store.Create(rel); err != nil { - log.Fatal(err) - } - } - } - // Must reset namespace to the proper one - mem.SetNamespace(settings.Namespace()) -} diff --git a/cmd/helm/helm_test.go b/cmd/helm/helm_test.go index 7d0bf5751..5431daad0 100644 --- a/cmd/helm/helm_test.go +++ b/cmd/helm/helm_test.go @@ -18,153 +18,12 @@ package main import ( "bytes" - "io" "os" "os/exec" "runtime" - "strings" "testing" - - shellwords "github.com/mattn/go-shellwords" - "github.com/spf13/cobra" - - "helm.sh/helm/v3/internal/test" - "helm.sh/helm/v3/pkg/action" - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/cli" - kubefake "helm.sh/helm/v3/pkg/kube/fake" - "helm.sh/helm/v3/pkg/release" - "helm.sh/helm/v3/pkg/storage" - "helm.sh/helm/v3/pkg/storage/driver" - "helm.sh/helm/v3/pkg/time" ) -func testTimestamper() time.Time { return time.Unix(242085845, 0).UTC() } - -func init() { - action.Timestamper = testTimestamper -} - -func runTestCmd(t *testing.T, tests []cmdTestCase) { - t.Helper() - for _, tt := range tests { - for i := 0; i <= tt.repeat; i++ { - t.Run(tt.name, func(t *testing.T) { - defer resetEnv()() - - storage := storageFixture() - for _, rel := range tt.rels { - if err := storage.Create(rel); err != nil { - t.Fatal(err) - } - } - t.Logf("running cmd (attempt %d): %s", i+1, tt.cmd) - _, out, err := executeActionCommandC(storage, tt.cmd) - if tt.wantError && err == nil { - t.Errorf("expected error, got success with the following output:\n%s", out) - } - if !tt.wantError && err != nil { - t.Errorf("expected no error, got: '%v'", err) - } - if tt.golden != "" { - test.AssertGoldenString(t, out, tt.golden) - } - }) - } - } -} - -func storageFixture() *storage.Storage { - return storage.Init(driver.NewMemory()) -} - -func executeActionCommandC(store *storage.Storage, cmd string) (*cobra.Command, string, error) { - return executeActionCommandStdinC(store, nil, cmd) -} - -func executeActionCommandStdinC(store *storage.Storage, in *os.File, cmd string) (*cobra.Command, string, error) { - args, err := shellwords.Parse(cmd) - if err != nil { - return nil, "", err - } - - buf := new(bytes.Buffer) - - actionConfig := &action.Configuration{ - Releases: store, - KubeClient: &kubefake.PrintingKubeClient{Out: io.Discard}, - Capabilities: chartutil.DefaultCapabilities, - Log: func(_ string, _ ...interface{}) {}, - } - - root, err := newRootCmd(actionConfig, buf, args) - if err != nil { - return nil, "", err - } - - root.SetOut(buf) - root.SetErr(buf) - root.SetArgs(args) - - oldStdin := os.Stdin - if in != nil { - root.SetIn(in) - os.Stdin = in - } - - if mem, ok := store.Driver.(*driver.Memory); ok { - mem.SetNamespace(settings.Namespace()) - } - c, err := root.ExecuteC() - - result := buf.String() - - os.Stdin = oldStdin - - return c, result, err -} - -// cmdTestCase describes a test case that works with releases. -type cmdTestCase struct { - name string - cmd string - golden string - wantError bool - // Rels are the available releases at the start of the test. - rels []*release.Release - // Number of repeats (in case a feature was previously flaky and the test checks - // it's now stably producing identical results). 0 means test is run exactly once. - repeat int -} - -func executeActionCommand(cmd string) (*cobra.Command, string, error) { - return executeActionCommandC(storageFixture(), cmd) -} - -func resetEnv() func() { - origEnv := os.Environ() - return func() { - os.Clearenv() - for _, pair := range origEnv { - kv := strings.SplitN(pair, "=", 2) - os.Setenv(kv[0], kv[1]) - } - 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) } -} - func TestPluginExitCode(t *testing.T) { if os.Getenv("RUN_MAIN_FOR_TESTING") == "1" { os.Args = []string{"helm", "exitwith", "2"} @@ -190,10 +49,8 @@ func TestPluginExitCode(t *testing.T) { "RUN_MAIN_FOR_TESTING=1", // See pkg/cli/environment.go for which envvars can be used for configuring these passes // and also see plugin_test.go for how a plugin env can be set up. - // We just does the same setup as plugin_test.go via envvars - "HELM_PLUGINS=testdata/helmhome/helm/plugins", - "HELM_REPOSITORY_CONFIG=testdata/helmhome/helm/repositories.yaml", - "HELM_REPOSITORY_CACHE=testdata/helmhome/helm/repository", + // This mimics the "exitwith" test case in TestLoadPlugins using envvars + "HELM_PLUGINS=../../pkg/cmd/testdata/helmhome/helm/plugins", ) stdout := &bytes.Buffer{} stderr := &bytes.Buffer{} diff --git a/cmd/helm/testdata/output/get-metadata.json b/cmd/helm/testdata/output/get-metadata.json deleted file mode 100644 index 1d5152b24..000000000 --- a/cmd/helm/testdata/output/get-metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"name":"thomas-guide","chart":"foo","version":"0.1.0-beta.1","appVersion":"1.0","namespace":"default","revision":1,"status":"deployed","deployedAt":"1977-09-02T22:04:05Z"} diff --git a/cmd/helm/testdata/output/get-metadata.yaml b/cmd/helm/testdata/output/get-metadata.yaml deleted file mode 100644 index b6d49b038..000000000 --- a/cmd/helm/testdata/output/get-metadata.yaml +++ /dev/null @@ -1,8 +0,0 @@ -appVersion: "1.0" -chart: foo -deployedAt: "1977-09-02T22:04:05Z" -name: thomas-guide -namespace: default -revision: 1 -status: deployed -version: 0.1.0-beta.1 diff --git a/cmd/helm/testdata/output/version-client-shorthand.txt b/cmd/helm/testdata/output/version-client-shorthand.txt deleted file mode 100644 index 1623b57d4..000000000 --- a/cmd/helm/testdata/output/version-client-shorthand.txt +++ /dev/null @@ -1 +0,0 @@ -version.BuildInfo{Version:"v3.16", GitCommit:"", GitTreeState:"", GoVersion:""} diff --git a/cmd/helm/testdata/output/version-client.txt b/cmd/helm/testdata/output/version-client.txt deleted file mode 100644 index 1623b57d4..000000000 --- a/cmd/helm/testdata/output/version-client.txt +++ /dev/null @@ -1 +0,0 @@ -version.BuildInfo{Version:"v3.16", GitCommit:"", GitTreeState:"", GoVersion:""} diff --git a/cmd/helm/testdata/output/version-short.txt b/cmd/helm/testdata/output/version-short.txt deleted file mode 100644 index 4951775a9..000000000 --- a/cmd/helm/testdata/output/version-short.txt +++ /dev/null @@ -1 +0,0 @@ -v3.16 diff --git a/cmd/helm/testdata/output/version-template.txt b/cmd/helm/testdata/output/version-template.txt deleted file mode 100644 index f1daff603..000000000 --- a/cmd/helm/testdata/output/version-template.txt +++ /dev/null @@ -1 +0,0 @@ -Version: v3.16 \ No newline at end of file diff --git a/cmd/helm/testdata/output/version.txt b/cmd/helm/testdata/output/version.txt deleted file mode 100644 index 1623b57d4..000000000 --- a/cmd/helm/testdata/output/version.txt +++ /dev/null @@ -1 +0,0 @@ -version.BuildInfo{Version:"v3.16", GitCommit:"", GitTreeState:"", GoVersion:""} diff --git a/go.mod b/go.mod index f77a5ec23..912d382bc 100644 --- a/go.mod +++ b/go.mod @@ -1,20 +1,20 @@ -module helm.sh/helm/v3 +module helm.sh/helm/v4 -go 1.22.0 +go 1.24.0 require ( github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 - github.com/BurntSushi/toml v1.4.0 + github.com/BurntSushi/toml v1.5.0 github.com/DATA-DOG/go-sqlmock v1.5.2 github.com/Masterminds/semver/v3 v3.3.0 github.com/Masterminds/sprig/v3 v3.3.0 github.com/Masterminds/squirrel v1.5.4 github.com/Masterminds/vcs v1.13.3 github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 - github.com/containerd/containerd v1.7.23 - github.com/cyphar/filepath-securejoin v0.3.4 - github.com/distribution/distribution/v3 v3.0.0-rc.1 - github.com/evanphx/json-patch v5.9.0+incompatible + github.com/cyphar/filepath-securejoin v0.4.1 + github.com/distribution/distribution/v3 v3.0.0 + github.com/evanphx/json-patch v5.9.11+incompatible + github.com/fluxcd/cli-utils v0.36.0-flux.12 github.com/foxcpp/go-mockdns v1.1.0 github.com/gobwas/glob v0.2.3 github.com/gofrs/flock v0.12.1 @@ -24,34 +24,35 @@ require ( github.com/lib/pq v1.10.9 github.com/mattn/go-shellwords v1.0.12 github.com/mitchellh/copystructure v1.2.0 - github.com/moby/term v0.5.0 - github.com/opencontainers/image-spec v1.1.0 + github.com/moby/term v0.5.2 + github.com/opencontainers/image-spec v1.1.1 github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 github.com/pkg/errors v0.9.1 - github.com/rubenv/sql-migrate v1.7.0 - github.com/sirupsen/logrus v1.9.3 - github.com/spf13/cobra v1.8.1 - github.com/spf13/pflag v1.0.5 - github.com/stretchr/testify v1.9.0 - github.com/xeipuuv/gojsonschema v1.2.0 - golang.org/x/crypto v0.29.0 - golang.org/x/term v0.26.0 - golang.org/x/text v0.20.0 - k8s.io/api v0.31.2 - k8s.io/apiextensions-apiserver v0.31.2 - k8s.io/apimachinery v0.31.2 - k8s.io/apiserver v0.31.2 - k8s.io/cli-runtime v0.31.2 - k8s.io/client-go v0.31.2 + github.com/rubenv/sql-migrate v1.8.0 + github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 + github.com/spf13/cobra v1.9.1 + github.com/spf13/pflag v1.0.6 + github.com/stretchr/testify v1.10.0 + golang.org/x/crypto v0.37.0 + golang.org/x/term v0.31.0 + golang.org/x/text v0.24.0 + gopkg.in/yaml.v3 v3.0.1 + k8s.io/api v0.32.3 + k8s.io/apiextensions-apiserver v0.32.3 + k8s.io/apimachinery v0.32.3 + k8s.io/apiserver v0.32.3 + k8s.io/cli-runtime v0.32.3 + k8s.io/client-go v0.32.3 k8s.io/klog/v2 v2.130.1 - k8s.io/kubectl v0.31.2 - oras.land/oras-go v1.2.5 + k8s.io/kubectl v0.32.3 + oras.land/oras-go/v2 v2.5.0 + sigs.k8s.io/controller-runtime v0.20.4 sigs.k8s.io/yaml v1.4.0 ) require ( dario.cat/mergo v1.0.1 // indirect - github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect + github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect github.com/MakeNowJust/heredoc v1.0.0 // indirect github.com/Masterminds/goutils v1.1.1 // indirect github.com/beorn7/perks v1.0.1 // indirect @@ -60,131 +61,123 @@ require ( github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/chai2010/gettext-go v1.0.2 // indirect - github.com/containerd/errdefs v0.3.0 // indirect - github.com/containerd/log v0.1.0 // indirect - github.com/containerd/platforms v0.2.1 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect - github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/distribution/reference v0.6.0 // indirect - github.com/docker/cli v25.0.1+incompatible // indirect - github.com/docker/distribution v2.8.3+incompatible // indirect - github.com/docker/docker v25.0.6+incompatible // indirect github.com/docker/docker-credential-helpers v0.8.2 // indirect - github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect github.com/docker/go-metrics v0.0.1 // indirect - github.com/emicklei/go-restful/v3 v3.11.0 // indirect - github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // 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/fatih/color v1.13.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect - github.com/go-errors/errors v1.4.2 // indirect + github.com/go-errors/errors v1.5.1 // indirect github.com/go-gorp/gorp/v3 v3.1.0 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-openapi/jsonpointer v0.19.6 // indirect - github.com/go-openapi/jsonreference v0.20.2 // indirect - github.com/go-openapi/swag v0.22.4 // indirect + github.com/go-openapi/jsonpointer v0.21.0 // indirect + github.com/go-openapi/jsonreference v0.21.0 // indirect + github.com/go-openapi/swag v0.23.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.4 // indirect - github.com/google/btree v1.0.1 // indirect - github.com/google/gnostic-models v0.6.8 // indirect + github.com/google/btree v1.1.3 // indirect + github.com/google/gnostic-models v0.6.9 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/handlers v1.5.2 // indirect github.com/gorilla/mux v1.8.1 // indirect - github.com/gorilla/websocket v1.5.0 // indirect - github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect + github.com/gorilla/websocket v1.5.3 // indirect + github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/golang-lru/arc/v2 v2.0.5 // indirect github.com/hashicorp/golang-lru/v2 v2.0.5 // indirect github.com/huandu/xstrings v1.5.0 // indirect - github.com/imdario/mergo v0.3.16 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/compress v1.17.9 // indirect + github.com/klauspost/compress v1.17.11 // indirect github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect - github.com/mailru/easyjson v0.7.7 // indirect + github.com/mailru/easyjson v0.9.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.17 // indirect github.com/mattn/go-runewidth v0.0.9 // indirect github.com/miekg/dns v1.1.57 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect - github.com/moby/locker v1.0.1 // indirect - github.com/moby/spdystream v0.4.0 // indirect + github.com/moby/spdystream v0.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect + github.com/onsi/gomega v1.36.2 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/prometheus/client_golang v1.20.1 // indirect + github.com/prometheus/client_golang v1.20.5 // indirect github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.55.0 // indirect + github.com/prometheus/common v0.62.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5 // indirect github.com/redis/go-redis/extra/redisotel/v9 v9.0.5 // indirect - github.com/redis/go-redis/v9 v9.1.0 // indirect + github.com/redis/go-redis/v9 v9.7.3 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/shopspring/decimal v1.4.0 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect github.com/spf13/cast v1.7.0 // indirect github.com/x448/float16 v0.8.4 // indirect - github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect - github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xlab/treeprint v1.2.0 // indirect - go.opentelemetry.io/contrib/bridges/prometheus v0.54.0 // indirect - go.opentelemetry.io/contrib/exporters/autoexport v0.54.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect - go.opentelemetry.io/otel v1.29.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.5.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.29.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.29.0 // indirect - go.opentelemetry.io/otel/exporters/prometheus v0.51.0 // indirect - go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.5.0 // indirect - go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0 // indirect - go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.29.0 // indirect - go.opentelemetry.io/otel/log v0.5.0 // indirect - go.opentelemetry.io/otel/metric v1.29.0 // indirect - go.opentelemetry.io/otel/sdk v1.29.0 // indirect - go.opentelemetry.io/otel/sdk/log v0.5.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.29.0 // indirect - go.opentelemetry.io/otel/trace v1.29.0 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/contrib/bridges/prometheus v0.57.0 // indirect + go.opentelemetry.io/contrib/exporters/autoexport v0.57.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.57.0 // indirect + go.opentelemetry.io/otel v1.34.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.32.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.32.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0 // indirect + go.opentelemetry.io/otel/exporters/prometheus v0.54.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.8.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.32.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.32.0 // indirect + go.opentelemetry.io/otel/log v0.8.0 // indirect + go.opentelemetry.io/otel/metric v1.34.0 // indirect + go.opentelemetry.io/otel/sdk v1.32.0 // indirect + go.opentelemetry.io/otel/sdk/log v0.8.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.32.0 // indirect + go.opentelemetry.io/otel/trace v1.34.0 // indirect go.opentelemetry.io/proto/otlp v1.3.1 // indirect - go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect - golang.org/x/mod v0.17.0 // indirect - golang.org/x/net v0.29.0 // indirect - golang.org/x/oauth2 v0.23.0 // indirect - golang.org/x/sync v0.9.0 // indirect - golang.org/x/sys v0.27.0 // indirect - golang.org/x/time v0.6.0 // indirect - golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect - google.golang.org/grpc v1.66.2 // indirect - google.golang.org/protobuf v1.34.2 // indirect + golang.org/x/mod v0.22.0 // indirect + golang.org/x/net v0.38.0 // indirect + golang.org/x/oauth2 v0.28.0 // indirect + golang.org/x/sync v0.13.0 // indirect + golang.org/x/sys v0.32.0 // indirect + golang.org/x/time v0.9.0 // indirect + golang.org/x/tools v0.29.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28 // indirect + google.golang.org/grpc v1.68.0 // indirect + google.golang.org/protobuf v1.36.4 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/component-base v0.31.2 // indirect - k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect - k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect - sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect - sigs.k8s.io/kustomize/api v0.17.2 // indirect - sigs.k8s.io/kustomize/kyaml v0.17.1 // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect + k8s.io/component-base v0.32.3 // indirect + k8s.io/kube-openapi v0.0.0-20241212222426-2c72e554b1e7 // indirect + k8s.io/utils v0.0.0-20241210054802-24370beab758 // indirect + sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect + sigs.k8s.io/kustomize/api v0.18.0 // indirect + sigs.k8s.io/kustomize/kyaml v0.19.0 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.5.0 // indirect ) diff --git a/go.sum b/go.sum index fe4db3afe..ea10b6adc 100644 --- a/go.sum +++ b/go.sum @@ -1,15 +1,13 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU= github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= -github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= -github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= -github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= +github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= +github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= @@ -24,10 +22,6 @@ github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8 github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= github.com/Masterminds/vcs v1.13.3 h1:IIA2aBdXvfbIM+yl/eTnL4hb1XwdpvuQLglAix1gweE= github.com/Masterminds/vcs v1.13.3/go.mod h1:TiE7xuEjl1N4j016moRd6vezp6e6Lz23gypeXfzXeW8= -github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= -github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= -github.com/Microsoft/hcsshim v0.11.7 h1:vl/nj3Bar/CvJSYo7gIQPyRWc9f3c6IeSNavBTSZNZQ= -github.com/Microsoft/hcsshim v0.11.7/go.mod h1:MV8xMfmECjl5HdO7U/3/hFVnkmSBjAjmA09d4bExKcU= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= @@ -43,89 +37,66 @@ github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2y github.com/bshuster-repo/logrus-logstash-hook v1.0.0 h1:e+C0SB5R1pu//O4MQ3f9cFuPGoOVeF2fE4Og9otCc70= github.com/bshuster-repo/logrus-logstash-hook v1.0.0/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= github.com/bsm/ginkgo/v2 v2.7.0/go.mod h1:AiKlXPm7ItEHNc/2+OkrNG4E0ITzojb9/xWzvQ9XZ9w= -github.com/bsm/ginkgo/v2 v2.9.5 h1:rtVBYPs3+TC5iLUVOis1B9tjLTup7Cj5IfzosKtvTJ0= -github.com/bsm/ginkgo/v2 v2.9.5/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= -github.com/bsm/gomega v1.26.0 h1:LhQm+AFcgV2M0WyKroMASzAzCAJVpAxQXv4SaI9a69Y= +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= github.com/bsm/gomega v1.26.0/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNSjIRk= github.com/chai2010/gettext-go v1.0.2/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM= -github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw= -github.com/containerd/containerd v1.7.23 h1:H2CClyUkmpKAGlhQp95g2WXHfLYc7whAuvZGBNYOOwQ= -github.com/containerd/containerd v1.7.23/go.mod h1:7QUzfURqZWCZV7RLNEn1XjUCQLEf0bkaK4GjUaZehxw= -github.com/containerd/continuity v0.4.2 h1:v3y/4Yz5jwnvqPKJJ+7Wf93fyWoCB3F5EclWG023MDM= -github.com/containerd/continuity v0.4.2/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ= -github.com/containerd/errdefs v0.3.0 h1:FSZgGOeK4yuT/+DnF07/Olde/q4KBoMsaamhXxIMDp4= -github.com/containerd/errdefs v0.3.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= -github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= -github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= -github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= -github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= -github.com/cyphar/filepath-securejoin v0.3.4 h1:VBWugsJh2ZxJmLFSM06/0qzQyiQX2Qs0ViKrUAcqdZ8= -github.com/cyphar/filepath-securejoin v0.3.4/go.mod h1:8s/MCNJREmFK0H02MF6Ihv1nakJe4L/w3WZLHNkvlYM= +github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s= +github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= -github.com/distribution/distribution/v3 v3.0.0-rc.1 h1:6M4ewmPBUhF7wtQ8URLOQ1W/PQuVKiD1u8ymwLDUGqQ= -github.com/distribution/distribution/v3 v3.0.0-rc.1/go.mod h1:tFjaPDeHCrLg28e4feBIy27cP+qmrc/mvkl6MFIfVi4= +github.com/distribution/distribution/v3 v3.0.0 h1:q4R8wemdRQDClzoNNStftB2ZAfqOiN6UX90KJc4HjyM= +github.com/distribution/distribution/v3 v3.0.0/go.mod h1:tRNuFoZsUdyRVegq8xGNeds4KLjwLCRin/tTo6i1DhU= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/cli v25.0.1+incompatible h1:mFpqnrS6Hsm3v1k7Wa/BO23oz0k121MTbTO1lpcGSkU= -github.com/docker/cli v25.0.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= -github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v25.0.6+incompatible h1:5cPwbwriIcsua2REJe8HqQV+6WlWc1byg2QSXzBxBGg= -github.com/docker/docker v25.0.6+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= +github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo= github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M= -github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= -github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8= github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8= github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= -github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1 h1:ZClxb8laGDf5arXfYcAtECDFgAgHklGI8CxgjHnXKJ4= -github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= -github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= -github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/evanphx/json-patch v5.9.0+incompatible h1:fBXyNpNMuTTDdquAq/uisOr2lShz4oaXpDTX2bLe7ls= -github.com/evanphx/json-patch v5.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d h1:105gxyaGwCFad8crR9dcMQWvV9Hvulu6hwUh4tWPJnM= -github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4= +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/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/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/go.mod h1:OSYXu++VVOHnXeitef/D8n/6y4QV8uLHSFXX4NeXMGc= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/fluxcd/cli-utils v0.36.0-flux.12 h1:8cD6SmaKa/lGo0KCu0XWiGrXJMLMBQwSsnoP0cG+Gjw= +github.com/fluxcd/cli-utils v0.36.0-flux.12/go.mod h1:Nb/zMqsJAzjz4/HIsEc2LTqxC6eC0rV26t4hkJT/F9o= github.com/foxcpp/go-mockdns v1.1.0 h1:jI0rD8M0wuYAxL7r/ynTrCQQq0BVqfB99Vgk7DlmewI= github.com/foxcpp/go-mockdns v1.1.0/go.mod h1:IhLeSFGed3mJIAXPH2aiRQB+kqz7oqu8ld2qVbOu7Wk= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= -github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= -github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= +github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk= +github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs= github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpjH8TixEw= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= @@ -136,13 +107,14 @@ github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= -github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= -github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= -github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= -github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= -github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU= -github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= +github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= +github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= +github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= +github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= +github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= @@ -156,39 +128,24 @@ github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeH github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= -github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= -github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= -github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= +github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= +github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw= +github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af h1:kmjWCqn2qkEml422C2Rrd27c3VGxi6a/6HNq8QmHRKM= -github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= +github.com/google/pprof v0.0.0-20250128161936-077ca0a936bf h1:BvBLUD2hkvLI3dJTJMiopAq8/wp43AAZKTP7qdpptbU= +github.com/google/pprof v0.0.0-20250128161936-077ca0a936bf/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -197,14 +154,14 @@ github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyE github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= -github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= -github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY= github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo= -github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM= -github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= +github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= +github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0 h1:ad0vkEBuk23VJzZR9nkLVG0YAoN9coASF1GusYX6AlU= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0/go.mod h1:igFoXX2ELCW06bol23DWPB5BEWfZISOzSP5K2sbLea0= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -216,8 +173,6 @@ github.com/hashicorp/golang-lru/v2 v2.0.5 h1:wW7h1TG88eUIJ2i69gaE3uNVtEPIagzhGvH github.com/hashicorp/golang-lru/v2 v2.0.5/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= -github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= -github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= @@ -232,15 +187,12 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE= -github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= -github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= +github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= @@ -253,8 +205,8 @@ github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= -github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= -github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= +github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= @@ -278,16 +230,10 @@ github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQ github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= -github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= -github.com/moby/spdystream v0.4.0 h1:Vy79D6mHeJJjiPdFEL2yku1kl0chZpJfZcPpb16BRl8= -github.com/moby/spdystream v0.4.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= -github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78= -github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI= -github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= -github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= -github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= -github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= +github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU= +github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= +github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= +github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -302,14 +248,14 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8m github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= -github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA= -github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To= -github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk= -github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0= +github.com/onsi/ginkgo/v2 v2.22.2 h1:/3X8Panh8/WwhU/3Ssa6rCKqPLuAkVY2I0RoyDLySlU= +github.com/onsi/ginkgo/v2 v2.22.2/go.mod h1:oeMosUL+8LtarXBHu/c0bx2D/K9zyQ6uX3cTyztHwsk= +github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8= +github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= -github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= +github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= +github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI= @@ -325,17 +271,16 @@ github.com/poy/onpar v1.1.2/go.mod h1:6X8FLNoxyr9kkmnlqpK6LSoiOtrO6MICtWwEuWkLjz github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= -github.com/prometheus/client_golang v1.20.1 h1:IMJXHOD6eARkQpxo8KkhgEVFlBNm+nkrFUyGlIu7Na8= -github.com/prometheus/client_golang v1.20.1/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= +github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= +github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= -github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= -github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= +github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= +github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= @@ -346,14 +291,16 @@ github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5/go.mod h1:fyalQWdtzDBECAQFBJu github.com/redis/go-redis/extra/redisotel/v9 v9.0.5 h1:EfpWLLCyXw8PSM2/XNJLjI3Pb27yVE+gIAfeqp8LUCc= github.com/redis/go-redis/extra/redisotel/v9 v9.0.5/go.mod h1:WZjPDy7VNzn77AAfnAfVjZNvfJTYfPetfZk5yoSTLaQ= github.com/redis/go-redis/v9 v9.0.5/go.mod h1:WqMKv5vnQbRuZstUwxQI195wHy+t4PuXDOjzMvcuQHk= -github.com/redis/go-redis/v9 v9.1.0 h1:137FnGdk+EQdCbye1FW+qOEcY5S+SpY9T0NiuqvtfMY= -github.com/redis/go-redis/v9 v9.1.0/go.mod h1:urWj3He21Dj5k4TK1y59xH8Uj6ATueP8AH1cY3lZl4c= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= -github.com/rubenv/sql-migrate v1.7.0 h1:HtQq1xyTN2ISmQDggnh0c9U3JlP8apWh8YO2jzlXpTI= -github.com/rubenv/sql-migrate v1.7.0/go.mod h1:S4wtDEG1CKn+0ShpTtzWhFpHHI5PvCUtiGI+C+Z2THE= +github.com/redis/go-redis/v9 v9.7.3 h1:YpPyAayJV+XErNsatSElgRZZVCwXX9QzkKYNvO7x0wM= +github.com/redis/go-redis/v9 v9.7.3/go.mod h1:bGUrSggJ9X9GUmZpZNEOQKaANxSGgOEBRltRTZHSvrA= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/rubenv/sql-migrate v1.8.0 h1:dXnYiJk9k3wetp7GfQbKJcPHjVJL6YK19tKj8t2Ns0o= +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/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.1/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/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= @@ -363,86 +310,79 @@ github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= -github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= -github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= +github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= +github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= +github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= -github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= -github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= -github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= -github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= -go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.opentelemetry.io/contrib/bridges/prometheus v0.54.0 h1:WWL67oxtknNVMb70lJXxXruf8UyK/a9hmIE1XO3Uedg= -go.opentelemetry.io/contrib/bridges/prometheus v0.54.0/go.mod h1:LqNcnXmyULp8ertk4hUTVtSUvKXj4h1Mx7gUCSSr/q0= -go.opentelemetry.io/contrib/exporters/autoexport v0.54.0 h1:dTmcmVm4J54IRPGm5oVjLci1uYat4UDea84E2tyBaAk= -go.opentelemetry.io/contrib/exporters/autoexport v0.54.0/go.mod h1:zPp5Fwpq2Hc7xMtVttg6GhZMcfTESjVbY9ONw2o/Dc4= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8= -go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= -go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.5.0 h1:4d++HQ+Ihdl+53zSjtsCUFDmNMju2FC9qFkUlTxPLqo= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.5.0/go.mod h1:mQX5dTO3Mh5ZF7bPKDkt5c/7C41u/SiDr9XgTpzXXn8= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0 h1:k6fQVDQexDE+3jG2SfCQjnHS7OamcP73YMoxEVq5B6k= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0/go.mod h1:t4BrYLHU450Zo9fnydWlIuswB1bm7rM8havDpWOJeDo= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0 h1:xvhQxJ/C9+RTnAj5DpTg7LSM1vbbMTiXt7e9hsfqHNw= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0/go.mod h1:Fcvs2Bz1jkDM+Wf5/ozBGmi3tQ/c9zPKLnsipnfhGAo= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0 h1:dIIDULZJpgdiHz5tXrTgKIMLkus6jEFa7x5SOKcyR7E= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0/go.mod h1:jlRVBe7+Z1wyxFSUs48L6OBQZ5JwH2Hg/Vbl+t9rAgI= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.29.0 h1:nSiV3s7wiCam610XcLbYOmMfJxB9gO4uK3Xgv5gmTgg= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.29.0/go.mod h1:hKn/e/Nmd19/x1gvIHwtOwVWM+VhuITSWip3JUDghj0= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.29.0 h1:JAv0Jwtl01UFiyWZEMiJZBiTlv5A50zNs8lsthXqIio= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.29.0/go.mod h1:QNKLmUEAq2QUbPQUfvw4fmv0bgbK7UlOSFCnXyfvSNc= -go.opentelemetry.io/otel/exporters/prometheus v0.51.0 h1:G7uexXb/K3T+T9fNLCCKncweEtNEBMTO+46hKX5EdKw= -go.opentelemetry.io/otel/exporters/prometheus v0.51.0/go.mod h1:v0mFe5Kk7woIh938mrZBJBmENYquyA0IICrlYm4Y0t4= -go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.5.0 h1:ThVXnEsdwNcxdBO+r96ci1xbF+PgNjwlk457VNuJODo= -go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.5.0/go.mod h1:rHWcSmC4q2h3gje/yOq6sAOaq8+UHxN/Ru3BbmDXOfY= -go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0 h1:WDdP9acbMYjbKIyJUhTvtzj601sVJOqgWdUxSdR/Ysc= -go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0/go.mod h1:BLbf7zbNIONBLPwvFnwNHGj4zge8uTCM/UPIVW1Mq2I= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.29.0 h1:X3ZjNp36/WlkSYx0ul2jw4PtbNEDDeLskw3VPsrpYM0= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.29.0/go.mod h1:2uL/xnOXh0CHOBFCWXz5u1A4GXLiW+0IQIzVbeOEQ0U= -go.opentelemetry.io/otel/log v0.5.0 h1:x1Pr6Y3gnXgl1iFBwtGy1W/mnzENoK0w0ZoaeOI3i30= -go.opentelemetry.io/otel/log v0.5.0/go.mod h1:NU/ozXeGuOR5/mjCRXYbTC00NFJ3NYuraV/7O78F0rE= -go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= -go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= -go.opentelemetry.io/otel/sdk v1.29.0 h1:vkqKjk7gwhS8VaWb0POZKmIEDimRCMsopNYnriHyryo= -go.opentelemetry.io/otel/sdk v1.29.0/go.mod h1:pM8Dx5WKnvxLCb+8lG1PRNIDxu9g9b9g59Qr7hfAAok= -go.opentelemetry.io/otel/sdk/log v0.5.0 h1:A+9lSjlZGxkQOr7QSBJcuyyYBw79CufQ69saiJLey7o= -go.opentelemetry.io/otel/sdk/log v0.5.0/go.mod h1:zjxIW7sw1IHolZL2KlSAtrUi8JHttoeiQy43Yl3WuVQ= -go.opentelemetry.io/otel/sdk/metric v1.29.0 h1:K2CfmJohnRgvZ9UAj2/FhIf/okdWcNdBwe1m8xFXiSY= -go.opentelemetry.io/otel/sdk/metric v1.29.0/go.mod h1:6zZLdCl2fkauYoZIOn/soQIDSWFmNSRcICarHfuhNJQ= -go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= -go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/contrib/bridges/prometheus v0.57.0 h1:UW0+QyeyBVhn+COBec3nGhfnFe5lwB0ic1JBVjzhk0w= +go.opentelemetry.io/contrib/bridges/prometheus v0.57.0/go.mod h1:ppciCHRLsyCio54qbzQv0E4Jyth/fLWDTJYfvWpcSVk= +go.opentelemetry.io/contrib/exporters/autoexport v0.57.0 h1:jmTVJ86dP60C01K3slFQa2NQ/Aoi7zA+wy7vMOKD9H4= +go.opentelemetry.io/contrib/exporters/autoexport v0.57.0/go.mod h1:EJBheUMttD/lABFyLXhce47Wr6DPWYReCzaZiXadH7g= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.57.0 h1:DheMAlT6POBP+gh8RUH19EOTnQIor5QE0uSRPtzCpSw= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.57.0/go.mod h1:wZcGmeVO9nzP67aYSLDqXNWK87EZWhi7JWj1v7ZXf94= +go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= +go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0 h1:WzNab7hOOLzdDF/EoWCt4glhrbMPVMOO5JYTmpz36Ls= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0/go.mod h1:hKvJwTzJdp90Vh7p6q/9PAOd55dI6WA6sWj62a/JvSs= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0 h1:S+LdBGiQXtJdowoJoQPEtI52syEP/JYBUpjO49EQhV8= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0/go.mod h1:5KXybFvPGds3QinJWQT7pmXf+TN5YIa7CNYObWRkj50= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0 h1:j7ZSD+5yn+lo3sGV69nW04rRR0jhYnBwjuX3r0HvnK0= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0/go.mod h1:WXbYJTUaZXAbYd8lbgGuvih0yuCfOFC5RJoYnoLcGz8= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.32.0 h1:t/Qur3vKSkUCcDVaSumWF2PKHt85pc7fRvFuoVT8qFU= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.32.0/go.mod h1:Rl61tySSdcOJWoEgYZVtmnKdA0GeKrSqkHC1t+91CH8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0 h1:IJFEoHiytixx8cMiVAO+GmHR6Frwu+u5Ur8njpFO6Ac= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0/go.mod h1:3rHrKNtLIoS0oZwkY2vxi+oJcwFRWdtUyRII+so45p8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.32.0 h1:9kV11HXBHZAvuPUZxmMWrH8hZn/6UnHX4K0mu36vNsU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.32.0/go.mod h1:JyA0FHXe22E1NeNiHmVp7kFHglnexDQ7uRWDiiJ1hKQ= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0 h1:cMyu9O88joYEaI47CnQkxO1XZdpoTF9fEnW2duIddhw= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0/go.mod h1:6Am3rn7P9TVVeXYG+wtcGE7IE1tsQ+bP3AuWcKt/gOI= +go.opentelemetry.io/otel/exporters/prometheus v0.54.0 h1:rFwzp68QMgtzu9PgP3jm9XaMICI6TsofWWPcBDKwlsU= +go.opentelemetry.io/otel/exporters/prometheus v0.54.0/go.mod h1:QyjcV9qDP6VeK5qPyKETvNjmaaEc7+gqjh4SS0ZYzDU= +go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.8.0 h1:CHXNXwfKWfzS65yrlB2PVds1IBZcdsX8Vepy9of0iRU= +go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.8.0/go.mod h1:zKU4zUgKiaRxrdovSS2amdM5gOc59slmo/zJwGX+YBg= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.32.0 h1:SZmDnHcgp3zwlPBS2JX2urGYe/jBKEIT6ZedHRUyCz8= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.32.0/go.mod h1:fdWW0HtZJ7+jNpTKUR0GpMEDP69nR8YBJQxNiVCE3jk= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.32.0 h1:cC2yDI3IQd0Udsux7Qmq8ToKAx1XCilTQECZ0KDZyTw= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.32.0/go.mod h1:2PD5Ex6z8CFzDbTdOlwyNIUywRr1DN0ospafJM1wJ+s= +go.opentelemetry.io/otel/log v0.8.0 h1:egZ8vV5atrUWUbnSsHn6vB8R21G2wrKqNiDt3iWertk= +go.opentelemetry.io/otel/log v0.8.0/go.mod h1:M9qvDdUTRCopJcGRKg57+JSQ9LgLBrwwfC32epk5NX8= +go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= +go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= +go.opentelemetry.io/otel/sdk v1.32.0 h1:RNxepc9vK59A8XsgZQouW8ue8Gkb4jpWtJm9ge5lEG4= +go.opentelemetry.io/otel/sdk v1.32.0/go.mod h1:LqgegDBjKMmb2GC6/PrTnteJG39I8/vJCAP9LlJXEjU= +go.opentelemetry.io/otel/sdk/log v0.8.0 h1:zg7GUYXqxk1jnGF/dTdLPrK06xJdrXgqgFLnI4Crxvs= +go.opentelemetry.io/otel/sdk/log v0.8.0/go.mod h1:50iXr0UVwQrYS45KbruFrEt4LvAdCaWWgIrsN3ZQggo= +go.opentelemetry.io/otel/sdk/metric v1.32.0 h1:rZvFnvmvawYb0alrYkjraqJq0Z4ZUJAiyYCU9snn1CU= +go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ= +go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= +go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= -go.starlark.net v0.0.0-20230525235612-a134d8f9ddca h1:VdD38733bfYv5tUZwEIskMM93VanwNIi5bIKnDrJdEY= -go.starlark.net v0.0.0-20230525235612-a134d8f9ddca/go.mod h1:jxU+3+j+71eXOW14274+SmmuW82qJzl6iZSeqEtTGds= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= 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-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -451,25 +391,17 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= -golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= -golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= +golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= 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.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= -golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= +golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/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-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -482,12 +414,10 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= -golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= -golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= -golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= +golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc= +golang.org/x/oauth2 v0.28.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-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -498,9 +428,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.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.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= -golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= +golang.org/x/sync v0.13.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-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -522,18 +451,17 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= -golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= +golang.org/x/sys v0.32.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-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww= -golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU= -golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E= +golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o= +golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= @@ -541,15 +469,11 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= -golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= -golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= -golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= +golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= +golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= +golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= @@ -557,36 +481,20 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk= -golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= -golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE= +golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588= 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-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 h1:hjSy6tcFQZ171igDaN5QHOw2n6vx40juYbC/x67CEhc= -google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:qpvKtACPCQhAdu3PyQgV4l3LMXZEtft7y8QcarRsp9I= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.66.2 h1:3QdXkuq3Bkh7w+ywLdLvM56cmGvQHUMZpiCzt6Rqaoo= -google.golang.org/grpc v1.66.2/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28 h1:M0KvPgPmDZHPlbRbaNU1APr28TvwvvdUPlSv7PUvy8g= +google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28/go.mod h1:dguCy7UOdZhTvLzDyt15+rOrawrpM4q7DD9dQ1P11P4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28 h1:XVhgTWWV3kGQlwJHR3upFWZeTsei6Oks1apkZSeonIE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= +google.golang.org/grpc v1.68.0 h1:aHQeeJbo8zAkAa3pRzrVjZlbz6uSfeOXlJNQM0RAbz0= +google.golang.org/grpc v1.68.0/go.mod h1:fmSPC5AsjSBCK54MyHRx48kpOti1/jRfOlwEWywNjWA= +google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM= +google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= @@ -596,47 +504,44 @@ gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWM gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o= -gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -k8s.io/api v0.31.2 h1:3wLBbL5Uom/8Zy98GRPXpJ254nEFpl+hwndmk9RwmL0= -k8s.io/api v0.31.2/go.mod h1:bWmGvrGPssSK1ljmLzd3pwCQ9MgoTsRCuK35u6SygUk= -k8s.io/apiextensions-apiserver v0.31.2 h1:W8EwUb8+WXBLu56ser5IudT2cOho0gAKeTOnywBLxd0= -k8s.io/apiextensions-apiserver v0.31.2/go.mod h1:i+Geh+nGCJEGiCGR3MlBDkS7koHIIKWVfWeRFiOsUcM= -k8s.io/apimachinery v0.31.2 h1:i4vUt2hPK56W6mlT7Ry+AO8eEsyxMD1U44NR22CLTYw= -k8s.io/apimachinery v0.31.2/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= -k8s.io/apiserver v0.31.2 h1:VUzOEUGRCDi6kX1OyQ801m4A7AUPglpsmGvdsekmcI4= -k8s.io/apiserver v0.31.2/go.mod h1:o3nKZR7lPlJqkU5I3Ove+Zx3JuoFjQobGX1Gctw6XuE= -k8s.io/cli-runtime v0.31.2 h1:7FQt4C4Xnqx8V1GJqymInK0FFsoC+fAZtbLqgXYVOLQ= -k8s.io/cli-runtime v0.31.2/go.mod h1:XROyicf+G7rQ6FQJMbeDV9jqxzkWXTYD6Uxd15noe0Q= -k8s.io/client-go v0.31.2 h1:Y2F4dxU5d3AQj+ybwSMqQnpZH9F30//1ObxOKlTI9yc= -k8s.io/client-go v0.31.2/go.mod h1:NPa74jSVR/+eez2dFsEIHNa+3o09vtNaWwWwb1qSxSs= -k8s.io/component-base v0.31.2 h1:Z1J1LIaC0AV+nzcPRFqfK09af6bZ4D1nAOpWsy9owlA= -k8s.io/component-base v0.31.2/go.mod h1:9PeyyFN/drHjtJZMCTkSpQJS3U9OXORnHQqMLDz0sUQ= +k8s.io/api v0.32.3 h1:Hw7KqxRusq+6QSplE3NYG4MBxZw1BZnq4aP4cJVINls= +k8s.io/api v0.32.3/go.mod h1:2wEDTXADtm/HA7CCMD8D8bK4yuBUptzaRhYcYEEYA3k= +k8s.io/apiextensions-apiserver v0.32.3 h1:4D8vy+9GWerlErCwVIbcQjsWunF9SUGNu7O7hiQTyPY= +k8s.io/apiextensions-apiserver v0.32.3/go.mod h1:8YwcvVRMVzw0r1Stc7XfGAzB/SIVLunqApySV5V7Dss= +k8s.io/apimachinery v0.32.3 h1:JmDuDarhDmA/Li7j3aPrwhpNBA94Nvk5zLeOge9HH1U= +k8s.io/apimachinery v0.32.3/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= +k8s.io/apiserver v0.32.3 h1:kOw2KBuHOA+wetX1MkmrxgBr648ksz653j26ESuWNY8= +k8s.io/apiserver v0.32.3/go.mod h1:q1x9B8E/WzShF49wh3ADOh6muSfpmFL0I2t+TG0Zdgc= +k8s.io/cli-runtime v0.32.3 h1:khLF2ivU2T6Q77H97atx3REY9tXiA3OLOjWJxUrdvss= +k8s.io/cli-runtime v0.32.3/go.mod h1:vZT6dZq7mZAca53rwUfdFSZjdtLyfF61mkf/8q+Xjak= +k8s.io/client-go v0.32.3 h1:RKPVltzopkSgHS7aS98QdscAgtgah/+zmpAogooIqVU= +k8s.io/client-go v0.32.3/go.mod h1:3v0+3k4IcT9bXTc4V2rt+d2ZPPG700Xy6Oi0Gdl2PaY= +k8s.io/component-base v0.32.3 h1:98WJvvMs3QZ2LYHBzvltFSeJjEx7t5+8s71P7M74u8k= +k8s.io/component-base v0.32.3/go.mod h1:LWi9cR+yPAv7cu2X9rZanTiFKB2kHA+JjmhkKjCZRpI= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= -k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= -k8s.io/kubectl v0.31.2 h1:gTxbvRkMBwvTSAlobiTVqsH6S8Aa1aGyBcu5xYLsn8M= -k8s.io/kubectl v0.31.2/go.mod h1:EyASYVU6PY+032RrTh5ahtSOMgoDRIux9V1JLKtG5xM= -k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A= -k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -oras.land/oras-go v1.2.5 h1:XpYuAwAb0DfQsunIyMfeET92emK8km3W4yEzZvUbsTo= -oras.land/oras-go v1.2.5/go.mod h1:PuAwRShRZCsZb7g8Ar3jKKQR/2A/qN+pkYxIOd/FAoo= -sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= -sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= -sigs.k8s.io/kustomize/api v0.17.2 h1:E7/Fjk7V5fboiuijoZHgs4aHuexi5Y2loXlVOAVAG5g= -sigs.k8s.io/kustomize/api v0.17.2/go.mod h1:UWTz9Ct+MvoeQsHcJ5e+vziRRkwimm3HytpZgIYqye0= -sigs.k8s.io/kustomize/kyaml v0.17.1 h1:TnxYQxFXzbmNG6gOINgGWQt09GghzgTP6mIurOgrLCQ= -sigs.k8s.io/kustomize/kyaml v0.17.1/go.mod h1:9V0mCjIEYjlXuCdYsSXvyoy2BTsLESH7TlGV81S282U= -sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= -sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= +k8s.io/kube-openapi v0.0.0-20241212222426-2c72e554b1e7 h1:hcha5B1kVACrLujCKLbr8XWMxCxzQx42DY8QKYJrDLg= +k8s.io/kube-openapi v0.0.0-20241212222426-2c72e554b1e7/go.mod h1:GewRfANuJ70iYzvn+i4lezLDAFzvjxZYK1gn1lWcfas= +k8s.io/kubectl v0.32.3 h1:VMi584rbboso+yjfv0d8uBHwwxbC438LKq+dXd5tOAI= +k8s.io/kubectl v0.32.3/go.mod h1:6Euv2aso5GKzo/UVMacV6C7miuyevpfI91SvBvV9Zdg= +k8s.io/utils v0.0.0-20241210054802-24370beab758 h1:sdbE21q2nlQtFh65saZY+rRM6x6aJJI8IUa1AmH/qa0= +k8s.io/utils v0.0.0-20241210054802-24370beab758/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +oras.land/oras-go/v2 v2.5.0 h1:o8Me9kLY74Vp5uw07QXPiitjsw7qNXi8Twd+19Zf02c= +oras.land/oras-go/v2 v2.5.0/go.mod h1:z4eisnLP530vwIOUOJeBIj0aGI0L1C3d53atvCBqZHg= +sigs.k8s.io/controller-runtime v0.20.4 h1:X3c+Odnxz+iPTRobG4tp092+CvBU9UK0t/bRf+n0DGU= +sigs.k8s.io/controller-runtime v0.20.4/go.mod h1:xg2XB0K5ShQzAgsoujxuKN4LNXR2LfwwHsPj7Iaw+XY= +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/kustomize/api v0.18.0 h1:hTzp67k+3NEVInwz5BHyzc9rGxIauoXferXyjv5lWPo= +sigs.k8s.io/kustomize/api v0.18.0/go.mod h1:f8isXnX+8b+SGLHQ6yO4JG1rdkZlvhaCf/uZbLVMb0U= +sigs.k8s.io/kustomize/kyaml v0.19.0 h1:RFge5qsO1uHhwJsu3ipV7RNolC7Uozc0jUBC/61XSlA= +sigs.k8s.io/kustomize/kyaml v0.19.0/go.mod h1:FeKD5jEOH+FbZPpqUghBP8mrLjJ3+zD3/rf9NNu1cwY= +sigs.k8s.io/structured-merge-diff/v4 v4.5.0 h1:nbCitCK2hfnhyiKo6uf2HxUPTCodY6Qaf85SbDIaMBk= +sigs.k8s.io/structured-merge-diff/v4 v4.5.0/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/internal/fileutil/fileutil.go b/internal/fileutil/fileutil.go index 4ea09cca4..39e0e330f 100644 --- a/internal/fileutil/fileutil.go +++ b/internal/fileutil/fileutil.go @@ -21,7 +21,7 @@ import ( "os" "path/filepath" - "helm.sh/helm/v3/internal/third_party/dep/fs" + "helm.sh/helm/v4/internal/third_party/dep/fs" ) // AtomicWriteFile atomically (as atomic as os.Rename allows) writes a file to a diff --git a/internal/logging/logging.go b/internal/logging/logging.go new file mode 100644 index 000000000..946a211ef --- /dev/null +++ b/internal/logging/logging.go @@ -0,0 +1,87 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package logging + +import ( + "context" + "log/slog" + "os" +) + +// DebugEnabledFunc is a function type that determines if debug logging is enabled +// We use a function because we want to check the setting at log time, not when the logger is created +type DebugEnabledFunc func() bool + +// DebugCheckHandler checks settings.Debug at log time +type DebugCheckHandler struct { + handler slog.Handler + debugEnabled DebugEnabledFunc +} + +// Enabled implements slog.Handler.Enabled +func (h *DebugCheckHandler) Enabled(_ context.Context, level slog.Level) bool { + if level == slog.LevelDebug { + return h.debugEnabled() + } + return true // Always log other levels +} + +// Handle implements slog.Handler.Handle +func (h *DebugCheckHandler) Handle(ctx context.Context, r slog.Record) error { + return h.handler.Handle(ctx, r) +} + +// WithAttrs implements slog.Handler.WithAttrs +func (h *DebugCheckHandler) WithAttrs(attrs []slog.Attr) slog.Handler { + return &DebugCheckHandler{ + handler: h.handler.WithAttrs(attrs), + debugEnabled: h.debugEnabled, + } +} + +// WithGroup implements slog.Handler.WithGroup +func (h *DebugCheckHandler) WithGroup(name string) slog.Handler { + return &DebugCheckHandler{ + handler: h.handler.WithGroup(name), + debugEnabled: h.debugEnabled, + } +} + +// NewLogger creates a new logger with dynamic debug checking +func NewLogger(debugEnabled DebugEnabledFunc) *slog.Logger { + // Create base handler that removes timestamps + baseHandler := slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{ + // Always use LevelDebug here to allow all messages through + // Our custom handler will do the filtering + Level: slog.LevelDebug, + ReplaceAttr: func(_ []string, a slog.Attr) slog.Attr { + // Remove the time attribute + if a.Key == slog.TimeKey { + return slog.Attr{} + } + return a + }, + }) + + // Wrap with our dynamic debug-checking handler + dynamicHandler := &DebugCheckHandler{ + handler: baseHandler, + debugEnabled: debugEnabled, + } + + return slog.New(dynamicHandler) +} diff --git a/internal/monocular/client.go b/internal/monocular/client.go index 88a2564b9..f4ef5d647 100644 --- a/internal/monocular/client.go +++ b/internal/monocular/client.go @@ -29,9 +29,6 @@ type Client struct { // The base URL for requests BaseURL string - - // The internal logger to use - Log func(string, ...interface{}) } // New creates a new client @@ -44,12 +41,9 @@ func New(u string) (*Client, error) { return &Client{ BaseURL: u, - Log: nopLogger, }, nil } -var nopLogger = func(_ string, _ ...interface{}) {} - // Validate if the base URL for monocular is valid. func validate(u string) error { diff --git a/internal/monocular/search.go b/internal/monocular/search.go index 4e7e8c002..6912be2ce 100644 --- a/internal/monocular/search.go +++ b/internal/monocular/search.go @@ -24,8 +24,8 @@ import ( "path" "time" - "helm.sh/helm/v3/internal/version" - "helm.sh/helm/v3/pkg/chart" + "helm.sh/helm/v4/internal/version" + chart "helm.sh/helm/v4/pkg/chart/v2" ) // SearchPath is the url path to the search API in monocular. diff --git a/internal/resolver/resolver.go b/internal/resolver/resolver.go index 24d92b809..13dcd2ce9 100644 --- a/internal/resolver/resolver.go +++ b/internal/resolver/resolver.go @@ -28,12 +28,12 @@ import ( "github.com/Masterminds/semver/v3" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chart/loader" - "helm.sh/helm/v3/pkg/helmpath" - "helm.sh/helm/v3/pkg/provenance" - "helm.sh/helm/v3/pkg/registry" - "helm.sh/helm/v3/pkg/repo" + chart "helm.sh/helm/v4/pkg/chart/v2" + "helm.sh/helm/v4/pkg/chart/v2/loader" + "helm.sh/helm/v4/pkg/helmpath" + "helm.sh/helm/v4/pkg/provenance" + "helm.sh/helm/v4/pkg/registry" + "helm.sh/helm/v4/pkg/repo" ) // Resolver resolves dependencies from semantic version ranges to a particular version. diff --git a/internal/resolver/resolver_test.go b/internal/resolver/resolver_test.go index a79852175..1e33837a9 100644 --- a/internal/resolver/resolver_test.go +++ b/internal/resolver/resolver_test.go @@ -19,8 +19,8 @@ import ( "runtime" "testing" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/registry" + chart "helm.sh/helm/v4/pkg/chart/v2" + "helm.sh/helm/v4/pkg/registry" ) func TestResolve(t *testing.T) { diff --git a/internal/statusreaders/job_status_reader.go b/internal/statusreaders/job_status_reader.go new file mode 100644 index 000000000..3cd9ac7ac --- /dev/null +++ b/internal/statusreaders/job_status_reader.go @@ -0,0 +1,121 @@ +/* +Copyright The Helm Authors. +This file was initially copied and modified from + https://github.com/fluxcd/kustomize-controller/blob/main/internal/statusreaders/job.go +Copyright 2022 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package statusreaders + +import ( + "context" + "fmt" + + batchv1 "k8s.io/api/batch/v1" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + + "github.com/fluxcd/cli-utils/pkg/kstatus/polling/engine" + "github.com/fluxcd/cli-utils/pkg/kstatus/polling/event" + "github.com/fluxcd/cli-utils/pkg/kstatus/polling/statusreaders" + "github.com/fluxcd/cli-utils/pkg/kstatus/status" + "github.com/fluxcd/cli-utils/pkg/object" +) + +type customJobStatusReader struct { + genericStatusReader engine.StatusReader +} + +func NewCustomJobStatusReader(mapper meta.RESTMapper) engine.StatusReader { + genericStatusReader := statusreaders.NewGenericStatusReader(mapper, jobConditions) + return &customJobStatusReader{ + genericStatusReader: genericStatusReader, + } +} + +func (j *customJobStatusReader) Supports(gk schema.GroupKind) bool { + return gk == batchv1.SchemeGroupVersion.WithKind("Job").GroupKind() +} + +func (j *customJobStatusReader) ReadStatus(ctx context.Context, reader engine.ClusterReader, resource object.ObjMetadata) (*event.ResourceStatus, error) { + return j.genericStatusReader.ReadStatus(ctx, reader, resource) +} + +func (j *customJobStatusReader) ReadStatusForObject(ctx context.Context, reader engine.ClusterReader, resource *unstructured.Unstructured) (*event.ResourceStatus, error) { + return j.genericStatusReader.ReadStatusForObject(ctx, reader, resource) +} + +// Ref: https://github.com/kubernetes-sigs/cli-utils/blob/v0.29.4/pkg/kstatus/status/core.go +// Modified to return Current status only when the Job has completed as opposed to when it's in progress. +func jobConditions(u *unstructured.Unstructured) (*status.Result, error) { + obj := u.UnstructuredContent() + + parallelism := status.GetIntField(obj, ".spec.parallelism", 1) + completions := status.GetIntField(obj, ".spec.completions", parallelism) + succeeded := status.GetIntField(obj, ".status.succeeded", 0) + failed := status.GetIntField(obj, ".status.failed", 0) + + // Conditions + // https://github.com/kubernetes/kubernetes/blob/master/pkg/controller/job/utils.go#L24 + objc, err := status.GetObjectWithConditions(obj) + if err != nil { + return nil, err + } + for _, c := range objc.Status.Conditions { + switch c.Type { + case "Complete": + if c.Status == corev1.ConditionTrue { + message := fmt.Sprintf("Job Completed. succeeded: %d/%d", succeeded, completions) + return &status.Result{ + Status: status.CurrentStatus, + Message: message, + Conditions: []status.Condition{}, + }, nil + } + case "Failed": + message := fmt.Sprintf("Job Failed. failed: %d/%d", failed, completions) + if c.Status == corev1.ConditionTrue { + return &status.Result{ + Status: status.FailedStatus, + Message: message, + Conditions: []status.Condition{ + { + Type: status.ConditionStalled, + Status: corev1.ConditionTrue, + Reason: "JobFailed", + Message: message, + }, + }, + }, nil + } + } + } + + message := "Job in progress" + return &status.Result{ + Status: status.InProgressStatus, + Message: message, + Conditions: []status.Condition{ + { + Type: status.ConditionReconciling, + Status: corev1.ConditionTrue, + Reason: "JobInProgress", + Message: message, + }, + }, + }, nil +} diff --git a/internal/statusreaders/job_status_reader_test.go b/internal/statusreaders/job_status_reader_test.go new file mode 100644 index 000000000..6e9ed5a79 --- /dev/null +++ b/internal/statusreaders/job_status_reader_test.go @@ -0,0 +1,116 @@ +/* +Copyright The Helm Authors. +This file was initially copied and modified from + https://github.com/fluxcd/kustomize-controller/blob/main/internal/statusreaders/job_test.go +Copyright 2022 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package statusreaders + +import ( + "testing" + + "github.com/stretchr/testify/assert" + batchv1 "k8s.io/api/batch/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + + "github.com/fluxcd/cli-utils/pkg/kstatus/status" +) + +func toUnstructured(t *testing.T, obj runtime.Object) (*unstructured.Unstructured, error) { + t.Helper() + // If the incoming object is already unstructured, perform a deep copy first + // otherwise DefaultUnstructuredConverter ends up returning the inner map without + // making a copy. + if _, ok := obj.(runtime.Unstructured); ok { + obj = obj.DeepCopyObject() + } + rawMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj) + if err != nil { + return nil, err + } + return &unstructured.Unstructured{Object: rawMap}, nil +} + +func TestJobConditions(t *testing.T) { + t.Parallel() + tests := []struct { + name string + job *batchv1.Job + expectedStatus status.Status + }{ + { + name: "job without Complete condition returns InProgress status", + job: &batchv1.Job{ + ObjectMeta: metav1.ObjectMeta{ + Name: "job-no-condition", + }, + Spec: batchv1.JobSpec{}, + Status: batchv1.JobStatus{}, + }, + expectedStatus: status.InProgressStatus, + }, + { + name: "job with Complete condition as True returns Current status", + job: &batchv1.Job{ + ObjectMeta: metav1.ObjectMeta{ + Name: "job-complete", + }, + Spec: batchv1.JobSpec{}, + Status: batchv1.JobStatus{ + Conditions: []batchv1.JobCondition{ + { + Type: batchv1.JobComplete, + Status: corev1.ConditionTrue, + }, + }, + }, + }, + expectedStatus: status.CurrentStatus, + }, + { + name: "job with Failed condition as True returns Failed status", + job: &batchv1.Job{ + ObjectMeta: metav1.ObjectMeta{ + Name: "job-failed", + }, + Spec: batchv1.JobSpec{}, + Status: batchv1.JobStatus{ + Conditions: []batchv1.JobCondition{ + { + Type: batchv1.JobFailed, + Status: corev1.ConditionTrue, + }, + }, + }, + }, + expectedStatus: status.FailedStatus, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + us, err := toUnstructured(t, tc.job) + assert.NoError(t, err) + result, err := jobConditions(us) + assert.NoError(t, err) + assert.Equal(t, tc.expectedStatus, result.Status) + }) + } +} diff --git a/internal/statusreaders/pod_status_reader.go b/internal/statusreaders/pod_status_reader.go new file mode 100644 index 000000000..c074c3487 --- /dev/null +++ b/internal/statusreaders/pod_status_reader.go @@ -0,0 +1,104 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package statusreaders + +import ( + "context" + "fmt" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + + "github.com/fluxcd/cli-utils/pkg/kstatus/polling/engine" + "github.com/fluxcd/cli-utils/pkg/kstatus/polling/event" + "github.com/fluxcd/cli-utils/pkg/kstatus/polling/statusreaders" + "github.com/fluxcd/cli-utils/pkg/kstatus/status" + "github.com/fluxcd/cli-utils/pkg/object" +) + +type customPodStatusReader struct { + genericStatusReader engine.StatusReader +} + +func NewCustomPodStatusReader(mapper meta.RESTMapper) engine.StatusReader { + genericStatusReader := statusreaders.NewGenericStatusReader(mapper, podConditions) + return &customPodStatusReader{ + genericStatusReader: genericStatusReader, + } +} + +func (j *customPodStatusReader) Supports(gk schema.GroupKind) bool { + return gk == corev1.SchemeGroupVersion.WithKind("Pod").GroupKind() +} + +func (j *customPodStatusReader) ReadStatus(ctx context.Context, reader engine.ClusterReader, resource object.ObjMetadata) (*event.ResourceStatus, error) { + return j.genericStatusReader.ReadStatus(ctx, reader, resource) +} + +func (j *customPodStatusReader) ReadStatusForObject(ctx context.Context, reader engine.ClusterReader, resource *unstructured.Unstructured) (*event.ResourceStatus, error) { + return j.genericStatusReader.ReadStatusForObject(ctx, reader, resource) +} + +func podConditions(u *unstructured.Unstructured) (*status.Result, error) { + obj := u.UnstructuredContent() + phase := status.GetStringField(obj, ".status.phase", "") + switch corev1.PodPhase(phase) { + case corev1.PodSucceeded: + message := fmt.Sprintf("pod %s succeeded", u.GetName()) + return &status.Result{ + Status: status.CurrentStatus, + Message: message, + Conditions: []status.Condition{ + { + Type: status.ConditionStalled, + Status: corev1.ConditionTrue, + Message: message, + }, + }, + }, nil + case corev1.PodFailed: + message := fmt.Sprintf("pod %s failed", u.GetName()) + return &status.Result{ + Status: status.FailedStatus, + Message: message, + Conditions: []status.Condition{ + { + Type: status.ConditionStalled, + Status: corev1.ConditionTrue, + Reason: "PodFailed", + Message: message, + }, + }, + }, nil + } + + message := "Pod in progress" + return &status.Result{ + Status: status.InProgressStatus, + Message: message, + Conditions: []status.Condition{ + { + Type: status.ConditionReconciling, + Status: corev1.ConditionTrue, + Reason: "PodInProgress", + Message: message, + }, + }, + }, nil +} diff --git a/internal/statusreaders/pod_status_reader_test.go b/internal/statusreaders/pod_status_reader_test.go new file mode 100644 index 000000000..ba0d1f1bb --- /dev/null +++ b/internal/statusreaders/pod_status_reader_test.go @@ -0,0 +1,111 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package statusreaders + +import ( + "testing" + + "github.com/stretchr/testify/assert" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/fluxcd/cli-utils/pkg/kstatus/status" +) + +func TestPodConditions(t *testing.T) { + tests := []struct { + name string + pod *v1.Pod + expectedStatus status.Status + }{ + { + name: "pod without status returns in progress", + pod: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "pod-no-status"}, + Spec: v1.PodSpec{}, + Status: v1.PodStatus{}, + }, + expectedStatus: status.InProgressStatus, + }, + { + name: "pod succeeded returns current status", + pod: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "pod-succeeded"}, + Spec: v1.PodSpec{}, + Status: v1.PodStatus{ + Phase: v1.PodSucceeded, + }, + }, + expectedStatus: status.CurrentStatus, + }, + { + name: "pod failed returns failed status", + pod: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "pod-failed"}, + Spec: v1.PodSpec{}, + Status: v1.PodStatus{ + Phase: v1.PodFailed, + }, + }, + expectedStatus: status.FailedStatus, + }, + { + name: "pod pending returns in progress status", + pod: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "pod-pending"}, + Spec: v1.PodSpec{}, + Status: v1.PodStatus{ + Phase: v1.PodPending, + }, + }, + expectedStatus: status.InProgressStatus, + }, + { + name: "pod running returns in progress status", + pod: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "pod-running"}, + Spec: v1.PodSpec{}, + Status: v1.PodStatus{ + Phase: v1.PodRunning, + }, + }, + expectedStatus: status.InProgressStatus, + }, + { + name: "pod with unknown phase returns in progress status", + pod: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "pod-unknown"}, + Spec: v1.PodSpec{}, + Status: v1.PodStatus{ + Phase: v1.PodUnknown, + }, + }, + expectedStatus: status.InProgressStatus, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + us, err := toUnstructured(t, tc.pod) + assert.NoError(t, err) + result, err := podConditions(us) + assert.NoError(t, err) + assert.Equal(t, tc.expectedStatus, result.Status) + }) + } +} diff --git a/internal/sympath/walk.go b/internal/sympath/walk.go index 5ee988ede..f67b9f1b9 100644 --- a/internal/sympath/walk.go +++ b/internal/sympath/walk.go @@ -22,7 +22,7 @@ package sympath import ( "fmt" - "log" + "log/slog" "os" "path/filepath" "sort" @@ -70,8 +70,8 @@ func symwalk(path string, info os.FileInfo, walkFn filepath.WalkFunc) error { if err != nil { return fmt.Errorf("error evaluating symlink %s: %w", path, err) } - // This log message is to highlight a symlink that is being used within a chart, symlinks can be used for nefarious reasons. - log.Printf("found symbolic link in path: %s resolves to %s. Contents of linked file included and used", path, resolved) + //This log message is to highlight a symlink that is being used within a chart, symlinks can be used for nefarious reasons. + slog.Info("found symbolic link in path. Contents of linked file included and used", "path", path, "resolved", resolved) if info, err = os.Lstat(resolved); err != nil { return err } diff --git a/internal/sympath/walk_test.go b/internal/sympath/walk_test.go index 04e0738b9..d4e2ceeaa 100644 --- a/internal/sympath/walk_test.go +++ b/internal/sympath/walk_test.go @@ -108,12 +108,12 @@ func checkMarks(t *testing.T, report bool) { } // Assumes that each node name is unique. Good enough for a test. -// If clear is true, any incoming error is cleared before return. The errors -// are always accumulated, though. -func mark(info os.FileInfo, err error, errors *[]error, clear bool) error { +// If clearIncomingError is true, any incoming error is cleared before +// return. The errors are always accumulated, though. +func mark(info os.FileInfo, err error, errors *[]error, clearIncomingError bool) error { if err != nil { *errors = append(*errors, err) - if clear { + if clearIncomingError { return nil } return err @@ -130,9 +130,8 @@ func mark(info os.FileInfo, err error, errors *[]error, clear bool) error { func TestWalk(t *testing.T) { makeTree(t) errors := make([]error, 0, 10) - clear := true markFn := func(_ string, info os.FileInfo, err error) error { - return mark(info, err, &errors, clear) + return mark(info, err, &errors, true) } // Expect no errors. err := Walk(tree.name, markFn) diff --git a/internal/test/ensure/ensure.go b/internal/test/ensure/ensure.go index ff2d180fe..0d8dd9abc 100644 --- a/internal/test/ensure/ensure.go +++ b/internal/test/ensure/ensure.go @@ -21,8 +21,8 @@ import ( "path/filepath" "testing" - "helm.sh/helm/v3/pkg/helmpath" - "helm.sh/helm/v3/pkg/helmpath/xdg" + "helm.sh/helm/v4/pkg/helmpath" + "helm.sh/helm/v4/pkg/helmpath/xdg" ) // HelmHome sets up a Helm Home in a temp dir. diff --git a/internal/test/test.go b/internal/test/test.go index 53eb1c34b..632bc72fd 100644 --- a/internal/test/test.go +++ b/internal/test/test.go @@ -91,5 +91,5 @@ func update(filename string, in []byte) error { } func normalize(in []byte) []byte { - return bytes.Replace(in, []byte("\r\n"), []byte("\n"), -1) + return bytes.ReplaceAll(in, []byte("\r\n"), []byte("\n")) } diff --git a/internal/third_party/dep/fs/fs.go b/internal/third_party/dep/fs/fs.go index b9734d1fa..717eff04d 100644 --- a/internal/third_party/dep/fs/fs.go +++ b/internal/third_party/dep/fs/fs.go @@ -177,28 +177,28 @@ func copyFile(src, dst string) (err error) { in, err := os.Open(src) if err != nil { - return + return err } defer in.Close() out, err := os.Create(dst) if err != nil { - return + return err } if _, err = io.Copy(out, in); err != nil { out.Close() - return + return err } // Check for write errors on Close if err = out.Close(); err != nil { - return + return err } si, err := os.Stat(src) if err != nil { - return + return err } // Temporary fix for Go < 1.9 @@ -210,7 +210,7 @@ func copyFile(src, dst string) (err error) { } err = os.Chmod(dst, si.Mode()) - return + return err } // cloneSymlink will create a new symlink that points to the resolved path of sl. diff --git a/internal/third_party/dep/fs/fs_test.go b/internal/third_party/dep/fs/fs_test.go index d42c3f110..22c59868c 100644 --- a/internal/third_party/dep/fs/fs_test.go +++ b/internal/third_party/dep/fs/fs_test.go @@ -33,17 +33,11 @@ package fs import ( "os" - "os/exec" "path/filepath" "runtime" - "sync" "testing" ) -var ( - mu sync.Mutex -) - func TestRenameWithFallback(t *testing.T) { dir := t.TempDir() @@ -360,19 +354,6 @@ func TestCopyFile(t *testing.T) { } } -func cleanUpDir(dir string) { - // NOTE(mattn): It seems that sometimes git.exe is not dead - // when cleanUpDir() is called. But we do not know any way to wait for it. - if runtime.GOOS == "windows" { - mu.Lock() - exec.Command(`taskkill`, `/F`, `/IM`, `git.exe`).Run() - mu.Unlock() - } - if dir != "" { - os.RemoveAll(dir) - } -} - func TestCopyFileSymlink(t *testing.T) { tempdir := t.TempDir() diff --git a/internal/tlsutil/cfg.go b/internal/tlsutil/cfg.go deleted file mode 100644 index 84377621c..000000000 --- a/internal/tlsutil/cfg.go +++ /dev/null @@ -1,58 +0,0 @@ -/* -Copyright The Helm Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package tlsutil - -import ( - "crypto/tls" - "crypto/x509" - "errors" - "fmt" - "io/fs" -) - -// Options represents configurable options used to create client and server TLS configurations. -type Options struct { - CaCertFile string - // If either the KeyFile or CertFile is empty, ClientConfig() will not load them. - KeyFile string - CertFile string - // Client-only options - InsecureSkipVerify bool -} - -// ClientConfig returns a TLS configuration for use by a Helm client. -func ClientConfig(opts Options) (cfg *tls.Config, err error) { - var cert *tls.Certificate - var pool *x509.CertPool - - if opts.CertFile != "" || opts.KeyFile != "" { - if cert, err = CertFromFilePair(opts.CertFile, opts.KeyFile); err != nil { - if errors.Is(err, fs.ErrNotExist) { - return nil, fmt.Errorf("could not load x509 key pair (cert: %q, key: %q): %w", opts.CertFile, opts.KeyFile, err) - } - return nil, fmt.Errorf("could not read x509 key pair (cert: %q, key: %q): %w", opts.CertFile, opts.KeyFile, err) - } - } - if !opts.InsecureSkipVerify && opts.CaCertFile != "" { - if pool, err = CertPoolFromFile(opts.CaCertFile); err != nil { - return nil, err - } - } - - cfg = &tls.Config{InsecureSkipVerify: opts.InsecureSkipVerify, Certificates: []tls.Certificate{*cert}, RootCAs: pool} - return cfg, nil -} diff --git a/internal/tlsutil/tls.go b/internal/tlsutil/tls.go index 5ba3ca8ee..645834c29 100644 --- a/internal/tlsutil/tls.go +++ b/internal/tlsutil/tls.go @@ -21,57 +21,102 @@ import ( "crypto/x509" "fmt" "os" + + "errors" ) -// NewClientTLS returns tls.Config appropriate for client auth. -func NewClientTLS(certFile, keyFile, caFile string, insecureSkipTLSverify bool) (*tls.Config, error) { - config := tls.Config{ - InsecureSkipVerify: insecureSkipTLSverify, +type TLSConfigOptions struct { + insecureSkipTLSverify bool + certPEMBlock, keyPEMBlock []byte + caPEMBlock []byte +} + +type TLSConfigOption func(options *TLSConfigOptions) error + +func WithInsecureSkipVerify(insecureSkipTLSverify bool) TLSConfigOption { + return func(options *TLSConfigOptions) error { + options.insecureSkipTLSverify = insecureSkipTLSverify + + return nil } +} - if certFile != "" && keyFile != "" { - cert, err := CertFromFilePair(certFile, keyFile) +func WithCertKeyPairFiles(certFile, keyFile string) TLSConfigOption { + return func(options *TLSConfigOptions) error { + if certFile == "" && keyFile == "" { + return nil + } + + certPEMBlock, err := os.ReadFile(certFile) if err != nil { - return nil, err + return fmt.Errorf("unable to read cert file: %q: %w", certFile, err) } - config.Certificates = []tls.Certificate{*cert} - } - if caFile != "" { - cp, err := CertPoolFromFile(caFile) + keyPEMBlock, err := os.ReadFile(keyFile) if err != nil { - return nil, err + return fmt.Errorf("unable to read key file: %q: %w", keyFile, err) } - config.RootCAs = cp + + options.certPEMBlock = certPEMBlock + options.keyPEMBlock = keyPEMBlock + + return nil } +} - return &config, nil +func WithCAFile(caFile string) TLSConfigOption { + return func(options *TLSConfigOptions) error { + if caFile == "" { + return nil + } + + caPEMBlock, err := os.ReadFile(caFile) + if err != nil { + return fmt.Errorf("can't read CA file: %q: %w", caFile, err) + } + + options.caPEMBlock = caPEMBlock + + return nil + } } -// CertPoolFromFile returns an x509.CertPool containing the certificates -// in the given PEM-encoded file. -// Returns an error if the file could not be read, a certificate could not -// be parsed, or if the file does not contain any certificates -func CertPoolFromFile(filename string) (*x509.CertPool, error) { - b, err := os.ReadFile(filename) - if err != nil { - return nil, fmt.Errorf("can't read CA file: %v", filename) +func NewTLSConfig(options ...TLSConfigOption) (*tls.Config, error) { + to := TLSConfigOptions{} + + errs := []error{} + for _, option := range options { + err := option(&to) + if err != nil { + errs = append(errs, err) + } + } + + if len(errs) > 0 { + return nil, errors.Join(errs...) } - cp := x509.NewCertPool() - if !cp.AppendCertsFromPEM(b) { - return nil, fmt.Errorf("failed to append certificates from file: %s", filename) + + config := tls.Config{ + InsecureSkipVerify: to.insecureSkipTLSverify, } - return cp, nil -} -// CertFromFilePair returns a tls.Certificate containing the -// certificates public/private key pair from a pair of given PEM-encoded files. -// Returns an error if the file could not be read, a certificate could not -// be parsed, or if the file does not contain any certificates -func CertFromFilePair(certFile, keyFile string) (*tls.Certificate, error) { - cert, err := tls.LoadX509KeyPair(certFile, keyFile) - if err != nil { - return nil, fmt.Errorf("can't load key pair from cert %s and key %s: %w", certFile, keyFile, err) + if len(to.certPEMBlock) > 0 && len(to.keyPEMBlock) > 0 { + cert, err := tls.X509KeyPair(to.certPEMBlock, to.keyPEMBlock) + if err != nil { + return nil, fmt.Errorf("unable to load cert from key pair: %w", err) + } + + config.Certificates = []tls.Certificate{cert} + } + + if len(to.caPEMBlock) > 0 { + cp := x509.NewCertPool() + if !cp.AppendCertsFromPEM(to.caPEMBlock) { + return nil, fmt.Errorf("failed to append certificates from pem block") + } + + config.RootCAs = cp } - return &cert, err + + return &config, nil } diff --git a/internal/tlsutil/tls_test.go b/internal/tlsutil/tls_test.go new file mode 100644 index 000000000..eb1cc183e --- /dev/null +++ b/internal/tlsutil/tls_test.go @@ -0,0 +1,105 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package tlsutil + +import ( + "path/filepath" + "testing" +) + +const tlsTestDir = "../../testdata" + +const ( + testCaCertFile = "rootca.crt" + testCertFile = "crt.pem" + testKeyFile = "key.pem" +) + +func testfile(t *testing.T, file string) (path string) { + var err error + if path, err = filepath.Abs(filepath.Join(tlsTestDir, file)); err != nil { + t.Fatalf("error getting absolute path to test file %q: %v", file, err) + } + return path +} + +func TestNewTLSConfig(t *testing.T) { + certFile := testfile(t, testCertFile) + keyFile := testfile(t, testKeyFile) + caCertFile := testfile(t, testCaCertFile) + insecureSkipTLSverify := false + + { + cfg, err := NewTLSConfig( + WithInsecureSkipVerify(insecureSkipTLSverify), + WithCertKeyPairFiles(certFile, keyFile), + WithCAFile(caCertFile), + ) + if err != nil { + t.Error(err) + } + + if got := len(cfg.Certificates); got != 1 { + t.Fatalf("expecting 1 client certificates, got %d", got) + } + if cfg.InsecureSkipVerify { + t.Fatalf("insecure skip verify mismatch, expecting false") + } + if cfg.RootCAs == nil { + t.Fatalf("mismatch tls RootCAs, expecting non-nil") + } + } + { + cfg, err := NewTLSConfig( + WithInsecureSkipVerify(insecureSkipTLSverify), + WithCAFile(caCertFile), + ) + if err != nil { + t.Error(err) + } + + if got := len(cfg.Certificates); got != 0 { + t.Fatalf("expecting 0 client certificates, got %d", got) + } + if cfg.InsecureSkipVerify { + t.Fatalf("insecure skip verify mismatch, expecting false") + } + if cfg.RootCAs == nil { + t.Fatalf("mismatch tls RootCAs, expecting non-nil") + } + } + + { + cfg, err := NewTLSConfig( + WithInsecureSkipVerify(insecureSkipTLSverify), + WithCertKeyPairFiles(certFile, keyFile), + ) + if err != nil { + t.Error(err) + } + + if got := len(cfg.Certificates); got != 1 { + t.Fatalf("expecting 1 client certificates, got %d", got) + } + if cfg.InsecureSkipVerify { + t.Fatalf("insecure skip verify mismatch, expecting false") + } + if cfg.RootCAs != nil { + t.Fatalf("mismatch tls RootCAs, expecting nil") + } + } +} diff --git a/internal/tlsutil/tlsutil_test.go b/internal/tlsutil/tlsutil_test.go deleted file mode 100644 index e31a873d3..000000000 --- a/internal/tlsutil/tlsutil_test.go +++ /dev/null @@ -1,114 +0,0 @@ -/* -Copyright The Helm Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package tlsutil - -import ( - "path/filepath" - "testing" -) - -const tlsTestDir = "../../testdata" - -const ( - testCaCertFile = "rootca.crt" - testCertFile = "crt.pem" - testKeyFile = "key.pem" -) - -func TestClientConfig(t *testing.T) { - opts := Options{ - CaCertFile: testfile(t, testCaCertFile), - CertFile: testfile(t, testCertFile), - KeyFile: testfile(t, testKeyFile), - InsecureSkipVerify: false, - } - - cfg, err := ClientConfig(opts) - if err != nil { - t.Fatalf("error building tls client config: %v", err) - } - - if got := len(cfg.Certificates); got != 1 { - t.Fatalf("expecting 1 client certificates, got %d", got) - } - if cfg.InsecureSkipVerify { - t.Fatalf("insecure skip verify mismatch, expecting false") - } - if cfg.RootCAs == nil { - t.Fatalf("mismatch tls RootCAs, expecting non-nil") - } -} - -func testfile(t *testing.T, file string) (path string) { - var err error - if path, err = filepath.Abs(filepath.Join(tlsTestDir, file)); err != nil { - t.Fatalf("error getting absolute path to test file %q: %v", file, err) - } - return path -} - -func TestNewClientTLS(t *testing.T) { - certFile := testfile(t, testCertFile) - keyFile := testfile(t, testKeyFile) - caCertFile := testfile(t, testCaCertFile) - insecureSkipTLSverify := false - - cfg, err := NewClientTLS(certFile, keyFile, caCertFile, insecureSkipTLSverify) - if err != nil { - t.Error(err) - } - - if got := len(cfg.Certificates); got != 1 { - t.Fatalf("expecting 1 client certificates, got %d", got) - } - if cfg.InsecureSkipVerify { - t.Fatalf("insecure skip verify mismatch, expecting false") - } - if cfg.RootCAs == nil { - t.Fatalf("mismatch tls RootCAs, expecting non-nil") - } - - cfg, err = NewClientTLS("", "", caCertFile, insecureSkipTLSverify) - if err != nil { - t.Error(err) - } - - if got := len(cfg.Certificates); got != 0 { - t.Fatalf("expecting 0 client certificates, got %d", got) - } - if cfg.InsecureSkipVerify { - t.Fatalf("insecure skip verify mismatch, expecting false") - } - if cfg.RootCAs == nil { - t.Fatalf("mismatch tls RootCAs, expecting non-nil") - } - - cfg, err = NewClientTLS(certFile, keyFile, "", insecureSkipTLSverify) - if err != nil { - t.Error(err) - } - - if got := len(cfg.Certificates); got != 1 { - t.Fatalf("expecting 1 client certificates, got %d", got) - } - if cfg.InsecureSkipVerify { - t.Fatalf("insecure skip verify mismatch, expecting false") - } - if cfg.RootCAs != nil { - t.Fatalf("mismatch tls RootCAs, expecting nil") - } -} diff --git a/internal/version/version.go b/internal/version/version.go index 539659f5e..aa64e618f 100644 --- a/internal/version/version.go +++ b/internal/version/version.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package version // import "helm.sh/helm/v3/internal/version" +package version // import "helm.sh/helm/v4/internal/version" import ( "flag" @@ -29,7 +29,7 @@ var ( // // Increment major number for new feature additions and behavioral changes. // Increment minor number for bug fixes and performance enhancements. - version = "v3.16" + version = "v4.0" // metadata is extra build time data metadata = "" diff --git a/pkg/action/action.go b/pkg/action/action.go index ffb4ef0c6..6905f3f44 100644 --- a/pkg/action/action.go +++ b/pkg/action/action.go @@ -20,11 +20,13 @@ import ( "bytes" "errors" "fmt" + "io" + "log/slog" "os" "path" "path/filepath" - "regexp" "strings" + "text/template" "k8s.io/apimachinery/pkg/api/meta" "k8s.io/cli-runtime/pkg/genericclioptions" @@ -32,17 +34,17 @@ import ( "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/engine" - "helm.sh/helm/v3/pkg/kube" - "helm.sh/helm/v3/pkg/postrender" - "helm.sh/helm/v3/pkg/registry" - "helm.sh/helm/v3/pkg/release" - "helm.sh/helm/v3/pkg/releaseutil" - "helm.sh/helm/v3/pkg/storage" - "helm.sh/helm/v3/pkg/storage/driver" - "helm.sh/helm/v3/pkg/time" + chart "helm.sh/helm/v4/pkg/chart/v2" + chartutil "helm.sh/helm/v4/pkg/chart/v2/util" + "helm.sh/helm/v4/pkg/engine" + "helm.sh/helm/v4/pkg/kube" + "helm.sh/helm/v4/pkg/postrender" + "helm.sh/helm/v4/pkg/registry" + releaseutil "helm.sh/helm/v4/pkg/release/util" + release "helm.sh/helm/v4/pkg/release/v1" + "helm.sh/helm/v4/pkg/storage" + "helm.sh/helm/v4/pkg/storage/driver" + "helm.sh/helm/v4/pkg/time" ) // Timestamper is a function capable of producing a timestamp.Timestamper. @@ -62,21 +64,6 @@ var ( errPending = errors.New("another operation (install/upgrade/rollback) is in progress") ) -// ValidName is a regular expression for resource names. -// -// DEPRECATED: This will be removed in Helm 4, and is no longer used here. See -// pkg/lint/rules.validateMetadataNameFunc for the replacement. -// -// According to the Kubernetes help text, the regular expression it uses is: -// -// [a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)* -// -// This follows the above regular expression (but requires a full string match, not partial). -// -// The Kubernetes documentation is here, though it is not entirely correct: -// https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names -var ValidName = regexp.MustCompile(`^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$`) - // Configuration injects the dependencies that all actions share. type Configuration struct { // RESTClientGetter is an interface that loads Kubernetes clients. @@ -94,7 +81,11 @@ type Configuration struct { // Capabilities describes the capabilities of the Kubernetes cluster. Capabilities *chartutil.Capabilities - Log func(string, ...interface{}) + // CustomTemplateFuncs is defined by users to provide custom template funcs + CustomTemplateFuncs template.FuncMap + + // HookOutputFunc called with container name and returns and expects writer that will receive the log output. + HookOutputFunc func(namespace, pod, container string) io.Writer } // renderResources renders the templates in a chart @@ -122,7 +113,7 @@ func (cfg *Configuration) renderResources(ch *chart.Chart, values chartutil.Valu var err2 error // A `helm template` should not talk to the remote cluster. However, commands with the flag - //`--dry-run` with the value of `false`, `none`, or `server` should try to interact with the cluster. + // `--dry-run` with the value of `false`, `none`, or `server` should try to interact with the cluster. // It may break in interesting and exotic ways because other data (e.g. discovery) is mocked. if interactWithRemote && cfg.RESTClientGetter != nil { restConfig, err := cfg.RESTClientGetter.ToRESTConfig() @@ -131,10 +122,14 @@ func (cfg *Configuration) renderResources(ch *chart.Chart, values chartutil.Valu } e := engine.New(restConfig) e.EnableDNS = enableDNS + e.CustomTemplateFuncs = cfg.CustomTemplateFuncs + files, err2 = e.Render(ch, values) } else { var e engine.Engine e.EnableDNS = enableDNS + e.CustomTemplateFuncs = cfg.CustomTemplateFuncs + files, err2 = e.Render(ch, values) } @@ -239,9 +234,6 @@ type RESTClientGetter interface { ToRESTMapper() (meta.RESTMapper, error) } -// DebugLog sets the logger that writes debug strings -type DebugLog func(format string, v ...interface{}) - // capabilities builds a Capabilities from discovery information. func (cfg *Configuration) getCapabilities() (*chartutil.Capabilities, error) { if cfg.Capabilities != nil { @@ -265,8 +257,8 @@ func (cfg *Configuration) getCapabilities() (*chartutil.Capabilities, error) { apiVersions, err := GetVersionSet(dc) if err != nil { if discovery.IsGroupDiscoveryFailedError(err) { - cfg.Log("WARNING: The Kubernetes server has an orphaned API service. Server reports: %s", err) - cfg.Log("WARNING: To fix this, kubectl delete apiservice ") + slog.Warn("the kubernetes server has an orphaned API service", slog.Any("error", err)) + slog.Warn("to fix this, kubectl delete apiservice ") } else { return nil, fmt.Errorf("could not get apiVersions from Kubernetes: %w", err) } @@ -365,14 +357,13 @@ func GetVersionSet(client discovery.ServerResourcesInterface) (chartutil.Version // recordRelease with an update operation in case reuse has been set. func (cfg *Configuration) recordRelease(r *release.Release) { if err := cfg.Releases.Update(r); err != nil { - cfg.Log("warning: Failed to update release %s: %s", r.Name, err) + slog.Warn("failed to update release", "name", r.Name, "revision", r.Version, slog.Any("error", err)) } } // Init initializes the action configuration -func (cfg *Configuration) Init(getter genericclioptions.RESTClientGetter, namespace, helmDriver string, log DebugLog) error { +func (cfg *Configuration) Init(getter genericclioptions.RESTClientGetter, namespace, helmDriver string) error { kc := kube.New(getter) - kc.Log = log lazyClient := &lazyClient{ namespace: namespace, @@ -383,11 +374,9 @@ func (cfg *Configuration) Init(getter genericclioptions.RESTClientGetter, namesp switch helmDriver { case "secret", "secrets", "": d := driver.NewSecrets(newSecretClient(lazyClient)) - d.Log = log store = storage.Init(d) case "configmap", "configmaps": d := driver.NewConfigMaps(newConfigMapClient(lazyClient)) - d.Log = log store = storage.Init(d) case "memory": var d *driver.Memory @@ -407,7 +396,6 @@ func (cfg *Configuration) Init(getter genericclioptions.RESTClientGetter, namesp case "sql": d, err := driver.NewSQL( os.Getenv("HELM_DRIVER_SQL_CONNECTION_STRING"), - log, namespace, ) if err != nil { @@ -421,7 +409,12 @@ func (cfg *Configuration) Init(getter genericclioptions.RESTClientGetter, namesp cfg.RESTClientGetter = getter cfg.KubeClient = kc cfg.Releases = store - cfg.Log = log + cfg.HookOutputFunc = func(_, _, _ string) io.Writer { return io.Discard } return nil } + +// SetHookOutputFunc sets the HookOutputFunc on the Configuration. +func (cfg *Configuration) SetHookOutputFunc(hookOutputFunc func(_, _, _ string) io.Writer) { + cfg.HookOutputFunc = hookOutputFunc +} diff --git a/pkg/action/action_test.go b/pkg/action/action_test.go index 149eb85b1..f808163fb 100644 --- a/pkg/action/action_test.go +++ b/pkg/action/action_test.go @@ -19,26 +19,38 @@ import ( "flag" "fmt" "io" + "log/slog" "testing" "github.com/stretchr/testify/assert" fakeclientset "k8s.io/client-go/kubernetes/fake" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chartutil" - kubefake "helm.sh/helm/v3/pkg/kube/fake" - "helm.sh/helm/v3/pkg/registry" - "helm.sh/helm/v3/pkg/release" - "helm.sh/helm/v3/pkg/storage" - "helm.sh/helm/v3/pkg/storage/driver" - "helm.sh/helm/v3/pkg/time" + "helm.sh/helm/v4/internal/logging" + chart "helm.sh/helm/v4/pkg/chart/v2" + chartutil "helm.sh/helm/v4/pkg/chart/v2/util" + "helm.sh/helm/v4/pkg/kube" + kubefake "helm.sh/helm/v4/pkg/kube/fake" + "helm.sh/helm/v4/pkg/registry" + release "helm.sh/helm/v4/pkg/release/v1" + "helm.sh/helm/v4/pkg/storage" + "helm.sh/helm/v4/pkg/storage/driver" + "helm.sh/helm/v4/pkg/time" ) -var verbose = flag.Bool("test.log", false, "enable test logging") +var verbose = flag.Bool("test.log", false, "enable test logging (debug by default)") func actionConfigFixture(t *testing.T) *Configuration { + return actionConfigFixtureWithDummyResources(t, nil) +} + +func actionConfigFixtureWithDummyResources(t *testing.T, dummyResources kube.ResourceList) *Configuration { t.Helper() + logger := logging.NewLogger(func() bool { + return *verbose + }) + slog.SetDefault(logger) + registryClient, err := registry.NewClient() if err != nil { t.Fatal(err) @@ -46,15 +58,9 @@ func actionConfigFixture(t *testing.T) *Configuration { return &Configuration{ Releases: storage.Init(driver.NewMemory()), - KubeClient: &kubefake.FailingKubeClient{PrintingKubeClient: kubefake.PrintingKubeClient{Out: io.Discard}}, + KubeClient: &kubefake.FailingKubeClient{PrintingKubeClient: kubefake.PrintingKubeClient{Out: io.Discard}, DummyResources: dummyResources}, Capabilities: chartutil.DefaultCapabilities, RegistryClient: registryClient, - Log: func(format string, v ...interface{}) { - t.Helper() - if *verbose { - t.Logf(format, v...) - } - }, } } @@ -111,6 +117,14 @@ type chartOptions struct { type chartOption func(*chartOptions) func buildChart(opts ...chartOption) *chart.Chart { + defaultTemplates := []*chart.File{ + {Name: "templates/hello", Data: []byte("hello: world")}, + {Name: "templates/hooks", Data: []byte(manifestWithHook)}, + } + return buildChartWithTemplates(defaultTemplates, opts...) +} + +func buildChartWithTemplates(templates []*chart.File, opts ...chartOption) *chart.Chart { c := &chartOptions{ Chart: &chart.Chart{ // TODO: This should be more complete. @@ -119,18 +133,13 @@ func buildChart(opts ...chartOption) *chart.Chart { Name: "hello", Version: "0.1.0", }, - // This adds a basic template and hooks. - Templates: []*chart.File{ - {Name: "templates/hello", Data: []byte("hello: world")}, - {Name: "templates/hooks", Data: []byte(manifestWithHook)}, - }, + Templates: templates, }, } for _, opt := range opts { opt(c) } - return c.Chart } @@ -331,7 +340,7 @@ func TestConfiguration_Init(t *testing.T) { t.Run(tt.name, func(t *testing.T) { cfg := &Configuration{} - actualErr := cfg.Init(nil, "default", tt.helmDriver, nil) + actualErr := cfg.Init(nil, "default", tt.helmDriver) if tt.expectErr { assert.Error(t, actualErr) assert.Contains(t, actualErr.Error(), tt.errMsg) @@ -344,7 +353,7 @@ func TestConfiguration_Init(t *testing.T) { } func TestGetVersionSet(t *testing.T) { - client := fakeclientset.NewSimpleClientset() + client := fakeclientset.NewClientset() vs, err := GetVersionSet(client.Discovery()) if err != nil { diff --git a/pkg/action/dependency.go b/pkg/action/dependency.go index 3265f1f17..03c370c8e 100644 --- a/pkg/action/dependency.go +++ b/pkg/action/dependency.go @@ -26,18 +26,25 @@ import ( "github.com/Masterminds/semver/v3" "github.com/gosuri/uitable" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chart/loader" + chart "helm.sh/helm/v4/pkg/chart/v2" + "helm.sh/helm/v4/pkg/chart/v2/loader" ) // Dependency is the action for building a given chart's dependency tree. // // It provides the implementation of 'helm dependency' and its respective subcommands. type Dependency struct { - Verify bool - Keyring string - SkipRefresh bool - ColumnWidth uint + Verify bool + Keyring string + SkipRefresh bool + ColumnWidth uint + Username string + Password string + CertFile string + KeyFile string + CaFile string + InsecureSkipTLSverify bool + PlainHTTP bool } // NewDependency creates a new Dependency object with the given configuration. diff --git a/pkg/action/dependency_test.go b/pkg/action/dependency_test.go index c29587aec..5be7bf5a9 100644 --- a/pkg/action/dependency_test.go +++ b/pkg/action/dependency_test.go @@ -24,9 +24,9 @@ import ( "github.com/stretchr/testify/assert" - "helm.sh/helm/v3/internal/test" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chartutil" + "helm.sh/helm/v4/internal/test" + chart "helm.sh/helm/v4/pkg/chart/v2" + chartutil "helm.sh/helm/v4/pkg/chart/v2/util" ) func TestList(t *testing.T) { diff --git a/pkg/action/get.go b/pkg/action/get.go index f44b53307..dbe5f4cb3 100644 --- a/pkg/action/get.go +++ b/pkg/action/get.go @@ -17,7 +17,7 @@ limitations under the License. package action import ( - "helm.sh/helm/v3/pkg/release" + release "helm.sh/helm/v4/pkg/release/v1" ) // Get is the action for checking a given release's information. diff --git a/pkg/action/get_metadata.go b/pkg/action/get_metadata.go index ec096ae16..e760ae4d1 100644 --- a/pkg/action/get_metadata.go +++ b/pkg/action/get_metadata.go @@ -16,7 +16,13 @@ limitations under the License. package action -import "time" +import ( + "sort" + "strings" + "time" + + chart "helm.sh/helm/v4/pkg/chart/v2" +) // GetMetadata is the action for checking a given release's metadata. // @@ -28,14 +34,16 @@ type GetMetadata struct { } type Metadata struct { - Name string `json:"name" yaml:"name"` - Chart string `json:"chart" yaml:"chart"` - Version string `json:"version" yaml:"version"` - AppVersion string `json:"appVersion" yaml:"appVersion"` - Namespace string `json:"namespace" yaml:"namespace"` - Revision int `json:"revision" yaml:"revision"` - Status string `json:"status" yaml:"status"` - DeployedAt string `json:"deployedAt" yaml:"deployedAt"` + Name string `json:"name" yaml:"name"` + Chart string `json:"chart" yaml:"chart"` + Version string `json:"version" yaml:"version"` + AppVersion string `json:"appVersion" yaml:"appVersion"` + Annotations map[string]string `json:"annotations,omitempty" yaml:"annotations,omitempty"` + Dependencies []*chart.Dependency `json:"dependencies,omitempty" yaml:"dependencies,omitempty"` + Namespace string `json:"namespace" yaml:"namespace"` + Revision int `json:"revision" yaml:"revision"` + Status string `json:"status" yaml:"status"` + DeployedAt string `json:"deployedAt" yaml:"deployedAt"` } // NewGetMetadata creates a new GetMetadata object with the given configuration. @@ -57,13 +65,26 @@ func (g *GetMetadata) Run(name string) (*Metadata, error) { } return &Metadata{ - Name: rel.Name, - Chart: rel.Chart.Metadata.Name, - Version: rel.Chart.Metadata.Version, - AppVersion: rel.Chart.Metadata.AppVersion, - Namespace: rel.Namespace, - Revision: rel.Version, - Status: rel.Info.Status.String(), - DeployedAt: rel.Info.LastDeployed.Format(time.RFC3339), + Name: rel.Name, + Chart: rel.Chart.Metadata.Name, + Version: rel.Chart.Metadata.Version, + AppVersion: rel.Chart.Metadata.AppVersion, + Dependencies: rel.Chart.Metadata.Dependencies, + Annotations: rel.Chart.Metadata.Annotations, + Namespace: rel.Namespace, + Revision: rel.Version, + Status: rel.Info.Status.String(), + DeployedAt: rel.Info.LastDeployed.Format(time.RFC3339), }, nil } + +// FormattedDepNames formats metadata.dependencies names into a comma-separated list. +func (m *Metadata) FormattedDepNames() string { + depsNames := make([]string, 0, len(m.Dependencies)) + for _, dep := range m.Dependencies { + depsNames = append(depsNames, dep.Name) + } + sort.StringSlice(depsNames).Sort() + + return strings.Join(depsNames, ",") +} diff --git a/pkg/action/get_values.go b/pkg/action/get_values.go index 9c32db213..18b8b4838 100644 --- a/pkg/action/get_values.go +++ b/pkg/action/get_values.go @@ -17,7 +17,7 @@ limitations under the License. package action import ( - "helm.sh/helm/v3/pkg/chartutil" + chartutil "helm.sh/helm/v4/pkg/chart/v2/util" ) // GetValues is the action for checking a given release's values. diff --git a/pkg/action/history.go b/pkg/action/history.go index 125d9a317..d7af1d6a4 100644 --- a/pkg/action/history.go +++ b/pkg/action/history.go @@ -17,10 +17,12 @@ limitations under the License. package action import ( + "log/slog" + "fmt" - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/release" + chartutil "helm.sh/helm/v4/pkg/chart/v2/util" + release "helm.sh/helm/v4/pkg/release/v1" ) // History is the action for checking the release's ledger. @@ -53,6 +55,6 @@ func (h *History) Run(name string) ([]*release.Release, error) { return nil, fmt.Errorf("release name is invalid: %s", name) } - h.cfg.Log("getting history for release %s", name) + slog.Debug("getting history for release", "release", name) return h.cfg.Releases.History(name) } diff --git a/pkg/action/hooks.go b/pkg/action/hooks.go index bb6977990..0ef00a832 100644 --- a/pkg/action/hooks.go +++ b/pkg/action/hooks.go @@ -18,16 +18,23 @@ package action import ( "bytes" "fmt" + "log" + "slices" "sort" "time" - "helm.sh/helm/v3/pkg/kube" - "helm.sh/helm/v3/pkg/release" - helmtime "helm.sh/helm/v3/pkg/time" + "github.com/pkg/errors" + "helm.sh/helm/v4/pkg/kube" + + "gopkg.in/yaml.v3" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + release "helm.sh/helm/v4/pkg/release/v1" + helmtime "helm.sh/helm/v4/pkg/time" ) // execHook executes all of the hooks for the given hook event. -func (cfg *Configuration) execHook(rl *release.Release, hook release.HookEvent, timeout time.Duration) error { +func (cfg *Configuration) execHook(rl *release.Release, hook release.HookEvent, waitStrategy kube.WaitStrategy, timeout time.Duration) error { executingHooks := []*release.Hook{} for _, h := range rl.Hooks { @@ -41,9 +48,9 @@ func (cfg *Configuration) execHook(rl *release.Release, hook release.HookEvent, // hooke are pre-ordered by kind, so keep order stable sort.Stable(hookByWeight(executingHooks)) - for _, h := range executingHooks { + for i, h := range executingHooks { // Set default delete policy to before-hook-creation - if h.DeletePolicies == nil || len(h.DeletePolicies) == 0 { + 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 @@ -51,7 +58,7 @@ func (cfg *Configuration) execHook(rl *release.Release, hook release.HookEvent, h.DeletePolicies = []release.HookDeletePolicy{release.HookBeforeHookCreation} } - if err := cfg.deleteHookByPolicy(h, release.HookBeforeHookCreation, timeout); err != nil { + if err := cfg.deleteHookByPolicy(h, release.HookBeforeHookCreation, waitStrategy, timeout); err != nil { return err } @@ -79,28 +86,49 @@ func (cfg *Configuration) execHook(rl *release.Release, hook release.HookEvent, return fmt.Errorf("warning: Hook %s %s failed: %w", hook, h.Path, err) } + waiter, err := cfg.KubeClient.GetWaiter(waitStrategy) + if err != nil { + return errors.Wrapf(err, "unable to get waiter") + } // Watch hook resources until they have completed - err = cfg.KubeClient.WatchUntilReady(resources, timeout) + err = waiter.WatchUntilReady(resources, timeout) // Note the time of success/failure h.LastRun.CompletedAt = helmtime.Now() // Mark hook as succeeded or failed if err != nil { h.LastRun.Phase = release.HookPhaseFailed + // If a hook is failed, check the annotation of the hook to determine if we should copy the logs client side + if errOutputting := cfg.outputLogsByPolicy(h, rl.Namespace, release.HookOutputOnFailed); errOutputting != nil { + // We log the error here as we want to propagate the hook failure upwards to the release object. + log.Printf("error outputting logs for hook failure: %v", errOutputting) + } // If a hook is failed, check the annotation of the hook to determine whether the hook should be deleted // under failed condition. If so, then clear the corresponding resource object in the hook - if err := cfg.deleteHookByPolicy(h, release.HookFailed, timeout); err != nil { + if errDeleting := cfg.deleteHookByPolicy(h, release.HookFailed, waitStrategy, timeout); errDeleting != nil { + // We log the error here as we want to propagate the hook failure upwards to the release object. + log.Printf("error deleting the hook resource on hook failure: %v", errDeleting) + } + + // If a hook is failed, check the annotation of the previous successful hooks to determine whether the hooks + // should be deleted under succeeded condition. + if err := cfg.deleteHooksByPolicy(executingHooks[0:i], release.HookSucceeded, waitStrategy, timeout); err != nil { return err } + return err } h.LastRun.Phase = release.HookPhaseSucceeded } // If all hooks are successful, check the annotation of each hook to determine whether the hook should be deleted - // under succeeded condition. If so, then clear the corresponding resource object in each hook + // or output should be logged under succeeded condition. If so, then clear the corresponding resource object in each hook for i := len(executingHooks) - 1; i >= 0; i-- { h := executingHooks[i] - if err := cfg.deleteHookByPolicy(h, release.HookSucceeded, timeout); err != nil { + if err := cfg.outputLogsByPolicy(h, rl.Namespace, release.HookOutputOnSucceeded); err != nil { + // We log here as we still want to attempt hook resource deletion even if output logging fails. + log.Printf("error outputting logs for hook failure: %v", err) + } + if err := cfg.deleteHookByPolicy(h, release.HookSucceeded, waitStrategy, timeout); err != nil { return err } } @@ -121,7 +149,7 @@ func (x hookByWeight) Less(i, j int) bool { } // deleteHookByPolicy deletes a hook if the hook policy instructs it to -func (cfg *Configuration) deleteHookByPolicy(h *release.Hook, policy release.HookDeletePolicy, timeout time.Duration) error { +func (cfg *Configuration) deleteHookByPolicy(h *release.Hook, policy release.HookDeletePolicy, waitStrategy kube.WaitStrategy, timeout time.Duration) error { // Never delete CustomResourceDefinitions; this could cause lots of // cascading garbage collection. if h.Kind == "CustomResourceDefinition" { @@ -137,16 +165,28 @@ func (cfg *Configuration) deleteHookByPolicy(h *release.Hook, policy release.Hoo return joinErrors(errs, "; ") } - // wait for resources until they are deleted to avoid conflicts - if kubeClient, ok := cfg.KubeClient.(kube.InterfaceExt); ok { - if err := kubeClient.WaitForDelete(resources, timeout); err != nil { - return err - } + waiter, err := cfg.KubeClient.GetWaiter(waitStrategy) + if err != nil { + return err + } + if err := waiter.WaitForDelete(resources, timeout); err != nil { + return err } } return nil } +// deleteHooksByPolicy deletes all hooks if the hook policy instructs it to +func (cfg *Configuration) deleteHooksByPolicy(hooks []*release.Hook, policy release.HookDeletePolicy, waitStrategy kube.WaitStrategy, timeout time.Duration) error { + for _, h := range hooks { + if err := cfg.deleteHookByPolicy(h, policy, waitStrategy, timeout); err != nil { + return err + } + } + + return nil +} + // 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. func hookHasDeletePolicy(h *release.Hook, policy release.HookDeletePolicy) bool { @@ -157,3 +197,57 @@ func hookHasDeletePolicy(h *release.Hook, policy release.HookDeletePolicy) bool } return false } + +// outputLogsByPolicy outputs a pods logs if the hook policy instructs it to +func (cfg *Configuration) outputLogsByPolicy(h *release.Hook, releaseNamespace string, policy release.HookOutputLogPolicy) error { + if !hookHasOutputLogPolicy(h, policy) { + return nil + } + namespace, err := cfg.deriveNamespace(h, releaseNamespace) + if err != nil { + return err + } + switch h.Kind { + case "Job": + return cfg.outputContainerLogsForListOptions(namespace, metav1.ListOptions{LabelSelector: fmt.Sprintf("job-name=%s", h.Name)}) + case "Pod": + return cfg.outputContainerLogsForListOptions(namespace, metav1.ListOptions{FieldSelector: fmt.Sprintf("metadata.name=%s", h.Name)}) + default: + return nil + } +} + +func (cfg *Configuration) outputContainerLogsForListOptions(namespace string, listOptions metav1.ListOptions) error { + // TODO Helm 4: Remove this check when GetPodList and OutputContainerLogsForPodList are moved from InterfaceLogs to Interface + if kubeClient, ok := cfg.KubeClient.(kube.InterfaceLogs); ok { + podList, err := kubeClient.GetPodList(namespace, listOptions) + if err != nil { + return err + } + err = kubeClient.OutputContainerLogsForPodList(podList, namespace, cfg.HookOutputFunc) + return err + } + return nil +} + +func (cfg *Configuration) deriveNamespace(h *release.Hook, namespace string) (string, error) { + tmp := struct { + Metadata struct { + Namespace string + } + }{} + err := yaml.Unmarshal([]byte(h.Manifest), &tmp) + if err != nil { + return "", errors.Wrapf(err, "unable to parse metadata.namespace from kubernetes manifest for output logs hook %s", h.Path) + } + if tmp.Metadata.Namespace == "" { + return namespace, nil + } + return tmp.Metadata.Namespace, nil +} + +// hookHasOutputLogPolicy determines whether the defined hook output log policy matches the hook output log policies +// supported by helm. +func hookHasOutputLogPolicy(h *release.Hook, policy release.HookOutputLogPolicy) bool { + return slices.Contains(h.OutputLogPolicies, policy) +} diff --git a/pkg/action/hooks_test.go b/pkg/action/hooks_test.go new file mode 100644 index 000000000..9ca42ec6a --- /dev/null +++ b/pkg/action/hooks_test.go @@ -0,0 +1,401 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package action + +import ( + "bytes" + "fmt" + "io" + "reflect" + "testing" + "time" + + "github.com/stretchr/testify/assert" + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/util/yaml" + "k8s.io/cli-runtime/pkg/resource" + + chart "helm.sh/helm/v4/pkg/chart/v2" + chartutil "helm.sh/helm/v4/pkg/chart/v2/util" + "helm.sh/helm/v4/pkg/kube" + kubefake "helm.sh/helm/v4/pkg/kube/fake" + release "helm.sh/helm/v4/pkg/release/v1" + "helm.sh/helm/v4/pkg/storage" + "helm.sh/helm/v4/pkg/storage/driver" +) + +func podManifestWithOutputLogs(hookDefinitions []release.HookOutputLogPolicy) string { + hookDefinitionString := convertHooksToCommaSeparated(hookDefinitions) + return fmt.Sprintf(`kind: Pod +metadata: + name: finding-sharky, + annotations: + "helm.sh/hook": pre-install + "helm.sh/hook-output-log-policy": %s +spec: + containers: + - name: sharky-test + image: fake-image + cmd: fake-command`, hookDefinitionString) +} + +func podManifestWithOutputLogWithNamespace(hookDefinitions []release.HookOutputLogPolicy) string { + hookDefinitionString := convertHooksToCommaSeparated(hookDefinitions) + return fmt.Sprintf(`kind: Pod +metadata: + name: finding-george + namespace: sneaky-namespace + annotations: + "helm.sh/hook": pre-install + "helm.sh/hook-output-log-policy": %s +spec: + containers: + - name: george-test + image: fake-image + cmd: fake-command`, hookDefinitionString) +} + +func jobManifestWithOutputLog(hookDefinitions []release.HookOutputLogPolicy) string { + hookDefinitionString := convertHooksToCommaSeparated(hookDefinitions) + return fmt.Sprintf(`kind: Job +apiVersion: batch/v1 +metadata: + name: losing-religion + annotations: + "helm.sh/hook": pre-install + "helm.sh/hook-output-log-policy": %s +spec: + completions: 1 + parallelism: 1 + activeDeadlineSeconds: 30 + template: + spec: + containers: + - name: religion-container + image: religion-image + cmd: religion-command`, hookDefinitionString) +} + +func jobManifestWithOutputLogWithNamespace(hookDefinitions []release.HookOutputLogPolicy) string { + hookDefinitionString := convertHooksToCommaSeparated(hookDefinitions) + return fmt.Sprintf(`kind: Job +apiVersion: batch/v1 +metadata: + name: losing-religion + namespace: rem-namespace + annotations: + "helm.sh/hook": pre-install + "helm.sh/hook-output-log-policy": %s +spec: + completions: 1 + parallelism: 1 + activeDeadlineSeconds: 30 + template: + spec: + containers: + - name: religion-container + image: religion-image + cmd: religion-command`, hookDefinitionString) +} + +func convertHooksToCommaSeparated(hookDefinitions []release.HookOutputLogPolicy) string { + var commaSeparated string + for i, policy := range hookDefinitions { + if i+1 == len(hookDefinitions) { + commaSeparated += policy.String() + } else { + commaSeparated += policy.String() + "," + } + } + return commaSeparated +} + +func TestInstallRelease_HookOutputLogsOnFailure(t *testing.T) { + // Should output on failure with expected namespace if hook-failed is set + runInstallForHooksWithFailure(t, podManifestWithOutputLogs([]release.HookOutputLogPolicy{release.HookOutputOnFailed}), "spaced", true) + runInstallForHooksWithFailure(t, podManifestWithOutputLogWithNamespace([]release.HookOutputLogPolicy{release.HookOutputOnFailed}), "sneaky-namespace", true) + runInstallForHooksWithFailure(t, jobManifestWithOutputLog([]release.HookOutputLogPolicy{release.HookOutputOnFailed}), "spaced", true) + runInstallForHooksWithFailure(t, jobManifestWithOutputLogWithNamespace([]release.HookOutputLogPolicy{release.HookOutputOnFailed}), "rem-namespace", true) + + // Should not output on failure with expected namespace if hook-succeed is set + runInstallForHooksWithFailure(t, podManifestWithOutputLogs([]release.HookOutputLogPolicy{release.HookOutputOnSucceeded}), "", false) + runInstallForHooksWithFailure(t, podManifestWithOutputLogWithNamespace([]release.HookOutputLogPolicy{release.HookOutputOnSucceeded}), "", false) + runInstallForHooksWithFailure(t, jobManifestWithOutputLog([]release.HookOutputLogPolicy{release.HookOutputOnSucceeded}), "", false) + runInstallForHooksWithFailure(t, jobManifestWithOutputLogWithNamespace([]release.HookOutputLogPolicy{release.HookOutputOnSucceeded}), "", false) +} + +func TestInstallRelease_HookOutputLogsOnSuccess(t *testing.T) { + // Should output on success with expected namespace if hook-succeeded is set + runInstallForHooksWithSuccess(t, podManifestWithOutputLogs([]release.HookOutputLogPolicy{release.HookOutputOnSucceeded}), "spaced", true) + runInstallForHooksWithSuccess(t, podManifestWithOutputLogWithNamespace([]release.HookOutputLogPolicy{release.HookOutputOnSucceeded}), "sneaky-namespace", true) + runInstallForHooksWithSuccess(t, jobManifestWithOutputLog([]release.HookOutputLogPolicy{release.HookOutputOnSucceeded}), "spaced", true) + runInstallForHooksWithSuccess(t, jobManifestWithOutputLogWithNamespace([]release.HookOutputLogPolicy{release.HookOutputOnSucceeded}), "rem-namespace", true) + + // Should not output on success if hook-failed is set + runInstallForHooksWithSuccess(t, podManifestWithOutputLogs([]release.HookOutputLogPolicy{release.HookOutputOnFailed}), "", false) + runInstallForHooksWithSuccess(t, podManifestWithOutputLogWithNamespace([]release.HookOutputLogPolicy{release.HookOutputOnFailed}), "", false) + runInstallForHooksWithSuccess(t, jobManifestWithOutputLog([]release.HookOutputLogPolicy{release.HookOutputOnFailed}), "", false) + runInstallForHooksWithSuccess(t, jobManifestWithOutputLogWithNamespace([]release.HookOutputLogPolicy{release.HookOutputOnFailed}), "", false) +} + +func TestInstallRelease_HooksOutputLogsOnSuccessAndFailure(t *testing.T) { + // Should output on success with expected namespace if hook-succeeded and hook-failed is set + runInstallForHooksWithSuccess(t, podManifestWithOutputLogs([]release.HookOutputLogPolicy{release.HookOutputOnSucceeded, release.HookOutputOnFailed}), "spaced", true) + runInstallForHooksWithSuccess(t, podManifestWithOutputLogWithNamespace([]release.HookOutputLogPolicy{release.HookOutputOnSucceeded, release.HookOutputOnFailed}), "sneaky-namespace", true) + runInstallForHooksWithSuccess(t, jobManifestWithOutputLog([]release.HookOutputLogPolicy{release.HookOutputOnSucceeded, release.HookOutputOnFailed}), "spaced", true) + runInstallForHooksWithSuccess(t, jobManifestWithOutputLogWithNamespace([]release.HookOutputLogPolicy{release.HookOutputOnSucceeded, release.HookOutputOnFailed}), "rem-namespace", true) + + // Should output on failure if hook-succeeded and hook-failed is set + runInstallForHooksWithFailure(t, podManifestWithOutputLogs([]release.HookOutputLogPolicy{release.HookOutputOnSucceeded, release.HookOutputOnFailed}), "spaced", true) + runInstallForHooksWithFailure(t, podManifestWithOutputLogWithNamespace([]release.HookOutputLogPolicy{release.HookOutputOnSucceeded, release.HookOutputOnFailed}), "sneaky-namespace", true) + runInstallForHooksWithFailure(t, jobManifestWithOutputLog([]release.HookOutputLogPolicy{release.HookOutputOnSucceeded, release.HookOutputOnFailed}), "spaced", true) + runInstallForHooksWithFailure(t, jobManifestWithOutputLogWithNamespace([]release.HookOutputLogPolicy{release.HookOutputOnSucceeded, release.HookOutputOnFailed}), "rem-namespace", true) +} + +func runInstallForHooksWithSuccess(t *testing.T, manifest, expectedNamespace string, shouldOutput bool) { + var expectedOutput string + if shouldOutput { + expectedOutput = fmt.Sprintf("attempted to output logs for namespace: %s", expectedNamespace) + } + is := assert.New(t) + instAction := installAction(t) + instAction.ReleaseName = "failed-hooks" + outBuffer := &bytes.Buffer{} + instAction.cfg.KubeClient = &kubefake.PrintingKubeClient{Out: io.Discard, LogOutput: outBuffer} + + templates := []*chart.File{ + {Name: "templates/hello", Data: []byte("hello: world")}, + {Name: "templates/hooks", Data: []byte(manifest)}, + } + vals := map[string]interface{}{} + + res, err := instAction.Run(buildChartWithTemplates(templates), vals) + is.NoError(err) + is.Equal(expectedOutput, outBuffer.String()) + is.Equal(release.StatusDeployed, res.Info.Status) +} + +func runInstallForHooksWithFailure(t *testing.T, manifest, expectedNamespace string, shouldOutput bool) { + var expectedOutput string + if shouldOutput { + expectedOutput = fmt.Sprintf("attempted to output logs for namespace: %s", expectedNamespace) + } + is := assert.New(t) + instAction := installAction(t) + instAction.ReleaseName = "failed-hooks" + failingClient := instAction.cfg.KubeClient.(*kubefake.FailingKubeClient) + failingClient.WatchUntilReadyError = fmt.Errorf("failed watch") + instAction.cfg.KubeClient = failingClient + outBuffer := &bytes.Buffer{} + failingClient.PrintingKubeClient = kubefake.PrintingKubeClient{Out: io.Discard, LogOutput: outBuffer} + + templates := []*chart.File{ + {Name: "templates/hello", Data: []byte("hello: world")}, + {Name: "templates/hooks", Data: []byte(manifest)}, + } + vals := map[string]interface{}{} + + res, err := instAction.Run(buildChartWithTemplates(templates), vals) + is.Error(err) + is.Contains(res.Info.Description, "failed pre-install") + is.Equal(expectedOutput, outBuffer.String()) + is.Equal(release.StatusFailed, res.Info.Status) +} + +type HookFailedError struct{} + +func (e *HookFailedError) Error() string { + return "Hook failed!" +} + +type HookFailingKubeClient struct { + kubefake.PrintingKubeClient + failOn resource.Info + deleteRecord []resource.Info +} + +type HookFailingKubeWaiter struct { + *kubefake.PrintingKubeWaiter + failOn resource.Info +} + +func (*HookFailingKubeClient) Build(reader io.Reader, _ bool) (kube.ResourceList, error) { + configMap := &v1.ConfigMap{} + + err := yaml.NewYAMLOrJSONDecoder(reader, 1000).Decode(configMap) + + if err != nil { + return kube.ResourceList{}, err + } + + return kube.ResourceList{{ + Name: configMap.Name, + Namespace: configMap.Namespace, + }}, nil +} + +func (h *HookFailingKubeWaiter) WatchUntilReady(resources kube.ResourceList, _ time.Duration) error { + for _, res := range resources { + if res.Name == h.failOn.Name && res.Namespace == h.failOn.Namespace { + return &HookFailedError{} + } + } + return nil +} + +func (h *HookFailingKubeClient) Delete(resources kube.ResourceList) (*kube.Result, []error) { + for _, res := range resources { + h.deleteRecord = append(h.deleteRecord, resource.Info{ + Name: res.Name, + Namespace: res.Namespace, + }) + } + + return h.PrintingKubeClient.Delete(resources) +} + +func (h *HookFailingKubeClient) GetWaiter(strategy kube.WaitStrategy) (kube.Waiter, error) { + waiter, _ := h.PrintingKubeClient.GetWaiter(strategy) + return &HookFailingKubeWaiter{ + PrintingKubeWaiter: waiter.(*kubefake.PrintingKubeWaiter), + failOn: h.failOn, + }, nil +} + +func TestHooksCleanUp(t *testing.T) { + hookEvent := release.HookPreInstall + + testCases := []struct { + name string + inputRelease release.Release + failOn resource.Info + expectedDeleteRecord []resource.Info + expectError bool + }{ + { + "Deletion hook runs for previously successful hook on failure of a heavier weight hook", + release.Release{ + Name: "test-release", + Namespace: "test", + Hooks: []*release.Hook{ + { + Name: "hook-1", + Kind: "ConfigMap", + Path: "templates/service_account.yaml", + Manifest: `apiVersion: v1 +kind: ConfigMap +metadata: + name: build-config-1 + namespace: test +data: + foo: bar +`, + Weight: -5, + Events: []release.HookEvent{ + hookEvent, + }, + DeletePolicies: []release.HookDeletePolicy{ + release.HookBeforeHookCreation, + release.HookSucceeded, + release.HookFailed, + }, + LastRun: release.HookExecution{ + Phase: release.HookPhaseSucceeded, + }, + }, + { + Name: "hook-2", + Kind: "ConfigMap", + Path: "templates/job.yaml", + Manifest: `apiVersion: v1 +kind: ConfigMap +metadata: + name: build-config-2 + namespace: test +data: + foo: bar +`, + Weight: 0, + Events: []release.HookEvent{ + hookEvent, + }, + DeletePolicies: []release.HookDeletePolicy{ + release.HookBeforeHookCreation, + release.HookSucceeded, + release.HookFailed, + }, + LastRun: release.HookExecution{ + Phase: release.HookPhaseFailed, + }, + }, + }, + }, resource.Info{ + Name: "build-config-2", + Namespace: "test", + }, []resource.Info{ + { + // This should be in the record for `before-hook-creation` + Name: "build-config-1", + Namespace: "test", + }, + { + // This should be in the record for `before-hook-creation` + Name: "build-config-2", + Namespace: "test", + }, + { + // This should be in the record for cleaning up (the failure first) + Name: "build-config-2", + Namespace: "test", + }, + { + // This should be in the record for cleaning up (then the previously successful) + Name: "build-config-1", + Namespace: "test", + }, + }, true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + kubeClient := &HookFailingKubeClient{ + kubefake.PrintingKubeClient{Out: io.Discard}, tc.failOn, []resource.Info{}, + } + + configuration := &Configuration{ + Releases: storage.Init(driver.NewMemory()), + KubeClient: kubeClient, + Capabilities: chartutil.DefaultCapabilities, + } + + err := configuration.execHook(&tc.inputRelease, hookEvent, kube.StatusWatcherStrategy, 600) + + if !reflect.DeepEqual(kubeClient.deleteRecord, tc.expectedDeleteRecord) { + t.Fatalf("Got unexpected delete record, expected: %#v, but got: %#v", kubeClient.deleteRecord, tc.expectedDeleteRecord) + } + + if err != nil && !tc.expectError { + t.Fatalf("Got an unexpected error.") + } + + if err == nil && tc.expectError { + t.Fatalf("Expected and error but did not get it.") + } + }) + } +} diff --git a/pkg/action/install.go b/pkg/action/install.go index 7ad7d1804..0ea5efb81 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -23,6 +23,7 @@ import ( "fmt" "io" "io/fs" + "log/slog" "net/url" "os" "path" @@ -40,20 +41,20 @@ import ( "k8s.io/cli-runtime/pkg/resource" "sigs.k8s.io/yaml" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/cli" - "helm.sh/helm/v3/pkg/downloader" - "helm.sh/helm/v3/pkg/getter" - "helm.sh/helm/v3/pkg/kube" - kubefake "helm.sh/helm/v3/pkg/kube/fake" - "helm.sh/helm/v3/pkg/postrender" - "helm.sh/helm/v3/pkg/registry" - "helm.sh/helm/v3/pkg/release" - "helm.sh/helm/v3/pkg/releaseutil" - "helm.sh/helm/v3/pkg/repo" - "helm.sh/helm/v3/pkg/storage" - "helm.sh/helm/v3/pkg/storage/driver" + chart "helm.sh/helm/v4/pkg/chart/v2" + chartutil "helm.sh/helm/v4/pkg/chart/v2/util" + "helm.sh/helm/v4/pkg/cli" + "helm.sh/helm/v4/pkg/downloader" + "helm.sh/helm/v4/pkg/getter" + "helm.sh/helm/v4/pkg/kube" + kubefake "helm.sh/helm/v4/pkg/kube/fake" + "helm.sh/helm/v4/pkg/postrender" + "helm.sh/helm/v4/pkg/registry" + releaseutil "helm.sh/helm/v4/pkg/release/util" + release "helm.sh/helm/v4/pkg/release/v1" + "helm.sh/helm/v4/pkg/repo" + "helm.sh/helm/v4/pkg/storage" + "helm.sh/helm/v4/pkg/storage/driver" ) // notesFileSuffix that we want to treat special. It goes through the templating engine @@ -80,7 +81,7 @@ type Install struct { HideSecret bool DisableHooks bool Replace bool - Wait bool + WaitStrategy kube.WaitStrategy WaitForJobs bool Devel bool DependencyUpdate bool @@ -143,19 +144,19 @@ func NewInstall(cfg *Configuration) *Install { in := &Install{ cfg: cfg, } - in.ChartPathOptions.registryClient = cfg.RegistryClient + in.registryClient = cfg.RegistryClient return in } // SetRegistryClient sets the registry client for the install action func (i *Install) SetRegistryClient(registryClient *registry.Client) { - i.ChartPathOptions.registryClient = registryClient + i.registryClient = registryClient } // GetRegistryClient get the registry client. func (i *Install) GetRegistryClient() *registry.Client { - return i.ChartPathOptions.registryClient + return i.registryClient } func (i *Install) installCRDs(crds []chart.CRD) error { @@ -173,7 +174,7 @@ func (i *Install) installCRDs(crds []chart.CRD) error { // If the error is CRD already exists, continue. if apierrors.IsAlreadyExists(err) { crdName := res[0].Name - i.cfg.Log("CRD %s is already present. Skipping.", crdName) + slog.Debug("CRD is already present. Skipping", "crd", crdName) continue } return fmt.Errorf("failed to install CRD %s: %w", obj.Name, err) @@ -181,8 +182,12 @@ func (i *Install) installCRDs(crds []chart.CRD) error { totalItems = append(totalItems, res...) } if len(totalItems) > 0 { + waiter, err := i.cfg.KubeClient.GetWaiter(i.WaitStrategy) + if err != nil { + return errors.Wrapf(err, "unable to get waiter") + } // Give time for the CRD to be recognized. - if err := i.cfg.KubeClient.Wait(totalItems, 60*time.Second); err != nil { + if err := waiter.Wait(totalItems, 60*time.Second); err != nil { return err } @@ -197,7 +202,7 @@ func (i *Install) installCRDs(crds []chart.CRD) error { return err } - i.cfg.Log("Clearing discovery cache") + slog.Debug("clearing discovery cache") discoveryClient.Invalidate() _, _ = discoveryClient.ServerGroups() @@ -210,7 +215,7 @@ func (i *Install) installCRDs(crds []chart.CRD) error { return err } if resettable, ok := restMapper.(meta.ResettableRESTMapper); ok { - i.cfg.Log("Clearing REST mapper cache") + slog.Debug("clearing REST mapper cache") resettable.Reset() } } @@ -234,21 +239,25 @@ func (i *Install) RunWithContext(ctx context.Context, chrt *chart.Chart, vals ma // Check reachability of cluster unless in client-only mode (e.g. `helm template` without `--validate`) if !i.ClientOnly { if err := i.cfg.KubeClient.IsReachable(); err != nil { - return nil, err + slog.Error(fmt.Sprintf("cluster reachability check failed: %v", err)) + return nil, errors.Wrap(err, "cluster reachability check failed") } } // HideSecret must be used with dry run. Otherwise, return an error. if !i.isDryRun() && i.HideSecret { + slog.Error("hiding Kubernetes secrets requires a dry-run mode") return nil, errors.New("Hiding Kubernetes secrets requires a dry-run mode") } if err := i.availableName(); err != nil { - return nil, err + slog.Error("release name check failed", slog.Any("error", err)) + return nil, errors.Wrap(err, "release name check failed") } - if err := chartutil.ProcessDependenciesWithMerge(chrt, vals); err != nil { - return nil, err + if err := chartutil.ProcessDependencies(chrt, vals); err != nil { + slog.Error("chart dependencies processing failed", slog.Any("error", err)) + return nil, errors.Wrap(err, "chart dependencies processing failed") } var interactWithRemote bool @@ -261,7 +270,7 @@ func (i *Install) RunWithContext(ctx context.Context, chrt *chart.Chart, vals ma if crds := chrt.CRDObjects(); !i.ClientOnly && !i.SkipCRDs && len(crds) > 0 { // On dry run, bail here if i.isDryRun() { - i.cfg.Log("WARNING: This chart or one of its subcharts contains CRDs. Rendering may fail or contain inaccuracies.") + slog.Warn("This chart or one of its subcharts contains CRDs. Rendering may fail or contain inaccuracies.") } else if err := i.installCRDs(crds); err != nil { return nil, err } @@ -281,12 +290,14 @@ func (i *Install) RunWithContext(ctx context.Context, chrt *chart.Chart, vals ma mem.SetNamespace(i.Namespace) i.cfg.Releases = storage.Init(mem) } else if !i.ClientOnly && len(i.APIVersions) > 0 { - i.cfg.Log("API Version list given outside of client only mode, this list will be ignored") + slog.Debug("API Version list given outside of client only mode, this list will be ignored") } // Make sure if Atomic is set, that wait is set as well. This makes it so // the user doesn't have to specify both - i.Wait = i.Wait || i.Atomic + if i.WaitStrategy == kube.HookOnlyStrategy && i.Atomic { + i.WaitStrategy = kube.StatusWatcherStrategy + } caps, err := i.cfg.getCapabilities() if err != nil { @@ -445,7 +456,7 @@ func (i *Install) performInstall(rel *release.Release, toBeAdopted kube.Resource var err error // pre-install hooks if !i.DisableHooks { - if err := i.cfg.execHook(rel, release.HookPreInstall, i.Timeout); err != nil { + if err := i.cfg.execHook(rel, release.HookPreInstall, i.WaitStrategy, i.Timeout); err != nil { return rel, fmt.Errorf("failed pre-install: %s", err) } } @@ -456,25 +467,32 @@ func (i *Install) performInstall(rel *release.Release, toBeAdopted kube.Resource if len(toBeAdopted) == 0 && len(resources) > 0 { _, err = i.cfg.KubeClient.Create(resources) } else if len(resources) > 0 { - _, err = i.cfg.KubeClient.Update(toBeAdopted, resources, i.Force) + if i.TakeOwnership { + _, err = i.cfg.KubeClient.(kube.InterfaceThreeWayMerge).UpdateThreeWayMerge(toBeAdopted, resources, i.Force) + } else { + _, err = i.cfg.KubeClient.Update(toBeAdopted, resources, i.Force) + } } if err != nil { return rel, err } - if i.Wait { - if i.WaitForJobs { - err = i.cfg.KubeClient.WaitWithJobs(resources, i.Timeout) - } else { - err = i.cfg.KubeClient.Wait(resources, i.Timeout) - } - if err != nil { - return rel, err - } + waiter, err := i.cfg.KubeClient.GetWaiter(i.WaitStrategy) + if err != nil { + return rel, fmt.Errorf("failed to get waiter: %w", err) + } + + if i.WaitForJobs { + err = waiter.WaitWithJobs(resources, i.Timeout) + } else { + err = waiter.Wait(resources, i.Timeout) + } + if err != nil { + return rel, err } if !i.DisableHooks { - if err := i.cfg.execHook(rel, release.HookPostInstall, i.Timeout); err != nil { + if err := i.cfg.execHook(rel, release.HookPostInstall, i.WaitStrategy, i.Timeout); err != nil { return rel, fmt.Errorf("failed post-install: %s", err) } } @@ -493,7 +511,7 @@ func (i *Install) performInstall(rel *release.Release, toBeAdopted kube.Resource // One possible strategy would be to do a timed retry to see if we can get // this stored in the future. if err := i.recordRelease(rel); err != nil { - i.cfg.Log("failed to record the release: %s", err) + slog.Error("failed to record the release", slog.Any("error", err)) } return rel, nil @@ -502,7 +520,7 @@ func (i *Install) performInstall(rel *release.Release, toBeAdopted kube.Resource func (i *Install) failRelease(rel *release.Release, err error) (*release.Release, error) { rel.SetStatus(release.StatusFailed, fmt.Sprintf("Release %q failed: %s", i.ReleaseName, err.Error())) if i.Atomic { - i.cfg.Log("Install failed and atomic is set, uninstalling release") + slog.Debug("install failed, uninstalling release", "release", i.ReleaseName) uninstall := NewUninstall(i.cfg) uninstall.DisableHooks = i.DisableHooks uninstall.KeepHistory = false @@ -599,8 +617,8 @@ func (i *Install) replaceRelease(rel *release.Release) error { return i.recordRelease(last) } -// write the to /. controls if the file is created or content will be appended -func writeToFile(outputDir string, name string, data string, append bool) error { +// write the to /. controls if the file is created or content will be appended +func writeToFile(outputDir string, name string, data string, appendData bool) error { outfileName := strings.Join([]string{outputDir, name}, string(filepath.Separator)) err := ensureDirectoryForFile(outfileName) @@ -608,14 +626,15 @@ func writeToFile(outputDir string, name string, data string, append bool) error return err } - f, err := createOrOpenFile(outfileName, append) + f, err := createOrOpenFile(outfileName, appendData) if err != nil { return err } defer f.Close() - _, err = f.WriteString(fmt.Sprintf("---\n# Source: %s\n%s\n", name, data)) + _, err = fmt.Fprintf(f, "---\n# Source: %s\n%s\n", name, data) + if err != nil { return err } @@ -624,8 +643,8 @@ func writeToFile(outputDir string, name string, data string, append bool) error return nil } -func createOrOpenFile(filename string, append bool) (*os.File, error) { - if append { +func createOrOpenFile(filename string, appendData bool) (*os.File, error) { + if appendData { return os.OpenFile(filename, os.O_APPEND|os.O_WRONLY, 0600) } return os.Create(filename) @@ -770,6 +789,7 @@ func (c *ChartPathOptions) LocateChart(name string, settings *cli.EnvSettings) ( getter.WithTLSClientConfig(c.CertFile, c.KeyFile, c.CaFile), getter.WithInsecureSkipVerifyTLS(c.InsecureSkipTLSverify), getter.WithPlainHTTP(c.PlainHTTP), + getter.WithBasicAuth(c.Username, c.Password), }, RepositoryConfig: settings.RepositoryConfig, RepositoryCache: settings.RepositoryCache, @@ -784,8 +804,16 @@ func (c *ChartPathOptions) LocateChart(name string, settings *cli.EnvSettings) ( dl.Verify = downloader.VerifyAlways } if c.RepoURL != "" { - chartURL, err := repo.FindChartInAuthAndTLSAndPassRepoURL(c.RepoURL, c.Username, c.Password, name, version, - c.CertFile, c.KeyFile, c.CaFile, c.InsecureSkipTLSverify, c.PassCredentialsAll, getter.All(settings)) + chartURL, err := repo.FindChartInRepoURL( + c.RepoURL, + name, + getter.All(settings), + repo.WithChartVersion(version), + repo.WithClientTLS(c.CertFile, c.KeyFile, c.CaFile), + repo.WithUsernamePassword(c.Username, c.Password), + repo.WithInsecureSkipTLSverify(c.InsecureSkipTLSverify), + repo.WithPassCredentialsAll(c.PassCredentialsAll), + ) if err != nil { return "", err } diff --git a/pkg/action/install_test.go b/pkg/action/install_test.go index c6de71191..f8bd2b001 100644 --- a/pkg/action/install_test.go +++ b/pkg/action/install_test.go @@ -17,11 +17,13 @@ limitations under the License. package action import ( + "bytes" "context" "errors" "fmt" "io" "io/fs" + "net/http" "os" "path/filepath" "regexp" @@ -32,14 +34,23 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - - "helm.sh/helm/v3/internal/test" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chartutil" - kubefake "helm.sh/helm/v3/pkg/kube/fake" - "helm.sh/helm/v3/pkg/release" - "helm.sh/helm/v3/pkg/storage/driver" - helmtime "helm.sh/helm/v3/pkg/time" + appsv1 "k8s.io/api/apps/v1" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + kuberuntime "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/cli-runtime/pkg/resource" + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest/fake" + + "helm.sh/helm/v4/internal/test" + chart "helm.sh/helm/v4/pkg/chart/v2" + chartutil "helm.sh/helm/v4/pkg/chart/v2/util" + "helm.sh/helm/v4/pkg/kube" + kubefake "helm.sh/helm/v4/pkg/kube/fake" + release "helm.sh/helm/v4/pkg/release/v1" + "helm.sh/helm/v4/pkg/storage/driver" + helmtime "helm.sh/helm/v4/pkg/time" ) type nameTemplateTestCase struct { @@ -48,6 +59,62 @@ type nameTemplateTestCase struct { expectedErrorStr string } +func createDummyResourceList(owned bool) kube.ResourceList { + obj := &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "dummyName", + Namespace: "spaced", + }, + } + + if owned { + obj.Labels = map[string]string{ + "app.kubernetes.io/managed-by": "Helm", + } + obj.Annotations = map[string]string{ + "meta.helm.sh/release-name": "test-install-release", + "meta.helm.sh/release-namespace": "spaced", + } + } + + resInfo := resource.Info{ + Name: "dummyName", + Namespace: "spaced", + Mapping: &meta.RESTMapping{ + Resource: schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployment"}, + GroupVersionKind: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"}, + Scope: meta.RESTScopeNamespace, + }, + Object: obj, + } + body := io.NopCloser(bytes.NewReader([]byte(kuberuntime.EncodeOrDie(appsv1Codec, obj)))) + + resInfo.Client = &fake.RESTClient{ + GroupVersion: schema.GroupVersion{Group: "apps", Version: "v1"}, + NegotiatedSerializer: scheme.Codecs.WithoutConversion(), + Client: fake.CreateHTTPClient(func(_ *http.Request) (*http.Response, error) { + header := http.Header{} + header.Set("Content-Type", kuberuntime.ContentTypeJSON) + return &http.Response{ + StatusCode: http.StatusOK, + Header: header, + Body: body, + }, nil + }), + } + var resourceList kube.ResourceList + resourceList.Append(&resInfo) + return resourceList +} + +func installActionWithConfig(config *Configuration) *Install { + instAction := NewInstall(config) + instAction.Namespace = "spaced" + instAction.ReleaseName = "test-install-release" + + return instAction +} + func installAction(t *testing.T) *Install { config := actionConfigFixture(t) instAction := NewInstall(config) @@ -93,6 +160,61 @@ func TestInstallRelease(t *testing.T) { is.Equal(lastRelease.Info.Status, release.StatusDeployed) } +func TestInstallReleaseWithTakeOwnership_ResourceNotOwned(t *testing.T) { + // This test will test checking ownership of a resource + // returned by the fake client. If the resource is not + // owned by the chart, ownership is taken. + // To verify ownership has been taken, the fake client + // needs to store state which is a bigger rewrite. + // TODO: Ensure fake kube client stores state. Maybe using + // "k8s.io/client-go/kubernetes/fake" could be sufficient? i.e + // "Client{Namespace: namespace, kubeClient: k8sfake.NewClientset()}" + + is := assert.New(t) + + // Resource list from cluster is NOT owned by helm chart + config := actionConfigFixtureWithDummyResources(t, createDummyResourceList(false)) + instAction := installActionWithConfig(config) + instAction.TakeOwnership = true + res, err := instAction.Run(buildChart(), nil) + if err != nil { + t.Fatalf("Failed install: %s", err) + } + + rel, err := instAction.cfg.Releases.Get(res.Name, res.Version) + is.NoError(err) + + is.Equal(rel.Info.Description, "Install complete") +} + +func TestInstallReleaseWithTakeOwnership_ResourceOwned(t *testing.T) { + is := assert.New(t) + + // Resource list from cluster is owned by helm chart + config := actionConfigFixtureWithDummyResources(t, createDummyResourceList(true)) + instAction := installActionWithConfig(config) + instAction.TakeOwnership = false + res, err := instAction.Run(buildChart(), nil) + if err != nil { + t.Fatalf("Failed install: %s", err) + } + rel, err := instAction.cfg.Releases.Get(res.Name, res.Version) + is.NoError(err) + + is.Equal(rel.Info.Description, "Install complete") +} + +func TestInstallReleaseWithTakeOwnership_ResourceOwnedNoFlag(t *testing.T) { + is := assert.New(t) + + // Resource list from cluster is NOT owned by helm chart + config := actionConfigFixtureWithDummyResources(t, createDummyResourceList(false)) + instAction := installActionWithConfig(config) + _, err := instAction.Run(buildChart(), nil) + is.Error(err) + is.Contains(err.Error(), "Unable to continue with install") +} + func TestInstallReleaseWithValues(t *testing.T) { is := assert.New(t) instAction := installAction(t) @@ -356,11 +478,14 @@ func TestInstallRelease_FailedHooks(t *testing.T) { failer := instAction.cfg.KubeClient.(*kubefake.FailingKubeClient) failer.WatchUntilReadyError = fmt.Errorf("Failed watch") instAction.cfg.KubeClient = failer + outBuffer := &bytes.Buffer{} + failer.PrintingKubeClient = kubefake.PrintingKubeClient{Out: io.Discard, LogOutput: outBuffer} vals := map[string]interface{}{} res, err := instAction.Run(buildChart(), vals) is.Error(err) is.Contains(res.Info.Description, "failed post-install") + is.Equal("", outBuffer.String()) is.Equal(release.StatusFailed, res.Info.Status) } @@ -409,7 +534,7 @@ func TestInstallRelease_Wait(t *testing.T) { failer := instAction.cfg.KubeClient.(*kubefake.FailingKubeClient) failer.WaitError = fmt.Errorf("I timed out") instAction.cfg.KubeClient = failer - instAction.Wait = true + instAction.WaitStrategy = kube.StatusWatcherStrategy vals := map[string]interface{}{} goroutines := runtime.NumGoroutine() @@ -428,7 +553,7 @@ func TestInstallRelease_Wait_Interrupted(t *testing.T) { failer := instAction.cfg.KubeClient.(*kubefake.FailingKubeClient) failer.WaitDuration = 10 * time.Second instAction.cfg.KubeClient = failer - instAction.Wait = true + instAction.WaitStrategy = kube.StatusWatcherStrategy vals := map[string]interface{}{} ctx, cancel := context.WithCancel(context.Background()) @@ -451,7 +576,7 @@ func TestInstallRelease_WaitForJobs(t *testing.T) { failer := instAction.cfg.KubeClient.(*kubefake.FailingKubeClient) failer.WaitError = fmt.Errorf("I timed out") instAction.cfg.KubeClient = failer - instAction.Wait = true + instAction.WaitStrategy = kube.StatusWatcherStrategy instAction.WaitForJobs = true vals := map[string]interface{}{} @@ -518,6 +643,8 @@ func TestInstallRelease_Atomic_Interrupted(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) time.AfterFunc(time.Second, cancel) + goroutines := runtime.NumGoroutine() + res, err := instAction.RunWithContext(ctx, buildChart(), vals) is.Error(err) is.Contains(err.Error(), "context canceled") @@ -528,6 +655,9 @@ func TestInstallRelease_Atomic_Interrupted(t *testing.T) { _, err = instAction.cfg.Releases.Get(res.Name, res.Version) is.Error(err) is.Equal(err, driver.ErrReleaseNotFound) + is.Equal(goroutines+1, runtime.NumGoroutine()) // installation goroutine still is in background + time.Sleep(10 * time.Second) // wait for goroutine to finish + is.Equal(goroutines, runtime.NumGoroutine()) } func TestNameTemplate(t *testing.T) { diff --git a/pkg/action/lint.go b/pkg/action/lint.go index 9fd83e0ea..7b3c00ad2 100644 --- a/pkg/action/lint.go +++ b/pkg/action/lint.go @@ -22,9 +22,9 @@ import ( "path/filepath" "strings" - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/lint" - "helm.sh/helm/v3/pkg/lint/support" + chartutil "helm.sh/helm/v4/pkg/chart/v2/util" + "helm.sh/helm/v4/pkg/lint" + "helm.sh/helm/v4/pkg/lint/support" ) // Lint is the action for checking that the semantics of a chart are well-formed. @@ -125,5 +125,11 @@ func lintChart(path string, vals map[string]interface{}, namespace string, kubeV return linter, fmt.Errorf("unable to check Chart.yaml file in chart: %w", err) } - return lint.AllWithKubeVersionAndSchemaValidation(chartPath, vals, namespace, kubeVersion, skipSchemaValidation), nil + return lint.RunAll( + chartPath, + vals, + namespace, + lint.WithKubeVersion(kubeVersion), + lint.WithSkipSchemaValidation(skipSchemaValidation), + ), nil } diff --git a/pkg/action/list.go b/pkg/action/list.go index af0725c4a..82500582f 100644 --- a/pkg/action/list.go +++ b/pkg/action/list.go @@ -22,8 +22,8 @@ import ( "k8s.io/apimachinery/pkg/labels" - "helm.sh/helm/v3/pkg/release" - "helm.sh/helm/v3/pkg/releaseutil" + releaseutil "helm.sh/helm/v4/pkg/release/util" + release "helm.sh/helm/v4/pkg/release/v1" ) // ListStates represents zero or more status codes that a list item may have set diff --git a/pkg/action/list_test.go b/pkg/action/list_test.go index 73009d523..e41949310 100644 --- a/pkg/action/list_test.go +++ b/pkg/action/list_test.go @@ -21,8 +21,8 @@ import ( "github.com/stretchr/testify/assert" - "helm.sh/helm/v3/pkg/release" - "helm.sh/helm/v3/pkg/storage" + release "helm.sh/helm/v4/pkg/release/v1" + "helm.sh/helm/v4/pkg/storage" ) func TestListStates(t *testing.T) { diff --git a/pkg/action/package.go b/pkg/action/package.go index f6136c4b9..e57ce4921 100644 --- a/pkg/action/package.go +++ b/pkg/action/package.go @@ -26,9 +26,9 @@ import ( "github.com/Masterminds/semver/v3" "golang.org/x/term" - "helm.sh/helm/v3/pkg/chart/loader" - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/provenance" + "helm.sh/helm/v4/pkg/chart/v2/loader" + chartutil "helm.sh/helm/v4/pkg/chart/v2/util" + "helm.sh/helm/v4/pkg/provenance" ) // Package is the action for packaging a chart. @@ -39,15 +39,27 @@ type Package struct { Key string Keyring string PassphraseFile string + cachedPassphrase []byte Version string AppVersion string Destination string DependencyUpdate bool - RepositoryConfig string - RepositoryCache string + RepositoryConfig string + RepositoryCache string + PlainHTTP bool + Username string + Password string + CertFile string + KeyFile string + CaFile string + InsecureSkipTLSverify bool } +const ( + passPhraseFileStdin = "-" +) + // NewPackage creates a new Package object with the given configuration. func NewPackage() *Package { return &Package{} @@ -121,7 +133,7 @@ func (p *Package) Clearsign(filename string) error { passphraseFetcher := promptUser if p.PassphraseFile != "" { - passphraseFetcher, err = passphraseFileFetcher(p.PassphraseFile, os.Stdin) + passphraseFetcher, err = p.passphraseFileFetcher(p.PassphraseFile, os.Stdin) if err != nil { return err } @@ -149,25 +161,42 @@ func promptUser(name string) ([]byte, error) { return pw, err } -func passphraseFileFetcher(passphraseFile string, stdin *os.File) (provenance.PassphraseFetcher, error) { - file, err := openPassphraseFile(passphraseFile, stdin) - if err != nil { - return nil, err - } - defer file.Close() +func (p *Package) passphraseFileFetcher(passphraseFile string, stdin *os.File) (provenance.PassphraseFetcher, error) { + // When reading from stdin we cache the passphrase here. If we are + // packaging multiple charts, we reuse the cached passphrase. This + // allows giving the passphrase once on stdin without failing with + // complaints about stdin already being closed. + // + // An alternative to this would be to omit file.Close() for stdin + // below and require the user to provide the same passphrase once + // per chart on stdin, but that does not seem very user-friendly. + + if p.cachedPassphrase == nil { + file, err := openPassphraseFile(passphraseFile, stdin) + if err != nil { + return nil, err + } + defer file.Close() - reader := bufio.NewReader(file) - passphrase, _, err := reader.ReadLine() - if err != nil { - return nil, err + reader := bufio.NewReader(file) + passphrase, _, err := reader.ReadLine() + if err != nil { + return nil, err + } + p.cachedPassphrase = passphrase + + return func(_ string) ([]byte, error) { + return passphrase, nil + }, nil } + return func(_ string) ([]byte, error) { - return passphrase, nil + return p.cachedPassphrase, nil }, nil } func openPassphraseFile(passphraseFile string, stdin *os.File) (*os.File, error) { - if passphraseFile == "-" { + if passphraseFile == passPhraseFileStdin { stat, err := stdin.Stat() if err != nil { return nil, err diff --git a/pkg/action/package_test.go b/pkg/action/package_test.go index d04efdaa6..12bea10dd 100644 --- a/pkg/action/package_test.go +++ b/pkg/action/package_test.go @@ -23,14 +23,15 @@ import ( "github.com/Masterminds/semver/v3" - "helm.sh/helm/v3/internal/test/ensure" + "helm.sh/helm/v4/internal/test/ensure" ) func TestPassphraseFileFetcher(t *testing.T) { secret := "secret" directory := ensure.TempFile(t, "passphrase-file", []byte(secret)) + testPkg := NewPackage() - fetcher, err := passphraseFileFetcher(path.Join(directory, "passphrase-file"), nil) + fetcher, err := testPkg.passphraseFileFetcher(path.Join(directory, "passphrase-file"), nil) if err != nil { t.Fatal("Unable to create passphraseFileFetcher", err) } @@ -48,8 +49,9 @@ func TestPassphraseFileFetcher(t *testing.T) { func TestPassphraseFileFetcher_WithLineBreak(t *testing.T) { secret := "secret" directory := ensure.TempFile(t, "passphrase-file", []byte(secret+"\n\n.")) + testPkg := NewPackage() - fetcher, err := passphraseFileFetcher(path.Join(directory, "passphrase-file"), nil) + fetcher, err := testPkg.passphraseFileFetcher(path.Join(directory, "passphrase-file"), nil) if err != nil { t.Fatal("Unable to create passphraseFileFetcher", err) } @@ -66,17 +68,48 @@ func TestPassphraseFileFetcher_WithLineBreak(t *testing.T) { func TestPassphraseFileFetcher_WithInvalidStdin(t *testing.T) { directory := t.TempDir() + testPkg := NewPackage() stdin, err := os.CreateTemp(directory, "non-existing") if err != nil { t.Fatal("Unable to create test file", err) } - if _, err := passphraseFileFetcher("-", stdin); err == nil { + if _, err := testPkg.passphraseFileFetcher("-", stdin); err == nil { t.Error("Expected passphraseFileFetcher returning an error") } } +func TestPassphraseFileFetcher_WithStdinAndMultipleFetches(t *testing.T) { + testPkg := NewPackage() + stdin, w, err := os.Pipe() + if err != nil { + t.Fatal("Unable to create pipe", err) + } + + passphrase := "secret-from-stdin" + + go func() { + w.Write([]byte(passphrase + "\n")) + }() + + for i := 0; i < 4; i++ { + fetcher, err := testPkg.passphraseFileFetcher("-", stdin) + if err != nil { + t.Errorf("Expected passphraseFileFetcher to not return an error, but got %v", err) + } + + pass, err := fetcher("key") + if err != nil { + t.Errorf("Expected passphraseFileFetcher invocation to succeed, failed with %v", err) + } + + if string(pass) != string(passphrase) { + t.Errorf("Expected multiple passphrase fetch to return %q, got %q", passphrase, pass) + } + } +} + func TestValidateVersion(t *testing.T) { type args struct { ver string diff --git a/pkg/action/pull.go b/pkg/action/pull.go index 184b17a9a..a2f53af0d 100644 --- a/pkg/action/pull.go +++ b/pkg/action/pull.go @@ -22,12 +22,12 @@ import ( "path/filepath" "strings" - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/cli" - "helm.sh/helm/v3/pkg/downloader" - "helm.sh/helm/v3/pkg/getter" - "helm.sh/helm/v3/pkg/registry" - "helm.sh/helm/v3/pkg/repo" + chartutil "helm.sh/helm/v4/pkg/chart/v2/util" + "helm.sh/helm/v4/pkg/cli" + "helm.sh/helm/v4/pkg/downloader" + "helm.sh/helm/v4/pkg/getter" + "helm.sh/helm/v4/pkg/registry" + "helm.sh/helm/v4/pkg/repo" ) // Pull is the action for checking a given release's information. @@ -54,13 +54,8 @@ func WithConfig(cfg *Configuration) PullOpt { } } -// NewPull creates a new Pull object. -func NewPull() *Pull { - return NewPullWithOpts() -} - -// NewPullWithOpts creates a new pull, with configuration options. -func NewPullWithOpts(opts ...PullOpt) *Pull { +// NewPull creates a new Pull with configuration options. +func NewPull(opts ...PullOpt) *Pull { p := &Pull{} for _, fn := range opts { fn(p) @@ -120,7 +115,16 @@ func (p *Pull) Run(chartRef string) (string, error) { } if p.RepoURL != "" { - chartURL, err := repo.FindChartInAuthAndTLSAndPassRepoURL(p.RepoURL, p.Username, p.Password, chartRef, p.Version, p.CertFile, p.KeyFile, p.CaFile, p.InsecureSkipTLSverify, p.PassCredentialsAll, getter.All(p.Settings)) + chartURL, err := repo.FindChartInRepoURL( + p.RepoURL, + chartRef, + getter.All(p.Settings), + repo.WithChartVersion(p.Version), + repo.WithClientTLS(p.CertFile, p.KeyFile, p.CaFile), + repo.WithUsernamePassword(p.Username, p.Password), + repo.WithInsecureSkipTLSverify(p.InsecureSkipTLSverify), + repo.WithPassCredentialsAll(p.PassCredentialsAll), + ) if err != nil { return out.String(), err } diff --git a/pkg/action/push.go b/pkg/action/push.go index 70e5c1582..35472415c 100644 --- a/pkg/action/push.go +++ b/pkg/action/push.go @@ -20,10 +20,10 @@ import ( "io" "strings" - "helm.sh/helm/v3/pkg/cli" - "helm.sh/helm/v3/pkg/pusher" - "helm.sh/helm/v3/pkg/registry" - "helm.sh/helm/v3/pkg/uploader" + "helm.sh/helm/v4/pkg/cli" + "helm.sh/helm/v4/pkg/pusher" + "helm.sh/helm/v4/pkg/registry" + "helm.sh/helm/v4/pkg/uploader" ) // Push is the action for uploading a chart. diff --git a/pkg/action/registry_login.go b/pkg/action/registry_login.go index cd144e1e7..fd9d4bfc6 100644 --- a/pkg/action/registry_login.go +++ b/pkg/action/registry_login.go @@ -19,16 +19,17 @@ package action import ( "io" - "helm.sh/helm/v3/pkg/registry" + "helm.sh/helm/v4/pkg/registry" ) // RegistryLogin performs a registry login operation. type RegistryLogin struct { - cfg *Configuration - certFile string - keyFile string - caFile string - insecure bool + cfg *Configuration + certFile string + keyFile string + caFile string + insecure bool + plainHTTP bool } type RegistryLoginOpt func(*RegistryLogin) error @@ -41,7 +42,7 @@ func WithCertFile(certFile string) RegistryLoginOpt { } } -// WithKeyFile specifies whether to very certificates when communicating. +// WithInsecure specifies whether to verify certificates. func WithInsecure(insecure bool) RegistryLoginOpt { return func(r *RegistryLogin) error { r.insecure = insecure @@ -65,6 +66,14 @@ func WithCAFile(caFile string) RegistryLoginOpt { } } +// WithPlainHTTPLogin use http rather than https for login. +func WithPlainHTTPLogin(isPlain bool) RegistryLoginOpt { + return func(r *RegistryLogin) error { + r.plainHTTP = isPlain + return nil + } +} + // NewRegistryLogin creates a new RegistryLogin object with the given configuration. func NewRegistryLogin(cfg *Configuration) *RegistryLogin { return &RegistryLogin{ @@ -84,5 +93,7 @@ func (a *RegistryLogin) Run(_ io.Writer, hostname string, username string, passw hostname, registry.LoginOptBasicAuth(username, password), registry.LoginOptInsecure(a.insecure), - registry.LoginOptTLSClientConfig(a.certFile, a.keyFile, a.caFile)) + registry.LoginOptTLSClientConfig(a.certFile, a.keyFile, a.caFile), + registry.LoginOptPlainText(a.plainHTTP), + ) } diff --git a/pkg/action/release_testing.go b/pkg/action/release_testing.go index 17f1b5275..b5f6fe712 100644 --- a/pkg/action/release_testing.go +++ b/pkg/action/release_testing.go @@ -20,13 +20,15 @@ import ( "context" "fmt" "io" + "slices" "sort" "time" v1 "k8s.io/api/core/v1" - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/release" + chartutil "helm.sh/helm/v4/pkg/chart/v2/util" + "helm.sh/helm/v4/pkg/kube" + release "helm.sh/helm/v4/pkg/release/v1" ) const ( @@ -74,7 +76,7 @@ func (r *ReleaseTesting) Run(name string) (*release.Release, error) { executingHooks := []*release.Hook{} if len(r.Filters[ExcludeNameFilter]) != 0 { for _, h := range rel.Hooks { - if contains(r.Filters[ExcludeNameFilter], h.Name) { + if slices.Contains(r.Filters[ExcludeNameFilter], h.Name) { skippedHooks = append(skippedHooks, h) } else { executingHooks = append(executingHooks, h) @@ -85,7 +87,7 @@ func (r *ReleaseTesting) Run(name string) (*release.Release, error) { if len(r.Filters[IncludeNameFilter]) != 0 { executingHooks = nil for _, h := range rel.Hooks { - if contains(r.Filters[IncludeNameFilter], h.Name) { + if slices.Contains(r.Filters[IncludeNameFilter], h.Name) { executingHooks = append(executingHooks, h) } else { skippedHooks = append(skippedHooks, h) @@ -94,7 +96,7 @@ func (r *ReleaseTesting) Run(name string) (*release.Release, error) { rel.Hooks = executingHooks } - if err := r.cfg.execHook(rel, release.HookTest, r.Timeout); err != nil { + if err := r.cfg.execHook(rel, release.HookTest, kube.StatusWatcherStrategy, r.Timeout); err != nil { rel.Hooks = append(skippedHooks, rel.Hooks...) r.cfg.Releases.Update(rel) return rel, err @@ -118,10 +120,10 @@ func (r *ReleaseTesting) GetPodLogs(out io.Writer, rel *release.Release) error { for _, h := range hooksByWight { for _, e := range h.Events { if e == release.HookTest { - if contains(r.Filters[ExcludeNameFilter], h.Name) { + if slices.Contains(r.Filters[ExcludeNameFilter], h.Name) { continue } - if len(r.Filters[IncludeNameFilter]) > 0 && !contains(r.Filters[IncludeNameFilter], h.Name) { + if len(r.Filters[IncludeNameFilter]) > 0 && !slices.Contains(r.Filters[IncludeNameFilter], h.Name) { continue } req := client.CoreV1().Pods(r.Namespace).GetLogs(h.Name, &v1.PodLogOptions{}) @@ -141,12 +143,3 @@ func (r *ReleaseTesting) GetPodLogs(out io.Writer, rel *release.Release) error { } return nil } - -func contains(arr []string, value string) bool { - for _, item := range arr { - if item == value { - return true - } - } - return false -} diff --git a/pkg/action/resource_policy.go b/pkg/action/resource_policy.go index 63e83f3d9..b72e94124 100644 --- a/pkg/action/resource_policy.go +++ b/pkg/action/resource_policy.go @@ -19,8 +19,8 @@ package action import ( "strings" - "helm.sh/helm/v3/pkg/kube" - "helm.sh/helm/v3/pkg/releaseutil" + "helm.sh/helm/v4/pkg/kube" + releaseutil "helm.sh/helm/v4/pkg/release/util" ) func filterManifestsToKeep(manifests []releaseutil.Manifest) (keep, remaining []releaseutil.Manifest) { diff --git a/pkg/action/rollback.go b/pkg/action/rollback.go index 09cb2db37..ac8a28fe0 100644 --- a/pkg/action/rollback.go +++ b/pkg/action/rollback.go @@ -19,12 +19,14 @@ package action import ( "bytes" "fmt" + "log/slog" "strings" "time" - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/release" - helmtime "helm.sh/helm/v3/pkg/time" + chartutil "helm.sh/helm/v4/pkg/chart/v2/util" + "helm.sh/helm/v4/pkg/kube" + release "helm.sh/helm/v4/pkg/release/v1" + helmtime "helm.sh/helm/v4/pkg/time" ) // Rollback is the action for rolling back to a given release. @@ -35,7 +37,7 @@ type Rollback struct { Version int Timeout time.Duration - Wait bool + WaitStrategy kube.WaitStrategy WaitForJobs bool DisableHooks bool DryRun bool @@ -60,26 +62,26 @@ func (r *Rollback) Run(name string) error { r.cfg.Releases.MaxHistory = r.MaxHistory - r.cfg.Log("preparing rollback of %s", name) + slog.Debug("preparing rollback", "name", name) currentRelease, targetRelease, err := r.prepareRollback(name) if err != nil { return err } if !r.DryRun { - r.cfg.Log("creating rolled back release for %s", name) + slog.Debug("creating rolled back release", "name", name) if err := r.cfg.Releases.Create(targetRelease); err != nil { return err } } - r.cfg.Log("performing rollback of %s", name) + slog.Debug("performing rollback", "name", name) if _, err := r.performRollback(currentRelease, targetRelease); err != nil { return err } if !r.DryRun { - r.cfg.Log("updating status for rolled back release for %s", name) + slog.Debug("updating status for rolled back release", "name", name) if err := r.cfg.Releases.Update(targetRelease); err != nil { return err } @@ -126,7 +128,7 @@ func (r *Rollback) prepareRollback(name string) (*release.Release, *release.Rele return nil, nil, fmt.Errorf("release has no %d version", previousVersion) } - r.cfg.Log("rolling back %s (current: v%d, target: v%d)", name, currentRelease.Version, previousVersion) + slog.Debug("rolling back", "name", name, "currentVersion", currentRelease.Version, "targetVersion", previousVersion) previousRelease, err := r.cfg.Releases.Get(name, previousVersion) if err != nil { @@ -159,7 +161,7 @@ func (r *Rollback) prepareRollback(name string) (*release.Release, *release.Rele func (r *Rollback) performRollback(currentRelease, targetRelease *release.Release) (*release.Release, error) { if r.DryRun { - r.cfg.Log("dry run for %s", targetRelease.Name) + slog.Debug("dry run", "name", targetRelease.Name) return targetRelease, nil } @@ -174,11 +176,11 @@ func (r *Rollback) performRollback(currentRelease, targetRelease *release.Releas // pre-rollback hooks if !r.DisableHooks { - if err := r.cfg.execHook(targetRelease, release.HookPreRollback, r.Timeout); err != nil { + if err := r.cfg.execHook(targetRelease, release.HookPreRollback, r.WaitStrategy, r.Timeout); err != nil { return targetRelease, err } } else { - r.cfg.Log("rollback hooks disabled for %s", targetRelease.Name) + slog.Debug("rollback hooks disabled", "name", targetRelease.Name) } // It is safe to use "force" here because these are resources currently rendered by the chart. @@ -190,21 +192,21 @@ func (r *Rollback) performRollback(currentRelease, targetRelease *release.Releas if err != nil { msg := fmt.Sprintf("Rollback %q failed: %s", targetRelease.Name, err) - r.cfg.Log("warning: %s", msg) + slog.Warn(msg) currentRelease.Info.Status = release.StatusSuperseded targetRelease.Info.Status = release.StatusFailed targetRelease.Info.Description = msg r.cfg.recordRelease(currentRelease) r.cfg.recordRelease(targetRelease) if r.CleanupOnFail { - r.cfg.Log("Cleanup on fail set, cleaning up %d resources", len(results.Created)) + slog.Debug("cleanup on fail set, cleaning up resources", "count", len(results.Created)) _, errs := r.cfg.KubeClient.Delete(results.Created) if errs != nil { return targetRelease, fmt.Errorf( "an error occurred while cleaning up resources. original rollback error: %w", fmt.Errorf("unable to cleanup resources: %w", joinErrors(errs, ", "))) } - r.cfg.Log("Resource cleanup complete") + slog.Debug("resource cleanup complete") } return targetRelease, err } @@ -215,31 +217,32 @@ func (r *Rollback) performRollback(currentRelease, targetRelease *release.Releas // levels, we should make these error level logs so users are notified // that they'll need to go do the cleanup on their own if err := recreate(r.cfg, results.Updated); err != nil { - r.cfg.Log(err.Error()) + slog.Error(err.Error()) } } - - if r.Wait { - if r.WaitForJobs { - if err := r.cfg.KubeClient.WaitWithJobs(target, r.Timeout); err != nil { - targetRelease.SetStatus(release.StatusFailed, fmt.Sprintf("Release %q failed: %s", targetRelease.Name, err.Error())) - r.cfg.recordRelease(currentRelease) - r.cfg.recordRelease(targetRelease) - return targetRelease, fmt.Errorf("release %s failed: %w", targetRelease.Name, err) - } - } else { - if err := r.cfg.KubeClient.Wait(target, r.Timeout); err != nil { - targetRelease.SetStatus(release.StatusFailed, fmt.Sprintf("Release %q failed: %s", targetRelease.Name, err.Error())) - r.cfg.recordRelease(currentRelease) - r.cfg.recordRelease(targetRelease) - return targetRelease, fmt.Errorf("release %s failed: %w", targetRelease.Name, err) - } + waiter, err := r.cfg.KubeClient.GetWaiter(r.WaitStrategy) + if err != nil { + return nil, fmt.Errorf("unable to set metadata visitor from target release: %w", err) + } + if r.WaitForJobs { + if err := waiter.WaitWithJobs(target, r.Timeout); err != nil { + targetRelease.SetStatus(release.StatusFailed, fmt.Sprintf("Release %q failed: %s", targetRelease.Name, err.Error())) + r.cfg.recordRelease(currentRelease) + r.cfg.recordRelease(targetRelease) + return targetRelease, fmt.Errorf("release %s failed: %w", targetRelease.Name, err) + } + } else { + if err := waiter.Wait(target, r.Timeout); err != nil { + targetRelease.SetStatus(release.StatusFailed, fmt.Sprintf("Release %q failed: %s", targetRelease.Name, err.Error())) + r.cfg.recordRelease(currentRelease) + r.cfg.recordRelease(targetRelease) + return targetRelease, fmt.Errorf("release %s failed: %w", targetRelease.Name, err) } } // post-rollback hooks if !r.DisableHooks { - if err := r.cfg.execHook(targetRelease, release.HookPostRollback, r.Timeout); err != nil { + if err := r.cfg.execHook(targetRelease, release.HookPostRollback, r.WaitStrategy, r.Timeout); err != nil { return targetRelease, err } } @@ -250,7 +253,7 @@ func (r *Rollback) performRollback(currentRelease, targetRelease *release.Releas } // Supersede all previous deployments, see issue #2941. for _, rel := range deployed { - r.cfg.Log("superseding previous deployment %d", rel.Version) + slog.Debug("superseding previous deployment", "version", rel.Version) rel.Info.Status = release.StatusSuperseded r.cfg.recordRelease(rel) } diff --git a/pkg/action/show.go b/pkg/action/show.go index f02005176..a3fbcfa9e 100644 --- a/pkg/action/show.go +++ b/pkg/action/show.go @@ -24,10 +24,10 @@ import ( "k8s.io/cli-runtime/pkg/printers" "sigs.k8s.io/yaml" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chart/loader" - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/registry" + chart "helm.sh/helm/v4/pkg/chart/v2" + "helm.sh/helm/v4/pkg/chart/v2/loader" + chartutil "helm.sh/helm/v4/pkg/chart/v2/util" + "helm.sh/helm/v4/pkg/registry" ) // ShowOutputFormat is the format of the output of `helm show` @@ -64,27 +64,18 @@ type Show struct { } // NewShow creates a new Show object with the given configuration. -// Deprecated: Use NewShowWithConfig -// TODO Helm 4: Fold NewShowWithConfig back into NewShow -func NewShow(output ShowOutputFormat) *Show { - return &Show{ - OutputFormat: output, - } -} - -// NewShowWithConfig creates a new Show object with the given configuration. -func NewShowWithConfig(output ShowOutputFormat, cfg *Configuration) *Show { +func NewShow(output ShowOutputFormat, cfg *Configuration) *Show { sh := &Show{ OutputFormat: output, } - sh.ChartPathOptions.registryClient = cfg.RegistryClient + sh.registryClient = cfg.RegistryClient return sh } // SetRegistryClient sets the registry client to use when pulling a chart from a registry. func (s *Show) SetRegistryClient(client *registry.Client) { - s.ChartPathOptions.registryClient = client + s.registryClient = client } // Run executes 'helm show' against the given release. diff --git a/pkg/action/show_test.go b/pkg/action/show_test.go index 8b617ea85..b1c5d6164 100644 --- a/pkg/action/show_test.go +++ b/pkg/action/show_test.go @@ -19,12 +19,12 @@ package action import ( "testing" - "helm.sh/helm/v3/pkg/chart" + chart "helm.sh/helm/v4/pkg/chart/v2" ) func TestShow(t *testing.T) { config := actionConfigFixture(t) - client := NewShowWithConfig(ShowAll, config) + client := NewShow(ShowAll, config) client.chart = &chart.Chart{ Metadata: &chart.Metadata{Name: "alpine"}, Files: []*chart.File{ @@ -65,7 +65,8 @@ bar } func TestShowNoValues(t *testing.T) { - client := NewShow(ShowAll) + config := actionConfigFixture(t) + client := NewShow(ShowAll, config) client.chart = new(chart.Chart) // Regression tests for missing values. See issue #1024. @@ -81,7 +82,8 @@ func TestShowNoValues(t *testing.T) { } func TestShowValuesByJsonPathFormat(t *testing.T) { - client := NewShow(ShowValues) + config := actionConfigFixture(t) + client := NewShow(ShowValues, config) client.JSONPathTemplate = "{$.nestedKey.simpleKey}" client.chart = buildChart(withSampleValues()) output, err := client.Run("") @@ -95,7 +97,8 @@ func TestShowValuesByJsonPathFormat(t *testing.T) { } func TestShowCRDs(t *testing.T) { - client := NewShow(ShowCRDs) + config := actionConfigFixture(t) + client := NewShow(ShowCRDs, config) client.chart = &chart.Chart{ Metadata: &chart.Metadata{Name: "alpine"}, Files: []*chart.File{ @@ -123,7 +126,8 @@ bar } func TestShowNoReadme(t *testing.T) { - client := NewShow(ShowAll) + config := actionConfigFixture(t) + client := NewShow(ShowAll, config) client.chart = &chart.Chart{ Metadata: &chart.Metadata{Name: "alpine"}, Files: []*chart.File{ diff --git a/pkg/action/status.go b/pkg/action/status.go index ee1c9d613..509c52cd9 100644 --- a/pkg/action/status.go +++ b/pkg/action/status.go @@ -20,8 +20,8 @@ import ( "bytes" "errors" - "helm.sh/helm/v3/pkg/kube" - "helm.sh/helm/v3/pkg/release" + "helm.sh/helm/v4/pkg/kube" + release "helm.sh/helm/v4/pkg/release/v1" ) // Status is the action for checking the deployment status of releases. @@ -32,15 +32,6 @@ type Status struct { Version int - // If true, display description to output format, - // only affect print type table. - // TODO Helm 4: Remove this flag and output the description by default. - ShowDescription bool - - // ShowResources sets if the resources should be retrieved with the status. - // TODO Helm 4: Remove this flag and output the resources by default. - ShowResources bool - // ShowResourcesTable is used with ShowResources. When true this will cause // the resulting objects to be retrieved as a kind=table. ShowResourcesTable bool @@ -59,10 +50,6 @@ func (s *Status) Run(name string) (*release.Release, error) { return nil, err } - if !s.ShowResources { - return s.cfg.releaseContent(name, s.Version) - } - rel, err := s.cfg.releaseContent(name, s.Version) if err != nil { return nil, err diff --git a/pkg/action/uninstall.go b/pkg/action/uninstall.go index 822d138af..61e10b2c8 100644 --- a/pkg/action/uninstall.go +++ b/pkg/action/uninstall.go @@ -18,16 +18,17 @@ package action import ( "fmt" + "log/slog" "strings" "time" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/kube" - "helm.sh/helm/v3/pkg/release" - "helm.sh/helm/v3/pkg/releaseutil" - helmtime "helm.sh/helm/v3/pkg/time" + chartutil "helm.sh/helm/v4/pkg/chart/v2/util" + "helm.sh/helm/v4/pkg/kube" + releaseutil "helm.sh/helm/v4/pkg/release/util" + release "helm.sh/helm/v4/pkg/release/v1" + helmtime "helm.sh/helm/v4/pkg/time" ) // Uninstall is the action for uninstalling releases. @@ -40,7 +41,7 @@ type Uninstall struct { DryRun bool IgnoreNotFound bool KeepHistory bool - Wait bool + WaitStrategy kube.WaitStrategy DeletionPropagation string Timeout time.Duration Description string @@ -59,6 +60,11 @@ func (u *Uninstall) Run(name string) (*release.UninstallReleaseResponse, error) return nil, err } + waiter, err := u.cfg.KubeClient.GetWaiter(u.WaitStrategy) + if err != nil { + return nil, err + } + if u.DryRun { // In the dry run case, just see if the release exists r, err := u.cfg.releaseContent(name, 0) @@ -98,29 +104,29 @@ func (u *Uninstall) Run(name string) (*release.UninstallReleaseResponse, error) return nil, fmt.Errorf("the release named %q is already deleted", name) } - u.cfg.Log("uninstall: Deleting %s", name) + slog.Debug("uninstall: deleting release", "name", name) rel.Info.Status = release.StatusUninstalling rel.Info.Deleted = helmtime.Now() rel.Info.Description = "Deletion in progress (or silently failed)" res := &release.UninstallReleaseResponse{Release: rel} if !u.DisableHooks { - if err := u.cfg.execHook(rel, release.HookPreDelete, u.Timeout); err != nil { + if err := u.cfg.execHook(rel, release.HookPreDelete, u.WaitStrategy, u.Timeout); err != nil { return res, err } } else { - u.cfg.Log("delete hooks disabled for %s", name) + slog.Debug("delete hooks disabled", "release", name) } // From here on out, the release is currently considered to be in StatusUninstalling // state. if err := u.cfg.Releases.Update(rel); err != nil { - u.cfg.Log("uninstall: Failed to store updated release: %s", err) + slog.Debug("uninstall: Failed to store updated release", slog.Any("error", err)) } deletedResources, kept, errs := u.deleteRelease(rel) if errs != nil { - u.cfg.Log("uninstall: Failed to delete release: %s", errs) + slog.Debug("uninstall: Failed to delete release", slog.Any("error", errs)) return nil, fmt.Errorf("failed to delete release: %s", name) } @@ -129,16 +135,12 @@ func (u *Uninstall) Run(name string) (*release.UninstallReleaseResponse, error) } res.Info = kept - if u.Wait { - if kubeClient, ok := u.cfg.KubeClient.(kube.InterfaceExt); ok { - if err := kubeClient.WaitForDelete(deletedResources, u.Timeout); err != nil { - errs = append(errs, err) - } - } + if err := waiter.WaitForDelete(deletedResources, u.Timeout); err != nil { + errs = append(errs, err) } if !u.DisableHooks { - if err := u.cfg.execHook(rel, release.HookPostDelete, u.Timeout); err != nil { + if err := u.cfg.execHook(rel, release.HookPostDelete, u.WaitStrategy, u.Timeout); err != nil { errs = append(errs, err) } } @@ -151,7 +153,7 @@ func (u *Uninstall) Run(name string) (*release.UninstallReleaseResponse, error) } if !u.KeepHistory { - u.cfg.Log("purge requested for %s", name) + slog.Debug("purge requested", "release", name) err := u.purgeReleases(rels...) if err != nil { errs = append(errs, fmt.Errorf("uninstall: Failed to purge the release: %w", err)) @@ -166,7 +168,7 @@ func (u *Uninstall) Run(name string) (*release.UninstallReleaseResponse, error) } if err := u.cfg.Releases.Update(rel); err != nil { - u.cfg.Log("uninstall: Failed to store updated release: %s", err) + slog.Debug("uninstall: Failed to store updated release", slog.Any("error", err)) } if len(errs) > 0 { @@ -239,7 +241,7 @@ func (u *Uninstall) deleteRelease(rel *release.Release) (kube.ResourceList, stri } if len(resources) > 0 { if kubeClient, ok := u.cfg.KubeClient.(kube.InterfaceDeletionPropagation); ok { - _, errs = kubeClient.DeleteWithPropagationPolicy(resources, parseCascadingFlag(u.cfg, u.DeletionPropagation)) + _, errs = kubeClient.DeleteWithPropagationPolicy(resources, parseCascadingFlag(u.DeletionPropagation)) return resources, kept, errs } _, errs = u.cfg.KubeClient.Delete(resources) @@ -247,7 +249,7 @@ func (u *Uninstall) deleteRelease(rel *release.Release) (kube.ResourceList, stri return resources, kept, errs } -func parseCascadingFlag(cfg *Configuration, cascadingFlag string) v1.DeletionPropagation { +func parseCascadingFlag(cascadingFlag string) v1.DeletionPropagation { switch cascadingFlag { case "orphan": return v1.DeletePropagationOrphan @@ -256,7 +258,7 @@ func parseCascadingFlag(cfg *Configuration, cascadingFlag string) v1.DeletionPro case "background": return v1.DeletePropagationBackground default: - cfg.Log("uninstall: given cascade value: %s, defaulting to delete propagation background", cascadingFlag) + slog.Debug("uninstall: given cascade value, defaulting to delete propagation background", "value", cascadingFlag) return v1.DeletePropagationBackground } } diff --git a/pkg/action/uninstall_test.go b/pkg/action/uninstall_test.go index 869ffb8c7..a83e4bc75 100644 --- a/pkg/action/uninstall_test.go +++ b/pkg/action/uninstall_test.go @@ -22,8 +22,9 @@ import ( "github.com/stretchr/testify/assert" - kubefake "helm.sh/helm/v3/pkg/kube/fake" - "helm.sh/helm/v3/pkg/release" + "helm.sh/helm/v4/pkg/kube" + kubefake "helm.sh/helm/v4/pkg/kube/fake" + release "helm.sh/helm/v4/pkg/release/v1" ) func uninstallAction(t *testing.T) *Uninstall { @@ -82,7 +83,7 @@ func TestUninstallRelease_Wait(t *testing.T) { unAction := uninstallAction(t) unAction.DisableHooks = true unAction.DryRun = false - unAction.Wait = true + unAction.WaitStrategy = kube.StatusWatcherStrategy rel := releaseStub() rel.Name = "come-fail-away" @@ -99,7 +100,7 @@ func TestUninstallRelease_Wait(t *testing.T) { }` unAction.cfg.Releases.Create(rel) failer := unAction.cfg.KubeClient.(*kubefake.FailingKubeClient) - failer.WaitError = fmt.Errorf("U timed out") + failer.WaitForDeleteError = fmt.Errorf("U timed out") unAction.cfg.KubeClient = failer res, err := unAction.Run(rel.Name) is.Error(err) @@ -113,7 +114,7 @@ func TestUninstallRelease_Cascade(t *testing.T) { unAction := uninstallAction(t) unAction.DisableHooks = true unAction.DryRun = false - unAction.Wait = false + unAction.WaitStrategy = kube.HookOnlyStrategy unAction.DeletionPropagation = "foreground" rel := releaseStub() diff --git a/pkg/action/upgrade.go b/pkg/action/upgrade.go index 1c79fbe51..ed0905136 100644 --- a/pkg/action/upgrade.go +++ b/pkg/action/upgrade.go @@ -21,6 +21,7 @@ import ( "context" "errors" "fmt" + "log/slog" "strings" "sync" "time" @@ -28,14 +29,14 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/cli-runtime/pkg/resource" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/kube" - "helm.sh/helm/v3/pkg/postrender" - "helm.sh/helm/v3/pkg/registry" - "helm.sh/helm/v3/pkg/release" - "helm.sh/helm/v3/pkg/releaseutil" - "helm.sh/helm/v3/pkg/storage/driver" + chart "helm.sh/helm/v4/pkg/chart/v2" + chartutil "helm.sh/helm/v4/pkg/chart/v2/util" + "helm.sh/helm/v4/pkg/kube" + "helm.sh/helm/v4/pkg/postrender" + "helm.sh/helm/v4/pkg/registry" + releaseutil "helm.sh/helm/v4/pkg/release/util" + release "helm.sh/helm/v4/pkg/release/v1" + "helm.sh/helm/v4/pkg/storage/driver" ) // Upgrade is the action for upgrading releases. @@ -64,8 +65,8 @@ type Upgrade struct { SkipCRDs bool // Timeout is the timeout for this operation Timeout time.Duration - // Wait determines whether the wait operation should be performed after the upgrade is requested. - Wait bool + // WaitStrategy determines what type of waiting should be done + WaitStrategy kube.WaitStrategy // WaitForJobs determines whether the wait operation for the Jobs should be performed after the upgrade is requested. WaitForJobs bool // DisableHooks disables hook processing if set to true. @@ -104,7 +105,7 @@ type Upgrade struct { // Description is the description of this operation Description string Labels map[string]string - // PostRender is an optional post-renderer + // PostRenderer is an optional post-renderer // // If this is non-nil, then after templates are rendered, they will be sent to the // post renderer before sending to the Kubernetes API server. @@ -131,14 +132,14 @@ func NewUpgrade(cfg *Configuration) *Upgrade { up := &Upgrade{ cfg: cfg, } - up.ChartPathOptions.registryClient = cfg.RegistryClient + up.registryClient = cfg.RegistryClient return up } // SetRegistryClient sets the registry client to use when fetching charts. func (u *Upgrade) SetRegistryClient(client *registry.Client) { - u.ChartPathOptions.registryClient = client + u.registryClient = client } // Run executes the upgrade on the given release. @@ -155,13 +156,15 @@ func (u *Upgrade) RunWithContext(ctx context.Context, name string, chart *chart. // Make sure if Atomic is set, that wait is set as well. This makes it so // the user doesn't have to specify both - u.Wait = u.Wait || u.Atomic + if u.WaitStrategy == kube.HookOnlyStrategy && u.Atomic { + u.WaitStrategy = kube.StatusWatcherStrategy + } if err := chartutil.ValidateReleaseName(name); err != nil { return nil, fmt.Errorf("release name is invalid: %s", name) } - u.cfg.Log("preparing upgrade for %s", name) + slog.Debug("preparing upgrade", "name", name) currentRelease, upgradedRelease, err := u.prepareUpgrade(name, chart, vals) if err != nil { return nil, err @@ -169,7 +172,7 @@ func (u *Upgrade) RunWithContext(ctx context.Context, name string, chart *chart. u.cfg.Releases.MaxHistory = u.MaxHistory - u.cfg.Log("performing update for %s", name) + slog.Debug("performing update", "name", name) res, err := u.performUpgrade(ctx, currentRelease, upgradedRelease) if err != nil { return res, err @@ -177,7 +180,7 @@ func (u *Upgrade) RunWithContext(ctx context.Context, name string, chart *chart. // Do not update for dry runs if !u.isDryRun() { - u.cfg.Log("updating status for upgraded release for %s", name) + slog.Debug("updating status for upgraded release", "name", name) if err := u.cfg.Releases.Update(upgradedRelease); err != nil { return res, err } @@ -243,7 +246,7 @@ func (u *Upgrade) prepareUpgrade(name string, chart *chart.Chart, vals map[strin return nil, nil, err } - if err := chartutil.ProcessDependenciesWithMerge(chart, vals); err != nil { + if err := chartutil.ProcessDependencies(chart, vals); err != nil { return nil, nil, err } @@ -363,7 +366,7 @@ func (u *Upgrade) performUpgrade(ctx context.Context, originalRelease, upgradedR // Run if it is a dry run if u.isDryRun() { - u.cfg.Log("dry run for %s", upgradedRelease.Name) + slog.Debug("dry run for release", "name", upgradedRelease.Name) if len(u.Description) > 0 { upgradedRelease.Info.Description = u.Description } else { @@ -372,7 +375,7 @@ func (u *Upgrade) performUpgrade(ctx context.Context, originalRelease, upgradedR return upgradedRelease, nil } - u.cfg.Log("creating upgraded release for %s", upgradedRelease.Name) + slog.Debug("creating upgraded release", "name", upgradedRelease.Name) if err := u.cfg.Releases.Create(upgradedRelease); err != nil { return nil, err } @@ -418,12 +421,12 @@ func (u *Upgrade) releasingUpgrade(c chan<- resultMessage, upgradedRelease *rele // pre-upgrade hooks if !u.DisableHooks { - if err := u.cfg.execHook(upgradedRelease, release.HookPreUpgrade, u.Timeout); err != nil { + if err := u.cfg.execHook(upgradedRelease, release.HookPreUpgrade, u.WaitStrategy, u.Timeout); err != nil { u.reportToPerformUpgrade(c, upgradedRelease, kube.ResourceList{}, fmt.Errorf("pre-upgrade hooks failed: %s", err)) return } } else { - u.cfg.Log("upgrade hooks disabled for %s", upgradedRelease.Name) + slog.Debug("upgrade hooks disabled", "name", upgradedRelease.Name) } results, err := u.cfg.KubeClient.Update(current, target, u.Force) @@ -439,32 +442,32 @@ func (u *Upgrade) releasingUpgrade(c chan<- resultMessage, upgradedRelease *rele // levels, we should make these error level logs so users are notified // that they'll need to go do the cleanup on their own if err := recreate(u.cfg, results.Updated); err != nil { - u.cfg.Log(err.Error()) + slog.Error(err.Error()) } } - - if u.Wait { - u.cfg.Log( - "waiting for release %s resources (created: %d updated: %d deleted: %d)", - upgradedRelease.Name, len(results.Created), len(results.Updated), len(results.Deleted)) - if u.WaitForJobs { - if err := u.cfg.KubeClient.WaitWithJobs(target, u.Timeout); err != nil { - u.cfg.recordRelease(originalRelease) - u.reportToPerformUpgrade(c, upgradedRelease, results.Created, err) - return - } - } else { - if err := u.cfg.KubeClient.Wait(target, u.Timeout); err != nil { - u.cfg.recordRelease(originalRelease) - u.reportToPerformUpgrade(c, upgradedRelease, results.Created, err) - return - } + waiter, err := u.cfg.KubeClient.GetWaiter(u.WaitStrategy) + if err != nil { + u.cfg.recordRelease(originalRelease) + u.reportToPerformUpgrade(c, upgradedRelease, results.Created, err) + return + } + if u.WaitForJobs { + if err := waiter.WaitWithJobs(target, u.Timeout); err != nil { + u.cfg.recordRelease(originalRelease) + u.reportToPerformUpgrade(c, upgradedRelease, results.Created, err) + return + } + } else { + if err := waiter.Wait(target, u.Timeout); err != nil { + u.cfg.recordRelease(originalRelease) + u.reportToPerformUpgrade(c, upgradedRelease, results.Created, err) + return } } // post-upgrade hooks if !u.DisableHooks { - if err := u.cfg.execHook(upgradedRelease, release.HookPostUpgrade, u.Timeout); err != nil { + if err := u.cfg.execHook(upgradedRelease, release.HookPostUpgrade, u.WaitStrategy, u.Timeout); err != nil { u.reportToPerformUpgrade(c, upgradedRelease, results.Created, fmt.Errorf("post-upgrade hooks failed: %s", err)) return } @@ -484,13 +487,13 @@ func (u *Upgrade) releasingUpgrade(c chan<- resultMessage, upgradedRelease *rele func (u *Upgrade) failRelease(rel *release.Release, created kube.ResourceList, err error) (*release.Release, error) { msg := fmt.Sprintf("Upgrade %q failed: %s", rel.Name, err) - u.cfg.Log("warning: %s", msg) + slog.Warn("upgrade failed", "name", rel.Name, slog.Any("error", err)) rel.Info.Status = release.StatusFailed rel.Info.Description = msg u.cfg.recordRelease(rel) if u.CleanupOnFail && len(created) > 0 { - u.cfg.Log("Cleanup on fail set, cleaning up %d resources", len(created)) + slog.Debug("cleanup on fail set", "cleaning_resources", len(created)) _, errs := u.cfg.KubeClient.Delete(created) if errs != nil { return rel, fmt.Errorf( @@ -502,10 +505,10 @@ func (u *Upgrade) failRelease(rel *release.Release, created kube.ResourceList, e ), ) } - u.cfg.Log("Resource cleanup complete") + slog.Debug("resource cleanup complete") } if u.Atomic { - u.cfg.Log("Upgrade failed and atomic is set, rolling back to last successful release") + slog.Debug("upgrade failed and atomic is set, rolling back to last successful release") // As a protection, get the last successful release before rollback. // If there are no successful releases, bail out @@ -529,7 +532,9 @@ func (u *Upgrade) failRelease(rel *release.Release, created kube.ResourceList, e rollin := NewRollback(u.cfg) rollin.Version = filteredHistory[0].Version - rollin.Wait = true + if u.WaitStrategy == kube.HookOnlyStrategy { + rollin.WaitStrategy = kube.StatusWatcherStrategy + } rollin.WaitForJobs = u.WaitForJobs rollin.DisableHooks = u.DisableHooks rollin.Recreate = u.Recreate @@ -555,13 +560,13 @@ func (u *Upgrade) failRelease(rel *release.Release, created kube.ResourceList, e func (u *Upgrade) reuseValues(chart *chart.Chart, current *release.Release, newVals map[string]interface{}) (map[string]interface{}, error) { if u.ResetValues { // If ResetValues is set, we completely ignore current.Config. - u.cfg.Log("resetting values to the chart's original version") + slog.Debug("resetting values to the chart's original version") return newVals, nil } // If the ReuseValues flag is set, we always copy the old values over the new config's values. if u.ReuseValues { - u.cfg.Log("reusing the old release's values") + slog.Debug("reusing the old release's values") // We have to regenerate the old coalesced values: oldVals, err := chartutil.CoalesceValues(current.Chart, current.Config) @@ -578,7 +583,7 @@ func (u *Upgrade) reuseValues(chart *chart.Chart, current *release.Release, newV // If the ResetThenReuseValues flag is set, we use the new chart's values, but we copy the old config's values over the new config's values. if u.ResetThenReuseValues { - u.cfg.Log("merging values from old release to new values") + slog.Debug("merging values from old release to new values") newVals = chartutil.CoalesceTables(newVals, current.Config) @@ -586,7 +591,7 @@ func (u *Upgrade) reuseValues(chart *chart.Chart, current *release.Release, newV } if len(newVals) == 0 && len(current.Config) > 0 { - u.cfg.Log("copying values from %s (v%d) to new release.", current.Name, current.Version) + slog.Debug("copying values from old release", "name", current.Name, "version", current.Version) newVals = current.Config } return newVals, nil diff --git a/pkg/action/upgrade_test.go b/pkg/action/upgrade_test.go index db8486340..19869f6d6 100644 --- a/pkg/action/upgrade_test.go +++ b/pkg/action/upgrade_test.go @@ -23,15 +23,16 @@ import ( "testing" "time" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/storage/driver" + chart "helm.sh/helm/v4/pkg/chart/v2" + "helm.sh/helm/v4/pkg/kube" + "helm.sh/helm/v4/pkg/storage/driver" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - kubefake "helm.sh/helm/v3/pkg/kube/fake" - "helm.sh/helm/v3/pkg/release" - helmtime "helm.sh/helm/v3/pkg/time" + kubefake "helm.sh/helm/v4/pkg/kube/fake" + release "helm.sh/helm/v4/pkg/release/v1" + helmtime "helm.sh/helm/v4/pkg/time" ) func upgradeAction(t *testing.T) *Upgrade { @@ -52,7 +53,7 @@ func TestUpgradeRelease_Success(t *testing.T) { rel.Info.Status = release.StatusDeployed req.NoError(upAction.cfg.Releases.Create(rel)) - upAction.Wait = true + upAction.WaitStrategy = kube.StatusWatcherStrategy vals := map[string]interface{}{} ctx, done := context.WithCancel(context.Background()) @@ -82,7 +83,7 @@ func TestUpgradeRelease_Wait(t *testing.T) { failer := upAction.cfg.KubeClient.(*kubefake.FailingKubeClient) failer.WaitError = fmt.Errorf("I timed out") upAction.cfg.KubeClient = failer - upAction.Wait = true + upAction.WaitStrategy = kube.StatusWatcherStrategy vals := map[string]interface{}{} res, err := upAction.Run(rel.Name, buildChart(), vals) @@ -104,7 +105,7 @@ func TestUpgradeRelease_WaitForJobs(t *testing.T) { failer := upAction.cfg.KubeClient.(*kubefake.FailingKubeClient) failer.WaitError = fmt.Errorf("I timed out") upAction.cfg.KubeClient = failer - upAction.Wait = true + upAction.WaitStrategy = kube.StatusWatcherStrategy upAction.WaitForJobs = true vals := map[string]interface{}{} @@ -128,7 +129,7 @@ func TestUpgradeRelease_CleanupOnFail(t *testing.T) { failer.WaitError = fmt.Errorf("I timed out") failer.DeleteError = fmt.Errorf("I tried to delete nil") upAction.cfg.KubeClient = failer - upAction.Wait = true + upAction.WaitStrategy = kube.StatusWatcherStrategy upAction.CleanupOnFail = true vals := map[string]interface{}{} @@ -395,7 +396,7 @@ func TestUpgradeRelease_Interrupted_Wait(t *testing.T) { failer := upAction.cfg.KubeClient.(*kubefake.FailingKubeClient) failer.WaitDuration = 10 * time.Second upAction.cfg.KubeClient = failer - upAction.Wait = true + upAction.WaitStrategy = kube.StatusWatcherStrategy vals := map[string]interface{}{} ctx := context.Background() diff --git a/pkg/action/validate.go b/pkg/action/validate.go index f5bf75ed7..22db74041 100644 --- a/pkg/action/validate.go +++ b/pkg/action/validate.go @@ -24,7 +24,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/cli-runtime/pkg/resource" - "helm.sh/helm/v3/pkg/kube" + "helm.sh/helm/v4/pkg/kube" ) var accessor = meta.NewAccessor() diff --git a/pkg/action/validate_test.go b/pkg/action/validate_test.go index e73594e86..3efecd6ff 100644 --- a/pkg/action/validate_test.go +++ b/pkg/action/validate_test.go @@ -22,7 +22,7 @@ import ( "net/http" "testing" - "helm.sh/helm/v3/pkg/kube" + "helm.sh/helm/v4/pkg/kube" "github.com/stretchr/testify/assert" diff --git a/pkg/action/verify.go b/pkg/action/verify.go index f36239496..68a5e2d88 100644 --- a/pkg/action/verify.go +++ b/pkg/action/verify.go @@ -20,7 +20,7 @@ import ( "fmt" "strings" - "helm.sh/helm/v3/pkg/downloader" + "helm.sh/helm/v4/pkg/downloader" ) // Verify is the action for building a given chart's Verify tree. diff --git a/pkg/chart/chart.go b/pkg/chart/v2/chart.go similarity index 99% rename from pkg/chart/chart.go rename to pkg/chart/v2/chart.go index a3bed63a3..dcc2a43eb 100644 --- a/pkg/chart/chart.go +++ b/pkg/chart/v2/chart.go @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package chart +package v2 import ( "path/filepath" diff --git a/pkg/chart/chart_test.go b/pkg/chart/v2/chart_test.go similarity index 99% rename from pkg/chart/chart_test.go rename to pkg/chart/v2/chart_test.go index 62d60765c..d6311085b 100644 --- a/pkg/chart/chart_test.go +++ b/pkg/chart/v2/chart_test.go @@ -13,7 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ -package chart +package v2 import ( "encoding/json" diff --git a/pkg/chart/dependency.go b/pkg/chart/v2/dependency.go similarity index 83% rename from pkg/chart/dependency.go rename to pkg/chart/v2/dependency.go index 4ef5eeb32..8a590a036 100644 --- a/pkg/chart/dependency.go +++ b/pkg/chart/v2/dependency.go @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package chart +package v2 import "time" @@ -25,28 +25,28 @@ type Dependency struct { // Name is the name of the dependency. // // This must mach the name in the dependency's Chart.yaml. - Name string `json:"name"` + Name string `json:"name" yaml:"name"` // Version is the version (range) of this chart. // // A lock file will always produce a single version, while a dependency // may contain a semantic version range. - Version string `json:"version,omitempty"` + Version string `json:"version,omitempty" yaml:"version,omitempty"` // The URL to the repository. // // Appending `index.yaml` to this string should result in a URL that can be // used to fetch the repository index. - Repository string `json:"repository"` + Repository string `json:"repository" yaml:"repository"` // A yaml path that resolves to a boolean, used for enabling/disabling charts (e.g. subchart1.enabled ) - Condition string `json:"condition,omitempty"` + Condition string `json:"condition,omitempty" yaml:"condition,omitempty"` // Tags can be used to group charts for enabling/disabling together - Tags []string `json:"tags,omitempty"` + Tags []string `json:"tags,omitempty" yaml:"tags,omitempty"` // Enabled bool determines if chart should be loaded - Enabled bool `json:"enabled,omitempty"` + Enabled bool `json:"enabled,omitempty" yaml:"enabled,omitempty"` // ImportValues holds the mapping of source values to parent key to be imported. Each item can be a // string or pair of child/parent sublist items. - ImportValues []interface{} `json:"import-values,omitempty"` + ImportValues []interface{} `json:"import-values,omitempty" yaml:"import-values,omitempty"` // Alias usable alias to be used for the chart - Alias string `json:"alias,omitempty"` + Alias string `json:"alias,omitempty" yaml:"alias,omitempty"` } // Validate checks for common problems with the dependency datastructure in diff --git a/pkg/chart/dependency_test.go b/pkg/chart/v2/dependency_test.go similarity index 98% rename from pkg/chart/dependency_test.go rename to pkg/chart/v2/dependency_test.go index 90488a966..35919bd7a 100644 --- a/pkg/chart/dependency_test.go +++ b/pkg/chart/v2/dependency_test.go @@ -13,7 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ -package chart +package v2 import ( "testing" diff --git a/pkg/kube/config.go b/pkg/chart/v2/doc.go similarity index 53% rename from pkg/kube/config.go rename to pkg/chart/v2/doc.go index e00c9acb1..d36ca3ec4 100644 --- a/pkg/kube/config.go +++ b/pkg/chart/v2/doc.go @@ -1,11 +1,10 @@ /* Copyright The Helm Authors. - Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 +http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, @@ -14,17 +13,11 @@ See the License for the specific language governing permissions and limitations under the License. */ -package kube // import "helm.sh/helm/v3/pkg/kube" - -import "k8s.io/cli-runtime/pkg/genericclioptions" +/* +Package v2 provides chart handling for apiVersion v1 and v2 charts -// GetConfig returns a Kubernetes client config. -// -// Deprecated -func GetConfig(kubeconfig, context, namespace string) *genericclioptions.ConfigFlags { - cf := genericclioptions.NewConfigFlags(true) - cf.Namespace = &namespace - cf.Context = &context - cf.KubeConfig = &kubeconfig - return cf -} +This package and its sub-packages provide handling for apiVersion v1 and v2 charts. +The changes from v1 to v2 charts are minor and were able to be handled with minor +switches based on characteristics. +*/ +package v2 diff --git a/pkg/chart/errors.go b/pkg/chart/v2/errors.go similarity index 98% rename from pkg/chart/errors.go rename to pkg/chart/v2/errors.go index 2fad5f370..eeef75315 100644 --- a/pkg/chart/errors.go +++ b/pkg/chart/v2/errors.go @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package chart +package v2 import "fmt" diff --git a/pkg/chart/file.go b/pkg/chart/v2/file.go similarity index 98% rename from pkg/chart/file.go rename to pkg/chart/v2/file.go index 9dd7c08d5..a2eeb0fcd 100644 --- a/pkg/chart/file.go +++ b/pkg/chart/v2/file.go @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package chart +package v2 // File represents a file as a name/value pair. // diff --git a/pkg/chart/fuzz_test.go b/pkg/chart/v2/fuzz_test.go similarity index 98% rename from pkg/chart/fuzz_test.go rename to pkg/chart/v2/fuzz_test.go index f3c768444..a897ef7b9 100644 --- a/pkg/chart/fuzz_test.go +++ b/pkg/chart/v2/fuzz_test.go @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package chart +package v2 import ( "testing" diff --git a/pkg/chart/loader/archive.go b/pkg/chart/v2/loader/archive.go similarity index 79% rename from pkg/chart/loader/archive.go rename to pkg/chart/v2/loader/archive.go index 24d5b5c24..b9f370f56 100644 --- a/pkg/chart/loader/archive.go +++ b/pkg/chart/v2/loader/archive.go @@ -29,9 +29,18 @@ import ( "regexp" "strings" - "helm.sh/helm/v3/pkg/chart" + chart "helm.sh/helm/v4/pkg/chart/v2" ) +// MaxDecompressedChartSize is the maximum size of a chart archive that will be +// decompressed. This is the decompressed size of all the files. +// The default value is 100 MiB. +var MaxDecompressedChartSize int64 = 100 * 1024 * 1024 // Default 100 MiB + +// MaxDecompressedFileSize is the size of the largest file that Helm will attempt to load. +// The size of the file is the decompressed version of it when it is stored in an archive. +var MaxDecompressedFileSize int64 = 5 * 1024 * 1024 // Default 5 MiB + var drivePathPattern = regexp.MustCompile(`^[a-zA-Z]:/`) // FileLoader loads a chart from a file @@ -118,6 +127,7 @@ func LoadArchiveFiles(in io.Reader) ([]*BufferedFile, error) { files := []*BufferedFile{} tr := tar.NewReader(unzipped) + remainingSize := MaxDecompressedChartSize for { b := bytes.NewBuffer(nil) hd, err := tr.Next() @@ -177,10 +187,30 @@ func LoadArchiveFiles(in io.Reader) ([]*BufferedFile, error) { return nil, errors.New("chart yaml not in base directory") } - if _, err := io.Copy(b, tr); err != nil { + if hd.Size > remainingSize { + return nil, fmt.Errorf("decompressed chart is larger than the maximum size %d", MaxDecompressedChartSize) + } + + if hd.Size > MaxDecompressedFileSize { + return nil, fmt.Errorf("decompressed chart file %q is larger than the maximum file size %d", hd.Name, MaxDecompressedFileSize) + } + + limitedReader := io.LimitReader(tr, remainingSize) + + bytesWritten, err := io.Copy(b, limitedReader) + if err != nil { return nil, err } + remainingSize -= bytesWritten + // When the bytesWritten are less than the file size it means the limit reader ended + // copying early. Here we report that error. This is important if the last file extracted + // is the one that goes over the limit. It assumes the Size stored in the tar header + // is correct, something many applications do. + if bytesWritten < hd.Size || remainingSize <= 0 { + return nil, fmt.Errorf("decompressed chart is larger than the maximum size %d", MaxDecompressedChartSize) + } + data := bytes.TrimPrefix(b.Bytes(), utf8bom) files = append(files, &BufferedFile{Name: n, Data: data}) diff --git a/pkg/chart/loader/archive_test.go b/pkg/chart/v2/loader/archive_test.go similarity index 100% rename from pkg/chart/loader/archive_test.go rename to pkg/chart/v2/loader/archive_test.go diff --git a/pkg/chart/loader/directory.go b/pkg/chart/v2/loader/directory.go similarity index 91% rename from pkg/chart/loader/directory.go rename to pkg/chart/v2/loader/directory.go index 4c95742cb..4f72925dc 100644 --- a/pkg/chart/loader/directory.go +++ b/pkg/chart/v2/loader/directory.go @@ -23,9 +23,9 @@ import ( "path/filepath" "strings" - "helm.sh/helm/v3/internal/sympath" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/ignore" + "helm.sh/helm/v4/internal/sympath" + chart "helm.sh/helm/v4/pkg/chart/v2" + "helm.sh/helm/v4/pkg/ignore" ) var utf8bom = []byte{0xEF, 0xBB, 0xBF} @@ -99,6 +99,10 @@ func LoadDir(dir string) (*chart.Chart, error) { return fmt.Errorf("cannot load irregular file %s as it has file mode type bits set", name) } + if fi.Size() > MaxDecompressedFileSize { + return fmt.Errorf("chart file %q is larger than the maximum file size %d", fi.Name(), MaxDecompressedFileSize) + } + data, err := os.ReadFile(name) if err != nil { return fmt.Errorf("error reading %s: %w", n, err) diff --git a/pkg/chart/loader/load.go b/pkg/chart/v2/loader/load.go similarity index 79% rename from pkg/chart/loader/load.go rename to pkg/chart/v2/loader/load.go index 86a7353d0..8af743c4f 100644 --- a/pkg/chart/loader/load.go +++ b/pkg/chart/v2/loader/load.go @@ -17,17 +17,21 @@ limitations under the License. package loader import ( + "bufio" "bytes" + "encoding/json" "errors" "fmt" + "io" "log" "os" "path/filepath" "strings" + utilyaml "k8s.io/apimachinery/pkg/util/yaml" "sigs.k8s.io/yaml" - "helm.sh/helm/v3/pkg/chart" + chart "helm.sh/helm/v4/pkg/chart/v2" ) // ChartLoader loads a chart. @@ -103,10 +107,11 @@ func LoadFiles(files []*BufferedFile) (*chart.Chart, error) { return c, fmt.Errorf("cannot load Chart.lock: %w", err) } case f.Name == "values.yaml": - c.Values = make(map[string]interface{}) - if err := yaml.Unmarshal(f.Data, &c.Values); err != nil { + values, err := LoadValues(bytes.NewReader(f.Data)) + if err != nil { return c, fmt.Errorf("cannot load values.yaml: %w", err) } + c.Values = values case f.Name == "values.schema.json": c.Schema = f.Data @@ -201,3 +206,51 @@ func LoadFiles(files []*BufferedFile) (*chart.Chart, error) { return c, nil } + +// LoadValues loads values from a reader. +// +// The reader is expected to contain one or more YAML documents, the values of which are merged. +// And the values can be either a chart's default values or a user-supplied values. +func LoadValues(data io.Reader) (map[string]interface{}, error) { + values := map[string]interface{}{} + reader := utilyaml.NewYAMLReader(bufio.NewReader(data)) + for { + currentMap := map[string]interface{}{} + raw, err := reader.Read() + if err != nil { + if err == io.EOF { + break + } + return nil, fmt.Errorf("error reading yaml document: %w", err) + } + if err := yaml.Unmarshal(raw, ¤tMap, func(d *json.Decoder) *json.Decoder { + d.UseNumber() + return d + }); err != nil { + return nil, fmt.Errorf("cannot unmarshal yaml document: %w", err) + } + values = MergeMaps(values, currentMap) + } + return values, nil +} + +// MergeMaps merges two maps. If a key exists in both maps, the value from b will be used. +// If the value is a map, the maps will be merged recursively. +func MergeMaps(a, b map[string]interface{}) map[string]interface{} { + out := make(map[string]interface{}, len(a)) + for k, v := range a { + out[k] = v + } + for k, v := range b { + if v, ok := v.(map[string]interface{}); ok { + if bv, ok := out[k]; ok { + if bv, ok := bv.(map[string]interface{}); ok { + out[k] = MergeMaps(bv, v) + continue + } + } + } + out[k] = v + } + return out +} diff --git a/pkg/chart/loader/load_test.go b/pkg/chart/v2/loader/load_test.go similarity index 87% rename from pkg/chart/loader/load_test.go rename to pkg/chart/v2/loader/load_test.go index 098e6155f..2e16b8560 100644 --- a/pkg/chart/loader/load_test.go +++ b/pkg/chart/v2/loader/load_test.go @@ -24,12 +24,13 @@ import ( "log" "os" "path/filepath" + "reflect" "runtime" "strings" "testing" "time" - "helm.sh/helm/v3/pkg/chart" + chart "helm.sh/helm/v4/pkg/chart/v2" ) func TestLoadDir(t *testing.T) { @@ -488,6 +489,115 @@ func TestLoadInvalidArchive(t *testing.T) { } } +func TestLoadValues(t *testing.T) { + testCases := map[string]struct { + data []byte + expctedValues map[string]interface{} + }{ + "It should load values correctly": { + data: []byte(` +foo: + image: foo:v1 +bar: + version: v2 +`), + expctedValues: map[string]interface{}{ + "foo": map[string]interface{}{ + "image": "foo:v1", + }, + "bar": map[string]interface{}{ + "version": "v2", + }, + }, + }, + "It should load values correctly with multiple documents in one file": { + data: []byte(` +foo: + image: foo:v1 +bar: + version: v2 +--- +foo: + image: foo:v2 +`), + expctedValues: map[string]interface{}{ + "foo": map[string]interface{}{ + "image": "foo:v2", + }, + "bar": map[string]interface{}{ + "version": "v2", + }, + }, + }, + } + for testName, testCase := range testCases { + t.Run(testName, func(tt *testing.T) { + values, err := LoadValues(bytes.NewReader(testCase.data)) + if err != nil { + tt.Fatal(err) + } + if !reflect.DeepEqual(values, testCase.expctedValues) { + tt.Errorf("Expected values: %v, got %v", testCase.expctedValues, values) + } + }) + } +} + +func TestMergeValues(t *testing.T) { + nestedMap := map[string]interface{}{ + "foo": "bar", + "baz": map[string]string{ + "cool": "stuff", + }, + } + anotherNestedMap := map[string]interface{}{ + "foo": "bar", + "baz": map[string]string{ + "cool": "things", + "awesome": "stuff", + }, + } + flatMap := map[string]interface{}{ + "foo": "bar", + "baz": "stuff", + } + anotherFlatMap := map[string]interface{}{ + "testing": "fun", + } + + testMap := MergeMaps(flatMap, nestedMap) + equal := reflect.DeepEqual(testMap, nestedMap) + if !equal { + t.Errorf("Expected a nested map to overwrite a flat value. Expected: %v, got %v", nestedMap, testMap) + } + + testMap = MergeMaps(nestedMap, flatMap) + equal = reflect.DeepEqual(testMap, flatMap) + if !equal { + t.Errorf("Expected a flat value to overwrite a map. Expected: %v, got %v", flatMap, testMap) + } + + testMap = MergeMaps(nestedMap, anotherNestedMap) + equal = reflect.DeepEqual(testMap, anotherNestedMap) + if !equal { + t.Errorf("Expected a nested map to overwrite another nested map. Expected: %v, got %v", anotherNestedMap, testMap) + } + + testMap = MergeMaps(anotherFlatMap, anotherNestedMap) + expectedMap := map[string]interface{}{ + "testing": "fun", + "foo": "bar", + "baz": map[string]string{ + "cool": "things", + "awesome": "stuff", + }, + } + equal = reflect.DeepEqual(testMap, expectedMap) + if !equal { + t.Errorf("Expected a map with different keys to merge properly with another map. Expected: %v, got %v", expectedMap, testMap) + } +} + func verifyChart(t *testing.T, c *chart.Chart) { t.Helper() if c.Name() == "" { diff --git a/pkg/chart/loader/testdata/LICENSE b/pkg/chart/v2/loader/testdata/LICENSE similarity index 100% rename from pkg/chart/loader/testdata/LICENSE rename to pkg/chart/v2/loader/testdata/LICENSE diff --git a/pkg/chart/loader/testdata/albatross/Chart.yaml b/pkg/chart/v2/loader/testdata/albatross/Chart.yaml similarity index 100% rename from pkg/chart/loader/testdata/albatross/Chart.yaml rename to pkg/chart/v2/loader/testdata/albatross/Chart.yaml diff --git a/pkg/chart/loader/testdata/albatross/values.yaml b/pkg/chart/v2/loader/testdata/albatross/values.yaml similarity index 100% rename from pkg/chart/loader/testdata/albatross/values.yaml rename to pkg/chart/v2/loader/testdata/albatross/values.yaml diff --git a/pkg/chart/loader/testdata/frobnitz-1.2.3.tgz b/pkg/chart/v2/loader/testdata/frobnitz-1.2.3.tgz similarity index 100% rename from pkg/chart/loader/testdata/frobnitz-1.2.3.tgz rename to pkg/chart/v2/loader/testdata/frobnitz-1.2.3.tgz diff --git a/pkg/chart/loader/testdata/frobnitz.v1.tgz b/pkg/chart/v2/loader/testdata/frobnitz.v1.tgz similarity index 100% rename from pkg/chart/loader/testdata/frobnitz.v1.tgz rename to pkg/chart/v2/loader/testdata/frobnitz.v1.tgz diff --git a/pkg/chart/loader/testdata/frobnitz.v1/.helmignore b/pkg/chart/v2/loader/testdata/frobnitz.v1/.helmignore similarity index 100% rename from pkg/chart/loader/testdata/frobnitz.v1/.helmignore rename to pkg/chart/v2/loader/testdata/frobnitz.v1/.helmignore diff --git a/pkg/chart/loader/testdata/frobnitz.v1/Chart.lock b/pkg/chart/v2/loader/testdata/frobnitz.v1/Chart.lock similarity index 100% rename from pkg/chart/loader/testdata/frobnitz.v1/Chart.lock rename to pkg/chart/v2/loader/testdata/frobnitz.v1/Chart.lock diff --git a/pkg/chart/loader/testdata/frobnitz.v1/Chart.yaml b/pkg/chart/v2/loader/testdata/frobnitz.v1/Chart.yaml similarity index 100% rename from pkg/chart/loader/testdata/frobnitz.v1/Chart.yaml rename to pkg/chart/v2/loader/testdata/frobnitz.v1/Chart.yaml diff --git a/pkg/chart/loader/testdata/frobnitz.v1/INSTALL.txt b/pkg/chart/v2/loader/testdata/frobnitz.v1/INSTALL.txt similarity index 100% rename from pkg/chart/loader/testdata/frobnitz.v1/INSTALL.txt rename to pkg/chart/v2/loader/testdata/frobnitz.v1/INSTALL.txt diff --git a/pkg/chart/loader/testdata/frobnitz.v1/LICENSE b/pkg/chart/v2/loader/testdata/frobnitz.v1/LICENSE similarity index 100% rename from pkg/chart/loader/testdata/frobnitz.v1/LICENSE rename to pkg/chart/v2/loader/testdata/frobnitz.v1/LICENSE diff --git a/pkg/chart/loader/testdata/frobnitz.v1/README.md b/pkg/chart/v2/loader/testdata/frobnitz.v1/README.md similarity index 100% rename from pkg/chart/loader/testdata/frobnitz.v1/README.md rename to pkg/chart/v2/loader/testdata/frobnitz.v1/README.md diff --git a/pkg/chart/loader/testdata/frobnitz.v1/charts/_ignore_me b/pkg/chart/v2/loader/testdata/frobnitz.v1/charts/_ignore_me similarity index 100% rename from pkg/chart/loader/testdata/frobnitz.v1/charts/_ignore_me rename to pkg/chart/v2/loader/testdata/frobnitz.v1/charts/_ignore_me diff --git a/pkg/chart/loader/testdata/frobnitz.v1/charts/alpine/Chart.yaml b/pkg/chart/v2/loader/testdata/frobnitz.v1/charts/alpine/Chart.yaml similarity index 100% rename from pkg/chart/loader/testdata/frobnitz.v1/charts/alpine/Chart.yaml rename to pkg/chart/v2/loader/testdata/frobnitz.v1/charts/alpine/Chart.yaml diff --git a/pkg/chart/loader/testdata/frobnitz.v1/charts/alpine/README.md b/pkg/chart/v2/loader/testdata/frobnitz.v1/charts/alpine/README.md similarity index 100% rename from pkg/chart/loader/testdata/frobnitz.v1/charts/alpine/README.md rename to pkg/chart/v2/loader/testdata/frobnitz.v1/charts/alpine/README.md diff --git a/pkg/chart/loader/testdata/frobnitz.v1/charts/alpine/charts/mast1/Chart.yaml b/pkg/chart/v2/loader/testdata/frobnitz.v1/charts/alpine/charts/mast1/Chart.yaml similarity index 100% rename from pkg/chart/loader/testdata/frobnitz.v1/charts/alpine/charts/mast1/Chart.yaml rename to pkg/chart/v2/loader/testdata/frobnitz.v1/charts/alpine/charts/mast1/Chart.yaml diff --git a/pkg/chart/loader/testdata/frobnitz.v1/charts/alpine/charts/mast1/values.yaml b/pkg/chart/v2/loader/testdata/frobnitz.v1/charts/alpine/charts/mast1/values.yaml similarity index 100% rename from pkg/chart/loader/testdata/frobnitz.v1/charts/alpine/charts/mast1/values.yaml rename to pkg/chart/v2/loader/testdata/frobnitz.v1/charts/alpine/charts/mast1/values.yaml diff --git a/pkg/chart/loader/testdata/frobnitz.v1/charts/alpine/charts/mast2-0.1.0.tgz b/pkg/chart/v2/loader/testdata/frobnitz.v1/charts/alpine/charts/mast2-0.1.0.tgz similarity index 100% rename from pkg/chart/loader/testdata/frobnitz.v1/charts/alpine/charts/mast2-0.1.0.tgz rename to pkg/chart/v2/loader/testdata/frobnitz.v1/charts/alpine/charts/mast2-0.1.0.tgz diff --git a/pkg/chart/loader/testdata/frobnitz.v1/charts/alpine/templates/alpine-pod.yaml b/pkg/chart/v2/loader/testdata/frobnitz.v1/charts/alpine/templates/alpine-pod.yaml similarity index 100% rename from pkg/chart/loader/testdata/frobnitz.v1/charts/alpine/templates/alpine-pod.yaml rename to pkg/chart/v2/loader/testdata/frobnitz.v1/charts/alpine/templates/alpine-pod.yaml diff --git a/pkg/chart/loader/testdata/frobnitz.v1/charts/alpine/values.yaml b/pkg/chart/v2/loader/testdata/frobnitz.v1/charts/alpine/values.yaml similarity index 100% rename from pkg/chart/loader/testdata/frobnitz.v1/charts/alpine/values.yaml rename to pkg/chart/v2/loader/testdata/frobnitz.v1/charts/alpine/values.yaml diff --git a/pkg/chart/loader/testdata/frobnitz.v1/charts/mariner-4.3.2.tgz b/pkg/chart/v2/loader/testdata/frobnitz.v1/charts/mariner-4.3.2.tgz similarity index 100% rename from pkg/chart/loader/testdata/frobnitz.v1/charts/mariner-4.3.2.tgz rename to pkg/chart/v2/loader/testdata/frobnitz.v1/charts/mariner-4.3.2.tgz diff --git a/pkg/chart/loader/testdata/frobnitz.v1/docs/README.md b/pkg/chart/v2/loader/testdata/frobnitz.v1/docs/README.md similarity index 100% rename from pkg/chart/loader/testdata/frobnitz.v1/docs/README.md rename to pkg/chart/v2/loader/testdata/frobnitz.v1/docs/README.md diff --git a/pkg/chart/loader/testdata/frobnitz.v1/icon.svg b/pkg/chart/v2/loader/testdata/frobnitz.v1/icon.svg similarity index 100% rename from pkg/chart/loader/testdata/frobnitz.v1/icon.svg rename to pkg/chart/v2/loader/testdata/frobnitz.v1/icon.svg diff --git a/pkg/chart/loader/testdata/frobnitz.v1/ignore/me.txt b/pkg/chart/v2/loader/testdata/frobnitz.v1/ignore/me.txt similarity index 100% rename from pkg/chart/loader/testdata/frobnitz.v1/ignore/me.txt rename to pkg/chart/v2/loader/testdata/frobnitz.v1/ignore/me.txt diff --git a/pkg/chart/loader/testdata/frobnitz.v1/requirements.yaml b/pkg/chart/v2/loader/testdata/frobnitz.v1/requirements.yaml similarity index 100% rename from pkg/chart/loader/testdata/frobnitz.v1/requirements.yaml rename to pkg/chart/v2/loader/testdata/frobnitz.v1/requirements.yaml diff --git a/pkg/chart/loader/testdata/frobnitz.v1/templates/template.tpl b/pkg/chart/v2/loader/testdata/frobnitz.v1/templates/template.tpl similarity index 100% rename from pkg/chart/loader/testdata/frobnitz.v1/templates/template.tpl rename to pkg/chart/v2/loader/testdata/frobnitz.v1/templates/template.tpl diff --git a/pkg/chart/loader/testdata/frobnitz.v1/values.yaml b/pkg/chart/v2/loader/testdata/frobnitz.v1/values.yaml similarity index 100% rename from pkg/chart/loader/testdata/frobnitz.v1/values.yaml rename to pkg/chart/v2/loader/testdata/frobnitz.v1/values.yaml diff --git a/pkg/chart/loader/testdata/frobnitz.v2.reqs/.helmignore b/pkg/chart/v2/loader/testdata/frobnitz.v2.reqs/.helmignore similarity index 100% rename from pkg/chart/loader/testdata/frobnitz.v2.reqs/.helmignore rename to pkg/chart/v2/loader/testdata/frobnitz.v2.reqs/.helmignore diff --git a/pkg/chart/loader/testdata/frobnitz.v2.reqs/Chart.yaml b/pkg/chart/v2/loader/testdata/frobnitz.v2.reqs/Chart.yaml similarity index 100% rename from pkg/chart/loader/testdata/frobnitz.v2.reqs/Chart.yaml rename to pkg/chart/v2/loader/testdata/frobnitz.v2.reqs/Chart.yaml diff --git a/pkg/chart/loader/testdata/frobnitz.v2.reqs/INSTALL.txt b/pkg/chart/v2/loader/testdata/frobnitz.v2.reqs/INSTALL.txt similarity index 100% rename from pkg/chart/loader/testdata/frobnitz.v2.reqs/INSTALL.txt rename to pkg/chart/v2/loader/testdata/frobnitz.v2.reqs/INSTALL.txt diff --git a/pkg/chart/loader/testdata/frobnitz.v2.reqs/LICENSE b/pkg/chart/v2/loader/testdata/frobnitz.v2.reqs/LICENSE similarity index 100% rename from pkg/chart/loader/testdata/frobnitz.v2.reqs/LICENSE rename to pkg/chart/v2/loader/testdata/frobnitz.v2.reqs/LICENSE diff --git a/pkg/chart/loader/testdata/frobnitz.v2.reqs/README.md b/pkg/chart/v2/loader/testdata/frobnitz.v2.reqs/README.md similarity index 100% rename from pkg/chart/loader/testdata/frobnitz.v2.reqs/README.md rename to pkg/chart/v2/loader/testdata/frobnitz.v2.reqs/README.md diff --git a/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/_ignore_me b/pkg/chart/v2/loader/testdata/frobnitz.v2.reqs/charts/_ignore_me similarity index 100% rename from pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/_ignore_me rename to pkg/chart/v2/loader/testdata/frobnitz.v2.reqs/charts/_ignore_me diff --git a/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/alpine/Chart.yaml b/pkg/chart/v2/loader/testdata/frobnitz.v2.reqs/charts/alpine/Chart.yaml similarity index 100% rename from pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/alpine/Chart.yaml rename to pkg/chart/v2/loader/testdata/frobnitz.v2.reqs/charts/alpine/Chart.yaml diff --git a/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/alpine/README.md b/pkg/chart/v2/loader/testdata/frobnitz.v2.reqs/charts/alpine/README.md similarity index 100% rename from pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/alpine/README.md rename to pkg/chart/v2/loader/testdata/frobnitz.v2.reqs/charts/alpine/README.md diff --git a/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/alpine/charts/mast1/Chart.yaml b/pkg/chart/v2/loader/testdata/frobnitz.v2.reqs/charts/alpine/charts/mast1/Chart.yaml similarity index 100% rename from pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/alpine/charts/mast1/Chart.yaml rename to pkg/chart/v2/loader/testdata/frobnitz.v2.reqs/charts/alpine/charts/mast1/Chart.yaml diff --git a/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/alpine/charts/mast1/values.yaml b/pkg/chart/v2/loader/testdata/frobnitz.v2.reqs/charts/alpine/charts/mast1/values.yaml similarity index 100% rename from pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/alpine/charts/mast1/values.yaml rename to pkg/chart/v2/loader/testdata/frobnitz.v2.reqs/charts/alpine/charts/mast1/values.yaml diff --git a/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/alpine/charts/mast2-0.1.0.tgz b/pkg/chart/v2/loader/testdata/frobnitz.v2.reqs/charts/alpine/charts/mast2-0.1.0.tgz similarity index 100% rename from pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/alpine/charts/mast2-0.1.0.tgz rename to pkg/chart/v2/loader/testdata/frobnitz.v2.reqs/charts/alpine/charts/mast2-0.1.0.tgz diff --git a/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/alpine/templates/alpine-pod.yaml b/pkg/chart/v2/loader/testdata/frobnitz.v2.reqs/charts/alpine/templates/alpine-pod.yaml similarity index 100% rename from pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/alpine/templates/alpine-pod.yaml rename to pkg/chart/v2/loader/testdata/frobnitz.v2.reqs/charts/alpine/templates/alpine-pod.yaml diff --git a/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/alpine/values.yaml b/pkg/chart/v2/loader/testdata/frobnitz.v2.reqs/charts/alpine/values.yaml similarity index 100% rename from pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/alpine/values.yaml rename to pkg/chart/v2/loader/testdata/frobnitz.v2.reqs/charts/alpine/values.yaml diff --git a/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/mariner-4.3.2.tgz b/pkg/chart/v2/loader/testdata/frobnitz.v2.reqs/charts/mariner-4.3.2.tgz similarity index 100% rename from pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/mariner-4.3.2.tgz rename to pkg/chart/v2/loader/testdata/frobnitz.v2.reqs/charts/mariner-4.3.2.tgz diff --git a/pkg/chart/loader/testdata/frobnitz.v2.reqs/docs/README.md b/pkg/chart/v2/loader/testdata/frobnitz.v2.reqs/docs/README.md similarity index 100% rename from pkg/chart/loader/testdata/frobnitz.v2.reqs/docs/README.md rename to pkg/chart/v2/loader/testdata/frobnitz.v2.reqs/docs/README.md diff --git a/pkg/chart/loader/testdata/frobnitz.v2.reqs/icon.svg b/pkg/chart/v2/loader/testdata/frobnitz.v2.reqs/icon.svg similarity index 100% rename from pkg/chart/loader/testdata/frobnitz.v2.reqs/icon.svg rename to pkg/chart/v2/loader/testdata/frobnitz.v2.reqs/icon.svg diff --git a/pkg/chart/loader/testdata/frobnitz.v2.reqs/ignore/me.txt b/pkg/chart/v2/loader/testdata/frobnitz.v2.reqs/ignore/me.txt similarity index 100% rename from pkg/chart/loader/testdata/frobnitz.v2.reqs/ignore/me.txt rename to pkg/chart/v2/loader/testdata/frobnitz.v2.reqs/ignore/me.txt diff --git a/pkg/chart/loader/testdata/frobnitz.v2.reqs/requirements.yaml b/pkg/chart/v2/loader/testdata/frobnitz.v2.reqs/requirements.yaml similarity index 100% rename from pkg/chart/loader/testdata/frobnitz.v2.reqs/requirements.yaml rename to pkg/chart/v2/loader/testdata/frobnitz.v2.reqs/requirements.yaml diff --git a/pkg/chart/loader/testdata/frobnitz.v2.reqs/templates/template.tpl b/pkg/chart/v2/loader/testdata/frobnitz.v2.reqs/templates/template.tpl similarity index 100% rename from pkg/chart/loader/testdata/frobnitz.v2.reqs/templates/template.tpl rename to pkg/chart/v2/loader/testdata/frobnitz.v2.reqs/templates/template.tpl diff --git a/pkg/chart/loader/testdata/frobnitz.v2.reqs/values.yaml b/pkg/chart/v2/loader/testdata/frobnitz.v2.reqs/values.yaml similarity index 100% rename from pkg/chart/loader/testdata/frobnitz.v2.reqs/values.yaml rename to pkg/chart/v2/loader/testdata/frobnitz.v2.reqs/values.yaml diff --git a/pkg/chart/loader/testdata/frobnitz/.helmignore b/pkg/chart/v2/loader/testdata/frobnitz/.helmignore similarity index 100% rename from pkg/chart/loader/testdata/frobnitz/.helmignore rename to pkg/chart/v2/loader/testdata/frobnitz/.helmignore diff --git a/pkg/chart/loader/testdata/frobnitz/Chart.lock b/pkg/chart/v2/loader/testdata/frobnitz/Chart.lock similarity index 100% rename from pkg/chart/loader/testdata/frobnitz/Chart.lock rename to pkg/chart/v2/loader/testdata/frobnitz/Chart.lock diff --git a/pkg/chart/loader/testdata/frobnitz/Chart.yaml b/pkg/chart/v2/loader/testdata/frobnitz/Chart.yaml similarity index 100% rename from pkg/chart/loader/testdata/frobnitz/Chart.yaml rename to pkg/chart/v2/loader/testdata/frobnitz/Chart.yaml diff --git a/pkg/chart/loader/testdata/frobnitz/INSTALL.txt b/pkg/chart/v2/loader/testdata/frobnitz/INSTALL.txt similarity index 100% rename from pkg/chart/loader/testdata/frobnitz/INSTALL.txt rename to pkg/chart/v2/loader/testdata/frobnitz/INSTALL.txt diff --git a/pkg/chart/loader/testdata/frobnitz/LICENSE b/pkg/chart/v2/loader/testdata/frobnitz/LICENSE similarity index 100% rename from pkg/chart/loader/testdata/frobnitz/LICENSE rename to pkg/chart/v2/loader/testdata/frobnitz/LICENSE diff --git a/pkg/chart/loader/testdata/frobnitz/README.md b/pkg/chart/v2/loader/testdata/frobnitz/README.md similarity index 100% rename from pkg/chart/loader/testdata/frobnitz/README.md rename to pkg/chart/v2/loader/testdata/frobnitz/README.md diff --git a/pkg/chart/loader/testdata/frobnitz/charts/_ignore_me b/pkg/chart/v2/loader/testdata/frobnitz/charts/_ignore_me similarity index 100% rename from pkg/chart/loader/testdata/frobnitz/charts/_ignore_me rename to pkg/chart/v2/loader/testdata/frobnitz/charts/_ignore_me diff --git a/pkg/chart/loader/testdata/frobnitz/charts/alpine/Chart.yaml b/pkg/chart/v2/loader/testdata/frobnitz/charts/alpine/Chart.yaml similarity index 100% rename from pkg/chart/loader/testdata/frobnitz/charts/alpine/Chart.yaml rename to pkg/chart/v2/loader/testdata/frobnitz/charts/alpine/Chart.yaml diff --git a/pkg/chart/loader/testdata/frobnitz/charts/alpine/README.md b/pkg/chart/v2/loader/testdata/frobnitz/charts/alpine/README.md similarity index 100% rename from pkg/chart/loader/testdata/frobnitz/charts/alpine/README.md rename to pkg/chart/v2/loader/testdata/frobnitz/charts/alpine/README.md diff --git a/pkg/chart/loader/testdata/frobnitz/charts/alpine/charts/mast1/Chart.yaml b/pkg/chart/v2/loader/testdata/frobnitz/charts/alpine/charts/mast1/Chart.yaml similarity index 100% rename from pkg/chart/loader/testdata/frobnitz/charts/alpine/charts/mast1/Chart.yaml rename to pkg/chart/v2/loader/testdata/frobnitz/charts/alpine/charts/mast1/Chart.yaml diff --git a/pkg/chart/loader/testdata/frobnitz/charts/alpine/charts/mast1/values.yaml b/pkg/chart/v2/loader/testdata/frobnitz/charts/alpine/charts/mast1/values.yaml similarity index 100% rename from pkg/chart/loader/testdata/frobnitz/charts/alpine/charts/mast1/values.yaml rename to pkg/chart/v2/loader/testdata/frobnitz/charts/alpine/charts/mast1/values.yaml diff --git a/pkg/chart/loader/testdata/frobnitz/charts/alpine/charts/mast2-0.1.0.tgz b/pkg/chart/v2/loader/testdata/frobnitz/charts/alpine/charts/mast2-0.1.0.tgz similarity index 100% rename from pkg/chart/loader/testdata/frobnitz/charts/alpine/charts/mast2-0.1.0.tgz rename to pkg/chart/v2/loader/testdata/frobnitz/charts/alpine/charts/mast2-0.1.0.tgz diff --git a/pkg/chart/loader/testdata/frobnitz/charts/alpine/templates/alpine-pod.yaml b/pkg/chart/v2/loader/testdata/frobnitz/charts/alpine/templates/alpine-pod.yaml similarity index 100% rename from pkg/chart/loader/testdata/frobnitz/charts/alpine/templates/alpine-pod.yaml rename to pkg/chart/v2/loader/testdata/frobnitz/charts/alpine/templates/alpine-pod.yaml diff --git a/pkg/chart/loader/testdata/frobnitz/charts/alpine/values.yaml b/pkg/chart/v2/loader/testdata/frobnitz/charts/alpine/values.yaml similarity index 100% rename from pkg/chart/loader/testdata/frobnitz/charts/alpine/values.yaml rename to pkg/chart/v2/loader/testdata/frobnitz/charts/alpine/values.yaml diff --git a/pkg/chart/loader/testdata/frobnitz/charts/mariner-4.3.2.tgz b/pkg/chart/v2/loader/testdata/frobnitz/charts/mariner-4.3.2.tgz similarity index 100% rename from pkg/chart/loader/testdata/frobnitz/charts/mariner-4.3.2.tgz rename to pkg/chart/v2/loader/testdata/frobnitz/charts/mariner-4.3.2.tgz diff --git a/pkg/chart/loader/testdata/frobnitz/docs/README.md b/pkg/chart/v2/loader/testdata/frobnitz/docs/README.md similarity index 100% rename from pkg/chart/loader/testdata/frobnitz/docs/README.md rename to pkg/chart/v2/loader/testdata/frobnitz/docs/README.md diff --git a/pkg/chart/loader/testdata/frobnitz/icon.svg b/pkg/chart/v2/loader/testdata/frobnitz/icon.svg similarity index 100% rename from pkg/chart/loader/testdata/frobnitz/icon.svg rename to pkg/chart/v2/loader/testdata/frobnitz/icon.svg diff --git a/pkg/chart/loader/testdata/frobnitz/ignore/me.txt b/pkg/chart/v2/loader/testdata/frobnitz/ignore/me.txt similarity index 100% rename from pkg/chart/loader/testdata/frobnitz/ignore/me.txt rename to pkg/chart/v2/loader/testdata/frobnitz/ignore/me.txt diff --git a/pkg/chart/loader/testdata/frobnitz/templates/template.tpl b/pkg/chart/v2/loader/testdata/frobnitz/templates/template.tpl similarity index 100% rename from pkg/chart/loader/testdata/frobnitz/templates/template.tpl rename to pkg/chart/v2/loader/testdata/frobnitz/templates/template.tpl diff --git a/pkg/chart/loader/testdata/frobnitz/values.yaml b/pkg/chart/v2/loader/testdata/frobnitz/values.yaml similarity index 100% rename from pkg/chart/loader/testdata/frobnitz/values.yaml rename to pkg/chart/v2/loader/testdata/frobnitz/values.yaml diff --git a/pkg/chart/loader/testdata/frobnitz_backslash-1.2.3.tgz b/pkg/chart/v2/loader/testdata/frobnitz_backslash-1.2.3.tgz similarity index 100% rename from pkg/chart/loader/testdata/frobnitz_backslash-1.2.3.tgz rename to pkg/chart/v2/loader/testdata/frobnitz_backslash-1.2.3.tgz diff --git a/pkg/chart/loader/testdata/frobnitz_backslash/.helmignore b/pkg/chart/v2/loader/testdata/frobnitz_backslash/.helmignore similarity index 100% rename from pkg/chart/loader/testdata/frobnitz_backslash/.helmignore rename to pkg/chart/v2/loader/testdata/frobnitz_backslash/.helmignore diff --git a/pkg/chart/loader/testdata/frobnitz_backslash/Chart.lock b/pkg/chart/v2/loader/testdata/frobnitz_backslash/Chart.lock similarity index 100% rename from pkg/chart/loader/testdata/frobnitz_backslash/Chart.lock rename to pkg/chart/v2/loader/testdata/frobnitz_backslash/Chart.lock diff --git a/pkg/chart/loader/testdata/frobnitz_backslash/Chart.yaml b/pkg/chart/v2/loader/testdata/frobnitz_backslash/Chart.yaml similarity index 100% rename from pkg/chart/loader/testdata/frobnitz_backslash/Chart.yaml rename to pkg/chart/v2/loader/testdata/frobnitz_backslash/Chart.yaml diff --git a/pkg/chart/loader/testdata/frobnitz_backslash/INSTALL.txt b/pkg/chart/v2/loader/testdata/frobnitz_backslash/INSTALL.txt similarity index 100% rename from pkg/chart/loader/testdata/frobnitz_backslash/INSTALL.txt rename to pkg/chart/v2/loader/testdata/frobnitz_backslash/INSTALL.txt diff --git a/pkg/chart/loader/testdata/frobnitz_backslash/LICENSE b/pkg/chart/v2/loader/testdata/frobnitz_backslash/LICENSE similarity index 100% rename from pkg/chart/loader/testdata/frobnitz_backslash/LICENSE rename to pkg/chart/v2/loader/testdata/frobnitz_backslash/LICENSE diff --git a/pkg/chart/loader/testdata/frobnitz_backslash/README.md b/pkg/chart/v2/loader/testdata/frobnitz_backslash/README.md similarity index 100% rename from pkg/chart/loader/testdata/frobnitz_backslash/README.md rename to pkg/chart/v2/loader/testdata/frobnitz_backslash/README.md diff --git a/pkg/chart/loader/testdata/frobnitz_backslash/charts/_ignore_me b/pkg/chart/v2/loader/testdata/frobnitz_backslash/charts/_ignore_me similarity index 100% rename from pkg/chart/loader/testdata/frobnitz_backslash/charts/_ignore_me rename to pkg/chart/v2/loader/testdata/frobnitz_backslash/charts/_ignore_me diff --git a/pkg/chart/loader/testdata/frobnitz_backslash/charts/alpine/Chart.yaml b/pkg/chart/v2/loader/testdata/frobnitz_backslash/charts/alpine/Chart.yaml similarity index 100% rename from pkg/chart/loader/testdata/frobnitz_backslash/charts/alpine/Chart.yaml rename to pkg/chart/v2/loader/testdata/frobnitz_backslash/charts/alpine/Chart.yaml diff --git a/pkg/chart/loader/testdata/frobnitz_backslash/charts/alpine/README.md b/pkg/chart/v2/loader/testdata/frobnitz_backslash/charts/alpine/README.md similarity index 100% rename from pkg/chart/loader/testdata/frobnitz_backslash/charts/alpine/README.md rename to pkg/chart/v2/loader/testdata/frobnitz_backslash/charts/alpine/README.md diff --git a/pkg/chart/loader/testdata/frobnitz_backslash/charts/alpine/charts/mast1/Chart.yaml b/pkg/chart/v2/loader/testdata/frobnitz_backslash/charts/alpine/charts/mast1/Chart.yaml similarity index 100% rename from pkg/chart/loader/testdata/frobnitz_backslash/charts/alpine/charts/mast1/Chart.yaml rename to pkg/chart/v2/loader/testdata/frobnitz_backslash/charts/alpine/charts/mast1/Chart.yaml diff --git a/pkg/chart/loader/testdata/frobnitz_backslash/charts/alpine/charts/mast1/values.yaml b/pkg/chart/v2/loader/testdata/frobnitz_backslash/charts/alpine/charts/mast1/values.yaml similarity index 100% rename from pkg/chart/loader/testdata/frobnitz_backslash/charts/alpine/charts/mast1/values.yaml rename to pkg/chart/v2/loader/testdata/frobnitz_backslash/charts/alpine/charts/mast1/values.yaml diff --git a/pkg/chart/loader/testdata/frobnitz_backslash/charts/alpine/charts/mast2-0.1.0.tgz b/pkg/chart/v2/loader/testdata/frobnitz_backslash/charts/alpine/charts/mast2-0.1.0.tgz similarity index 100% rename from pkg/chart/loader/testdata/frobnitz_backslash/charts/alpine/charts/mast2-0.1.0.tgz rename to pkg/chart/v2/loader/testdata/frobnitz_backslash/charts/alpine/charts/mast2-0.1.0.tgz diff --git a/pkg/chart/loader/testdata/frobnitz_backslash/charts/alpine/templates/alpine-pod.yaml b/pkg/chart/v2/loader/testdata/frobnitz_backslash/charts/alpine/templates/alpine-pod.yaml similarity index 100% rename from pkg/chart/loader/testdata/frobnitz_backslash/charts/alpine/templates/alpine-pod.yaml rename to pkg/chart/v2/loader/testdata/frobnitz_backslash/charts/alpine/templates/alpine-pod.yaml diff --git a/pkg/chart/loader/testdata/frobnitz_backslash/charts/alpine/values.yaml b/pkg/chart/v2/loader/testdata/frobnitz_backslash/charts/alpine/values.yaml similarity index 100% rename from pkg/chart/loader/testdata/frobnitz_backslash/charts/alpine/values.yaml rename to pkg/chart/v2/loader/testdata/frobnitz_backslash/charts/alpine/values.yaml diff --git a/pkg/chart/loader/testdata/frobnitz_backslash/charts/mariner-4.3.2.tgz b/pkg/chart/v2/loader/testdata/frobnitz_backslash/charts/mariner-4.3.2.tgz similarity index 100% rename from pkg/chart/loader/testdata/frobnitz_backslash/charts/mariner-4.3.2.tgz rename to pkg/chart/v2/loader/testdata/frobnitz_backslash/charts/mariner-4.3.2.tgz diff --git a/pkg/chart/loader/testdata/frobnitz_backslash/docs/README.md b/pkg/chart/v2/loader/testdata/frobnitz_backslash/docs/README.md similarity index 100% rename from pkg/chart/loader/testdata/frobnitz_backslash/docs/README.md rename to pkg/chart/v2/loader/testdata/frobnitz_backslash/docs/README.md diff --git a/pkg/chart/loader/testdata/frobnitz_backslash/icon.svg b/pkg/chart/v2/loader/testdata/frobnitz_backslash/icon.svg similarity index 100% rename from pkg/chart/loader/testdata/frobnitz_backslash/icon.svg rename to pkg/chart/v2/loader/testdata/frobnitz_backslash/icon.svg diff --git a/pkg/chart/loader/testdata/frobnitz_backslash/ignore/me.txt b/pkg/chart/v2/loader/testdata/frobnitz_backslash/ignore/me.txt similarity index 100% rename from pkg/chart/loader/testdata/frobnitz_backslash/ignore/me.txt rename to pkg/chart/v2/loader/testdata/frobnitz_backslash/ignore/me.txt diff --git a/pkg/chart/loader/testdata/frobnitz_backslash/templates/template.tpl b/pkg/chart/v2/loader/testdata/frobnitz_backslash/templates/template.tpl similarity index 100% rename from pkg/chart/loader/testdata/frobnitz_backslash/templates/template.tpl rename to pkg/chart/v2/loader/testdata/frobnitz_backslash/templates/template.tpl diff --git a/pkg/chart/loader/testdata/frobnitz_backslash/values.yaml b/pkg/chart/v2/loader/testdata/frobnitz_backslash/values.yaml similarity index 100% rename from pkg/chart/loader/testdata/frobnitz_backslash/values.yaml rename to pkg/chart/v2/loader/testdata/frobnitz_backslash/values.yaml diff --git a/pkg/chart/loader/testdata/frobnitz_with_bom.tgz b/pkg/chart/v2/loader/testdata/frobnitz_with_bom.tgz similarity index 100% rename from pkg/chart/loader/testdata/frobnitz_with_bom.tgz rename to pkg/chart/v2/loader/testdata/frobnitz_with_bom.tgz diff --git a/pkg/chart/loader/testdata/frobnitz_with_bom/.helmignore b/pkg/chart/v2/loader/testdata/frobnitz_with_bom/.helmignore similarity index 100% rename from pkg/chart/loader/testdata/frobnitz_with_bom/.helmignore rename to pkg/chart/v2/loader/testdata/frobnitz_with_bom/.helmignore diff --git a/pkg/chart/loader/testdata/frobnitz_with_bom/Chart.lock b/pkg/chart/v2/loader/testdata/frobnitz_with_bom/Chart.lock similarity index 100% rename from pkg/chart/loader/testdata/frobnitz_with_bom/Chart.lock rename to pkg/chart/v2/loader/testdata/frobnitz_with_bom/Chart.lock diff --git a/pkg/chart/loader/testdata/frobnitz_with_bom/Chart.yaml b/pkg/chart/v2/loader/testdata/frobnitz_with_bom/Chart.yaml similarity index 100% rename from pkg/chart/loader/testdata/frobnitz_with_bom/Chart.yaml rename to pkg/chart/v2/loader/testdata/frobnitz_with_bom/Chart.yaml diff --git a/pkg/chart/loader/testdata/frobnitz_with_bom/INSTALL.txt b/pkg/chart/v2/loader/testdata/frobnitz_with_bom/INSTALL.txt similarity index 100% rename from pkg/chart/loader/testdata/frobnitz_with_bom/INSTALL.txt rename to pkg/chart/v2/loader/testdata/frobnitz_with_bom/INSTALL.txt diff --git a/pkg/chart/loader/testdata/frobnitz_with_bom/LICENSE b/pkg/chart/v2/loader/testdata/frobnitz_with_bom/LICENSE similarity index 100% rename from pkg/chart/loader/testdata/frobnitz_with_bom/LICENSE rename to pkg/chart/v2/loader/testdata/frobnitz_with_bom/LICENSE diff --git a/pkg/chart/loader/testdata/frobnitz_with_bom/README.md b/pkg/chart/v2/loader/testdata/frobnitz_with_bom/README.md similarity index 100% rename from pkg/chart/loader/testdata/frobnitz_with_bom/README.md rename to pkg/chart/v2/loader/testdata/frobnitz_with_bom/README.md diff --git a/pkg/chart/loader/testdata/frobnitz_with_bom/charts/_ignore_me b/pkg/chart/v2/loader/testdata/frobnitz_with_bom/charts/_ignore_me similarity index 100% rename from pkg/chart/loader/testdata/frobnitz_with_bom/charts/_ignore_me rename to pkg/chart/v2/loader/testdata/frobnitz_with_bom/charts/_ignore_me diff --git a/pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/Chart.yaml b/pkg/chart/v2/loader/testdata/frobnitz_with_bom/charts/alpine/Chart.yaml similarity index 100% rename from pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/Chart.yaml rename to pkg/chart/v2/loader/testdata/frobnitz_with_bom/charts/alpine/Chart.yaml diff --git a/pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/README.md b/pkg/chart/v2/loader/testdata/frobnitz_with_bom/charts/alpine/README.md similarity index 100% rename from pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/README.md rename to pkg/chart/v2/loader/testdata/frobnitz_with_bom/charts/alpine/README.md diff --git a/pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/charts/mast1/Chart.yaml b/pkg/chart/v2/loader/testdata/frobnitz_with_bom/charts/alpine/charts/mast1/Chart.yaml similarity index 100% rename from pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/charts/mast1/Chart.yaml rename to pkg/chart/v2/loader/testdata/frobnitz_with_bom/charts/alpine/charts/mast1/Chart.yaml diff --git a/pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/charts/mast1/values.yaml b/pkg/chart/v2/loader/testdata/frobnitz_with_bom/charts/alpine/charts/mast1/values.yaml similarity index 100% rename from pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/charts/mast1/values.yaml rename to pkg/chart/v2/loader/testdata/frobnitz_with_bom/charts/alpine/charts/mast1/values.yaml diff --git a/pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/charts/mast2-0.1.0.tgz b/pkg/chart/v2/loader/testdata/frobnitz_with_bom/charts/alpine/charts/mast2-0.1.0.tgz similarity index 100% rename from pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/charts/mast2-0.1.0.tgz rename to pkg/chart/v2/loader/testdata/frobnitz_with_bom/charts/alpine/charts/mast2-0.1.0.tgz diff --git a/pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/templates/alpine-pod.yaml b/pkg/chart/v2/loader/testdata/frobnitz_with_bom/charts/alpine/templates/alpine-pod.yaml similarity index 100% rename from pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/templates/alpine-pod.yaml rename to pkg/chart/v2/loader/testdata/frobnitz_with_bom/charts/alpine/templates/alpine-pod.yaml diff --git a/pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/values.yaml b/pkg/chart/v2/loader/testdata/frobnitz_with_bom/charts/alpine/values.yaml similarity index 100% rename from pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/values.yaml rename to pkg/chart/v2/loader/testdata/frobnitz_with_bom/charts/alpine/values.yaml diff --git a/pkg/chart/loader/testdata/frobnitz_with_bom/charts/mariner-4.3.2.tgz b/pkg/chart/v2/loader/testdata/frobnitz_with_bom/charts/mariner-4.3.2.tgz similarity index 100% rename from pkg/chart/loader/testdata/frobnitz_with_bom/charts/mariner-4.3.2.tgz rename to pkg/chart/v2/loader/testdata/frobnitz_with_bom/charts/mariner-4.3.2.tgz diff --git a/pkg/chart/loader/testdata/frobnitz_with_bom/docs/README.md b/pkg/chart/v2/loader/testdata/frobnitz_with_bom/docs/README.md similarity index 100% rename from pkg/chart/loader/testdata/frobnitz_with_bom/docs/README.md rename to pkg/chart/v2/loader/testdata/frobnitz_with_bom/docs/README.md diff --git a/pkg/chart/loader/testdata/frobnitz_with_bom/icon.svg b/pkg/chart/v2/loader/testdata/frobnitz_with_bom/icon.svg similarity index 100% rename from pkg/chart/loader/testdata/frobnitz_with_bom/icon.svg rename to pkg/chart/v2/loader/testdata/frobnitz_with_bom/icon.svg diff --git a/pkg/chart/loader/testdata/frobnitz_with_bom/ignore/me.txt b/pkg/chart/v2/loader/testdata/frobnitz_with_bom/ignore/me.txt similarity index 100% rename from pkg/chart/loader/testdata/frobnitz_with_bom/ignore/me.txt rename to pkg/chart/v2/loader/testdata/frobnitz_with_bom/ignore/me.txt diff --git a/pkg/chart/loader/testdata/frobnitz_with_bom/templates/template.tpl b/pkg/chart/v2/loader/testdata/frobnitz_with_bom/templates/template.tpl similarity index 100% rename from pkg/chart/loader/testdata/frobnitz_with_bom/templates/template.tpl rename to pkg/chart/v2/loader/testdata/frobnitz_with_bom/templates/template.tpl diff --git a/pkg/chart/loader/testdata/frobnitz_with_bom/values.yaml b/pkg/chart/v2/loader/testdata/frobnitz_with_bom/values.yaml similarity index 100% rename from pkg/chart/loader/testdata/frobnitz_with_bom/values.yaml rename to pkg/chart/v2/loader/testdata/frobnitz_with_bom/values.yaml diff --git a/pkg/chart/loader/testdata/frobnitz_with_dev_null/.helmignore b/pkg/chart/v2/loader/testdata/frobnitz_with_dev_null/.helmignore similarity index 100% rename from pkg/chart/loader/testdata/frobnitz_with_dev_null/.helmignore rename to pkg/chart/v2/loader/testdata/frobnitz_with_dev_null/.helmignore diff --git a/pkg/chart/loader/testdata/frobnitz_with_dev_null/Chart.lock b/pkg/chart/v2/loader/testdata/frobnitz_with_dev_null/Chart.lock similarity index 100% rename from pkg/chart/loader/testdata/frobnitz_with_dev_null/Chart.lock rename to pkg/chart/v2/loader/testdata/frobnitz_with_dev_null/Chart.lock diff --git a/pkg/chart/loader/testdata/frobnitz_with_dev_null/Chart.yaml b/pkg/chart/v2/loader/testdata/frobnitz_with_dev_null/Chart.yaml similarity index 100% rename from pkg/chart/loader/testdata/frobnitz_with_dev_null/Chart.yaml rename to pkg/chart/v2/loader/testdata/frobnitz_with_dev_null/Chart.yaml diff --git a/pkg/chart/loader/testdata/frobnitz_with_dev_null/INSTALL.txt b/pkg/chart/v2/loader/testdata/frobnitz_with_dev_null/INSTALL.txt similarity index 100% rename from pkg/chart/loader/testdata/frobnitz_with_dev_null/INSTALL.txt rename to pkg/chart/v2/loader/testdata/frobnitz_with_dev_null/INSTALL.txt diff --git a/pkg/chart/loader/testdata/frobnitz_with_dev_null/LICENSE b/pkg/chart/v2/loader/testdata/frobnitz_with_dev_null/LICENSE similarity index 100% rename from pkg/chart/loader/testdata/frobnitz_with_dev_null/LICENSE rename to pkg/chart/v2/loader/testdata/frobnitz_with_dev_null/LICENSE diff --git a/pkg/chart/loader/testdata/frobnitz_with_dev_null/README.md b/pkg/chart/v2/loader/testdata/frobnitz_with_dev_null/README.md similarity index 100% rename from pkg/chart/loader/testdata/frobnitz_with_dev_null/README.md rename to pkg/chart/v2/loader/testdata/frobnitz_with_dev_null/README.md diff --git a/pkg/chart/loader/testdata/frobnitz_with_dev_null/charts/_ignore_me b/pkg/chart/v2/loader/testdata/frobnitz_with_dev_null/charts/_ignore_me similarity index 100% rename from pkg/chart/loader/testdata/frobnitz_with_dev_null/charts/_ignore_me rename to pkg/chart/v2/loader/testdata/frobnitz_with_dev_null/charts/_ignore_me diff --git a/pkg/chart/loader/testdata/frobnitz_with_dev_null/charts/alpine/Chart.yaml b/pkg/chart/v2/loader/testdata/frobnitz_with_dev_null/charts/alpine/Chart.yaml similarity index 100% rename from pkg/chart/loader/testdata/frobnitz_with_dev_null/charts/alpine/Chart.yaml rename to pkg/chart/v2/loader/testdata/frobnitz_with_dev_null/charts/alpine/Chart.yaml diff --git a/pkg/chart/loader/testdata/frobnitz_with_dev_null/charts/alpine/README.md b/pkg/chart/v2/loader/testdata/frobnitz_with_dev_null/charts/alpine/README.md similarity index 100% rename from pkg/chart/loader/testdata/frobnitz_with_dev_null/charts/alpine/README.md rename to pkg/chart/v2/loader/testdata/frobnitz_with_dev_null/charts/alpine/README.md diff --git a/pkg/chart/loader/testdata/frobnitz_with_dev_null/charts/alpine/charts/mast1/Chart.yaml b/pkg/chart/v2/loader/testdata/frobnitz_with_dev_null/charts/alpine/charts/mast1/Chart.yaml similarity index 100% rename from pkg/chart/loader/testdata/frobnitz_with_dev_null/charts/alpine/charts/mast1/Chart.yaml rename to pkg/chart/v2/loader/testdata/frobnitz_with_dev_null/charts/alpine/charts/mast1/Chart.yaml diff --git a/pkg/chart/loader/testdata/frobnitz_with_dev_null/charts/alpine/charts/mast1/values.yaml b/pkg/chart/v2/loader/testdata/frobnitz_with_dev_null/charts/alpine/charts/mast1/values.yaml similarity index 100% rename from pkg/chart/loader/testdata/frobnitz_with_dev_null/charts/alpine/charts/mast1/values.yaml rename to pkg/chart/v2/loader/testdata/frobnitz_with_dev_null/charts/alpine/charts/mast1/values.yaml diff --git a/pkg/chart/loader/testdata/frobnitz_with_dev_null/charts/alpine/charts/mast2-0.1.0.tgz b/pkg/chart/v2/loader/testdata/frobnitz_with_dev_null/charts/alpine/charts/mast2-0.1.0.tgz similarity index 100% rename from pkg/chart/loader/testdata/frobnitz_with_dev_null/charts/alpine/charts/mast2-0.1.0.tgz rename to pkg/chart/v2/loader/testdata/frobnitz_with_dev_null/charts/alpine/charts/mast2-0.1.0.tgz diff --git a/pkg/chart/loader/testdata/frobnitz_with_dev_null/charts/alpine/templates/alpine-pod.yaml b/pkg/chart/v2/loader/testdata/frobnitz_with_dev_null/charts/alpine/templates/alpine-pod.yaml similarity index 100% rename from pkg/chart/loader/testdata/frobnitz_with_dev_null/charts/alpine/templates/alpine-pod.yaml rename to pkg/chart/v2/loader/testdata/frobnitz_with_dev_null/charts/alpine/templates/alpine-pod.yaml diff --git a/pkg/chart/loader/testdata/frobnitz_with_dev_null/charts/alpine/values.yaml b/pkg/chart/v2/loader/testdata/frobnitz_with_dev_null/charts/alpine/values.yaml similarity index 100% rename from pkg/chart/loader/testdata/frobnitz_with_dev_null/charts/alpine/values.yaml rename to pkg/chart/v2/loader/testdata/frobnitz_with_dev_null/charts/alpine/values.yaml diff --git a/pkg/chart/loader/testdata/frobnitz_with_dev_null/charts/mariner-4.3.2.tgz b/pkg/chart/v2/loader/testdata/frobnitz_with_dev_null/charts/mariner-4.3.2.tgz similarity index 100% rename from pkg/chart/loader/testdata/frobnitz_with_dev_null/charts/mariner-4.3.2.tgz rename to pkg/chart/v2/loader/testdata/frobnitz_with_dev_null/charts/mariner-4.3.2.tgz diff --git a/pkg/chart/loader/testdata/frobnitz_with_dev_null/docs/README.md b/pkg/chart/v2/loader/testdata/frobnitz_with_dev_null/docs/README.md similarity index 100% rename from pkg/chart/loader/testdata/frobnitz_with_dev_null/docs/README.md rename to pkg/chart/v2/loader/testdata/frobnitz_with_dev_null/docs/README.md diff --git a/pkg/chart/loader/testdata/frobnitz_with_dev_null/icon.svg b/pkg/chart/v2/loader/testdata/frobnitz_with_dev_null/icon.svg similarity index 100% rename from pkg/chart/loader/testdata/frobnitz_with_dev_null/icon.svg rename to pkg/chart/v2/loader/testdata/frobnitz_with_dev_null/icon.svg diff --git a/pkg/chart/loader/testdata/frobnitz_with_dev_null/ignore/me.txt b/pkg/chart/v2/loader/testdata/frobnitz_with_dev_null/ignore/me.txt similarity index 100% rename from pkg/chart/loader/testdata/frobnitz_with_dev_null/ignore/me.txt rename to pkg/chart/v2/loader/testdata/frobnitz_with_dev_null/ignore/me.txt diff --git a/pkg/chart/loader/testdata/frobnitz_with_dev_null/null b/pkg/chart/v2/loader/testdata/frobnitz_with_dev_null/null similarity index 100% rename from pkg/chart/loader/testdata/frobnitz_with_dev_null/null rename to pkg/chart/v2/loader/testdata/frobnitz_with_dev_null/null diff --git a/pkg/chart/loader/testdata/frobnitz_with_dev_null/templates/template.tpl b/pkg/chart/v2/loader/testdata/frobnitz_with_dev_null/templates/template.tpl similarity index 100% rename from pkg/chart/loader/testdata/frobnitz_with_dev_null/templates/template.tpl rename to pkg/chart/v2/loader/testdata/frobnitz_with_dev_null/templates/template.tpl diff --git a/pkg/chart/loader/testdata/frobnitz_with_dev_null/values.yaml b/pkg/chart/v2/loader/testdata/frobnitz_with_dev_null/values.yaml similarity index 100% rename from pkg/chart/loader/testdata/frobnitz_with_dev_null/values.yaml rename to pkg/chart/v2/loader/testdata/frobnitz_with_dev_null/values.yaml diff --git a/pkg/chart/loader/testdata/frobnitz_with_symlink/.helmignore b/pkg/chart/v2/loader/testdata/frobnitz_with_symlink/.helmignore similarity index 100% rename from pkg/chart/loader/testdata/frobnitz_with_symlink/.helmignore rename to pkg/chart/v2/loader/testdata/frobnitz_with_symlink/.helmignore diff --git a/pkg/chart/loader/testdata/frobnitz_with_symlink/Chart.lock b/pkg/chart/v2/loader/testdata/frobnitz_with_symlink/Chart.lock similarity index 100% rename from pkg/chart/loader/testdata/frobnitz_with_symlink/Chart.lock rename to pkg/chart/v2/loader/testdata/frobnitz_with_symlink/Chart.lock diff --git a/pkg/chart/loader/testdata/frobnitz_with_symlink/Chart.yaml b/pkg/chart/v2/loader/testdata/frobnitz_with_symlink/Chart.yaml similarity index 100% rename from pkg/chart/loader/testdata/frobnitz_with_symlink/Chart.yaml rename to pkg/chart/v2/loader/testdata/frobnitz_with_symlink/Chart.yaml diff --git a/pkg/chart/loader/testdata/frobnitz_with_symlink/INSTALL.txt b/pkg/chart/v2/loader/testdata/frobnitz_with_symlink/INSTALL.txt similarity index 100% rename from pkg/chart/loader/testdata/frobnitz_with_symlink/INSTALL.txt rename to pkg/chart/v2/loader/testdata/frobnitz_with_symlink/INSTALL.txt diff --git a/pkg/chart/loader/testdata/frobnitz_with_symlink/README.md b/pkg/chart/v2/loader/testdata/frobnitz_with_symlink/README.md similarity index 100% rename from pkg/chart/loader/testdata/frobnitz_with_symlink/README.md rename to pkg/chart/v2/loader/testdata/frobnitz_with_symlink/README.md diff --git a/pkg/chart/loader/testdata/frobnitz_with_symlink/charts/_ignore_me b/pkg/chart/v2/loader/testdata/frobnitz_with_symlink/charts/_ignore_me similarity index 100% rename from pkg/chart/loader/testdata/frobnitz_with_symlink/charts/_ignore_me rename to pkg/chart/v2/loader/testdata/frobnitz_with_symlink/charts/_ignore_me diff --git a/pkg/chart/loader/testdata/frobnitz_with_symlink/charts/alpine/Chart.yaml b/pkg/chart/v2/loader/testdata/frobnitz_with_symlink/charts/alpine/Chart.yaml similarity index 100% rename from pkg/chart/loader/testdata/frobnitz_with_symlink/charts/alpine/Chart.yaml rename to pkg/chart/v2/loader/testdata/frobnitz_with_symlink/charts/alpine/Chart.yaml diff --git a/pkg/chart/loader/testdata/frobnitz_with_symlink/charts/alpine/README.md b/pkg/chart/v2/loader/testdata/frobnitz_with_symlink/charts/alpine/README.md similarity index 100% rename from pkg/chart/loader/testdata/frobnitz_with_symlink/charts/alpine/README.md rename to pkg/chart/v2/loader/testdata/frobnitz_with_symlink/charts/alpine/README.md diff --git a/pkg/chart/loader/testdata/frobnitz_with_symlink/charts/alpine/charts/mast1/Chart.yaml b/pkg/chart/v2/loader/testdata/frobnitz_with_symlink/charts/alpine/charts/mast1/Chart.yaml similarity index 100% rename from pkg/chart/loader/testdata/frobnitz_with_symlink/charts/alpine/charts/mast1/Chart.yaml rename to pkg/chart/v2/loader/testdata/frobnitz_with_symlink/charts/alpine/charts/mast1/Chart.yaml diff --git a/pkg/chart/loader/testdata/frobnitz_with_symlink/charts/alpine/charts/mast1/values.yaml b/pkg/chart/v2/loader/testdata/frobnitz_with_symlink/charts/alpine/charts/mast1/values.yaml similarity index 100% rename from pkg/chart/loader/testdata/frobnitz_with_symlink/charts/alpine/charts/mast1/values.yaml rename to pkg/chart/v2/loader/testdata/frobnitz_with_symlink/charts/alpine/charts/mast1/values.yaml diff --git a/pkg/chart/loader/testdata/frobnitz_with_symlink/charts/alpine/charts/mast2-0.1.0.tgz b/pkg/chart/v2/loader/testdata/frobnitz_with_symlink/charts/alpine/charts/mast2-0.1.0.tgz similarity index 100% rename from pkg/chart/loader/testdata/frobnitz_with_symlink/charts/alpine/charts/mast2-0.1.0.tgz rename to pkg/chart/v2/loader/testdata/frobnitz_with_symlink/charts/alpine/charts/mast2-0.1.0.tgz diff --git a/pkg/chart/loader/testdata/frobnitz_with_symlink/charts/alpine/templates/alpine-pod.yaml b/pkg/chart/v2/loader/testdata/frobnitz_with_symlink/charts/alpine/templates/alpine-pod.yaml similarity index 100% rename from pkg/chart/loader/testdata/frobnitz_with_symlink/charts/alpine/templates/alpine-pod.yaml rename to pkg/chart/v2/loader/testdata/frobnitz_with_symlink/charts/alpine/templates/alpine-pod.yaml diff --git a/pkg/chart/loader/testdata/frobnitz_with_symlink/charts/alpine/values.yaml b/pkg/chart/v2/loader/testdata/frobnitz_with_symlink/charts/alpine/values.yaml similarity index 100% rename from pkg/chart/loader/testdata/frobnitz_with_symlink/charts/alpine/values.yaml rename to pkg/chart/v2/loader/testdata/frobnitz_with_symlink/charts/alpine/values.yaml diff --git a/pkg/chart/loader/testdata/frobnitz_with_symlink/charts/mariner-4.3.2.tgz b/pkg/chart/v2/loader/testdata/frobnitz_with_symlink/charts/mariner-4.3.2.tgz similarity index 100% rename from pkg/chart/loader/testdata/frobnitz_with_symlink/charts/mariner-4.3.2.tgz rename to pkg/chart/v2/loader/testdata/frobnitz_with_symlink/charts/mariner-4.3.2.tgz diff --git a/pkg/chart/loader/testdata/frobnitz_with_symlink/docs/README.md b/pkg/chart/v2/loader/testdata/frobnitz_with_symlink/docs/README.md similarity index 100% rename from pkg/chart/loader/testdata/frobnitz_with_symlink/docs/README.md rename to pkg/chart/v2/loader/testdata/frobnitz_with_symlink/docs/README.md diff --git a/pkg/chart/loader/testdata/frobnitz_with_symlink/icon.svg b/pkg/chart/v2/loader/testdata/frobnitz_with_symlink/icon.svg similarity index 100% rename from pkg/chart/loader/testdata/frobnitz_with_symlink/icon.svg rename to pkg/chart/v2/loader/testdata/frobnitz_with_symlink/icon.svg diff --git a/pkg/chart/loader/testdata/frobnitz_with_symlink/ignore/me.txt b/pkg/chart/v2/loader/testdata/frobnitz_with_symlink/ignore/me.txt similarity index 100% rename from pkg/chart/loader/testdata/frobnitz_with_symlink/ignore/me.txt rename to pkg/chart/v2/loader/testdata/frobnitz_with_symlink/ignore/me.txt diff --git a/pkg/chart/loader/testdata/frobnitz_with_symlink/templates/template.tpl b/pkg/chart/v2/loader/testdata/frobnitz_with_symlink/templates/template.tpl similarity index 100% rename from pkg/chart/loader/testdata/frobnitz_with_symlink/templates/template.tpl rename to pkg/chart/v2/loader/testdata/frobnitz_with_symlink/templates/template.tpl diff --git a/pkg/chart/loader/testdata/frobnitz_with_symlink/values.yaml b/pkg/chart/v2/loader/testdata/frobnitz_with_symlink/values.yaml similarity index 100% rename from pkg/chart/loader/testdata/frobnitz_with_symlink/values.yaml rename to pkg/chart/v2/loader/testdata/frobnitz_with_symlink/values.yaml diff --git a/pkg/chart/loader/testdata/genfrob.sh b/pkg/chart/v2/loader/testdata/genfrob.sh similarity index 100% rename from pkg/chart/loader/testdata/genfrob.sh rename to pkg/chart/v2/loader/testdata/genfrob.sh diff --git a/pkg/chart/loader/testdata/mariner/Chart.yaml b/pkg/chart/v2/loader/testdata/mariner/Chart.yaml similarity index 100% rename from pkg/chart/loader/testdata/mariner/Chart.yaml rename to pkg/chart/v2/loader/testdata/mariner/Chart.yaml diff --git a/pkg/chart/loader/testdata/mariner/charts/albatross-0.1.0.tgz b/pkg/chart/v2/loader/testdata/mariner/charts/albatross-0.1.0.tgz similarity index 100% rename from pkg/chart/loader/testdata/mariner/charts/albatross-0.1.0.tgz rename to pkg/chart/v2/loader/testdata/mariner/charts/albatross-0.1.0.tgz diff --git a/pkg/chart/loader/testdata/mariner/templates/placeholder.tpl b/pkg/chart/v2/loader/testdata/mariner/templates/placeholder.tpl similarity index 100% rename from pkg/chart/loader/testdata/mariner/templates/placeholder.tpl rename to pkg/chart/v2/loader/testdata/mariner/templates/placeholder.tpl diff --git a/pkg/chart/loader/testdata/mariner/values.yaml b/pkg/chart/v2/loader/testdata/mariner/values.yaml similarity index 100% rename from pkg/chart/loader/testdata/mariner/values.yaml rename to pkg/chart/v2/loader/testdata/mariner/values.yaml diff --git a/pkg/chart/metadata.go b/pkg/chart/v2/metadata.go similarity index 99% rename from pkg/chart/metadata.go rename to pkg/chart/v2/metadata.go index a08a97cd1..d213a3491 100644 --- a/pkg/chart/metadata.go +++ b/pkg/chart/v2/metadata.go @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package chart +package v2 import ( "path/filepath" diff --git a/pkg/chart/metadata_test.go b/pkg/chart/v2/metadata_test.go similarity index 99% rename from pkg/chart/metadata_test.go rename to pkg/chart/v2/metadata_test.go index 62aea7261..7892f0209 100644 --- a/pkg/chart/metadata_test.go +++ b/pkg/chart/v2/metadata_test.go @@ -13,7 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ -package chart +package v2 import ( "testing" diff --git a/pkg/chartutil/capabilities.go b/pkg/chart/v2/util/capabilities.go similarity index 98% rename from pkg/chartutil/capabilities.go rename to pkg/chart/v2/util/capabilities.go index 48fab0ea4..d4b420b2f 100644 --- a/pkg/chartutil/capabilities.go +++ b/pkg/chart/v2/util/capabilities.go @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package chartutil +package util import ( "fmt" @@ -25,7 +25,7 @@ import ( apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" - helmversion "helm.sh/helm/v3/internal/version" + helmversion "helm.sh/helm/v4/internal/version" ) var ( diff --git a/pkg/chartutil/capabilities_test.go b/pkg/chart/v2/util/capabilities_test.go similarity index 94% rename from pkg/chartutil/capabilities_test.go rename to pkg/chart/v2/util/capabilities_test.go index ecdf68289..aa9be9db8 100644 --- a/pkg/chartutil/capabilities_test.go +++ b/pkg/chart/v2/util/capabilities_test.go @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package chartutil +package util import ( "testing" @@ -62,8 +62,8 @@ func TestDefaultCapabilities(t *testing.T) { func TestDefaultCapabilitiesHelmVersion(t *testing.T) { hv := DefaultCapabilities.HelmVersion - if hv.Version != "v3.16" { - t.Errorf("Expected default HelmVersion to be v3.16, got %q", hv.Version) + if hv.Version != "v4.0" { + t.Errorf("Expected default HelmVersion to be v4.0, got %q", hv.Version) } } diff --git a/pkg/chartutil/chartfile.go b/pkg/chart/v2/util/chartfile.go similarity index 87% rename from pkg/chartutil/chartfile.go rename to pkg/chart/v2/util/chartfile.go index 392ef3572..6748c6a91 100644 --- a/pkg/chartutil/chartfile.go +++ b/pkg/chart/v2/util/chartfile.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package chartutil +package util import ( "errors" @@ -25,7 +25,7 @@ import ( "sigs.k8s.io/yaml" - "helm.sh/helm/v3/pkg/chart" + chart "helm.sh/helm/v4/pkg/chart/v2" ) // LoadChartfile loads a Chart.yaml file into a *chart.Metadata. @@ -39,6 +39,17 @@ func LoadChartfile(filename string) (*chart.Metadata, error) { return y, err } +// StrictLoadChartFile loads a Chart.yaml into a *chart.Metadata using a strict unmarshaling +func StrictLoadChartfile(filename string) (*chart.Metadata, error) { + b, err := os.ReadFile(filename) + if err != nil { + return nil, err + } + y := new(chart.Metadata) + err = yaml.UnmarshalStrict(b, y) + return y, err +} + // SaveChartfile saves the given metadata as a Chart.yaml file at the given path. // // 'filename' should be the complete path and filename ('foo/Chart.yaml') diff --git a/pkg/chartutil/chartfile_test.go b/pkg/chart/v2/util/chartfile_test.go similarity index 98% rename from pkg/chartutil/chartfile_test.go rename to pkg/chart/v2/util/chartfile_test.go index ef5c5462a..a2896b235 100644 --- a/pkg/chartutil/chartfile_test.go +++ b/pkg/chart/v2/util/chartfile_test.go @@ -14,12 +14,12 @@ See the License for the specific language governing permissions and limitations under the License. */ -package chartutil +package util import ( "testing" - "helm.sh/helm/v3/pkg/chart" + chart "helm.sh/helm/v4/pkg/chart/v2" ) const testfile = "testdata/chartfiletest.yaml" diff --git a/pkg/chartutil/coalesce.go b/pkg/chart/v2/util/coalesce.go similarity index 96% rename from pkg/chartutil/coalesce.go rename to pkg/chart/v2/util/coalesce.go index c0a0ecf43..11b829a3d 100644 --- a/pkg/chartutil/coalesce.go +++ b/pkg/chart/v2/util/coalesce.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package chartutil +package util import ( "fmt" @@ -22,7 +22,7 @@ import ( "github.com/mitchellh/copystructure" - "helm.sh/helm/v3/pkg/chart" + chart "helm.sh/helm/v4/pkg/chart/v2" ) func concatPrefix(a, b string) string { @@ -236,6 +236,9 @@ func coalesceValues(printf printFn, c *chart.Chart, v map[string]interface{}, pr printf("warning: skipped value for %s.%s: Not a table.", subPrefix, key) } } else { + // If the key is a child chart, coalesce tables with Merge set to true + merge := childChartMergeTrue(c, key, merge) + // Because v has higher precedence than nv, dest values override src // values. coalesceTablesFullKey(printf, dest, src, concatPrefix(subPrefix, key), merge) @@ -248,6 +251,15 @@ func coalesceValues(printf printFn, c *chart.Chart, v map[string]interface{}, pr } } +func childChartMergeTrue(chrt *chart.Chart, key string, merge bool) bool { + for _, subchart := range chrt.Dependencies() { + if subchart.Name() == key { + return true + } + } + return merge +} + // CoalesceTables merges a source map into a destination map. // // dest is considered authoritative. diff --git a/pkg/chartutil/coalesce_test.go b/pkg/chart/v2/util/coalesce_test.go similarity index 94% rename from pkg/chartutil/coalesce_test.go rename to pkg/chart/v2/util/coalesce_test.go index 61b718d97..3d4ee4fa8 100644 --- a/pkg/chartutil/coalesce_test.go +++ b/pkg/chart/v2/util/coalesce_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package chartutil +package util import ( "encoding/json" @@ -23,7 +23,7 @@ import ( "github.com/stretchr/testify/assert" - "helm.sh/helm/v3/pkg/chart" + chart "helm.sh/helm/v4/pkg/chart/v2" ) // ref: http://www.yaml.org/spec/1.2/spec.html#id2803362 @@ -44,18 +44,21 @@ global: boat: true pequod: + boat: null global: name: Stinky harpooner: Tashtego nested: boat: false sail: true + foo2: null ahab: scope: whale boat: null nested: foo: true - bar: null + boat: null + object: null `) func withDeps(c *chart.Chart, deps ...*chart.Chart) *chart.Chart { @@ -82,6 +85,13 @@ func TestCoalesceValues(t *testing.T) { "global": map[string]interface{}{ "nested2": map[string]interface{}{"l0": "moby"}, }, + "pequod": map[string]interface{}{ + "boat": "maybe", + "ahab": map[string]interface{}{ + "boat": "maybe", + "nested": map[string]interface{}{"boat": "maybe"}, + }, + }, }, }, withDeps(&chart.Chart{ @@ -92,19 +102,25 @@ func TestCoalesceValues(t *testing.T) { "global": map[string]interface{}{ "nested2": map[string]interface{}{"l1": "pequod"}, }, + "boat": false, + "ahab": map[string]interface{}{ + "boat": false, + "nested": map[string]interface{}{"boat": false}, + }, }, }, &chart.Chart{ Metadata: &chart.Metadata{Name: "ahab"}, Values: map[string]interface{}{ "global": map[string]interface{}{ - "nested": map[string]interface{}{"foo": "bar"}, + "nested": map[string]interface{}{"foo": "bar", "foo2": "bar2"}, "nested2": map[string]interface{}{"l2": "ahab"}, }, "scope": "ahab", "name": "ahab", "boat": true, - "nested": map[string]interface{}{"foo": false, "bar": true}, + "nested": map[string]interface{}{"foo": false, "boat": true}, + "object": map[string]interface{}{"foo": "bar"}, }, }, ), @@ -155,6 +171,7 @@ func TestCoalesceValues(t *testing.T) { {"{{.pequod.ahab.nested.foo}}", "true"}, {"{{.pequod.ahab.global.name}}", "Ishmael"}, {"{{.pequod.ahab.global.nested.foo}}", "bar"}, + {"{{.pequod.ahab.global.nested.foo2}}", ""}, {"{{.pequod.ahab.global.subject}}", "Queequeg"}, {"{{.pequod.ahab.global.harpooner}}", "Tashtego"}, {"{{.pequod.global.name}}", "Ishmael"}, @@ -200,13 +217,22 @@ func TestCoalesceValues(t *testing.T) { t.Error("Expected nested boat key to be removed, still present") } - subchart := v["pequod"].(map[string]interface{})["ahab"].(map[string]interface{}) + subchart := v["pequod"].(map[string]interface{}) if _, ok := subchart["boat"]; ok { t.Error("Expected subchart boat key to be removed, still present") } - if _, ok := subchart["nested"].(map[string]interface{})["bar"]; ok { - t.Error("Expected subchart nested bar key to be removed, still present") + subsubchart := subchart["ahab"].(map[string]interface{}) + if _, ok := subsubchart["boat"]; ok { + t.Error("Expected sub-subchart ahab boat key to be removed, still present") + } + + if _, ok := subsubchart["nested"].(map[string]interface{})["boat"]; ok { + t.Error("Expected sub-subchart nested boat key to be removed, still present") + } + + if _, ok := subsubchart["object"]; ok { + t.Error("Expected sub-subchart object map to be removed, still present") } // CoalesceValues should not mutate the passed arguments diff --git a/pkg/chartutil/compatible.go b/pkg/chart/v2/util/compatible.go similarity index 98% rename from pkg/chartutil/compatible.go rename to pkg/chart/v2/util/compatible.go index f4656c913..d384d2d45 100644 --- a/pkg/chartutil/compatible.go +++ b/pkg/chart/v2/util/compatible.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package chartutil +package util import "github.com/Masterminds/semver/v3" diff --git a/pkg/chartutil/compatible_test.go b/pkg/chart/v2/util/compatible_test.go similarity index 98% rename from pkg/chartutil/compatible_test.go rename to pkg/chart/v2/util/compatible_test.go index df7be6161..e17d33e35 100644 --- a/pkg/chartutil/compatible_test.go +++ b/pkg/chart/v2/util/compatible_test.go @@ -15,7 +15,7 @@ limitations under the License. */ // Package version represents the current version of the project. -package chartutil +package util import "testing" diff --git a/pkg/chartutil/create.go b/pkg/chart/v2/util/create.go similarity index 85% rename from pkg/chartutil/create.go rename to pkg/chart/v2/util/create.go index a6fab5c4d..93699e0fd 100644 --- a/pkg/chartutil/create.go +++ b/pkg/chart/v2/util/create.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package chartutil +package util import ( "fmt" @@ -26,8 +26,8 @@ import ( "sigs.k8s.io/yaml" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chart/loader" + chart "helm.sh/helm/v4/pkg/chart/v2" + "helm.sh/helm/v4/pkg/chart/v2/loader" ) // chartName is a regular expression for testing the supplied name of a chart. @@ -53,6 +53,8 @@ const ( IgnorefileName = ".helmignore" // IngressFileName is the name of the example ingress file. IngressFileName = TemplatesDir + sep + "ingress.yaml" + // HTTPRouteFileName is the name of the example HTTPRoute file. + HTTPRouteFileName = TemplatesDir + sep + "httproute.yaml" // DeploymentName is the name of the example deployment file. DeploymentName = TemplatesDir + sep + "deployment.yaml" // ServiceName is the name of the example service file. @@ -176,6 +178,44 @@ ingress: # hosts: # - chart-example.local +# -- Expose the service via gateway-api HTTPRoute +# Requires Gateway API resources and suitable controller installed within the cluster +# (see: https://gateway-api.sigs.k8s.io/guides/) +httpRoute: + # HTTPRoute enabled. + enabled: false + # HTTPRoute annotations. + annotations: {} + # Which Gateways this Route is attached to. + parentRefs: + - name: gateway + sectionName: http + # namespace: default + # Hostnames matching HTTP header. + hostnames: + - chart-example.local + # List of rules and filters applied. + rules: + - matches: + - path: + type: PathPrefix + value: /headers + # filters: + # - type: RequestHeaderModifier + # requestHeaderModifier: + # set: + # - name: My-Overwrite-Header + # value: this-is-the-only-value + # remove: + # - User-Agent + # - matches: + # - path: + # type: PathPrefix + # value: /echo + # headers: + # - name: version + # value: v2 + resources: {} # We usually recommend not to specify default resources and to leave this as a conscious # choice for the user. This also increases chances charts run on environments with little @@ -296,6 +336,46 @@ spec: {{- end }} ` +const defaultHTTPRoute = `{{- if .Values.httpRoute.enabled -}} +{{- $fullName := include ".fullname" . -}} +{{- $svcPort := .Values.service.port -}} +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: {{ $fullName }} + labels: + {{- include ".labels" . | nindent 4 }} + {{- with .Values.httpRoute.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + parentRefs: + {{- with .Values.httpRoute.parentRefs }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.httpRoute.hostnames }} + hostnames: + {{- toYaml . | nindent 4 }} + {{- end }} + rules: + {{- range .Values.httpRoute.rules }} + {{- with .matches }} + - matches: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .filters }} + filters: + {{- toYaml . | nindent 8 }} + {{- end }} + backendRefs: + - name: {{ $fullName }} + port: {{ $svcPort }} + weight: 1 + {{- end }} +{{- end }} +` + const defaultDeployment = `apiVersion: apps/v1 kind: Deployment metadata: @@ -326,24 +406,34 @@ spec: {{- toYaml . | nindent 8 }} {{- end }} serviceAccountName: {{ include ".serviceAccountName" . }} + {{- with .Values.podSecurityContext }} securityContext: - {{- toYaml .Values.podSecurityContext | nindent 8 }} + {{- toYaml . | nindent 8 }} + {{- end }} containers: - name: {{ .Chart.Name }} + {{- with .Values.securityContext }} securityContext: - {{- toYaml .Values.securityContext | nindent 12 }} + {{- toYaml . | nindent 12 }} + {{- end }} image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" imagePullPolicy: {{ .Values.image.pullPolicy }} ports: - name: http containerPort: {{ .Values.service.port }} protocol: TCP + {{- with .Values.livenessProbe }} livenessProbe: - {{- toYaml .Values.livenessProbe | nindent 12 }} + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.readinessProbe }} readinessProbe: - {{- toYaml .Values.readinessProbe | nindent 12 }} + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.resources }} resources: - {{- toYaml .Values.resources | nindent 12 }} + {{- toYaml . | nindent 12 }} + {{- end }} {{- with .Values.volumeMounts }} volumeMounts: {{- toYaml . | nindent 12 }} @@ -433,7 +523,20 @@ spec: ` const defaultNotes = `1. Get the application URL by running these commands: -{{- if .Values.ingress.enabled }} +{{- if .Values.httpRoute.enabled }} +{{- if .Values.httpRoute.hostnames }} + export APP_HOSTNAME={{ .Values.httpRoute.hostnames | first }} +{{- else }} + export APP_HOSTNAME=$(kubectl get --namespace {{(first .Values.httpRoute.parentRefs).namespace | default .Release.Namespace }} gateway/{{ (first .Values.httpRoute.parentRefs).name }} -o jsonpath="{.spec.listeners[0].hostname}") + {{- end }} +{{- if and .Values.httpRoute.rules (first .Values.httpRoute.rules).matches (first (first .Values.httpRoute.rules).matches).path.value }} + echo "Visit http://$APP_HOSTNAME{{ (first (first .Values.httpRoute.rules).matches).path.value }} to use your application" + + NOTE: Your HTTPRoute depends on the listener configuration of your gateway and your HTTPRoute rules. + The rules can be set for path, method, header and query parameters. + You can check the gateway configuration with 'kubectl get --namespace {{(first .Values.httpRoute.parentRefs).namespace | default .Release.Namespace }} gateway/{{ (first .Values.httpRoute.parentRefs).name }} -o yaml' +{{- end }} +{{- else if .Values.ingress.enabled }} {{- range $host := .Values.ingress.hosts }} {{- range .paths }} http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} @@ -647,6 +750,11 @@ func Create(name, dir string) (string, error) { path: filepath.Join(cdir, IngressFileName), content: transform(defaultIngress, name), }, + { + // httproute.yaml + path: filepath.Join(cdir, HTTPRouteFileName), + content: transform(defaultHTTPRoute, name), + }, { // deployment.yaml path: filepath.Join(cdir, DeploymentName), diff --git a/pkg/chartutil/create_test.go b/pkg/chart/v2/util/create_test.go similarity index 97% rename from pkg/chartutil/create_test.go rename to pkg/chart/v2/util/create_test.go index 1697c4218..086c4e5c8 100644 --- a/pkg/chartutil/create_test.go +++ b/pkg/chart/v2/util/create_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package chartutil +package util import ( "bytes" @@ -22,8 +22,8 @@ import ( "path/filepath" "testing" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chart/loader" + chart "helm.sh/helm/v4/pkg/chart/v2" + "helm.sh/helm/v4/pkg/chart/v2/loader" ) func TestCreate(t *testing.T) { diff --git a/pkg/chartutil/dependencies.go b/pkg/chart/v2/util/dependencies.go similarity index 90% rename from pkg/chartutil/dependencies.go rename to pkg/chart/v2/util/dependencies.go index 36a341927..b7f78010b 100644 --- a/pkg/chartutil/dependencies.go +++ b/pkg/chart/v2/util/dependencies.go @@ -13,31 +13,19 @@ See the License for the specific language governing permissions and limitations under the License. */ -package chartutil +package util import ( - "log" + "log/slog" "strings" "github.com/mitchellh/copystructure" - "helm.sh/helm/v3/pkg/chart" + chart "helm.sh/helm/v4/pkg/chart/v2" ) // ProcessDependencies checks through this chart's dependencies, processing accordingly. -// -// TODO: For Helm v4 this can be combined with or turned into ProcessDependenciesWithMerge func ProcessDependencies(c *chart.Chart, v Values) error { - if err := processDependencyEnabled(c, v, ""); err != nil { - return err - } - return processDependencyImportValues(c, false) -} - -// ProcessDependenciesWithMerge checks through this chart's dependencies, processing accordingly. -// It is similar to ProcessDependencies but it does not remove nil values during -// the import/export handling process. -func ProcessDependenciesWithMerge(c *chart.Chart, v Values) error { if err := processDependencyEnabled(c, v, ""); err != nil { return err } @@ -60,10 +48,10 @@ func processDependencyConditions(reqs []*chart.Dependency, cvals Values, cpath s r.Enabled = bv break } - log.Printf("Warning: Condition path '%s' for chart %s returned non-bool value", c, r.Name) + slog.Warn("returned non-bool value", "path", c, "chart", r.Name) } else if _, ok := err.(ErrNoValue); !ok { // this is a real error - log.Printf("Warning: PathValue returned error %v", err) + slog.Warn("the method PathValue returned error", slog.Any("error", err)) } } } @@ -91,7 +79,7 @@ func processDependencyTags(reqs []*chart.Dependency, cvals Values) { hasFalse = true } } else { - log.Printf("Warning: Tag '%s' for chart %s returned non-bool value", k, r.Name) + slog.Warn("returned non-bool value", "tag", k, "chart", r.Name) } } } @@ -266,7 +254,7 @@ func processImportValues(c *chart.Chart, merge bool) error { // get child table vv, err := cvals.Table(r.Name + "." + child) if err != nil { - log.Printf("Warning: ImportValues missing table from chart %s: %v", r.Name, err) + slog.Warn("ImportValues missing table from chart", "chart", r.Name, slog.Any("error", err)) continue } // create value map from child to be merged into parent @@ -283,7 +271,7 @@ func processImportValues(c *chart.Chart, merge bool) error { }) vm, err := cvals.Table(r.Name + "." + child) if err != nil { - log.Printf("Warning: ImportValues missing table: %v", err) + slog.Warn("ImportValues missing table", slog.Any("error", err)) continue } if merge { diff --git a/pkg/chartutil/dependencies_test.go b/pkg/chart/v2/util/dependencies_test.go similarity index 95% rename from pkg/chartutil/dependencies_test.go rename to pkg/chart/v2/util/dependencies_test.go index 7e035be5f..5bd332990 100644 --- a/pkg/chartutil/dependencies_test.go +++ b/pkg/chart/v2/util/dependencies_test.go @@ -12,17 +12,18 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ -package chartutil +package util import ( + "encoding/json" "os" "path/filepath" "sort" "strconv" "testing" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chart/loader" + chart "helm.sh/helm/v4/pkg/chart/v2" + "helm.sh/helm/v4/pkg/chart/v2/loader" ) func loadChart(t *testing.T, path string) *chart.Chart { @@ -237,6 +238,20 @@ func TestProcessDependencyImportValues(t *testing.T) { 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) } + 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: if pv != vv { t.Errorf("failed to match imported string value %q with expected %q for key %q", pv, vv, kk) @@ -309,6 +324,10 @@ func TestProcessDependencyImportValuesMultiLevelPrecedence(t *testing.T) { if s := strconv.FormatFloat(pv, 'f', -1, 64); 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: if pv != vv { t.Errorf("failed to match imported string value %q with expected %q", pv, vv) diff --git a/pkg/chartutil/doc.go b/pkg/chart/v2/util/doc.go similarity index 92% rename from pkg/chartutil/doc.go rename to pkg/chart/v2/util/doc.go index 49c55ac52..141062074 100644 --- a/pkg/chartutil/doc.go +++ b/pkg/chart/v2/util/doc.go @@ -15,7 +15,7 @@ limitations under the License. */ /* -Package chartutil contains tools for working with charts. +package util contains tools for working with charts. Charts are described in the chart package (pkg/chart). This package provides utilities for serializing and deserializing charts. @@ -42,4 +42,4 @@ into a Chart. When creating charts in memory, use the 'helm.sh/helm/pkg/chart' package directly. */ -package chartutil // import "helm.sh/helm/v3/pkg/chartutil" +package util // import chartutil "helm.sh/helm/v4/pkg/chart/v2/util" diff --git a/pkg/chartutil/errors.go b/pkg/chart/v2/util/errors.go similarity index 98% rename from pkg/chartutil/errors.go rename to pkg/chart/v2/util/errors.go index 0a4046d2e..a175b9758 100644 --- a/pkg/chartutil/errors.go +++ b/pkg/chart/v2/util/errors.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package chartutil +package util import ( "fmt" diff --git a/pkg/chartutil/errors_test.go b/pkg/chart/v2/util/errors_test.go similarity index 97% rename from pkg/chartutil/errors_test.go rename to pkg/chart/v2/util/errors_test.go index 3f63e3733..b8ae86384 100644 --- a/pkg/chartutil/errors_test.go +++ b/pkg/chart/v2/util/errors_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package chartutil +package util import ( "testing" diff --git a/pkg/chartutil/expand.go b/pkg/chart/v2/util/expand.go similarity index 87% rename from pkg/chartutil/expand.go rename to pkg/chart/v2/util/expand.go index dda6d6364..9d08571ed 100644 --- a/pkg/chartutil/expand.go +++ b/pkg/chart/v2/util/expand.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package chartutil +package util import ( "errors" @@ -26,8 +26,8 @@ import ( securejoin "github.com/cyphar/filepath-securejoin" "sigs.k8s.io/yaml" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chart/loader" + chart "helm.sh/helm/v4/pkg/chart/v2" + "helm.sh/helm/v4/pkg/chart/v2/loader" ) // Expand uncompresses and extracts a chart into the specified directory. @@ -53,6 +53,9 @@ func Expand(dir string, r io.Reader) error { } // Find the base directory + // The directory needs to be cleaned prior to passing to SecureJoin or the location may end up + // being wrong or returning an error. This was introduced in v0.4.0. + dir = filepath.Clean(dir) chartdir, err := securejoin.SecureJoin(dir, chartName) if err != nil { return err diff --git a/pkg/chartutil/expand_test.go b/pkg/chart/v2/util/expand_test.go similarity index 99% rename from pkg/chartutil/expand_test.go rename to pkg/chart/v2/util/expand_test.go index b46ace01f..280995f7e 100644 --- a/pkg/chartutil/expand_test.go +++ b/pkg/chart/v2/util/expand_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package chartutil +package util import ( "os" diff --git a/pkg/chartutil/jsonschema.go b/pkg/chart/v2/util/jsonschema.go similarity index 52% rename from pkg/chartutil/jsonschema.go rename to pkg/chart/v2/util/jsonschema.go index 8d5dcc103..820e5953a 100644 --- a/pkg/chartutil/jsonschema.go +++ b/pkg/chart/v2/util/jsonschema.go @@ -14,31 +14,32 @@ See the License for the specific language governing permissions and limitations under the License. */ -package chartutil +package util import ( "bytes" "errors" "fmt" + "log/slog" "strings" - "github.com/xeipuuv/gojsonschema" - "sigs.k8s.io/yaml" + "github.com/santhosh-tekuri/jsonschema/v6" - "helm.sh/helm/v3/pkg/chart" + chart "helm.sh/helm/v4/pkg/chart/v2" ) // ValidateAgainstSchema checks that values does not violate the structure laid out in schema func ValidateAgainstSchema(chrt *chart.Chart, values map[string]interface{}) error { var sb strings.Builder if chrt.Schema != nil { + slog.Debug("chart name", "chart-name", chrt.Name()) err := ValidateAgainstSingleSchema(values, chrt.Schema) if err != nil { sb.WriteString(fmt.Sprintf("%s:\n", chrt.Name())) sb.WriteString(err.Error()) } } - + slog.Debug("number of dependencies in the chart", "dependencies", len(chrt.Dependencies())) // For each dependency, recursively call this function with the coalesced values for _, subchart := range chrt.Dependencies() { subchartValues := values[subchart.Name()].(map[string]interface{}) @@ -62,32 +63,51 @@ func ValidateAgainstSingleSchema(values Values, schemaJSON []byte) (reterr error } }() - valuesData, err := yaml.Marshal(values) + // This unmarshal function leverages UseNumber() for number precision. The parser + // used for values does this as well. + schema, err := jsonschema.UnmarshalJSON(bytes.NewReader(schemaJSON)) if err != nil { return err } - valuesJSON, err := yaml.YAMLToJSON(valuesData) + slog.Debug("unmarshalled JSON schema", "schema", schemaJSON) + + compiler := jsonschema.NewCompiler() + err = compiler.AddResource("file:///values.schema.json", schema) if err != nil { return err } - if bytes.Equal(valuesJSON, []byte("null")) { - valuesJSON = []byte("{}") - } - schemaLoader := gojsonschema.NewBytesLoader(schemaJSON) - valuesLoader := gojsonschema.NewBytesLoader(valuesJSON) - result, err := gojsonschema.Validate(schemaLoader, valuesLoader) + validator, err := compiler.Compile("file:///values.schema.json") if err != nil { return err } - if !result.Valid() { - var sb strings.Builder - for _, desc := range result.Errors() { - sb.WriteString(fmt.Sprintf("- %s\n", desc)) - } - return errors.New(sb.String()) + err = validator.Validate(values.AsMap()) + if err != nil { + return JSONSchemaValidationError{err} } return nil } + +// Note, JSONSchemaValidationError is used to wrap the error from the underlying +// validation package so that Helm has a clean interface and the validation package +// could be replaced without changing the Helm SDK API. + +// JSONSchemaValidationError is the error returned when there is a schema validation +// error. +type JSONSchemaValidationError struct { + embeddedErr error +} + +// Error prints the error message +func (e JSONSchemaValidationError) Error() string { + errStr := e.embeddedErr.Error() + + // This string prefixes all of our error details. Further up the stack of helm error message + // building more detail is provided to users. This is removed. + errStr = strings.TrimPrefix(errStr, "jsonschema validation failed with 'file:///values.schema.json#'\n") + + // The extra new line is needed for when there are sub-charts. + return errStr + "\n" +} diff --git a/pkg/chartutil/jsonschema_test.go b/pkg/chart/v2/util/jsonschema_test.go similarity index 66% rename from pkg/chartutil/jsonschema_test.go rename to pkg/chart/v2/util/jsonschema_test.go index 7610db337..d781aa4be 100644 --- a/pkg/chartutil/jsonschema_test.go +++ b/pkg/chart/v2/util/jsonschema_test.go @@ -14,13 +14,13 @@ See the License for the specific language governing permissions and limitations under the License. */ -package chartutil +package util import ( "os" "testing" - "helm.sh/helm/v3/pkg/chart" + chart "helm.sh/helm/v4/pkg/chart/v2" ) func TestValidateAgainstSingleSchema(t *testing.T) { @@ -69,7 +69,7 @@ func TestValidateAgainstSingleSchemaNegative(t *testing.T) { } schema, err := os.ReadFile("./testdata/test-values.schema.json") if err != nil { - t.Fatalf("Error reading YAML file: %s", err) + t.Fatalf("Error reading JSON file: %s", err) } var errString string @@ -79,8 +79,8 @@ func TestValidateAgainstSingleSchemaNegative(t *testing.T) { errString = err.Error() } - expectedErrString := `- (root): employmentInfo is required -- age: Must be greater than or equal to 0 + expectedErrString := `- at '': missing property 'employmentInfo' +- at '/age': minimum: got -5, want 0 ` if errString != expectedErrString { t.Errorf("Error string :\n`%s`\ndoes not match expected\n`%s`", errString, expectedErrString) @@ -104,6 +104,21 @@ const subchartSchema = `{ } ` +const subchartSchema2020 = `{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Values", + "type": "object", + "properties": { + "data": { + "type": "array", + "contains": { "type": "string" }, + "unevaluatedItems": { "type": "number" } + } + }, + "required": ["data"] +} +` + func TestValidateAgainstSchema(t *testing.T) { subchartJSON := []byte(subchartSchema) subchart := &chart.Chart{ @@ -159,7 +174,72 @@ func TestValidateAgainstSchemaNegative(t *testing.T) { } expectedErrString := `subchart: -- (root): age is required +- at '': missing property 'age' +` + if errString != expectedErrString { + t.Errorf("Error string :\n`%s`\ndoes not match expected\n`%s`", errString, expectedErrString) + } +} + +func TestValidateAgainstSchema2020(t *testing.T) { + subchartJSON := []byte(subchartSchema2020) + subchart := &chart.Chart{ + Metadata: &chart.Metadata{ + Name: "subchart", + }, + Schema: subchartJSON, + } + chrt := &chart.Chart{ + Metadata: &chart.Metadata{ + Name: "chrt", + }, + } + chrt.AddDependency(subchart) + + vals := map[string]interface{}{ + "name": "John", + "subchart": map[string]interface{}{ + "data": []any{"hello", 12}, + }, + } + + if err := ValidateAgainstSchema(chrt, vals); err != nil { + t.Errorf("Error validating Values against Schema: %s", err) + } +} + +func TestValidateAgainstSchema2020Negative(t *testing.T) { + subchartJSON := []byte(subchartSchema2020) + subchart := &chart.Chart{ + Metadata: &chart.Metadata{ + Name: "subchart", + }, + Schema: subchartJSON, + } + chrt := &chart.Chart{ + Metadata: &chart.Metadata{ + Name: "chrt", + }, + } + chrt.AddDependency(subchart) + + vals := map[string]interface{}{ + "name": "John", + "subchart": map[string]interface{}{ + "data": []any{12}, + }, + } + + var errString string + if err := ValidateAgainstSchema(chrt, vals); err == nil { + t.Fatalf("Expected an error, but got nil") + } else { + errString = err.Error() + } + + expectedErrString := `subchart: +- at '/data': no items match contains schema + - at '/data/0': got number, want string ` if errString != expectedErrString { t.Errorf("Error string :\n`%s`\ndoes not match expected\n`%s`", errString, expectedErrString) diff --git a/pkg/chartutil/save.go b/pkg/chart/v2/util/save.go similarity index 98% rename from pkg/chartutil/save.go rename to pkg/chart/v2/util/save.go index b2170ac8d..4b8886aca 100644 --- a/pkg/chartutil/save.go +++ b/pkg/chart/v2/util/save.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package chartutil +package util import ( "archive/tar" @@ -29,7 +29,7 @@ import ( "sigs.k8s.io/yaml" - "helm.sh/helm/v3/pkg/chart" + chart "helm.sh/helm/v4/pkg/chart/v2" ) var headerBytes = []byte("+aHR0cHM6Ly95b3V0dS5iZS96OVV6MWljandyTQo=") @@ -131,8 +131,8 @@ func Save(c *chart.Chart, outDir string) (string, error) { // Wrap in gzip writer zipper := gzip.NewWriter(f) - zipper.Header.Extra = headerBytes - zipper.Header.Comment = "Helm" + zipper.Extra = headerBytes + zipper.Comment = "Helm" // Wrap in tar writer twriter := tar.NewWriter(zipper) diff --git a/pkg/chartutil/save_test.go b/pkg/chart/v2/util/save_test.go similarity index 98% rename from pkg/chartutil/save_test.go rename to pkg/chart/v2/util/save_test.go index 98b4e641b..ff96331b5 100644 --- a/pkg/chartutil/save_test.go +++ b/pkg/chart/v2/util/save_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package chartutil +package util import ( "archive/tar" @@ -29,8 +29,8 @@ import ( "testing" "time" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chart/loader" + chart "helm.sh/helm/v4/pkg/chart/v2" + "helm.sh/helm/v4/pkg/chart/v2/loader" ) func TestSave(t *testing.T) { diff --git a/pkg/chartutil/testdata/chartfiletest.yaml b/pkg/chart/v2/util/testdata/chartfiletest.yaml similarity index 100% rename from pkg/chartutil/testdata/chartfiletest.yaml rename to pkg/chart/v2/util/testdata/chartfiletest.yaml diff --git a/pkg/chartutil/testdata/coleridge.yaml b/pkg/chart/v2/util/testdata/coleridge.yaml similarity index 100% rename from pkg/chartutil/testdata/coleridge.yaml rename to pkg/chart/v2/util/testdata/coleridge.yaml diff --git a/pkg/chartutil/testdata/dependent-chart-alias/.helmignore b/pkg/chart/v2/util/testdata/dependent-chart-alias/.helmignore similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-alias/.helmignore rename to pkg/chart/v2/util/testdata/dependent-chart-alias/.helmignore diff --git a/pkg/chartutil/testdata/dependent-chart-alias/Chart.lock b/pkg/chart/v2/util/testdata/dependent-chart-alias/Chart.lock similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-alias/Chart.lock rename to pkg/chart/v2/util/testdata/dependent-chart-alias/Chart.lock diff --git a/pkg/chartutil/testdata/dependent-chart-alias/Chart.yaml b/pkg/chart/v2/util/testdata/dependent-chart-alias/Chart.yaml similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-alias/Chart.yaml rename to pkg/chart/v2/util/testdata/dependent-chart-alias/Chart.yaml diff --git a/pkg/chartutil/testdata/dependent-chart-alias/INSTALL.txt b/pkg/chart/v2/util/testdata/dependent-chart-alias/INSTALL.txt similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-alias/INSTALL.txt rename to pkg/chart/v2/util/testdata/dependent-chart-alias/INSTALL.txt diff --git a/pkg/chartutil/testdata/dependent-chart-alias/LICENSE b/pkg/chart/v2/util/testdata/dependent-chart-alias/LICENSE similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-alias/LICENSE rename to pkg/chart/v2/util/testdata/dependent-chart-alias/LICENSE diff --git a/pkg/chartutil/testdata/dependent-chart-alias/README.md b/pkg/chart/v2/util/testdata/dependent-chart-alias/README.md similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-alias/README.md rename to pkg/chart/v2/util/testdata/dependent-chart-alias/README.md diff --git a/pkg/chartutil/testdata/dependent-chart-alias/charts/_ignore_me b/pkg/chart/v2/util/testdata/dependent-chart-alias/charts/_ignore_me similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-alias/charts/_ignore_me rename to pkg/chart/v2/util/testdata/dependent-chart-alias/charts/_ignore_me diff --git a/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/Chart.yaml b/pkg/chart/v2/util/testdata/dependent-chart-alias/charts/alpine/Chart.yaml similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/Chart.yaml rename to pkg/chart/v2/util/testdata/dependent-chart-alias/charts/alpine/Chart.yaml diff --git a/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/README.md b/pkg/chart/v2/util/testdata/dependent-chart-alias/charts/alpine/README.md similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/README.md rename to pkg/chart/v2/util/testdata/dependent-chart-alias/charts/alpine/README.md diff --git a/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/charts/mast1/Chart.yaml b/pkg/chart/v2/util/testdata/dependent-chart-alias/charts/alpine/charts/mast1/Chart.yaml similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/charts/mast1/Chart.yaml rename to pkg/chart/v2/util/testdata/dependent-chart-alias/charts/alpine/charts/mast1/Chart.yaml diff --git a/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/charts/mast1/values.yaml b/pkg/chart/v2/util/testdata/dependent-chart-alias/charts/alpine/charts/mast1/values.yaml similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/charts/mast1/values.yaml rename to pkg/chart/v2/util/testdata/dependent-chart-alias/charts/alpine/charts/mast1/values.yaml diff --git a/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/charts/mast2-0.1.0.tgz b/pkg/chart/v2/util/testdata/dependent-chart-alias/charts/alpine/charts/mast2-0.1.0.tgz similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/charts/mast2-0.1.0.tgz rename to pkg/chart/v2/util/testdata/dependent-chart-alias/charts/alpine/charts/mast2-0.1.0.tgz diff --git a/cmd/helm/testdata/testcharts/signtest/alpine/templates/alpine-pod.yaml b/pkg/chart/v2/util/testdata/dependent-chart-alias/charts/alpine/templates/alpine-pod.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/signtest/alpine/templates/alpine-pod.yaml rename to pkg/chart/v2/util/testdata/dependent-chart-alias/charts/alpine/templates/alpine-pod.yaml diff --git a/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/values.yaml b/pkg/chart/v2/util/testdata/dependent-chart-alias/charts/alpine/values.yaml similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/values.yaml rename to pkg/chart/v2/util/testdata/dependent-chart-alias/charts/alpine/values.yaml diff --git a/pkg/chartutil/testdata/dependent-chart-alias/charts/mariner-4.3.2.tgz b/pkg/chart/v2/util/testdata/dependent-chart-alias/charts/mariner-4.3.2.tgz similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-alias/charts/mariner-4.3.2.tgz rename to pkg/chart/v2/util/testdata/dependent-chart-alias/charts/mariner-4.3.2.tgz diff --git a/pkg/chartutil/testdata/dependent-chart-alias/docs/README.md b/pkg/chart/v2/util/testdata/dependent-chart-alias/docs/README.md similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-alias/docs/README.md rename to pkg/chart/v2/util/testdata/dependent-chart-alias/docs/README.md diff --git a/pkg/chartutil/testdata/dependent-chart-alias/icon.svg b/pkg/chart/v2/util/testdata/dependent-chart-alias/icon.svg similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-alias/icon.svg rename to pkg/chart/v2/util/testdata/dependent-chart-alias/icon.svg diff --git a/pkg/chartutil/testdata/dependent-chart-alias/ignore/me.txt b/pkg/chart/v2/util/testdata/dependent-chart-alias/ignore/me.txt similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-alias/ignore/me.txt rename to pkg/chart/v2/util/testdata/dependent-chart-alias/ignore/me.txt diff --git a/pkg/chartutil/testdata/dependent-chart-alias/templates/template.tpl b/pkg/chart/v2/util/testdata/dependent-chart-alias/templates/template.tpl similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-alias/templates/template.tpl rename to pkg/chart/v2/util/testdata/dependent-chart-alias/templates/template.tpl diff --git a/pkg/chartutil/testdata/dependent-chart-alias/values.yaml b/pkg/chart/v2/util/testdata/dependent-chart-alias/values.yaml similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-alias/values.yaml rename to pkg/chart/v2/util/testdata/dependent-chart-alias/values.yaml diff --git a/pkg/chartutil/testdata/dependent-chart-helmignore/.helmignore b/pkg/chart/v2/util/testdata/dependent-chart-helmignore/.helmignore similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-helmignore/.helmignore rename to pkg/chart/v2/util/testdata/dependent-chart-helmignore/.helmignore diff --git a/pkg/chartutil/testdata/dependent-chart-helmignore/Chart.yaml b/pkg/chart/v2/util/testdata/dependent-chart-helmignore/Chart.yaml similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-helmignore/Chart.yaml rename to pkg/chart/v2/util/testdata/dependent-chart-helmignore/Chart.yaml diff --git a/pkg/chartutil/testdata/dependent-chart-helmignore/charts/.ignore_me b/pkg/chart/v2/util/testdata/dependent-chart-helmignore/charts/.ignore_me similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-helmignore/charts/.ignore_me rename to pkg/chart/v2/util/testdata/dependent-chart-helmignore/charts/.ignore_me diff --git a/pkg/chartutil/testdata/dependent-chart-helmignore/charts/_ignore_me b/pkg/chart/v2/util/testdata/dependent-chart-helmignore/charts/_ignore_me similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-helmignore/charts/_ignore_me rename to pkg/chart/v2/util/testdata/dependent-chart-helmignore/charts/_ignore_me diff --git a/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/Chart.yaml b/pkg/chart/v2/util/testdata/dependent-chart-helmignore/charts/alpine/Chart.yaml similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/Chart.yaml rename to pkg/chart/v2/util/testdata/dependent-chart-helmignore/charts/alpine/Chart.yaml diff --git a/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/README.md b/pkg/chart/v2/util/testdata/dependent-chart-helmignore/charts/alpine/README.md similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/README.md rename to pkg/chart/v2/util/testdata/dependent-chart-helmignore/charts/alpine/README.md diff --git a/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/charts/mast1/Chart.yaml b/pkg/chart/v2/util/testdata/dependent-chart-helmignore/charts/alpine/charts/mast1/Chart.yaml similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/charts/mast1/Chart.yaml rename to pkg/chart/v2/util/testdata/dependent-chart-helmignore/charts/alpine/charts/mast1/Chart.yaml diff --git a/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/charts/mast1/values.yaml b/pkg/chart/v2/util/testdata/dependent-chart-helmignore/charts/alpine/charts/mast1/values.yaml similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/charts/mast1/values.yaml rename to pkg/chart/v2/util/testdata/dependent-chart-helmignore/charts/alpine/charts/mast1/values.yaml diff --git a/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/charts/mast2-0.1.0.tgz b/pkg/chart/v2/util/testdata/dependent-chart-helmignore/charts/alpine/charts/mast2-0.1.0.tgz similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/charts/mast2-0.1.0.tgz rename to pkg/chart/v2/util/testdata/dependent-chart-helmignore/charts/alpine/charts/mast2-0.1.0.tgz diff --git a/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/templates/alpine-pod.yaml b/pkg/chart/v2/util/testdata/dependent-chart-helmignore/charts/alpine/templates/alpine-pod.yaml similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/templates/alpine-pod.yaml rename to pkg/chart/v2/util/testdata/dependent-chart-helmignore/charts/alpine/templates/alpine-pod.yaml diff --git a/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/values.yaml b/pkg/chart/v2/util/testdata/dependent-chart-helmignore/charts/alpine/values.yaml similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/values.yaml rename to pkg/chart/v2/util/testdata/dependent-chart-helmignore/charts/alpine/values.yaml diff --git a/pkg/chartutil/testdata/dependent-chart-helmignore/templates/template.tpl b/pkg/chart/v2/util/testdata/dependent-chart-helmignore/templates/template.tpl similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-helmignore/templates/template.tpl rename to pkg/chart/v2/util/testdata/dependent-chart-helmignore/templates/template.tpl diff --git a/pkg/chartutil/testdata/dependent-chart-helmignore/values.yaml b/pkg/chart/v2/util/testdata/dependent-chart-helmignore/values.yaml similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-helmignore/values.yaml rename to pkg/chart/v2/util/testdata/dependent-chart-helmignore/values.yaml diff --git a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/.helmignore b/pkg/chart/v2/util/testdata/dependent-chart-no-requirements-yaml/.helmignore similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/.helmignore rename to pkg/chart/v2/util/testdata/dependent-chart-no-requirements-yaml/.helmignore diff --git a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/Chart.yaml b/pkg/chart/v2/util/testdata/dependent-chart-no-requirements-yaml/Chart.yaml similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/Chart.yaml rename to pkg/chart/v2/util/testdata/dependent-chart-no-requirements-yaml/Chart.yaml diff --git a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/INSTALL.txt b/pkg/chart/v2/util/testdata/dependent-chart-no-requirements-yaml/INSTALL.txt similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/INSTALL.txt rename to pkg/chart/v2/util/testdata/dependent-chart-no-requirements-yaml/INSTALL.txt diff --git a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/LICENSE b/pkg/chart/v2/util/testdata/dependent-chart-no-requirements-yaml/LICENSE similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/LICENSE rename to pkg/chart/v2/util/testdata/dependent-chart-no-requirements-yaml/LICENSE diff --git a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/README.md b/pkg/chart/v2/util/testdata/dependent-chart-no-requirements-yaml/README.md similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/README.md rename to pkg/chart/v2/util/testdata/dependent-chart-no-requirements-yaml/README.md diff --git a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/_ignore_me b/pkg/chart/v2/util/testdata/dependent-chart-no-requirements-yaml/charts/_ignore_me similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/_ignore_me rename to pkg/chart/v2/util/testdata/dependent-chart-no-requirements-yaml/charts/_ignore_me diff --git a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/Chart.yaml b/pkg/chart/v2/util/testdata/dependent-chart-no-requirements-yaml/charts/alpine/Chart.yaml similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/Chart.yaml rename to pkg/chart/v2/util/testdata/dependent-chart-no-requirements-yaml/charts/alpine/Chart.yaml diff --git a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/README.md b/pkg/chart/v2/util/testdata/dependent-chart-no-requirements-yaml/charts/alpine/README.md similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/README.md rename to pkg/chart/v2/util/testdata/dependent-chart-no-requirements-yaml/charts/alpine/README.md diff --git a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/charts/mast1/Chart.yaml b/pkg/chart/v2/util/testdata/dependent-chart-no-requirements-yaml/charts/alpine/charts/mast1/Chart.yaml similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/charts/mast1/Chart.yaml rename to pkg/chart/v2/util/testdata/dependent-chart-no-requirements-yaml/charts/alpine/charts/mast1/Chart.yaml diff --git a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/charts/mast1/values.yaml b/pkg/chart/v2/util/testdata/dependent-chart-no-requirements-yaml/charts/alpine/charts/mast1/values.yaml similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/charts/mast1/values.yaml rename to pkg/chart/v2/util/testdata/dependent-chart-no-requirements-yaml/charts/alpine/charts/mast1/values.yaml diff --git a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/charts/mast2-0.1.0.tgz b/pkg/chart/v2/util/testdata/dependent-chart-no-requirements-yaml/charts/alpine/charts/mast2-0.1.0.tgz similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/charts/mast2-0.1.0.tgz rename to pkg/chart/v2/util/testdata/dependent-chart-no-requirements-yaml/charts/alpine/charts/mast2-0.1.0.tgz diff --git a/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/templates/alpine-pod.yaml b/pkg/chart/v2/util/testdata/dependent-chart-no-requirements-yaml/charts/alpine/templates/alpine-pod.yaml similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/templates/alpine-pod.yaml rename to pkg/chart/v2/util/testdata/dependent-chart-no-requirements-yaml/charts/alpine/templates/alpine-pod.yaml diff --git a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/values.yaml b/pkg/chart/v2/util/testdata/dependent-chart-no-requirements-yaml/charts/alpine/values.yaml similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/values.yaml rename to pkg/chart/v2/util/testdata/dependent-chart-no-requirements-yaml/charts/alpine/values.yaml diff --git a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/mariner-4.3.2.tgz b/pkg/chart/v2/util/testdata/dependent-chart-no-requirements-yaml/charts/mariner-4.3.2.tgz similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/mariner-4.3.2.tgz rename to pkg/chart/v2/util/testdata/dependent-chart-no-requirements-yaml/charts/mariner-4.3.2.tgz diff --git a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/docs/README.md b/pkg/chart/v2/util/testdata/dependent-chart-no-requirements-yaml/docs/README.md similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/docs/README.md rename to pkg/chart/v2/util/testdata/dependent-chart-no-requirements-yaml/docs/README.md diff --git a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/icon.svg b/pkg/chart/v2/util/testdata/dependent-chart-no-requirements-yaml/icon.svg similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/icon.svg rename to pkg/chart/v2/util/testdata/dependent-chart-no-requirements-yaml/icon.svg diff --git a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/ignore/me.txt b/pkg/chart/v2/util/testdata/dependent-chart-no-requirements-yaml/ignore/me.txt similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/ignore/me.txt rename to pkg/chart/v2/util/testdata/dependent-chart-no-requirements-yaml/ignore/me.txt diff --git a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/templates/template.tpl b/pkg/chart/v2/util/testdata/dependent-chart-no-requirements-yaml/templates/template.tpl similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/templates/template.tpl rename to pkg/chart/v2/util/testdata/dependent-chart-no-requirements-yaml/templates/template.tpl diff --git a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/values.yaml b/pkg/chart/v2/util/testdata/dependent-chart-no-requirements-yaml/values.yaml similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/values.yaml rename to pkg/chart/v2/util/testdata/dependent-chart-no-requirements-yaml/values.yaml diff --git a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/.helmignore b/pkg/chart/v2/util/testdata/dependent-chart-with-all-in-requirements-yaml/.helmignore similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/.helmignore rename to pkg/chart/v2/util/testdata/dependent-chart-with-all-in-requirements-yaml/.helmignore diff --git a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/Chart.yaml b/pkg/chart/v2/util/testdata/dependent-chart-with-all-in-requirements-yaml/Chart.yaml similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/Chart.yaml rename to pkg/chart/v2/util/testdata/dependent-chart-with-all-in-requirements-yaml/Chart.yaml diff --git a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/INSTALL.txt b/pkg/chart/v2/util/testdata/dependent-chart-with-all-in-requirements-yaml/INSTALL.txt similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/INSTALL.txt rename to pkg/chart/v2/util/testdata/dependent-chart-with-all-in-requirements-yaml/INSTALL.txt diff --git a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/LICENSE b/pkg/chart/v2/util/testdata/dependent-chart-with-all-in-requirements-yaml/LICENSE similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/LICENSE rename to pkg/chart/v2/util/testdata/dependent-chart-with-all-in-requirements-yaml/LICENSE diff --git a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/README.md b/pkg/chart/v2/util/testdata/dependent-chart-with-all-in-requirements-yaml/README.md similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/README.md rename to pkg/chart/v2/util/testdata/dependent-chart-with-all-in-requirements-yaml/README.md diff --git a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/_ignore_me b/pkg/chart/v2/util/testdata/dependent-chart-with-all-in-requirements-yaml/charts/_ignore_me similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/_ignore_me rename to pkg/chart/v2/util/testdata/dependent-chart-with-all-in-requirements-yaml/charts/_ignore_me diff --git a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/Chart.yaml b/pkg/chart/v2/util/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/Chart.yaml similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/Chart.yaml rename to pkg/chart/v2/util/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/Chart.yaml diff --git a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/README.md b/pkg/chart/v2/util/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/README.md similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/README.md rename to pkg/chart/v2/util/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/README.md diff --git a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/charts/mast1/Chart.yaml b/pkg/chart/v2/util/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/charts/mast1/Chart.yaml similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/charts/mast1/Chart.yaml rename to pkg/chart/v2/util/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/charts/mast1/Chart.yaml diff --git a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/charts/mast1/values.yaml b/pkg/chart/v2/util/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/charts/mast1/values.yaml similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/charts/mast1/values.yaml rename to pkg/chart/v2/util/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/charts/mast1/values.yaml diff --git a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/charts/mast2-0.1.0.tgz b/pkg/chart/v2/util/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/charts/mast2-0.1.0.tgz similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/charts/mast2-0.1.0.tgz rename to pkg/chart/v2/util/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/charts/mast2-0.1.0.tgz diff --git a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/templates/alpine-pod.yaml b/pkg/chart/v2/util/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/templates/alpine-pod.yaml similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/templates/alpine-pod.yaml rename to pkg/chart/v2/util/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/templates/alpine-pod.yaml diff --git a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/values.yaml b/pkg/chart/v2/util/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/values.yaml similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/values.yaml rename to pkg/chart/v2/util/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/values.yaml diff --git a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/mariner-4.3.2.tgz b/pkg/chart/v2/util/testdata/dependent-chart-with-all-in-requirements-yaml/charts/mariner-4.3.2.tgz similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/mariner-4.3.2.tgz rename to pkg/chart/v2/util/testdata/dependent-chart-with-all-in-requirements-yaml/charts/mariner-4.3.2.tgz diff --git a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/docs/README.md b/pkg/chart/v2/util/testdata/dependent-chart-with-all-in-requirements-yaml/docs/README.md similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/docs/README.md rename to pkg/chart/v2/util/testdata/dependent-chart-with-all-in-requirements-yaml/docs/README.md diff --git a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/icon.svg b/pkg/chart/v2/util/testdata/dependent-chart-with-all-in-requirements-yaml/icon.svg similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/icon.svg rename to pkg/chart/v2/util/testdata/dependent-chart-with-all-in-requirements-yaml/icon.svg diff --git a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/ignore/me.txt b/pkg/chart/v2/util/testdata/dependent-chart-with-all-in-requirements-yaml/ignore/me.txt similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/ignore/me.txt rename to pkg/chart/v2/util/testdata/dependent-chart-with-all-in-requirements-yaml/ignore/me.txt diff --git a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/templates/template.tpl b/pkg/chart/v2/util/testdata/dependent-chart-with-all-in-requirements-yaml/templates/template.tpl similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/templates/template.tpl rename to pkg/chart/v2/util/testdata/dependent-chart-with-all-in-requirements-yaml/templates/template.tpl diff --git a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/values.yaml b/pkg/chart/v2/util/testdata/dependent-chart-with-all-in-requirements-yaml/values.yaml similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/values.yaml rename to pkg/chart/v2/util/testdata/dependent-chart-with-all-in-requirements-yaml/values.yaml diff --git a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/.helmignore b/pkg/chart/v2/util/testdata/dependent-chart-with-mixed-requirements-yaml/.helmignore similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/.helmignore rename to pkg/chart/v2/util/testdata/dependent-chart-with-mixed-requirements-yaml/.helmignore diff --git a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/Chart.yaml b/pkg/chart/v2/util/testdata/dependent-chart-with-mixed-requirements-yaml/Chart.yaml similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/Chart.yaml rename to pkg/chart/v2/util/testdata/dependent-chart-with-mixed-requirements-yaml/Chart.yaml diff --git a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/INSTALL.txt b/pkg/chart/v2/util/testdata/dependent-chart-with-mixed-requirements-yaml/INSTALL.txt similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/INSTALL.txt rename to pkg/chart/v2/util/testdata/dependent-chart-with-mixed-requirements-yaml/INSTALL.txt diff --git a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/LICENSE b/pkg/chart/v2/util/testdata/dependent-chart-with-mixed-requirements-yaml/LICENSE similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/LICENSE rename to pkg/chart/v2/util/testdata/dependent-chart-with-mixed-requirements-yaml/LICENSE diff --git a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/README.md b/pkg/chart/v2/util/testdata/dependent-chart-with-mixed-requirements-yaml/README.md similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/README.md rename to pkg/chart/v2/util/testdata/dependent-chart-with-mixed-requirements-yaml/README.md diff --git a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/_ignore_me b/pkg/chart/v2/util/testdata/dependent-chart-with-mixed-requirements-yaml/charts/_ignore_me similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/_ignore_me rename to pkg/chart/v2/util/testdata/dependent-chart-with-mixed-requirements-yaml/charts/_ignore_me diff --git a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/Chart.yaml b/pkg/chart/v2/util/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/Chart.yaml similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/Chart.yaml rename to pkg/chart/v2/util/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/Chart.yaml diff --git a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/README.md b/pkg/chart/v2/util/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/README.md similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/README.md rename to pkg/chart/v2/util/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/README.md diff --git a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/charts/mast1/Chart.yaml b/pkg/chart/v2/util/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/charts/mast1/Chart.yaml similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/charts/mast1/Chart.yaml rename to pkg/chart/v2/util/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/charts/mast1/Chart.yaml diff --git a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/charts/mast1/values.yaml b/pkg/chart/v2/util/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/charts/mast1/values.yaml similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/charts/mast1/values.yaml rename to pkg/chart/v2/util/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/charts/mast1/values.yaml diff --git a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/charts/mast2-0.1.0.tgz b/pkg/chart/v2/util/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/charts/mast2-0.1.0.tgz similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/charts/mast2-0.1.0.tgz rename to pkg/chart/v2/util/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/charts/mast2-0.1.0.tgz diff --git a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/templates/alpine-pod.yaml b/pkg/chart/v2/util/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/templates/alpine-pod.yaml similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/templates/alpine-pod.yaml rename to pkg/chart/v2/util/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/templates/alpine-pod.yaml diff --git a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/values.yaml b/pkg/chart/v2/util/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/values.yaml similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/values.yaml rename to pkg/chart/v2/util/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/values.yaml diff --git a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/mariner-4.3.2.tgz b/pkg/chart/v2/util/testdata/dependent-chart-with-mixed-requirements-yaml/charts/mariner-4.3.2.tgz similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/mariner-4.3.2.tgz rename to pkg/chart/v2/util/testdata/dependent-chart-with-mixed-requirements-yaml/charts/mariner-4.3.2.tgz diff --git a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/docs/README.md b/pkg/chart/v2/util/testdata/dependent-chart-with-mixed-requirements-yaml/docs/README.md similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/docs/README.md rename to pkg/chart/v2/util/testdata/dependent-chart-with-mixed-requirements-yaml/docs/README.md diff --git a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/icon.svg b/pkg/chart/v2/util/testdata/dependent-chart-with-mixed-requirements-yaml/icon.svg similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/icon.svg rename to pkg/chart/v2/util/testdata/dependent-chart-with-mixed-requirements-yaml/icon.svg diff --git a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/ignore/me.txt b/pkg/chart/v2/util/testdata/dependent-chart-with-mixed-requirements-yaml/ignore/me.txt similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/ignore/me.txt rename to pkg/chart/v2/util/testdata/dependent-chart-with-mixed-requirements-yaml/ignore/me.txt diff --git a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/templates/template.tpl b/pkg/chart/v2/util/testdata/dependent-chart-with-mixed-requirements-yaml/templates/template.tpl similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/templates/template.tpl rename to pkg/chart/v2/util/testdata/dependent-chart-with-mixed-requirements-yaml/templates/template.tpl diff --git a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/values.yaml b/pkg/chart/v2/util/testdata/dependent-chart-with-mixed-requirements-yaml/values.yaml similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/values.yaml rename to pkg/chart/v2/util/testdata/dependent-chart-with-mixed-requirements-yaml/values.yaml diff --git a/pkg/chartutil/testdata/frobnitz-1.2.3.tgz b/pkg/chart/v2/util/testdata/frobnitz-1.2.3.tgz similarity index 100% rename from pkg/chartutil/testdata/frobnitz-1.2.3.tgz rename to pkg/chart/v2/util/testdata/frobnitz-1.2.3.tgz diff --git a/pkg/chartutil/testdata/frobnitz/.helmignore b/pkg/chart/v2/util/testdata/frobnitz/.helmignore similarity index 100% rename from pkg/chartutil/testdata/frobnitz/.helmignore rename to pkg/chart/v2/util/testdata/frobnitz/.helmignore diff --git a/pkg/chartutil/testdata/frobnitz/Chart.lock b/pkg/chart/v2/util/testdata/frobnitz/Chart.lock similarity index 100% rename from pkg/chartutil/testdata/frobnitz/Chart.lock rename to pkg/chart/v2/util/testdata/frobnitz/Chart.lock diff --git a/pkg/chartutil/testdata/frobnitz/Chart.yaml b/pkg/chart/v2/util/testdata/frobnitz/Chart.yaml similarity index 100% rename from pkg/chartutil/testdata/frobnitz/Chart.yaml rename to pkg/chart/v2/util/testdata/frobnitz/Chart.yaml diff --git a/pkg/chartutil/testdata/frobnitz/INSTALL.txt b/pkg/chart/v2/util/testdata/frobnitz/INSTALL.txt similarity index 100% rename from pkg/chartutil/testdata/frobnitz/INSTALL.txt rename to pkg/chart/v2/util/testdata/frobnitz/INSTALL.txt diff --git a/pkg/chartutil/testdata/frobnitz/LICENSE b/pkg/chart/v2/util/testdata/frobnitz/LICENSE similarity index 100% rename from pkg/chartutil/testdata/frobnitz/LICENSE rename to pkg/chart/v2/util/testdata/frobnitz/LICENSE diff --git a/pkg/chartutil/testdata/frobnitz/README.md b/pkg/chart/v2/util/testdata/frobnitz/README.md similarity index 100% rename from pkg/chartutil/testdata/frobnitz/README.md rename to pkg/chart/v2/util/testdata/frobnitz/README.md diff --git a/pkg/chartutil/testdata/frobnitz/charts/_ignore_me b/pkg/chart/v2/util/testdata/frobnitz/charts/_ignore_me similarity index 100% rename from pkg/chartutil/testdata/frobnitz/charts/_ignore_me rename to pkg/chart/v2/util/testdata/frobnitz/charts/_ignore_me diff --git a/pkg/chartutil/testdata/frobnitz/charts/alpine/Chart.yaml b/pkg/chart/v2/util/testdata/frobnitz/charts/alpine/Chart.yaml similarity index 100% rename from pkg/chartutil/testdata/frobnitz/charts/alpine/Chart.yaml rename to pkg/chart/v2/util/testdata/frobnitz/charts/alpine/Chart.yaml diff --git a/pkg/chartutil/testdata/frobnitz/charts/alpine/README.md b/pkg/chart/v2/util/testdata/frobnitz/charts/alpine/README.md similarity index 100% rename from pkg/chartutil/testdata/frobnitz/charts/alpine/README.md rename to pkg/chart/v2/util/testdata/frobnitz/charts/alpine/README.md diff --git a/pkg/chartutil/testdata/frobnitz/charts/alpine/charts/mast1/Chart.yaml b/pkg/chart/v2/util/testdata/frobnitz/charts/alpine/charts/mast1/Chart.yaml similarity index 100% rename from pkg/chartutil/testdata/frobnitz/charts/alpine/charts/mast1/Chart.yaml rename to pkg/chart/v2/util/testdata/frobnitz/charts/alpine/charts/mast1/Chart.yaml diff --git a/pkg/chartutil/testdata/frobnitz/charts/alpine/charts/mast1/values.yaml b/pkg/chart/v2/util/testdata/frobnitz/charts/alpine/charts/mast1/values.yaml similarity index 100% rename from pkg/chartutil/testdata/frobnitz/charts/alpine/charts/mast1/values.yaml rename to pkg/chart/v2/util/testdata/frobnitz/charts/alpine/charts/mast1/values.yaml diff --git a/pkg/chartutil/testdata/frobnitz/charts/alpine/charts/mast2-0.1.0.tgz b/pkg/chart/v2/util/testdata/frobnitz/charts/alpine/charts/mast2-0.1.0.tgz similarity index 100% rename from pkg/chartutil/testdata/frobnitz/charts/alpine/charts/mast2-0.1.0.tgz rename to pkg/chart/v2/util/testdata/frobnitz/charts/alpine/charts/mast2-0.1.0.tgz diff --git a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/templates/alpine-pod.yaml b/pkg/chart/v2/util/testdata/frobnitz/charts/alpine/templates/alpine-pod.yaml similarity index 100% rename from pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/templates/alpine-pod.yaml rename to pkg/chart/v2/util/testdata/frobnitz/charts/alpine/templates/alpine-pod.yaml diff --git a/pkg/chartutil/testdata/frobnitz/charts/alpine/values.yaml b/pkg/chart/v2/util/testdata/frobnitz/charts/alpine/values.yaml similarity index 100% rename from pkg/chartutil/testdata/frobnitz/charts/alpine/values.yaml rename to pkg/chart/v2/util/testdata/frobnitz/charts/alpine/values.yaml diff --git a/pkg/chartutil/testdata/frobnitz/charts/mariner/Chart.yaml b/pkg/chart/v2/util/testdata/frobnitz/charts/mariner/Chart.yaml similarity index 100% rename from pkg/chartutil/testdata/frobnitz/charts/mariner/Chart.yaml rename to pkg/chart/v2/util/testdata/frobnitz/charts/mariner/Chart.yaml diff --git a/pkg/chartutil/testdata/frobnitz/charts/mariner/charts/albatross/Chart.yaml b/pkg/chart/v2/util/testdata/frobnitz/charts/mariner/charts/albatross/Chart.yaml similarity index 100% rename from pkg/chartutil/testdata/frobnitz/charts/mariner/charts/albatross/Chart.yaml rename to pkg/chart/v2/util/testdata/frobnitz/charts/mariner/charts/albatross/Chart.yaml diff --git a/pkg/chartutil/testdata/frobnitz/charts/mariner/charts/albatross/values.yaml b/pkg/chart/v2/util/testdata/frobnitz/charts/mariner/charts/albatross/values.yaml similarity index 100% rename from pkg/chartutil/testdata/frobnitz/charts/mariner/charts/albatross/values.yaml rename to pkg/chart/v2/util/testdata/frobnitz/charts/mariner/charts/albatross/values.yaml diff --git a/pkg/chartutil/testdata/frobnitz/charts/mariner/templates/placeholder.tpl b/pkg/chart/v2/util/testdata/frobnitz/charts/mariner/templates/placeholder.tpl similarity index 100% rename from pkg/chartutil/testdata/frobnitz/charts/mariner/templates/placeholder.tpl rename to pkg/chart/v2/util/testdata/frobnitz/charts/mariner/templates/placeholder.tpl diff --git a/pkg/chartutil/testdata/frobnitz/charts/mariner/values.yaml b/pkg/chart/v2/util/testdata/frobnitz/charts/mariner/values.yaml similarity index 100% rename from pkg/chartutil/testdata/frobnitz/charts/mariner/values.yaml rename to pkg/chart/v2/util/testdata/frobnitz/charts/mariner/values.yaml diff --git a/pkg/chartutil/testdata/frobnitz/docs/README.md b/pkg/chart/v2/util/testdata/frobnitz/docs/README.md similarity index 100% rename from pkg/chartutil/testdata/frobnitz/docs/README.md rename to pkg/chart/v2/util/testdata/frobnitz/docs/README.md diff --git a/pkg/chartutil/testdata/frobnitz/icon.svg b/pkg/chart/v2/util/testdata/frobnitz/icon.svg similarity index 100% rename from pkg/chartutil/testdata/frobnitz/icon.svg rename to pkg/chart/v2/util/testdata/frobnitz/icon.svg diff --git a/pkg/chartutil/testdata/frobnitz/ignore/me.txt b/pkg/chart/v2/util/testdata/frobnitz/ignore/me.txt similarity index 100% rename from pkg/chartutil/testdata/frobnitz/ignore/me.txt rename to pkg/chart/v2/util/testdata/frobnitz/ignore/me.txt diff --git a/pkg/chartutil/testdata/frobnitz/templates/template.tpl b/pkg/chart/v2/util/testdata/frobnitz/templates/template.tpl similarity index 100% rename from pkg/chartutil/testdata/frobnitz/templates/template.tpl rename to pkg/chart/v2/util/testdata/frobnitz/templates/template.tpl diff --git a/pkg/chartutil/testdata/frobnitz/values.yaml b/pkg/chart/v2/util/testdata/frobnitz/values.yaml similarity index 100% rename from pkg/chartutil/testdata/frobnitz/values.yaml rename to pkg/chart/v2/util/testdata/frobnitz/values.yaml diff --git a/pkg/chartutil/testdata/frobnitz_backslash-1.2.3.tgz b/pkg/chart/v2/util/testdata/frobnitz_backslash-1.2.3.tgz similarity index 100% rename from pkg/chartutil/testdata/frobnitz_backslash-1.2.3.tgz rename to pkg/chart/v2/util/testdata/frobnitz_backslash-1.2.3.tgz diff --git a/pkg/chartutil/testdata/genfrob.sh b/pkg/chart/v2/util/testdata/genfrob.sh similarity index 100% rename from pkg/chartutil/testdata/genfrob.sh rename to pkg/chart/v2/util/testdata/genfrob.sh diff --git a/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/Chart.lock b/pkg/chart/v2/util/testdata/import-values-from-enabled-subchart/parent-chart/Chart.lock similarity index 100% rename from pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/Chart.lock rename to pkg/chart/v2/util/testdata/import-values-from-enabled-subchart/parent-chart/Chart.lock diff --git a/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/Chart.yaml b/pkg/chart/v2/util/testdata/import-values-from-enabled-subchart/parent-chart/Chart.yaml similarity index 100% rename from pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/Chart.yaml rename to pkg/chart/v2/util/testdata/import-values-from-enabled-subchart/parent-chart/Chart.yaml diff --git a/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/charts/dev-v0.1.0.tgz b/pkg/chart/v2/util/testdata/import-values-from-enabled-subchart/parent-chart/charts/dev-v0.1.0.tgz similarity index 100% rename from pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/charts/dev-v0.1.0.tgz rename to pkg/chart/v2/util/testdata/import-values-from-enabled-subchart/parent-chart/charts/dev-v0.1.0.tgz diff --git a/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/charts/prod-v0.1.0.tgz b/pkg/chart/v2/util/testdata/import-values-from-enabled-subchart/parent-chart/charts/prod-v0.1.0.tgz similarity index 100% rename from pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/charts/prod-v0.1.0.tgz rename to pkg/chart/v2/util/testdata/import-values-from-enabled-subchart/parent-chart/charts/prod-v0.1.0.tgz diff --git a/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/envs/dev/Chart.yaml b/pkg/chart/v2/util/testdata/import-values-from-enabled-subchart/parent-chart/envs/dev/Chart.yaml similarity index 100% rename from pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/envs/dev/Chart.yaml rename to pkg/chart/v2/util/testdata/import-values-from-enabled-subchart/parent-chart/envs/dev/Chart.yaml diff --git a/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/envs/dev/values.yaml b/pkg/chart/v2/util/testdata/import-values-from-enabled-subchart/parent-chart/envs/dev/values.yaml similarity index 100% rename from pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/envs/dev/values.yaml rename to pkg/chart/v2/util/testdata/import-values-from-enabled-subchart/parent-chart/envs/dev/values.yaml diff --git a/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/envs/prod/Chart.yaml b/pkg/chart/v2/util/testdata/import-values-from-enabled-subchart/parent-chart/envs/prod/Chart.yaml similarity index 100% rename from pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/envs/prod/Chart.yaml rename to pkg/chart/v2/util/testdata/import-values-from-enabled-subchart/parent-chart/envs/prod/Chart.yaml diff --git a/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/envs/prod/values.yaml b/pkg/chart/v2/util/testdata/import-values-from-enabled-subchart/parent-chart/envs/prod/values.yaml similarity index 100% rename from pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/envs/prod/values.yaml rename to pkg/chart/v2/util/testdata/import-values-from-enabled-subchart/parent-chart/envs/prod/values.yaml diff --git a/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/templates/autoscaler.yaml b/pkg/chart/v2/util/testdata/import-values-from-enabled-subchart/parent-chart/templates/autoscaler.yaml similarity index 100% rename from pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/templates/autoscaler.yaml rename to pkg/chart/v2/util/testdata/import-values-from-enabled-subchart/parent-chart/templates/autoscaler.yaml diff --git a/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/values.yaml b/pkg/chart/v2/util/testdata/import-values-from-enabled-subchart/parent-chart/values.yaml similarity index 100% rename from pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/values.yaml rename to pkg/chart/v2/util/testdata/import-values-from-enabled-subchart/parent-chart/values.yaml diff --git a/pkg/chartutil/testdata/joonix/Chart.yaml b/pkg/chart/v2/util/testdata/joonix/Chart.yaml similarity index 100% rename from pkg/chartutil/testdata/joonix/Chart.yaml rename to pkg/chart/v2/util/testdata/joonix/Chart.yaml diff --git a/pkg/chartutil/testdata/joonix/charts/.gitkeep b/pkg/chart/v2/util/testdata/joonix/charts/.gitkeep similarity index 100% rename from pkg/chartutil/testdata/joonix/charts/.gitkeep rename to pkg/chart/v2/util/testdata/joonix/charts/.gitkeep diff --git a/pkg/chartutil/testdata/subpop/Chart.yaml b/pkg/chart/v2/util/testdata/subpop/Chart.yaml similarity index 100% rename from pkg/chartutil/testdata/subpop/Chart.yaml rename to pkg/chart/v2/util/testdata/subpop/Chart.yaml diff --git a/pkg/chartutil/testdata/subpop/README.md b/pkg/chart/v2/util/testdata/subpop/README.md similarity index 100% rename from pkg/chartutil/testdata/subpop/README.md rename to pkg/chart/v2/util/testdata/subpop/README.md diff --git a/pkg/chartutil/testdata/subpop/charts/subchart1/Chart.yaml b/pkg/chart/v2/util/testdata/subpop/charts/subchart1/Chart.yaml similarity index 100% rename from pkg/chartutil/testdata/subpop/charts/subchart1/Chart.yaml rename to pkg/chart/v2/util/testdata/subpop/charts/subchart1/Chart.yaml diff --git a/cmd/helm/testdata/testcharts/subchart/charts/subchartA/Chart.yaml b/pkg/chart/v2/util/testdata/subpop/charts/subchart1/charts/subchartA/Chart.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/subchart/charts/subchartA/Chart.yaml rename to pkg/chart/v2/util/testdata/subpop/charts/subchart1/charts/subchartA/Chart.yaml diff --git a/cmd/helm/testdata/testcharts/subchart/charts/subchartA/templates/service.yaml b/pkg/chart/v2/util/testdata/subpop/charts/subchart1/charts/subchartA/templates/service.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/subchart/charts/subchartA/templates/service.yaml rename to pkg/chart/v2/util/testdata/subpop/charts/subchart1/charts/subchartA/templates/service.yaml diff --git a/cmd/helm/testdata/testcharts/subchart/charts/subchartA/values.yaml b/pkg/chart/v2/util/testdata/subpop/charts/subchart1/charts/subchartA/values.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/subchart/charts/subchartA/values.yaml rename to pkg/chart/v2/util/testdata/subpop/charts/subchart1/charts/subchartA/values.yaml diff --git a/cmd/helm/testdata/testcharts/subchart/charts/subchartB/Chart.yaml b/pkg/chart/v2/util/testdata/subpop/charts/subchart1/charts/subchartB/Chart.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/subchart/charts/subchartB/Chart.yaml rename to pkg/chart/v2/util/testdata/subpop/charts/subchart1/charts/subchartB/Chart.yaml diff --git a/cmd/helm/testdata/testcharts/subchart/charts/subchartB/templates/service.yaml b/pkg/chart/v2/util/testdata/subpop/charts/subchart1/charts/subchartB/templates/service.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/subchart/charts/subchartB/templates/service.yaml rename to pkg/chart/v2/util/testdata/subpop/charts/subchart1/charts/subchartB/templates/service.yaml diff --git a/pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartB/values.yaml b/pkg/chart/v2/util/testdata/subpop/charts/subchart1/charts/subchartB/values.yaml similarity index 100% rename from pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartB/values.yaml rename to pkg/chart/v2/util/testdata/subpop/charts/subchart1/charts/subchartB/values.yaml diff --git a/pkg/chartutil/testdata/subpop/charts/subchart1/crds/crdA.yaml b/pkg/chart/v2/util/testdata/subpop/charts/subchart1/crds/crdA.yaml similarity index 100% rename from pkg/chartutil/testdata/subpop/charts/subchart1/crds/crdA.yaml rename to pkg/chart/v2/util/testdata/subpop/charts/subchart1/crds/crdA.yaml diff --git a/cmd/helm/testdata/testcharts/subchart/templates/NOTES.txt b/pkg/chart/v2/util/testdata/subpop/charts/subchart1/templates/NOTES.txt similarity index 100% rename from cmd/helm/testdata/testcharts/subchart/templates/NOTES.txt rename to pkg/chart/v2/util/testdata/subpop/charts/subchart1/templates/NOTES.txt diff --git a/cmd/helm/testdata/testcharts/subchart/templates/service.yaml b/pkg/chart/v2/util/testdata/subpop/charts/subchart1/templates/service.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/subchart/templates/service.yaml rename to pkg/chart/v2/util/testdata/subpop/charts/subchart1/templates/service.yaml diff --git a/pkg/chartutil/testdata/subpop/charts/subchart1/templates/subdir/role.yaml b/pkg/chart/v2/util/testdata/subpop/charts/subchart1/templates/subdir/role.yaml similarity index 100% rename from pkg/chartutil/testdata/subpop/charts/subchart1/templates/subdir/role.yaml rename to pkg/chart/v2/util/testdata/subpop/charts/subchart1/templates/subdir/role.yaml diff --git a/cmd/helm/testdata/testcharts/subchart/templates/subdir/rolebinding.yaml b/pkg/chart/v2/util/testdata/subpop/charts/subchart1/templates/subdir/rolebinding.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/subchart/templates/subdir/rolebinding.yaml rename to pkg/chart/v2/util/testdata/subpop/charts/subchart1/templates/subdir/rolebinding.yaml diff --git a/cmd/helm/testdata/testcharts/subchart/templates/subdir/serviceaccount.yaml b/pkg/chart/v2/util/testdata/subpop/charts/subchart1/templates/subdir/serviceaccount.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/subchart/templates/subdir/serviceaccount.yaml rename to pkg/chart/v2/util/testdata/subpop/charts/subchart1/templates/subdir/serviceaccount.yaml diff --git a/pkg/chartutil/testdata/subpop/charts/subchart1/values.yaml b/pkg/chart/v2/util/testdata/subpop/charts/subchart1/values.yaml similarity index 100% rename from pkg/chartutil/testdata/subpop/charts/subchart1/values.yaml rename to pkg/chart/v2/util/testdata/subpop/charts/subchart1/values.yaml diff --git a/pkg/chartutil/testdata/subpop/charts/subchart2/Chart.yaml b/pkg/chart/v2/util/testdata/subpop/charts/subchart2/Chart.yaml similarity index 100% rename from pkg/chartutil/testdata/subpop/charts/subchart2/Chart.yaml rename to pkg/chart/v2/util/testdata/subpop/charts/subchart2/Chart.yaml diff --git a/pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartB/Chart.yaml b/pkg/chart/v2/util/testdata/subpop/charts/subchart2/charts/subchartB/Chart.yaml similarity index 100% rename from pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartB/Chart.yaml rename to pkg/chart/v2/util/testdata/subpop/charts/subchart2/charts/subchartB/Chart.yaml diff --git a/pkg/chartutil/testdata/subpop/charts/subchart2/charts/subchartB/templates/service.yaml b/pkg/chart/v2/util/testdata/subpop/charts/subchart2/charts/subchartB/templates/service.yaml similarity index 100% rename from pkg/chartutil/testdata/subpop/charts/subchart2/charts/subchartB/templates/service.yaml rename to pkg/chart/v2/util/testdata/subpop/charts/subchart2/charts/subchartB/templates/service.yaml diff --git a/pkg/chartutil/testdata/subpop/charts/subchart2/charts/subchartB/values.yaml b/pkg/chart/v2/util/testdata/subpop/charts/subchart2/charts/subchartB/values.yaml similarity index 100% rename from pkg/chartutil/testdata/subpop/charts/subchart2/charts/subchartB/values.yaml rename to pkg/chart/v2/util/testdata/subpop/charts/subchart2/charts/subchartB/values.yaml diff --git a/pkg/chartutil/testdata/subpop/charts/subchart2/charts/subchartC/Chart.yaml b/pkg/chart/v2/util/testdata/subpop/charts/subchart2/charts/subchartC/Chart.yaml similarity index 100% rename from pkg/chartutil/testdata/subpop/charts/subchart2/charts/subchartC/Chart.yaml rename to pkg/chart/v2/util/testdata/subpop/charts/subchart2/charts/subchartC/Chart.yaml diff --git a/pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartA/templates/service.yaml b/pkg/chart/v2/util/testdata/subpop/charts/subchart2/charts/subchartC/templates/service.yaml similarity index 100% rename from pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartA/templates/service.yaml rename to pkg/chart/v2/util/testdata/subpop/charts/subchart2/charts/subchartC/templates/service.yaml diff --git a/pkg/chartutil/testdata/subpop/charts/subchart2/charts/subchartC/values.yaml b/pkg/chart/v2/util/testdata/subpop/charts/subchart2/charts/subchartC/values.yaml similarity index 100% rename from pkg/chartutil/testdata/subpop/charts/subchart2/charts/subchartC/values.yaml rename to pkg/chart/v2/util/testdata/subpop/charts/subchart2/charts/subchartC/values.yaml diff --git a/pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartB/templates/service.yaml b/pkg/chart/v2/util/testdata/subpop/charts/subchart2/templates/service.yaml similarity index 100% rename from pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartB/templates/service.yaml rename to pkg/chart/v2/util/testdata/subpop/charts/subchart2/templates/service.yaml diff --git a/pkg/chartutil/testdata/subpop/charts/subchart2/values.yaml b/pkg/chart/v2/util/testdata/subpop/charts/subchart2/values.yaml similarity index 100% rename from pkg/chartutil/testdata/subpop/charts/subchart2/values.yaml rename to pkg/chart/v2/util/testdata/subpop/charts/subchart2/values.yaml diff --git a/pkg/chartutil/testdata/subpop/noreqs/Chart.yaml b/pkg/chart/v2/util/testdata/subpop/noreqs/Chart.yaml similarity index 100% rename from pkg/chartutil/testdata/subpop/noreqs/Chart.yaml rename to pkg/chart/v2/util/testdata/subpop/noreqs/Chart.yaml diff --git a/pkg/chartutil/testdata/subpop/charts/subchart2/charts/subchartC/templates/service.yaml b/pkg/chart/v2/util/testdata/subpop/noreqs/templates/service.yaml similarity index 100% rename from pkg/chartutil/testdata/subpop/charts/subchart2/charts/subchartC/templates/service.yaml rename to pkg/chart/v2/util/testdata/subpop/noreqs/templates/service.yaml diff --git a/pkg/chartutil/testdata/subpop/noreqs/values.yaml b/pkg/chart/v2/util/testdata/subpop/noreqs/values.yaml similarity index 100% rename from pkg/chartutil/testdata/subpop/noreqs/values.yaml rename to pkg/chart/v2/util/testdata/subpop/noreqs/values.yaml diff --git a/pkg/chartutil/testdata/subpop/values.yaml b/pkg/chart/v2/util/testdata/subpop/values.yaml similarity index 100% rename from pkg/chartutil/testdata/subpop/values.yaml rename to pkg/chart/v2/util/testdata/subpop/values.yaml diff --git a/pkg/chartutil/testdata/test-values-invalid.schema.json b/pkg/chart/v2/util/testdata/test-values-invalid.schema.json similarity index 100% rename from pkg/chartutil/testdata/test-values-invalid.schema.json rename to pkg/chart/v2/util/testdata/test-values-invalid.schema.json diff --git a/pkg/chartutil/testdata/test-values-negative.yaml b/pkg/chart/v2/util/testdata/test-values-negative.yaml similarity index 100% rename from pkg/chartutil/testdata/test-values-negative.yaml rename to pkg/chart/v2/util/testdata/test-values-negative.yaml diff --git a/pkg/chartutil/testdata/test-values.schema.json b/pkg/chart/v2/util/testdata/test-values.schema.json similarity index 100% rename from pkg/chartutil/testdata/test-values.schema.json rename to pkg/chart/v2/util/testdata/test-values.schema.json diff --git a/pkg/chartutil/testdata/test-values.yaml b/pkg/chart/v2/util/testdata/test-values.yaml similarity index 100% rename from pkg/chartutil/testdata/test-values.yaml rename to pkg/chart/v2/util/testdata/test-values.yaml diff --git a/pkg/chartutil/testdata/three-level-dependent-chart/README.md b/pkg/chart/v2/util/testdata/three-level-dependent-chart/README.md similarity index 100% rename from pkg/chartutil/testdata/three-level-dependent-chart/README.md rename to pkg/chart/v2/util/testdata/three-level-dependent-chart/README.md diff --git a/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/Chart.yaml b/pkg/chart/v2/util/testdata/three-level-dependent-chart/umbrella/Chart.yaml similarity index 100% rename from pkg/chartutil/testdata/three-level-dependent-chart/umbrella/Chart.yaml rename to pkg/chart/v2/util/testdata/three-level-dependent-chart/umbrella/Chart.yaml diff --git a/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app1/Chart.yaml b/pkg/chart/v2/util/testdata/three-level-dependent-chart/umbrella/charts/app1/Chart.yaml similarity index 100% rename from pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app1/Chart.yaml rename to pkg/chart/v2/util/testdata/three-level-dependent-chart/umbrella/charts/app1/Chart.yaml diff --git a/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app1/charts/library/Chart.yaml b/pkg/chart/v2/util/testdata/three-level-dependent-chart/umbrella/charts/app1/charts/library/Chart.yaml similarity index 100% rename from pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app1/charts/library/Chart.yaml rename to pkg/chart/v2/util/testdata/three-level-dependent-chart/umbrella/charts/app1/charts/library/Chart.yaml diff --git a/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app1/charts/library/templates/service.yaml b/pkg/chart/v2/util/testdata/three-level-dependent-chart/umbrella/charts/app1/charts/library/templates/service.yaml similarity index 100% rename from pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app1/charts/library/templates/service.yaml rename to pkg/chart/v2/util/testdata/three-level-dependent-chart/umbrella/charts/app1/charts/library/templates/service.yaml diff --git a/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app1/charts/library/values.yaml b/pkg/chart/v2/util/testdata/three-level-dependent-chart/umbrella/charts/app1/charts/library/values.yaml similarity index 100% rename from pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app1/charts/library/values.yaml rename to pkg/chart/v2/util/testdata/three-level-dependent-chart/umbrella/charts/app1/charts/library/values.yaml diff --git a/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app1/templates/service.yaml b/pkg/chart/v2/util/testdata/three-level-dependent-chart/umbrella/charts/app1/templates/service.yaml similarity index 100% rename from pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app1/templates/service.yaml rename to pkg/chart/v2/util/testdata/three-level-dependent-chart/umbrella/charts/app1/templates/service.yaml diff --git a/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app1/values.yaml b/pkg/chart/v2/util/testdata/three-level-dependent-chart/umbrella/charts/app1/values.yaml similarity index 100% rename from pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app1/values.yaml rename to pkg/chart/v2/util/testdata/three-level-dependent-chart/umbrella/charts/app1/values.yaml diff --git a/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app2/Chart.yaml b/pkg/chart/v2/util/testdata/three-level-dependent-chart/umbrella/charts/app2/Chart.yaml similarity index 100% rename from pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app2/Chart.yaml rename to pkg/chart/v2/util/testdata/three-level-dependent-chart/umbrella/charts/app2/Chart.yaml diff --git a/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app2/charts/library/Chart.yaml b/pkg/chart/v2/util/testdata/three-level-dependent-chart/umbrella/charts/app2/charts/library/Chart.yaml similarity index 100% rename from pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app2/charts/library/Chart.yaml rename to pkg/chart/v2/util/testdata/three-level-dependent-chart/umbrella/charts/app2/charts/library/Chart.yaml diff --git a/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app2/charts/library/templates/service.yaml b/pkg/chart/v2/util/testdata/three-level-dependent-chart/umbrella/charts/app2/charts/library/templates/service.yaml similarity index 100% rename from pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app2/charts/library/templates/service.yaml rename to pkg/chart/v2/util/testdata/three-level-dependent-chart/umbrella/charts/app2/charts/library/templates/service.yaml diff --git a/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app2/charts/library/values.yaml b/pkg/chart/v2/util/testdata/three-level-dependent-chart/umbrella/charts/app2/charts/library/values.yaml similarity index 100% rename from pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app2/charts/library/values.yaml rename to pkg/chart/v2/util/testdata/three-level-dependent-chart/umbrella/charts/app2/charts/library/values.yaml diff --git a/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app2/templates/service.yaml b/pkg/chart/v2/util/testdata/three-level-dependent-chart/umbrella/charts/app2/templates/service.yaml similarity index 100% rename from pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app2/templates/service.yaml rename to pkg/chart/v2/util/testdata/three-level-dependent-chart/umbrella/charts/app2/templates/service.yaml diff --git a/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app2/values.yaml b/pkg/chart/v2/util/testdata/three-level-dependent-chart/umbrella/charts/app2/values.yaml similarity index 100% rename from pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app2/values.yaml rename to pkg/chart/v2/util/testdata/three-level-dependent-chart/umbrella/charts/app2/values.yaml diff --git a/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app3/Chart.yaml b/pkg/chart/v2/util/testdata/three-level-dependent-chart/umbrella/charts/app3/Chart.yaml similarity index 100% rename from pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app3/Chart.yaml rename to pkg/chart/v2/util/testdata/three-level-dependent-chart/umbrella/charts/app3/Chart.yaml diff --git a/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app3/charts/library/Chart.yaml b/pkg/chart/v2/util/testdata/three-level-dependent-chart/umbrella/charts/app3/charts/library/Chart.yaml similarity index 100% rename from pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app3/charts/library/Chart.yaml rename to pkg/chart/v2/util/testdata/three-level-dependent-chart/umbrella/charts/app3/charts/library/Chart.yaml diff --git a/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app3/charts/library/templates/service.yaml b/pkg/chart/v2/util/testdata/three-level-dependent-chart/umbrella/charts/app3/charts/library/templates/service.yaml similarity index 100% rename from pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app3/charts/library/templates/service.yaml rename to pkg/chart/v2/util/testdata/three-level-dependent-chart/umbrella/charts/app3/charts/library/templates/service.yaml diff --git a/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app3/charts/library/values.yaml b/pkg/chart/v2/util/testdata/three-level-dependent-chart/umbrella/charts/app3/charts/library/values.yaml similarity index 100% rename from pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app3/charts/library/values.yaml rename to pkg/chart/v2/util/testdata/three-level-dependent-chart/umbrella/charts/app3/charts/library/values.yaml diff --git a/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app3/templates/service.yaml b/pkg/chart/v2/util/testdata/three-level-dependent-chart/umbrella/charts/app3/templates/service.yaml similarity index 100% rename from pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app3/templates/service.yaml rename to pkg/chart/v2/util/testdata/three-level-dependent-chart/umbrella/charts/app3/templates/service.yaml diff --git a/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app3/values.yaml b/pkg/chart/v2/util/testdata/three-level-dependent-chart/umbrella/charts/app3/values.yaml similarity index 100% rename from pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app3/values.yaml rename to pkg/chart/v2/util/testdata/three-level-dependent-chart/umbrella/charts/app3/values.yaml diff --git a/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app4/Chart.yaml b/pkg/chart/v2/util/testdata/three-level-dependent-chart/umbrella/charts/app4/Chart.yaml similarity index 100% rename from pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app4/Chart.yaml rename to pkg/chart/v2/util/testdata/three-level-dependent-chart/umbrella/charts/app4/Chart.yaml diff --git a/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app4/charts/library/Chart.yaml b/pkg/chart/v2/util/testdata/three-level-dependent-chart/umbrella/charts/app4/charts/library/Chart.yaml similarity index 100% rename from pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app4/charts/library/Chart.yaml rename to pkg/chart/v2/util/testdata/three-level-dependent-chart/umbrella/charts/app4/charts/library/Chart.yaml diff --git a/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app4/charts/library/templates/service.yaml b/pkg/chart/v2/util/testdata/three-level-dependent-chart/umbrella/charts/app4/charts/library/templates/service.yaml similarity index 100% rename from pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app4/charts/library/templates/service.yaml rename to pkg/chart/v2/util/testdata/three-level-dependent-chart/umbrella/charts/app4/charts/library/templates/service.yaml diff --git a/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app4/charts/library/values.yaml b/pkg/chart/v2/util/testdata/three-level-dependent-chart/umbrella/charts/app4/charts/library/values.yaml similarity index 100% rename from pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app4/charts/library/values.yaml rename to pkg/chart/v2/util/testdata/three-level-dependent-chart/umbrella/charts/app4/charts/library/values.yaml diff --git a/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app4/templates/service.yaml b/pkg/chart/v2/util/testdata/three-level-dependent-chart/umbrella/charts/app4/templates/service.yaml similarity index 100% rename from pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app4/templates/service.yaml rename to pkg/chart/v2/util/testdata/three-level-dependent-chart/umbrella/charts/app4/templates/service.yaml diff --git a/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app4/values.yaml b/pkg/chart/v2/util/testdata/three-level-dependent-chart/umbrella/charts/app4/values.yaml similarity index 100% rename from pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app4/values.yaml rename to pkg/chart/v2/util/testdata/three-level-dependent-chart/umbrella/charts/app4/values.yaml diff --git a/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/values.yaml b/pkg/chart/v2/util/testdata/three-level-dependent-chart/umbrella/values.yaml similarity index 100% rename from pkg/chartutil/testdata/three-level-dependent-chart/umbrella/values.yaml rename to pkg/chart/v2/util/testdata/three-level-dependent-chart/umbrella/values.yaml diff --git a/pkg/chartutil/validate_name.go b/pkg/chart/v2/util/validate_name.go similarity index 99% rename from pkg/chartutil/validate_name.go rename to pkg/chart/v2/util/validate_name.go index 4f5c2efe0..6595e085d 100644 --- a/pkg/chartutil/validate_name.go +++ b/pkg/chart/v2/util/validate_name.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package chartutil +package util import ( "errors" diff --git a/pkg/chartutil/validate_name_test.go b/pkg/chart/v2/util/validate_name_test.go similarity index 99% rename from pkg/chartutil/validate_name_test.go rename to pkg/chart/v2/util/validate_name_test.go index 09dec9fbb..cfc62a0f7 100644 --- a/pkg/chartutil/validate_name_test.go +++ b/pkg/chart/v2/util/validate_name_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package chartutil +package util import "testing" diff --git a/pkg/chartutil/values.go b/pkg/chart/v2/util/values.go similarity index 97% rename from pkg/chartutil/values.go rename to pkg/chart/v2/util/values.go index a03d31bee..42b1a28e8 100644 --- a/pkg/chartutil/values.go +++ b/pkg/chart/v2/util/values.go @@ -14,9 +14,10 @@ See the License for the specific language governing permissions and limitations under the License. */ -package chartutil +package util import ( + "encoding/json" "errors" "fmt" "io" @@ -25,7 +26,7 @@ import ( "sigs.k8s.io/yaml" - "helm.sh/helm/v3/pkg/chart" + chart "helm.sh/helm/v4/pkg/chart/v2" ) // GlobalKey is the name of the Values key that is used for storing global vars. @@ -105,7 +106,10 @@ func tableLookup(v Values, simple string) (Values, error) { // ReadValues will parse YAML byte data into a Values. func ReadValues(data []byte) (vals Values, err error) { - err = yaml.Unmarshal(data, &vals) + err = yaml.Unmarshal(data, &vals, func(d *json.Decoder) *json.Decoder { + d.UseNumber() + return d + }) if len(vals) == 0 { vals = Values{} } diff --git a/pkg/chartutil/values_test.go b/pkg/chart/v2/util/values_test.go similarity index 99% rename from pkg/chartutil/values_test.go rename to pkg/chart/v2/util/values_test.go index dc8eae3ab..6a5400f78 100644 --- a/pkg/chartutil/values_test.go +++ b/pkg/chart/v2/util/values_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package chartutil +package util import ( "bytes" @@ -22,7 +22,7 @@ import ( "testing" "text/template" - "helm.sh/helm/v3/pkg/chart" + chart "helm.sh/helm/v4/pkg/chart/v2" ) func TestReadValues(t *testing.T) { diff --git a/pkg/cli/environment.go b/pkg/cli/environment.go index 635806344..3f2dc00b2 100644 --- a/pkg/cli/environment.go +++ b/pkg/cli/environment.go @@ -34,9 +34,9 @@ import ( "k8s.io/cli-runtime/pkg/genericclioptions" "k8s.io/client-go/rest" - "helm.sh/helm/v3/internal/version" - "helm.sh/helm/v3/pkg/helmpath" - "helm.sh/helm/v3/pkg/kube" + "helm.sh/helm/v4/internal/version" + "helm.sh/helm/v4/pkg/helmpath" + "helm.sh/helm/v4/pkg/kube" ) // defaultMaxHistory sets the maximum number of releases to 0: unlimited diff --git a/pkg/cli/environment_test.go b/pkg/cli/environment_test.go index f7709045c..8a3b87936 100644 --- a/pkg/cli/environment_test.go +++ b/pkg/cli/environment_test.go @@ -24,7 +24,7 @@ import ( "github.com/spf13/pflag" - "helm.sh/helm/v3/internal/version" + "helm.sh/helm/v4/internal/version" ) func TestSetNamespace(t *testing.T) { diff --git a/pkg/cli/values/options.go b/pkg/cli/values/options.go index 2d29a8f0d..cd65fa885 100644 --- a/pkg/cli/values/options.go +++ b/pkg/cli/values/options.go @@ -17,16 +17,17 @@ limitations under the License. package values import ( + "bytes" + "encoding/json" "fmt" "io" "net/url" "os" "strings" - "sigs.k8s.io/yaml" - - "helm.sh/helm/v3/pkg/getter" - "helm.sh/helm/v3/pkg/strvals" + "helm.sh/helm/v4/pkg/chart/v2/loader" + "helm.sh/helm/v4/pkg/getter" + "helm.sh/helm/v4/pkg/strvals" ) // Options captures the different ways to specify values @@ -46,24 +47,33 @@ func (opts *Options) MergeValues(p getter.Providers) (map[string]interface{}, er // User specified a values files via -f/--values for _, filePath := range opts.ValueFiles { - currentMap := map[string]interface{}{} - - bytes, err := readFile(filePath, p) + raw, err := readFile(filePath, p) if err != nil { return nil, err } - - if err := yaml.Unmarshal(bytes, ¤tMap); err != nil { + currentMap, err := loader.LoadValues(bytes.NewReader(raw)) + if err != nil { return nil, fmt.Errorf("failed to parse %s: %w", filePath, err) } // Merge with the previous map - base = mergeMaps(base, currentMap) + base = loader.MergeMaps(base, currentMap) } // User specified a value via --set-json for _, value := range opts.JSONValues { - if err := strvals.ParseJSON(value, base); err != nil { - return nil, fmt.Errorf("failed parsing --set-json data %s", value) + trimmedValue := strings.TrimSpace(value) + if len(trimmedValue) > 0 && trimmedValue[0] == '{' { + // If value is JSON object format, parse it as map + var jsonMap map[string]interface{} + if err := json.Unmarshal([]byte(trimmedValue), &jsonMap); err != nil { + return nil, fmt.Errorf("failed parsing --set-json data JSON: %s", value) + } + base = loader.MergeMaps(base, jsonMap) + } else { + // Otherwise, parse it as key=value format + if err := strvals.ParseJSON(value, base); err != nil { + return nil, fmt.Errorf("failed parsing --set-json data %s", value) + } } } @@ -105,25 +115,6 @@ func (opts *Options) MergeValues(p getter.Providers) (map[string]interface{}, er return base, nil } -func mergeMaps(a, b map[string]interface{}) map[string]interface{} { - out := make(map[string]interface{}, len(a)) - for k, v := range a { - out[k] = v - } - for k, v := range b { - if v, ok := v.(map[string]interface{}); ok { - if bv, ok := out[k]; ok { - if bv, ok := bv.(map[string]interface{}); ok { - out[k] = mergeMaps(bv, v) - continue - } - } - } - out[k] = v - } - return out -} - // readFile load a file from stdin, the local directory, or a remote file with a url. func readFile(filePath string, p getter.Providers) ([]byte, error) { if strings.TrimSpace(filePath) == "-" { @@ -143,5 +134,5 @@ func readFile(filePath string, p getter.Providers) ([]byte, error) { if err != nil { return nil, err } - return data.Bytes(), err + return data.Bytes(), nil } diff --git a/pkg/cli/values/options_test.go b/pkg/cli/values/options_test.go index 54124c0fa..c3bb0af33 100644 --- a/pkg/cli/values/options_test.go +++ b/pkg/cli/values/options_test.go @@ -20,64 +20,9 @@ import ( "reflect" "testing" - "helm.sh/helm/v3/pkg/getter" + "helm.sh/helm/v4/pkg/getter" ) -func TestMergeValues(t *testing.T) { - nestedMap := map[string]interface{}{ - "foo": "bar", - "baz": map[string]string{ - "cool": "stuff", - }, - } - anotherNestedMap := map[string]interface{}{ - "foo": "bar", - "baz": map[string]string{ - "cool": "things", - "awesome": "stuff", - }, - } - flatMap := map[string]interface{}{ - "foo": "bar", - "baz": "stuff", - } - anotherFlatMap := map[string]interface{}{ - "testing": "fun", - } - - testMap := mergeMaps(flatMap, nestedMap) - equal := reflect.DeepEqual(testMap, nestedMap) - if !equal { - t.Errorf("Expected a nested map to overwrite a flat value. Expected: %v, got %v", nestedMap, testMap) - } - - testMap = mergeMaps(nestedMap, flatMap) - equal = reflect.DeepEqual(testMap, flatMap) - if !equal { - t.Errorf("Expected a flat value to overwrite a map. Expected: %v, got %v", flatMap, testMap) - } - - testMap = mergeMaps(nestedMap, anotherNestedMap) - equal = reflect.DeepEqual(testMap, anotherNestedMap) - if !equal { - t.Errorf("Expected a nested map to overwrite another nested map. Expected: %v, got %v", anotherNestedMap, testMap) - } - - testMap = mergeMaps(anotherFlatMap, anotherNestedMap) - expectedMap := map[string]interface{}{ - "testing": "fun", - "foo": "bar", - "baz": map[string]string{ - "cool": "things", - "awesome": "stuff", - }, - } - equal = reflect.DeepEqual(testMap, expectedMap) - if !equal { - t.Errorf("Expected a map with different keys to merge properly with another map. Expected: %v, got %v", expectedMap, testMap) - } -} - func TestReadFile(t *testing.T) { var p getter.Providers filePath := "%a.txt" @@ -86,3 +31,97 @@ func TestReadFile(t *testing.T) { t.Errorf("Expected error when has special strings") } } + +func TestMergeValues(t *testing.T) { + tests := []struct { + name string + opts Options + expected map[string]interface{} + wantErr bool + }{ + { + name: "set-json object", + opts: Options{ + JSONValues: []string{`{"foo": {"bar": "baz"}}`}, + }, + expected: map[string]interface{}{ + "foo": map[string]interface{}{ + "bar": "baz", + }, + }, + }, + { + name: "set-json key=value", + opts: Options{ + JSONValues: []string{"foo.bar=[1,2,3]"}, + }, + expected: map[string]interface{}{ + "foo": map[string]interface{}{ + "bar": []interface{}{1.0, 2.0, 3.0}, + }, + }, + }, + { + name: "set regular value", + opts: Options{ + Values: []string{"foo=bar"}, + }, + expected: map[string]interface{}{ + "foo": "bar", + }, + }, + { + name: "set string value", + opts: Options{ + StringValues: []string{"foo=123"}, + }, + expected: map[string]interface{}{ + "foo": "123", + }, + }, + { + name: "set literal value", + opts: Options{ + LiteralValues: []string{"foo=true"}, + }, + expected: map[string]interface{}{ + "foo": "true", + }, + }, + { + name: "multiple options", + opts: Options{ + Values: []string{"a=foo"}, + StringValues: []string{"b=bar"}, + JSONValues: []string{`{"c": "foo1"}`}, + LiteralValues: []string{"d=bar1"}, + }, + expected: map[string]interface{}{ + "a": "foo", + "b": "bar", + "c": "foo1", + "d": "bar1", + }, + }, + { + name: "invalid json", + opts: Options{ + JSONValues: []string{`{invalid`}, + }, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.opts.MergeValues(getter.Providers{}) + if (err != nil) != tt.wantErr { + t.Errorf("MergeValues() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !tt.wantErr && !reflect.DeepEqual(got, tt.expected) { + t.Errorf("MergeValues() = %v, want %v", got, tt.expected) + } + }) + } +} diff --git a/cmd/helm/completion.go b/pkg/cmd/completion.go similarity index 99% rename from cmd/helm/completion.go rename to pkg/cmd/completion.go index 3dc4bb916..6f6dbd25d 100644 --- a/cmd/helm/completion.go +++ b/pkg/cmd/completion.go @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "fmt" @@ -23,7 +23,7 @@ import ( "github.com/spf13/cobra" - "helm.sh/helm/v3/cmd/helm/require" + "helm.sh/helm/v4/pkg/cmd/require" ) const completionDesc = ` diff --git a/cmd/helm/completion_test.go b/pkg/cmd/completion_test.go similarity index 96% rename from cmd/helm/completion_test.go rename to pkg/cmd/completion_test.go index 1143d6445..872da25f3 100644 --- a/cmd/helm/completion_test.go +++ b/pkg/cmd/completion_test.go @@ -14,15 +14,15 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "fmt" "strings" "testing" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/release" + chart "helm.sh/helm/v4/pkg/chart/v2" + release "helm.sh/helm/v4/pkg/release/v1" ) // Check if file completion should be performed according to parameter 'shouldBePerformed' diff --git a/cmd/helm/create.go b/pkg/cmd/create.go similarity index 95% rename from cmd/helm/create.go rename to pkg/cmd/create.go index 576d8a70f..435c8ca82 100644 --- a/cmd/helm/create.go +++ b/pkg/cmd/create.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "fmt" @@ -23,10 +23,10 @@ import ( "github.com/spf13/cobra" - "helm.sh/helm/v3/cmd/helm/require" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/helmpath" + chart "helm.sh/helm/v4/pkg/chart/v2" + chartutil "helm.sh/helm/v4/pkg/chart/v2/util" + "helm.sh/helm/v4/pkg/cmd/require" + "helm.sh/helm/v4/pkg/helmpath" ) const createDesc = ` diff --git a/cmd/helm/create_test.go b/pkg/cmd/create_test.go similarity index 94% rename from cmd/helm/create_test.go rename to pkg/cmd/create_test.go index 1a22d058f..26eabbfc3 100644 --- a/cmd/helm/create_test.go +++ b/pkg/cmd/create_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "fmt" @@ -22,11 +22,11 @@ import ( "path/filepath" "testing" - "helm.sh/helm/v3/internal/test/ensure" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chart/loader" - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/helmpath" + "helm.sh/helm/v4/internal/test/ensure" + chart "helm.sh/helm/v4/pkg/chart/v2" + "helm.sh/helm/v4/pkg/chart/v2/loader" + chartutil "helm.sh/helm/v4/pkg/chart/v2/util" + "helm.sh/helm/v4/pkg/helmpath" ) func TestCreateCmd(t *testing.T) { @@ -105,7 +105,7 @@ func TestCreateStarterCmd(t *testing.T) { t.Errorf("Wrong API version: %q", c.Metadata.APIVersion) } - expectedNumberOfTemplates := 9 + expectedNumberOfTemplates := 10 if l := len(c.Templates); l != expectedNumberOfTemplates { t.Errorf("Expected %d templates, got %d", expectedNumberOfTemplates, l) } @@ -173,7 +173,7 @@ func TestCreateStarterAbsoluteCmd(t *testing.T) { t.Errorf("Wrong API version: %q", c.Metadata.APIVersion) } - expectedNumberOfTemplates := 9 + expectedNumberOfTemplates := 10 if l := len(c.Templates); l != expectedNumberOfTemplates { t.Errorf("Expected %d templates, got %d", expectedNumberOfTemplates, l) } diff --git a/cmd/helm/dependency.go b/pkg/cmd/dependency.go similarity index 73% rename from cmd/helm/dependency.go rename to pkg/cmd/dependency.go index 228c73c80..34bbff6be 100644 --- a/cmd/helm/dependency.go +++ b/pkg/cmd/dependency.go @@ -13,16 +13,17 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "io" "path/filepath" "github.com/spf13/cobra" + "github.com/spf13/pflag" - "helm.sh/helm/v3/cmd/helm/require" - "helm.sh/helm/v3/pkg/action" + "helm.sh/helm/v4/pkg/action" + "helm.sh/helm/v4/pkg/cmd/require" ) const dependencyDesc = ` @@ -93,7 +94,7 @@ func newDependencyCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { cmd.AddCommand(newDependencyListCmd(out)) cmd.AddCommand(newDependencyUpdateCmd(cfg, out)) - cmd.AddCommand(newDependencyBuildCmd(cfg, out)) + cmd.AddCommand(newDependencyBuildCmd(out)) return cmd } @@ -120,3 +121,16 @@ func newDependencyListCmd(out io.Writer) *cobra.Command { f.UintVar(&client.ColumnWidth, "max-col-width", 80, "maximum column width for output table") return cmd } + +func addDependencySubcommandFlags(f *pflag.FlagSet, client *action.Dependency) { + f.BoolVar(&client.Verify, "verify", false, "verify the packages against signatures") + f.StringVar(&client.Keyring, "keyring", defaultKeyring(), "keyring containing public keys") + f.BoolVar(&client.SkipRefresh, "skip-refresh", false, "do not refresh the local repository cache") + f.StringVar(&client.Username, "username", "", "chart repository username where to locate the requested chart") + f.StringVar(&client.Password, "password", "", "chart repository password where to locate the requested chart") + f.StringVar(&client.CertFile, "cert-file", "", "identify HTTPS client using this SSL certificate file") + f.StringVar(&client.KeyFile, "key-file", "", "identify HTTPS client using this SSL key file") + f.BoolVar(&client.InsecureSkipTLSverify, "insecure-skip-tls-verify", false, "skip tls certificate checks for the chart download") + f.BoolVar(&client.PlainHTTP, "plain-http", false, "use insecure HTTP connections for the chart download") + f.StringVar(&client.CaFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle") +} diff --git a/cmd/helm/dependency_build.go b/pkg/cmd/dependency_build.go similarity index 79% rename from cmd/helm/dependency_build.go rename to pkg/cmd/dependency_build.go index 2cf0c6c81..16907facf 100644 --- a/cmd/helm/dependency_build.go +++ b/pkg/cmd/dependency_build.go @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "fmt" @@ -24,10 +24,10 @@ import ( "github.com/spf13/cobra" "k8s.io/client-go/util/homedir" - "helm.sh/helm/v3/cmd/helm/require" - "helm.sh/helm/v3/pkg/action" - "helm.sh/helm/v3/pkg/downloader" - "helm.sh/helm/v3/pkg/getter" + "helm.sh/helm/v4/pkg/action" + "helm.sh/helm/v4/pkg/cmd/require" + "helm.sh/helm/v4/pkg/downloader" + "helm.sh/helm/v4/pkg/getter" ) const dependencyBuildDesc = ` @@ -41,7 +41,7 @@ If no lock file is found, 'helm dependency build' will mirror the behavior of 'helm dependency update'. ` -func newDependencyBuildCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { +func newDependencyBuildCmd(out io.Writer) *cobra.Command { client := action.NewDependency() cmd := &cobra.Command{ @@ -54,13 +54,19 @@ func newDependencyBuildCmd(cfg *action.Configuration, out io.Writer) *cobra.Comm if len(args) > 0 { chartpath = filepath.Clean(args[0]) } + registryClient, err := newRegistryClient(client.CertFile, client.KeyFile, client.CaFile, + client.InsecureSkipTLSverify, client.PlainHTTP, client.Username, client.Password) + if err != nil { + return fmt.Errorf("missing registry client: %w", err) + } + man := &downloader.Manager{ Out: out, ChartPath: chartpath, Keyring: client.Keyring, SkipUpdate: client.SkipRefresh, Getters: getter.All(settings), - RegistryClient: cfg.RegistryClient, + RegistryClient: registryClient, RepositoryConfig: settings.RepositoryConfig, RepositoryCache: settings.RepositoryCache, Debug: settings.Debug, @@ -68,7 +74,7 @@ func newDependencyBuildCmd(cfg *action.Configuration, out io.Writer) *cobra.Comm if client.Verify { man.Verify = downloader.VerifyIfPossible } - err := man.Build() + err = man.Build() if e, ok := err.(downloader.ErrRepoNotFound); ok { return fmt.Errorf("%s. Please add the missing repos via 'helm repo add'", e.Error()) } @@ -77,9 +83,7 @@ func newDependencyBuildCmd(cfg *action.Configuration, out io.Writer) *cobra.Comm } f := cmd.Flags() - f.BoolVar(&client.Verify, "verify", false, "verify the packages against signatures") - f.StringVar(&client.Keyring, "keyring", defaultKeyring(), "keyring containing public keys") - f.BoolVar(&client.SkipRefresh, "skip-refresh", false, "do not refresh the local repository cache") + addDependencySubcommandFlags(f, client) return cmd } diff --git a/cmd/helm/dependency_build_test.go b/pkg/cmd/dependency_build_test.go similarity index 88% rename from cmd/helm/dependency_build_test.go rename to pkg/cmd/dependency_build_test.go index 37e3242c4..a4a89b7a9 100644 --- a/cmd/helm/dependency_build_test.go +++ b/pkg/cmd/dependency_build_test.go @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "fmt" @@ -22,18 +22,18 @@ import ( "strings" "testing" - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/provenance" - "helm.sh/helm/v3/pkg/repo" - "helm.sh/helm/v3/pkg/repo/repotest" + chartutil "helm.sh/helm/v4/pkg/chart/v2/util" + "helm.sh/helm/v4/pkg/provenance" + "helm.sh/helm/v4/pkg/repo" + "helm.sh/helm/v4/pkg/repo/repotest" ) func TestDependencyBuildCmd(t *testing.T) { - srv, err := repotest.NewTempServerWithCleanup(t, "testdata/testcharts/*.tgz") + srv := repotest.NewTempServer( + t, + repotest.WithChartSourceGlob("testdata/testcharts/*.tgz"), + ) defer srv.Stop() - if err != nil { - t.Fatal(err) - } rootDir := srv.Root() srv.LinkIndices() @@ -58,7 +58,7 @@ func TestDependencyBuildCmd(t *testing.T) { createTestingChart(t, rootDir, chartname, srv.URL()) repoFile := filepath.Join(rootDir, "repositories.yaml") - cmd := fmt.Sprintf("dependency build '%s' --repository-config %s --repository-cache %s", filepath.Join(rootDir, chartname), repoFile, rootDir) + cmd := fmt.Sprintf("dependency build '%s' --repository-config %s --repository-cache %s --plain-http", filepath.Join(rootDir, chartname), repoFile, rootDir) _, out, err := executeActionCommand(cmd) // In the first pass, we basically want the same results as an update. @@ -117,7 +117,7 @@ func TestDependencyBuildCmd(t *testing.T) { t.Errorf("mismatched versions. Expected %q, got %q", "0.1.0", v) } - skipRefreshCmd := fmt.Sprintf("dependency build '%s' --skip-refresh --repository-config %s --repository-cache %s", filepath.Join(rootDir, chartname), repoFile, rootDir) + skipRefreshCmd := fmt.Sprintf("dependency build '%s' --skip-refresh --repository-config %s --repository-cache %s --plain-http", filepath.Join(rootDir, chartname), repoFile, rootDir) _, out, err = executeActionCommand(skipRefreshCmd) // In this pass, we check --skip-refresh option becomes effective. @@ -134,7 +134,7 @@ func TestDependencyBuildCmd(t *testing.T) { if err := chartutil.SaveDir(c, dir()); err != nil { t.Fatal(err) } - cmd = fmt.Sprintf("dependency build '%s' --repository-config %s --repository-cache %s --registry-config %s/config.json", + cmd = fmt.Sprintf("dependency build '%s' --repository-config %s --repository-cache %s --registry-config %s/config.json --plain-http", dir(ociChartName), dir("repositories.yaml"), dir(), diff --git a/cmd/helm/dependency_test.go b/pkg/cmd/dependency_test.go similarity index 99% rename from cmd/helm/dependency_test.go rename to pkg/cmd/dependency_test.go index 34c6a25e1..d6bcebf1b 100644 --- a/cmd/helm/dependency_test.go +++ b/pkg/cmd/dependency_test.go @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "runtime" diff --git a/cmd/helm/dependency_update.go b/pkg/cmd/dependency_update.go similarity index 79% rename from cmd/helm/dependency_update.go rename to pkg/cmd/dependency_update.go index cb6e9c0cc..921e5ef49 100644 --- a/cmd/helm/dependency_update.go +++ b/pkg/cmd/dependency_update.go @@ -13,18 +13,19 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( + "fmt" "io" "path/filepath" "github.com/spf13/cobra" - "helm.sh/helm/v3/cmd/helm/require" - "helm.sh/helm/v3/pkg/action" - "helm.sh/helm/v3/pkg/downloader" - "helm.sh/helm/v3/pkg/getter" + "helm.sh/helm/v4/pkg/action" + "helm.sh/helm/v4/pkg/cmd/require" + "helm.sh/helm/v4/pkg/downloader" + "helm.sh/helm/v4/pkg/getter" ) const dependencyUpDesc = ` @@ -43,7 +44,7 @@ in the Chart.yaml file, but (b) at the wrong version. ` // newDependencyUpdateCmd creates a new dependency update command. -func newDependencyUpdateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { +func newDependencyUpdateCmd(_ *action.Configuration, out io.Writer) *cobra.Command { client := action.NewDependency() cmd := &cobra.Command{ @@ -57,13 +58,19 @@ func newDependencyUpdateCmd(cfg *action.Configuration, out io.Writer) *cobra.Com if len(args) > 0 { chartpath = filepath.Clean(args[0]) } + registryClient, err := newRegistryClient(client.CertFile, client.KeyFile, client.CaFile, + client.InsecureSkipTLSverify, client.PlainHTTP, client.Username, client.Password) + if err != nil { + return fmt.Errorf("missing registry client: %w", err) + } + man := &downloader.Manager{ Out: out, ChartPath: chartpath, Keyring: client.Keyring, SkipUpdate: client.SkipRefresh, Getters: getter.All(settings), - RegistryClient: cfg.RegistryClient, + RegistryClient: registryClient, RepositoryConfig: settings.RepositoryConfig, RepositoryCache: settings.RepositoryCache, Debug: settings.Debug, @@ -76,9 +83,7 @@ func newDependencyUpdateCmd(cfg *action.Configuration, out io.Writer) *cobra.Com } f := cmd.Flags() - f.BoolVar(&client.Verify, "verify", false, "verify the packages against signatures") - f.StringVar(&client.Keyring, "keyring", defaultKeyring(), "keyring containing public keys") - f.BoolVar(&client.SkipRefresh, "skip-refresh", false, "do not refresh the local repository cache") + addDependencySubcommandFlags(f, client) return cmd } diff --git a/cmd/helm/dependency_update_test.go b/pkg/cmd/dependency_update_test.go similarity index 87% rename from cmd/helm/dependency_update_test.go rename to pkg/cmd/dependency_update_test.go index 80b2734da..a450d4b22 100644 --- a/cmd/helm/dependency_update_test.go +++ b/pkg/cmd/dependency_update_test.go @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "errors" @@ -24,20 +24,20 @@ import ( "strings" "testing" - "helm.sh/helm/v3/internal/test/ensure" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/helmpath" - "helm.sh/helm/v3/pkg/provenance" - "helm.sh/helm/v3/pkg/repo" - "helm.sh/helm/v3/pkg/repo/repotest" + "helm.sh/helm/v4/internal/test/ensure" + chart "helm.sh/helm/v4/pkg/chart/v2" + chartutil "helm.sh/helm/v4/pkg/chart/v2/util" + "helm.sh/helm/v4/pkg/helmpath" + "helm.sh/helm/v4/pkg/provenance" + "helm.sh/helm/v4/pkg/repo" + "helm.sh/helm/v4/pkg/repo/repotest" ) func TestDependencyUpdateCmd(t *testing.T) { - srv, err := repotest.NewTempServerWithCleanup(t, "testdata/testcharts/*.tgz") - if err != nil { - t.Fatal(err) - } + srv := repotest.NewTempServer( + t, + repotest.WithChartSourceGlob("testdata/testcharts/*.tgz"), + ) defer srv.Stop() t.Logf("Listening on directory %s", srv.Root()) @@ -69,7 +69,7 @@ func TestDependencyUpdateCmd(t *testing.T) { } _, out, err := executeActionCommand( - fmt.Sprintf("dependency update '%s' --repository-config %s --repository-cache %s", dir(chartname), dir("repositories.yaml"), dir()), + fmt.Sprintf("dependency update '%s' --repository-config %s --repository-cache %s --plain-http", dir(chartname), dir("repositories.yaml"), dir()), ) if err != nil { t.Logf("Output: %s", out) @@ -112,7 +112,7 @@ func TestDependencyUpdateCmd(t *testing.T) { t.Fatal(err) } - _, out, err = executeActionCommand(fmt.Sprintf("dependency update '%s' --repository-config %s --repository-cache %s", dir(chartname), dir("repositories.yaml"), dir())) + _, out, err = executeActionCommand(fmt.Sprintf("dependency update '%s' --repository-config %s --repository-cache %s --plain-http", dir(chartname), dir("repositories.yaml"), dir())) if err != nil { t.Logf("Output: %s", out) t.Fatal(err) @@ -133,7 +133,7 @@ func TestDependencyUpdateCmd(t *testing.T) { if err := chartutil.SaveDir(c, dir()); err != nil { t.Fatal(err) } - cmd := fmt.Sprintf("dependency update '%s' --repository-config %s --repository-cache %s --registry-config %s/config.json", + cmd := fmt.Sprintf("dependency update '%s' --repository-config %s --repository-cache %s --registry-config %s/config.json --plain-http", dir(ociChartName), dir("repositories.yaml"), dir(), @@ -153,10 +153,10 @@ func TestDependencyUpdateCmd_DoNotDeleteOldChartsOnError(t *testing.T) { defer resetEnv()() ensure.HelmHome(t) - srv, err := repotest.NewTempServerWithCleanup(t, "testdata/testcharts/*.tgz") - if err != nil { - t.Fatal(err) - } + srv := repotest.NewTempServer( + t, + repotest.WithChartSourceGlob("testdata/testcharts/*.tgz"), + ) defer srv.Stop() t.Logf("Listening on directory %s", srv.Root()) @@ -171,7 +171,7 @@ func TestDependencyUpdateCmd_DoNotDeleteOldChartsOnError(t *testing.T) { } createTestingChart(t, dir(), chartname, srv.URL()) - _, output, err := executeActionCommand(fmt.Sprintf("dependency update %s --repository-config %s --repository-cache %s", dir(chartname), dir("repositories.yaml"), dir())) + _, output, err := executeActionCommand(fmt.Sprintf("dependency update %s --repository-config %s --repository-cache %s --plain-http", dir(chartname), dir("repositories.yaml"), dir())) if err != nil { t.Logf("Output: %s", output) t.Fatal(err) @@ -180,7 +180,7 @@ func TestDependencyUpdateCmd_DoNotDeleteOldChartsOnError(t *testing.T) { // Chart repo is down srv.Stop() - _, output, err = executeActionCommand(fmt.Sprintf("dependency update %s --repository-config %s --repository-cache %s", dir(chartname), dir("repositories.yaml"), dir())) + _, output, err = executeActionCommand(fmt.Sprintf("dependency update %s --repository-config %s --repository-cache %s --plain-http", dir(chartname), dir("repositories.yaml"), dir())) if err == nil { t.Logf("Output: %s", output) t.Fatal("Expected error, got nil") @@ -250,10 +250,10 @@ func TestDependencyUpdateCmd_WithRepoThatWasNotAdded(t *testing.T) { } func setupMockRepoServer(t *testing.T) *repotest.Server { - srv, err := repotest.NewTempServerWithCleanup(t, "testdata/testcharts/*.tgz") - if err != nil { - t.Fatal(err) - } + srv := repotest.NewTempServer( + t, + repotest.WithChartSourceGlob("testdata/testcharts/*.tgz"), + ) t.Logf("Listening on directory %s", srv.Root()) diff --git a/cmd/helm/docs.go b/pkg/cmd/docs.go similarity index 97% rename from cmd/helm/docs.go rename to pkg/cmd/docs.go index 658f18696..7fae60743 100644 --- a/cmd/helm/docs.go +++ b/pkg/cmd/docs.go @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "fmt" @@ -27,7 +27,7 @@ import ( "golang.org/x/text/cases" "golang.org/x/text/language" - "helm.sh/helm/v3/cmd/helm/require" + "helm.sh/helm/v4/pkg/cmd/require" ) const docsDesc = ` @@ -85,7 +85,7 @@ func (o *docsOptions) run(_ io.Writer) error { hdrFunc := func(filename string) string { base := filepath.Base(filename) name := strings.TrimSuffix(base, path.Ext(base)) - title := cases.Title(language.Und, cases.NoLower).String(strings.Replace(name, "_", " ", -1)) + title := cases.Title(language.Und, cases.NoLower).String(strings.ReplaceAll(name, "_", " ")) return fmt.Sprintf("---\ntitle: \"%s\"\n---\n\n", title) } diff --git a/cmd/helm/docs_test.go b/pkg/cmd/docs_test.go similarity index 98% rename from cmd/helm/docs_test.go rename to pkg/cmd/docs_test.go index fe5864d5e..4a8a8c687 100644 --- a/cmd/helm/docs_test.go +++ b/pkg/cmd/docs_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "testing" diff --git a/cmd/helm/env.go b/pkg/cmd/env.go similarity index 97% rename from cmd/helm/env.go rename to pkg/cmd/env.go index d9c7f4565..8da201031 100644 --- a/cmd/helm/env.go +++ b/pkg/cmd/env.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "fmt" @@ -23,7 +23,7 @@ import ( "github.com/spf13/cobra" - "helm.sh/helm/v3/cmd/helm/require" + "helm.sh/helm/v4/pkg/cmd/require" ) var envHelp = ` diff --git a/cmd/helm/env_test.go b/pkg/cmd/env_test.go similarity index 98% rename from cmd/helm/env_test.go rename to pkg/cmd/env_test.go index 01ef25933..c5d7af1b7 100644 --- a/cmd/helm/env_test.go +++ b/pkg/cmd/env_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "testing" diff --git a/cmd/helm/flags.go b/pkg/cmd/flags.go similarity index 80% rename from cmd/helm/flags.go rename to pkg/cmd/flags.go index 62e9f90fa..74c3c8352 100644 --- a/cmd/helm/flags.go +++ b/pkg/cmd/flags.go @@ -14,12 +14,13 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "flag" "fmt" "log" + "log/slog" "path/filepath" "sort" "strings" @@ -28,12 +29,13 @@ import ( "github.com/spf13/pflag" "k8s.io/klog/v2" - "helm.sh/helm/v3/pkg/action" - "helm.sh/helm/v3/pkg/cli/output" - "helm.sh/helm/v3/pkg/cli/values" - "helm.sh/helm/v3/pkg/helmpath" - "helm.sh/helm/v3/pkg/postrender" - "helm.sh/helm/v3/pkg/repo" + "helm.sh/helm/v4/pkg/action" + "helm.sh/helm/v4/pkg/cli/output" + "helm.sh/helm/v4/pkg/cli/values" + "helm.sh/helm/v4/pkg/helmpath" + "helm.sh/helm/v4/pkg/kube" + "helm.sh/helm/v4/pkg/postrender" + "helm.sh/helm/v4/pkg/repo" ) const ( @@ -47,10 +49,56 @@ func addValueOptionsFlags(f *pflag.FlagSet, v *values.Options) { f.StringArrayVar(&v.Values, "set", []string{}, "set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)") f.StringArrayVar(&v.StringValues, "set-string", []string{}, "set STRING values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)") f.StringArrayVar(&v.FileValues, "set-file", []string{}, "set values from respective files specified via the command line (can specify multiple or separate values with commas: key1=path1,key2=path2)") - f.StringArrayVar(&v.JSONValues, "set-json", []string{}, "set JSON values on the command line (can specify multiple or separate values with commas: key1=jsonval1,key2=jsonval2)") + f.StringArrayVar(&v.JSONValues, "set-json", []string{}, "set JSON values on the command line (can specify multiple or separate values with commas: key1=jsonval1,key2=jsonval2 or using json format: {\"key1\": jsonval1, \"key2\": \"jsonval2\"})") f.StringArrayVar(&v.LiteralValues, "set-literal", []string{}, "set a literal STRING value on the command line") } +func AddWaitFlag(cmd *cobra.Command, wait *kube.WaitStrategy) { + cmd.Flags().Var( + newWaitValue(kube.HookOnlyStrategy, wait), + "wait", + "if specified, will wait until all resources are in the expected state before marking the operation as successful. It will wait for as long as --timeout. Valid inputs are 'watcher' and 'legacy'", + ) + // Sets the strategy to use the watcher strategy if `--wait` is used without an argument + cmd.Flags().Lookup("wait").NoOptDefVal = string(kube.StatusWatcherStrategy) +} + +type waitValue kube.WaitStrategy + +func newWaitValue(defaultValue kube.WaitStrategy, ws *kube.WaitStrategy) *waitValue { + *ws = defaultValue + return (*waitValue)(ws) +} + +func (ws *waitValue) String() string { + if ws == nil { + return "" + } + return string(*ws) +} + +func (ws *waitValue) Set(s string) error { + switch s { + case string(kube.StatusWatcherStrategy), string(kube.LegacyStrategy): + *ws = waitValue(s) + return nil + case "true": + slog.Warn("--wait=true is deprecated (boolean value) and can be replaced with --wait=watcher") + *ws = waitValue(kube.StatusWatcherStrategy) + return nil + case "false": + slog.Warn("--wait=false is deprecated (boolean value) and can be replaced by omitting the --wait flag") + *ws = waitValue(kube.HookOnlyStrategy) + return nil + default: + return fmt.Errorf("invalid wait input %q. Valid inputs are %s, and %s", s, kube.StatusWatcherStrategy, kube.LegacyStrategy) + } +} + +func (ws *waitValue) Type() string { + return "WaitStrategy" +} + func addChartPathOptionsFlags(f *pflag.FlagSet, c *action.ChartPathOptions) { f.StringVar(&c.Version, "version", "", "specify a version constraint for the chart version to use. This constraint can be a specific tag (e.g. 1.1.1) or it may reference a valid range (e.g. ^2.0.0). If this is not specified, the latest version is used") f.BoolVar(&c.Verify, "verify", false, "verify the package before using it") @@ -143,6 +191,9 @@ func (p *postRendererString) Set(val string) error { if val == "" { return nil } + if p.options.binaryPath != "" { + return fmt.Errorf("cannot specify --post-renderer flag more than once") + } p.options.binaryPath = val pr, err := postrender.NewExec(p.options.binaryPath, p.options.args...) if err != nil { @@ -209,7 +260,7 @@ func compVersionFlag(chartRef string, _ string) ([]string, cobra.ShellCompDirect var versions []string if indexFile, err := repo.LoadIndexFile(path); err == nil { for _, details := range indexFile.Entries[chartName] { - appVersion := details.Metadata.AppVersion + appVersion := details.AppVersion appVersionDesc := "" if appVersion != "" { appVersionDesc = fmt.Sprintf("App: %s, ", appVersion) @@ -220,10 +271,10 @@ func compVersionFlag(chartRef string, _ string) ([]string, cobra.ShellCompDirect createdDesc = fmt.Sprintf("Created: %s ", created) } deprecated := "" - if details.Metadata.Deprecated { + if details.Deprecated { deprecated = "(deprecated)" } - versions = append(versions, fmt.Sprintf("%s\t%s%s%s", details.Metadata.Version, appVersionDesc, createdDesc, deprecated)) + versions = append(versions, fmt.Sprintf("%s\t%s%s%s", details.Version, appVersionDesc, createdDesc, deprecated)) } } diff --git a/cmd/helm/flags_test.go b/pkg/cmd/flags_test.go similarity index 79% rename from cmd/helm/flags_test.go rename to pkg/cmd/flags_test.go index 07d28c460..9d416f216 100644 --- a/cmd/helm/flags_test.go +++ b/pkg/cmd/flags_test.go @@ -14,15 +14,18 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "fmt" "testing" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/release" - helmtime "helm.sh/helm/v3/pkg/time" + "github.com/stretchr/testify/require" + + "helm.sh/helm/v4/pkg/action" + chart "helm.sh/helm/v4/pkg/chart/v2" + release "helm.sh/helm/v4/pkg/release/v1" + helmtime "helm.sh/helm/v4/pkg/time" ) func outputFlagCompletionTest(t *testing.T, cmdName string) { @@ -93,3 +96,24 @@ func outputFlagCompletionTest(t *testing.T, cmdName string) { }} runTestCmd(t, tests) } + +func TestPostRendererFlagSetOnce(t *testing.T) { + cfg := action.Configuration{} + client := action.NewInstall(&cfg) + str := postRendererString{ + options: &postRendererOptions{ + renderer: &client.PostRenderer, + }, + } + // Set the binary once + err := str.Set("echo") + require.NoError(t, err) + + // Set the binary again to the same value is not ok + err = str.Set("echo") + require.Error(t, err) + + // Set the binary again to a different value is not ok + err = str.Set("cat") + require.Error(t, err) +} diff --git a/cmd/helm/get.go b/pkg/cmd/get.go similarity index 94% rename from cmd/helm/get.go rename to pkg/cmd/get.go index 727cdaf88..1e672beea 100644 --- a/cmd/helm/get.go +++ b/pkg/cmd/get.go @@ -14,15 +14,15 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "io" "github.com/spf13/cobra" - "helm.sh/helm/v3/cmd/helm/require" - "helm.sh/helm/v3/pkg/action" + "helm.sh/helm/v4/pkg/action" + "helm.sh/helm/v4/pkg/cmd/require" ) var getHelp = ` diff --git a/cmd/helm/get_all.go b/pkg/cmd/get_all.go similarity index 88% rename from cmd/helm/get_all.go rename to pkg/cmd/get_all.go index f7af68213..aee92df51 100644 --- a/cmd/helm/get_all.go +++ b/pkg/cmd/get_all.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "io" @@ -22,9 +22,9 @@ import ( "github.com/spf13/cobra" - "helm.sh/helm/v3/cmd/helm/require" - "helm.sh/helm/v3/pkg/action" - "helm.sh/helm/v3/pkg/cli/output" + "helm.sh/helm/v4/pkg/action" + "helm.sh/helm/v4/pkg/cli/output" + "helm.sh/helm/v4/pkg/cmd/require" ) var getAllHelp = ` @@ -58,7 +58,12 @@ func newGetAllCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { } return tpl(template, data, out) } - return output.Table.Write(out, &statusPrinter{res, true, false, false, true, false}) + return output.Table.Write(out, &statusPrinter{ + release: res, + debug: true, + showMetadata: true, + hideNotes: false, + }) }, } @@ -70,7 +75,6 @@ func newGetAllCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { } return nil, cobra.ShellCompDirectiveNoFileComp }) - if err != nil { log.Fatal(err) } diff --git a/cmd/helm/get_all_test.go b/pkg/cmd/get_all_test.go similarity index 96% rename from cmd/helm/get_all_test.go rename to pkg/cmd/get_all_test.go index 948f0aa71..80bb7d332 100644 --- a/cmd/helm/get_all_test.go +++ b/pkg/cmd/get_all_test.go @@ -14,12 +14,12 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "testing" - "helm.sh/helm/v3/pkg/release" + release "helm.sh/helm/v4/pkg/release/v1" ) func TestGetCmd(t *testing.T) { diff --git a/cmd/helm/get_hooks.go b/pkg/cmd/get_hooks.go similarity index 96% rename from cmd/helm/get_hooks.go rename to pkg/cmd/get_hooks.go index 1fe7f0a25..7ffefd93c 100644 --- a/cmd/helm/get_hooks.go +++ b/pkg/cmd/get_hooks.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "fmt" @@ -23,8 +23,8 @@ import ( "github.com/spf13/cobra" - "helm.sh/helm/v3/cmd/helm/require" - "helm.sh/helm/v3/pkg/action" + "helm.sh/helm/v4/pkg/action" + "helm.sh/helm/v4/pkg/cmd/require" ) const getHooksHelp = ` diff --git a/cmd/helm/get_hooks_test.go b/pkg/cmd/get_hooks_test.go similarity index 96% rename from cmd/helm/get_hooks_test.go rename to pkg/cmd/get_hooks_test.go index 251d5c731..3be1d8500 100644 --- a/cmd/helm/get_hooks_test.go +++ b/pkg/cmd/get_hooks_test.go @@ -14,12 +14,12 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "testing" - "helm.sh/helm/v3/pkg/release" + release "helm.sh/helm/v4/pkg/release/v1" ) func TestGetHooks(t *testing.T) { diff --git a/cmd/helm/get_manifest.go b/pkg/cmd/get_manifest.go similarity index 96% rename from cmd/helm/get_manifest.go rename to pkg/cmd/get_manifest.go index 6431b4252..021495d8d 100644 --- a/cmd/helm/get_manifest.go +++ b/pkg/cmd/get_manifest.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "fmt" @@ -23,8 +23,8 @@ import ( "github.com/spf13/cobra" - "helm.sh/helm/v3/cmd/helm/require" - "helm.sh/helm/v3/pkg/action" + "helm.sh/helm/v4/pkg/action" + "helm.sh/helm/v4/pkg/cmd/require" ) var getManifestHelp = ` diff --git a/cmd/helm/get_manifest_test.go b/pkg/cmd/get_manifest_test.go similarity index 96% rename from cmd/helm/get_manifest_test.go rename to pkg/cmd/get_manifest_test.go index 2f27476b6..cfb5215bf 100644 --- a/cmd/helm/get_manifest_test.go +++ b/pkg/cmd/get_manifest_test.go @@ -14,12 +14,12 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "testing" - "helm.sh/helm/v3/pkg/release" + release "helm.sh/helm/v4/pkg/release/v1" ) func TestGetManifest(t *testing.T) { diff --git a/cmd/helm/get_metadata.go b/pkg/cmd/get_metadata.go similarity index 88% rename from cmd/helm/get_metadata.go rename to pkg/cmd/get_metadata.go index dbc7b6eff..9f58e0f4e 100644 --- a/cmd/helm/get_metadata.go +++ b/pkg/cmd/get_metadata.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "fmt" @@ -22,10 +22,11 @@ import ( "log" "github.com/spf13/cobra" + k8sLabels "k8s.io/apimachinery/pkg/labels" - "helm.sh/helm/v3/cmd/helm/require" - "helm.sh/helm/v3/pkg/action" - "helm.sh/helm/v3/pkg/cli/output" + "helm.sh/helm/v4/pkg/action" + "helm.sh/helm/v4/pkg/cli/output" + "helm.sh/helm/v4/pkg/cmd/require" ) type metadataWriter struct { @@ -78,10 +79,13 @@ func (w metadataWriter) WriteTable(out io.Writer) error { _, _ = fmt.Fprintf(out, "CHART: %v\n", w.metadata.Chart) _, _ = fmt.Fprintf(out, "VERSION: %v\n", w.metadata.Version) _, _ = fmt.Fprintf(out, "APP_VERSION: %v\n", w.metadata.AppVersion) + _, _ = fmt.Fprintf(out, "ANNOTATIONS: %v\n", k8sLabels.Set(w.metadata.Annotations).String()) + _, _ = fmt.Fprintf(out, "DEPENDENCIES: %v\n", w.metadata.FormattedDepNames()) _, _ = fmt.Fprintf(out, "NAMESPACE: %v\n", w.metadata.Namespace) _, _ = fmt.Fprintf(out, "REVISION: %v\n", w.metadata.Revision) _, _ = fmt.Fprintf(out, "STATUS: %v\n", w.metadata.Status) _, _ = fmt.Fprintf(out, "DEPLOYED_AT: %v\n", w.metadata.DeployedAt) + return nil } diff --git a/cmd/helm/get_metadata_test.go b/pkg/cmd/get_metadata_test.go similarity index 97% rename from cmd/helm/get_metadata_test.go rename to pkg/cmd/get_metadata_test.go index b6f0ab9f2..a2ab2cba1 100644 --- a/cmd/helm/get_metadata_test.go +++ b/pkg/cmd/get_metadata_test.go @@ -14,12 +14,12 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "testing" - "helm.sh/helm/v3/pkg/release" + release "helm.sh/helm/v4/pkg/release/v1" ) func TestGetMetadataCmd(t *testing.T) { diff --git a/cmd/helm/get_notes.go b/pkg/cmd/get_notes.go similarity index 95% rename from cmd/helm/get_notes.go rename to pkg/cmd/get_notes.go index b4c7ceddf..ae79d8bcc 100644 --- a/cmd/helm/get_notes.go +++ b/pkg/cmd/get_notes.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "fmt" @@ -23,8 +23,8 @@ import ( "github.com/spf13/cobra" - "helm.sh/helm/v3/cmd/helm/require" - "helm.sh/helm/v3/pkg/action" + "helm.sh/helm/v4/pkg/action" + "helm.sh/helm/v4/pkg/cmd/require" ) var getNotesHelp = ` diff --git a/cmd/helm/get_notes_test.go b/pkg/cmd/get_notes_test.go similarity index 96% rename from cmd/helm/get_notes_test.go rename to pkg/cmd/get_notes_test.go index 8be9a3f7c..b451dfa05 100644 --- a/cmd/helm/get_notes_test.go +++ b/pkg/cmd/get_notes_test.go @@ -14,12 +14,12 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "testing" - "helm.sh/helm/v3/pkg/release" + release "helm.sh/helm/v4/pkg/release/v1" ) func TestGetNotesCmd(t *testing.T) { diff --git a/cmd/helm/get_test.go b/pkg/cmd/get_test.go similarity index 98% rename from cmd/helm/get_test.go rename to pkg/cmd/get_test.go index 79f914bea..cf81e4df7 100644 --- a/cmd/helm/get_test.go +++ b/pkg/cmd/get_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "testing" diff --git a/cmd/helm/get_values.go b/pkg/cmd/get_values.go similarity index 95% rename from cmd/helm/get_values.go rename to pkg/cmd/get_values.go index 54a15eda9..02b195551 100644 --- a/cmd/helm/get_values.go +++ b/pkg/cmd/get_values.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "fmt" @@ -23,9 +23,9 @@ import ( "github.com/spf13/cobra" - "helm.sh/helm/v3/cmd/helm/require" - "helm.sh/helm/v3/pkg/action" - "helm.sh/helm/v3/pkg/cli/output" + "helm.sh/helm/v4/pkg/action" + "helm.sh/helm/v4/pkg/cli/output" + "helm.sh/helm/v4/pkg/cmd/require" ) var getValuesHelp = ` diff --git a/cmd/helm/get_values_test.go b/pkg/cmd/get_values_test.go similarity index 97% rename from cmd/helm/get_values_test.go rename to pkg/cmd/get_values_test.go index 423c32859..7bbe109f6 100644 --- a/cmd/helm/get_values_test.go +++ b/pkg/cmd/get_values_test.go @@ -14,12 +14,12 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "testing" - "helm.sh/helm/v3/pkg/release" + release "helm.sh/helm/v4/pkg/release/v1" ) func TestGetValuesCmd(t *testing.T) { diff --git a/pkg/cmd/helpers_test.go b/pkg/cmd/helpers_test.go new file mode 100644 index 000000000..b48f802b5 --- /dev/null +++ b/pkg/cmd/helpers_test.go @@ -0,0 +1,163 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package cmd + +import ( + "bytes" + "io" + "os" + "strings" + "testing" + + shellwords "github.com/mattn/go-shellwords" + "github.com/spf13/cobra" + + "helm.sh/helm/v4/internal/test" + "helm.sh/helm/v4/pkg/action" + chartutil "helm.sh/helm/v4/pkg/chart/v2/util" + "helm.sh/helm/v4/pkg/cli" + kubefake "helm.sh/helm/v4/pkg/kube/fake" + release "helm.sh/helm/v4/pkg/release/v1" + "helm.sh/helm/v4/pkg/storage" + "helm.sh/helm/v4/pkg/storage/driver" + "helm.sh/helm/v4/pkg/time" +) + +func testTimestamper() time.Time { return time.Unix(242085845, 0).UTC() } + +func init() { + action.Timestamper = testTimestamper +} + +func runTestCmd(t *testing.T, tests []cmdTestCase) { + t.Helper() + for _, tt := range tests { + for i := 0; i <= tt.repeat; i++ { + t.Run(tt.name, func(t *testing.T) { + defer resetEnv()() + + storage := storageFixture() + for _, rel := range tt.rels { + if err := storage.Create(rel); err != nil { + t.Fatal(err) + } + } + t.Logf("running cmd (attempt %d): %s", i+1, tt.cmd) + _, out, err := executeActionCommandC(storage, tt.cmd) + if tt.wantError && err == nil { + t.Errorf("expected error, got success with the following output:\n%s", out) + } + if !tt.wantError && err != nil { + t.Errorf("expected no error, got: '%v'", err) + } + if tt.golden != "" { + test.AssertGoldenString(t, out, tt.golden) + } + }) + } + } +} + +func storageFixture() *storage.Storage { + return storage.Init(driver.NewMemory()) +} + +func executeActionCommandC(store *storage.Storage, cmd string) (*cobra.Command, string, error) { + return executeActionCommandStdinC(store, nil, cmd) +} + +func executeActionCommandStdinC(store *storage.Storage, in *os.File, cmd string) (*cobra.Command, string, error) { + args, err := shellwords.Parse(cmd) + if err != nil { + return nil, "", err + } + + buf := new(bytes.Buffer) + + actionConfig := &action.Configuration{ + Releases: store, + KubeClient: &kubefake.PrintingKubeClient{Out: io.Discard}, + Capabilities: chartutil.DefaultCapabilities, + } + + root, err := newRootCmdWithConfig(actionConfig, buf, args) + if err != nil { + return nil, "", err + } + + root.SetOut(buf) + root.SetErr(buf) + root.SetArgs(args) + + oldStdin := os.Stdin + if in != nil { + root.SetIn(in) + os.Stdin = in + } + + if mem, ok := store.Driver.(*driver.Memory); ok { + mem.SetNamespace(settings.Namespace()) + } + c, err := root.ExecuteC() + + result := buf.String() + + os.Stdin = oldStdin + + return c, result, err +} + +// cmdTestCase describes a test case that works with releases. +type cmdTestCase struct { + name string + cmd string + golden string + wantError bool + // Rels are the available releases at the start of the test. + rels []*release.Release + // Number of repeats (in case a feature was previously flaky and the test checks + // it's now stably producing identical results). 0 means test is run exactly once. + repeat int +} + +func executeActionCommand(cmd string) (*cobra.Command, string, error) { + return executeActionCommandC(storageFixture(), cmd) +} + +func resetEnv() func() { + origEnv := os.Environ() + return func() { + os.Clearenv() + for _, pair := range origEnv { + kv := strings.SplitN(pair, "=", 2) + os.Setenv(kv[0], kv[1]) + } + 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) } +} diff --git a/cmd/helm/history.go b/pkg/cmd/history.go similarity index 90% rename from cmd/helm/history.go rename to pkg/cmd/history.go index 56383bfca..ec2a1bc12 100644 --- a/cmd/helm/history.go +++ b/pkg/cmd/history.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "fmt" @@ -25,13 +25,13 @@ import ( "github.com/gosuri/uitable" "github.com/spf13/cobra" - "helm.sh/helm/v3/cmd/helm/require" - "helm.sh/helm/v3/pkg/action" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/cli/output" - "helm.sh/helm/v3/pkg/release" - "helm.sh/helm/v3/pkg/releaseutil" - helmtime "helm.sh/helm/v3/pkg/time" + "helm.sh/helm/v4/pkg/action" + chart "helm.sh/helm/v4/pkg/chart/v2" + "helm.sh/helm/v4/pkg/cli/output" + "helm.sh/helm/v4/pkg/cmd/require" + releaseutil "helm.sh/helm/v4/pkg/release/util" + release "helm.sh/helm/v4/pkg/release/v1" + helmtime "helm.sh/helm/v4/pkg/time" ) var historyHelp = ` @@ -177,22 +177,15 @@ func formatAppVersion(c *chart.Chart) string { return c.AppVersion() } -func min(x, y int) int { - if x < y { - return x - } - return y -} - func compListRevisions(_ string, cfg *action.Configuration, releaseName string) ([]string, cobra.ShellCompDirective) { client := action.NewHistory(cfg) var revisions []string if hist, err := client.Run(releaseName); err == nil { - for _, release := range hist { - appVersion := fmt.Sprintf("App: %s", release.Chart.Metadata.AppVersion) - chartDesc := fmt.Sprintf("Chart: %s-%s", release.Chart.Metadata.Name, release.Chart.Metadata.Version) - revisions = append(revisions, fmt.Sprintf("%s\t%s, %s", strconv.Itoa(release.Version), appVersion, chartDesc)) + for _, version := range hist { + appVersion := fmt.Sprintf("App: %s", version.Chart.Metadata.AppVersion) + chartDesc := fmt.Sprintf("Chart: %s-%s", version.Chart.Metadata.Name, version.Chart.Metadata.Version) + revisions = append(revisions, fmt.Sprintf("%s\t%s, %s", strconv.Itoa(version.Version), appVersion, chartDesc)) } return revisions, cobra.ShellCompDirectiveNoFileComp } diff --git a/cmd/helm/history_test.go b/pkg/cmd/history_test.go similarity index 98% rename from cmd/helm/history_test.go rename to pkg/cmd/history_test.go index 07f2d85df..594d93d21 100644 --- a/cmd/helm/history_test.go +++ b/pkg/cmd/history_test.go @@ -14,13 +14,13 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "fmt" "testing" - "helm.sh/helm/v3/pkg/release" + release "helm.sh/helm/v4/pkg/release/v1" ) func TestHistoryCmd(t *testing.T) { diff --git a/cmd/helm/install.go b/pkg/cmd/install.go similarity index 90% rename from cmd/helm/install.go rename to pkg/cmd/install.go index ed49bd7a5..51e192602 100644 --- a/cmd/helm/install.go +++ b/pkg/cmd/install.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "context" @@ -22,6 +22,7 @@ import ( "fmt" "io" "log" + "log/slog" "os" "os/signal" "syscall" @@ -30,15 +31,15 @@ import ( "github.com/spf13/cobra" "github.com/spf13/pflag" - "helm.sh/helm/v3/cmd/helm/require" - "helm.sh/helm/v3/pkg/action" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chart/loader" - "helm.sh/helm/v3/pkg/cli/output" - "helm.sh/helm/v3/pkg/cli/values" - "helm.sh/helm/v3/pkg/downloader" - "helm.sh/helm/v3/pkg/getter" - "helm.sh/helm/v3/pkg/release" + "helm.sh/helm/v4/pkg/action" + chart "helm.sh/helm/v4/pkg/chart/v2" + "helm.sh/helm/v4/pkg/chart/v2/loader" + "helm.sh/helm/v4/pkg/cli/output" + "helm.sh/helm/v4/pkg/cli/values" + "helm.sh/helm/v4/pkg/cmd/require" + "helm.sh/helm/v4/pkg/downloader" + "helm.sh/helm/v4/pkg/getter" + release "helm.sh/helm/v4/pkg/release/v1" ) const installDesc = ` @@ -52,7 +53,7 @@ or use the '--set' flag and pass configuration from the command line, to force a string value use '--set-string'. You can use '--set-file' to set individual values from a file when the value itself is too long for the command line or is dynamically generated. You can also use '--set-json' to set json values -(scalars/objects/arrays) from the command line. +(scalars/objects/arrays) from the command line. Additionally, you can use '--set-json' and passing json object as a string. $ helm install -f myvalues.yaml myredis ./redis @@ -72,6 +73,9 @@ or $ helm install --set-json 'master.sidecars=[{"name":"sidecar","image":"myImage","imagePullPolicy":"Always","ports":[{"name":"portname","containerPort":1234}]}]' myredis ./redis +or + + $ helm install --set-json '{"master":{"sidecars":[{"name":"sidecar","image":"myImage","imagePullPolicy":"Always","ports":[{"name":"portname","containerPort":1234}]}]}}' myredis ./redis You can specify the '--values'/'-f' flag multiple times. The priority will be given to the last (right-most) file specified. For example, if both myvalues.yaml and override.yaml @@ -141,7 +145,7 @@ func newInstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { }, RunE: func(_ *cobra.Command, args []string) error { registryClient, err := newRegistryClient(client.CertFile, client.KeyFile, client.CaFile, - client.InsecureSkipTLSverify, client.PlainHTTP) + client.InsecureSkipTLSverify, client.PlainHTTP, client.Username, client.Password) if err != nil { return fmt.Errorf("missing registry client: %w", err) } @@ -158,7 +162,12 @@ func newInstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { return fmt.Errorf("INSTALLATION FAILED: %w", err) } - return outfmt.Write(out, &statusPrinter{rel, settings.Debug, false, false, false, client.HideNotes}) + return outfmt.Write(out, &statusPrinter{ + release: rel, + debug: settings.Debug, + showMetadata: false, + hideNotes: client.HideNotes, + }) }, } @@ -186,7 +195,6 @@ func addInstallFlags(cmd *cobra.Command, f *pflag.FlagSet, client *action.Instal f.BoolVar(&client.DisableHooks, "no-hooks", false, "prevent hooks from running during install") f.BoolVar(&client.Replace, "replace", false, "reuse the given name, only if that name is a deleted release which remains in the history. This is unsafe in production") f.DurationVar(&client.Timeout, "timeout", 300*time.Second, "time to wait for any individual Kubernetes operation (like Jobs for hooks)") - f.BoolVar(&client.Wait, "wait", false, "if set, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment, StatefulSet, or ReplicaSet are in a ready state before marking the release as successful. It will wait for as long as --timeout") f.BoolVar(&client.WaitForJobs, "wait-for-jobs", false, "if set and --wait enabled, will wait until all Jobs have been completed before marking the release as successful. It will wait for as long as --timeout") f.BoolVarP(&client.GenerateName, "generate-name", "g", false, "generate the name (and omit the NAME parameter)") f.StringVar(&client.NameTemplate, "name-template", "", "specify template used to name the release") @@ -194,15 +202,17 @@ func addInstallFlags(cmd *cobra.Command, f *pflag.FlagSet, client *action.Instal f.BoolVar(&client.Devel, "devel", false, "use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored") f.BoolVar(&client.DependencyUpdate, "dependency-update", false, "update dependencies if they are missing before installing the chart") f.BoolVar(&client.DisableOpenAPIValidation, "disable-openapi-validation", false, "if set, the installation process will not validate rendered templates against the Kubernetes OpenAPI Schema") - f.BoolVar(&client.Atomic, "atomic", false, "if set, the installation process deletes the installation on failure. The --wait flag will be set automatically if --atomic is used") + f.BoolVar(&client.Atomic, "atomic", false, "if set, the installation process deletes the installation on failure. The --wait flag will be set automatically to \"watcher\" if --atomic is used") f.BoolVar(&client.SkipCRDs, "skip-crds", false, "if set, no CRDs will be installed. By default, CRDs are installed if not already present") f.BoolVar(&client.SubNotes, "render-subchart-notes", false, "if set, render subchart notes along with the parent") f.BoolVar(&client.SkipSchemaValidation, "skip-schema-validation", false, "if set, disables JSON schema validation") f.StringToStringVarP(&client.Labels, "labels", "l", nil, "Labels that would be added to release metadata. Should be divided by comma.") f.BoolVar(&client.EnableDNS, "enable-dns", false, "enable DNS lookups when rendering templates") f.BoolVar(&client.HideNotes, "hide-notes", false, "if set, do not show notes in install output. Does not affect presence in chart metadata") + f.BoolVar(&client.TakeOwnership, "take-ownership", false, "if set, install will ignore the check for helm annotations and take ownership of the existing resources") addValueOptionsFlags(f, valueOpts) addChartPathOptionsFlags(f, &client.ChartPathOptions) + AddWaitFlag(cmd, &client.WaitStrategy) err := cmd.RegisterFlagCompletionFunc("version", func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { requiredArgs := 2 @@ -214,16 +224,15 @@ func addInstallFlags(cmd *cobra.Command, f *pflag.FlagSet, client *action.Instal } return compVersionFlag(args[requiredArgs-1], toComplete) }) - if err != nil { log.Fatal(err) } } func runInstall(args []string, client *action.Install, valueOpts *values.Options, out io.Writer) (*release.Release, error) { - debug("Original chart version: %q", client.Version) + slog.Debug("Original chart version", "version", client.Version) if client.Version == "" && client.Devel { - debug("setting version to >0.0.0-0") + slog.Debug("setting version to >0.0.0-0") client.Version = ">0.0.0-0" } @@ -233,12 +242,12 @@ func runInstall(args []string, client *action.Install, valueOpts *values.Options } client.ReleaseName = name - cp, err := client.ChartPathOptions.LocateChart(chart, settings) + cp, err := client.LocateChart(chart, settings) if err != nil { return nil, err } - debug("CHART PATH: %s\n", cp) + slog.Debug("Chart path", "path", cp) p := getter.All(settings) vals, err := valueOpts.MergeValues(p) @@ -257,7 +266,7 @@ func runInstall(args []string, client *action.Install, valueOpts *values.Options } if chartRequested.Metadata.Deprecated { - warning("This chart is deprecated") + slog.Warn("this chart is deprecated") } if req := chartRequested.Metadata.Dependencies; req != nil { @@ -269,7 +278,7 @@ func runInstall(args []string, client *action.Install, valueOpts *values.Options man := &downloader.Manager{ Out: out, ChartPath: cp, - Keyring: client.ChartPathOptions.Keyring, + Keyring: client.Keyring, SkipUpdate: false, Getters: p, RepositoryConfig: settings.RepositoryConfig, diff --git a/cmd/helm/install_test.go b/pkg/cmd/install_test.go similarity index 95% rename from cmd/helm/install_test.go rename to pkg/cmd/install_test.go index 682b25164..9cd244e84 100644 --- a/cmd/helm/install_test.go +++ b/pkg/cmd/install_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "fmt" @@ -23,23 +23,17 @@ import ( "path/filepath" "testing" - "helm.sh/helm/v3/pkg/repo/repotest" + "helm.sh/helm/v4/pkg/repo/repotest" ) func TestInstall(t *testing.T) { - srv, err := repotest.NewTempServerWithCleanup(t, "testdata/testcharts/*.tgz*") - if err != nil { - t.Fatal(err) - } + srv := repotest.NewTempServer( + t, + repotest.WithChartSourceGlob("testdata/testcharts/*.tgz*"), + repotest.WithMiddleware(repotest.BasicAuthMiddleware(t)), + ) defer srv.Stop() - srv.WithMiddleware(http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) { - username, password, ok := r.BasicAuth() - if !ok || username != "username" || password != "password" { - t.Errorf("Expected request to use basic auth and for username == 'username' and password == 'password', got '%v', '%s', '%s'", ok, username, password) - } - })) - srv2 := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { http.FileServer(http.Dir(srv.Root())).ServeHTTP(w, r) })) @@ -102,6 +96,12 @@ func TestInstall(t *testing.T) { cmd: "install aeneas testdata/testcharts/empty --replace", golden: "output/install-and-replace.txt", }, + // Install, take ownership + { + name: "install and replace release", + cmd: "install aeneas-take-ownership testdata/testcharts/empty --take-ownership", + golden: "output/install-and-take-ownership.txt", + }, // Install, with timeout { name: "install with a timeout", diff --git a/cmd/helm/lint.go b/pkg/cmd/lint.go similarity index 95% rename from cmd/helm/lint.go rename to pkg/cmd/lint.go index 216cdf077..78083a7ea 100644 --- a/cmd/helm/lint.go +++ b/pkg/cmd/lint.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "errors" @@ -26,11 +26,11 @@ import ( "github.com/spf13/cobra" - "helm.sh/helm/v3/pkg/action" - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/cli/values" - "helm.sh/helm/v3/pkg/getter" - "helm.sh/helm/v3/pkg/lint/support" + "helm.sh/helm/v4/pkg/action" + chartutil "helm.sh/helm/v4/pkg/chart/v2/util" + "helm.sh/helm/v4/pkg/cli/values" + "helm.sh/helm/v4/pkg/getter" + "helm.sh/helm/v4/pkg/lint/support" ) var longLintHelp = ` diff --git a/cmd/helm/lint_test.go b/pkg/cmd/lint_test.go similarity index 99% rename from cmd/helm/lint_test.go rename to pkg/cmd/lint_test.go index 166b69ba0..401c84d74 100644 --- a/cmd/helm/lint_test.go +++ b/pkg/cmd/lint_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "fmt" diff --git a/cmd/helm/list.go b/pkg/cmd/list.go similarity index 97% rename from cmd/helm/list.go rename to pkg/cmd/list.go index bbc7e979d..69a4ff36d 100644 --- a/cmd/helm/list.go +++ b/pkg/cmd/list.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "fmt" @@ -25,10 +25,10 @@ import ( "github.com/gosuri/uitable" "github.com/spf13/cobra" - "helm.sh/helm/v3/cmd/helm/require" - "helm.sh/helm/v3/pkg/action" - "helm.sh/helm/v3/pkg/cli/output" - "helm.sh/helm/v3/pkg/release" + "helm.sh/helm/v4/pkg/action" + "helm.sh/helm/v4/pkg/cli/output" + "helm.sh/helm/v4/pkg/cmd/require" + release "helm.sh/helm/v4/pkg/release/v1" ) var listHelp = ` @@ -71,7 +71,7 @@ func newListCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { ValidArgsFunction: noMoreArgsCompFunc, RunE: func(cmd *cobra.Command, _ []string) error { if client.AllNamespaces { - if err := cfg.Init(settings.RESTClientGetter(), "", os.Getenv("HELM_DRIVER"), debug); err != nil { + if err := cfg.Init(settings.RESTClientGetter(), "", os.Getenv("HELM_DRIVER")); err != nil { return err } } diff --git a/cmd/helm/list_test.go b/pkg/cmd/list_test.go similarity index 98% rename from cmd/helm/list_test.go rename to pkg/cmd/list_test.go index 97a1e284f..82b25a768 100644 --- a/cmd/helm/list_test.go +++ b/pkg/cmd/list_test.go @@ -14,14 +14,14 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "testing" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/release" - "helm.sh/helm/v3/pkg/time" + chart "helm.sh/helm/v4/pkg/chart/v2" + release "helm.sh/helm/v4/pkg/release/v1" + "helm.sh/helm/v4/pkg/time" ) func TestListCmd(t *testing.T) { diff --git a/cmd/helm/load_plugins.go b/pkg/cmd/load_plugins.go similarity index 98% rename from cmd/helm/load_plugins.go rename to pkg/cmd/load_plugins.go index cbf382862..2eef1fb3c 100644 --- a/cmd/helm/load_plugins.go +++ b/pkg/cmd/load_plugins.go @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "bytes" @@ -30,7 +30,7 @@ import ( "github.com/spf13/cobra" "sigs.k8s.io/yaml" - "helm.sh/helm/v3/pkg/plugin" + "helm.sh/helm/v4/pkg/plugin" ) const ( @@ -38,9 +38,9 @@ const ( pluginDynamicCompletionExecutable = "plugin.complete" ) -type pluginError struct { +type PluginError struct { error - code int + Code int } // loadPlugins loads plugins into the command list. @@ -136,9 +136,9 @@ func callPluginExecutable(pluginName string, main string, argv []string, out io. if eerr, ok := err.(*exec.ExitError); ok { os.Stderr.Write(eerr.Stderr) status := eerr.Sys().(syscall.WaitStatus) - return pluginError{ + return PluginError{ error: fmt.Errorf("plugin %q exited with error", pluginName), - code: status.ExitStatus(), + Code: status.ExitStatus(), } } return err diff --git a/cmd/helm/package.go b/pkg/cmd/package.go similarity index 75% rename from cmd/helm/package.go rename to pkg/cmd/package.go index 236a66188..40c503222 100644 --- a/cmd/helm/package.go +++ b/pkg/cmd/package.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "errors" @@ -25,10 +25,10 @@ import ( "github.com/spf13/cobra" - "helm.sh/helm/v3/pkg/action" - "helm.sh/helm/v3/pkg/cli/values" - "helm.sh/helm/v3/pkg/downloader" - "helm.sh/helm/v3/pkg/getter" + "helm.sh/helm/v4/pkg/action" + "helm.sh/helm/v4/pkg/cli/values" + "helm.sh/helm/v4/pkg/downloader" + "helm.sh/helm/v4/pkg/getter" ) const packageDesc = ` @@ -47,7 +47,7 @@ If '--keyring' is not specified, Helm usually defaults to the public keyring unless your environment is otherwise configured. ` -func newPackageCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { +func newPackageCmd(out io.Writer) *cobra.Command { client := action.NewPackage() valueOpts := &values.Options{} @@ -75,6 +75,12 @@ func newPackageCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { return err } + registryClient, err := newRegistryClient(client.CertFile, client.KeyFile, client.CaFile, + client.InsecureSkipTLSverify, client.PlainHTTP, client.Username, client.Password) + if err != nil { + return fmt.Errorf("missing registry client: %w", err) + } + for i := 0; i < len(args); i++ { path, err := filepath.Abs(args[i]) if err != nil { @@ -91,7 +97,7 @@ func newPackageCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { Keyring: client.Keyring, Getters: p, Debug: settings.Debug, - RegistryClient: cfg.RegistryClient, + RegistryClient: registryClient, RepositoryConfig: settings.RepositoryConfig, RepositoryCache: settings.RepositoryCache, } @@ -119,6 +125,13 @@ func newPackageCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { f.StringVar(&client.AppVersion, "app-version", "", "set the appVersion on the chart to this version") f.StringVarP(&client.Destination, "destination", "d", ".", "location to write the chart.") f.BoolVarP(&client.DependencyUpdate, "dependency-update", "u", false, `update dependencies from "Chart.yaml" to dir "charts/" before packaging`) + f.StringVar(&client.Username, "username", "", "chart repository username where to locate the requested chart") + f.StringVar(&client.Password, "password", "", "chart repository password where to locate the requested chart") + f.StringVar(&client.CertFile, "cert-file", "", "identify HTTPS client using this SSL certificate file") + f.StringVar(&client.KeyFile, "key-file", "", "identify HTTPS client using this SSL key file") + f.BoolVar(&client.InsecureSkipTLSverify, "insecure-skip-tls-verify", false, "skip tls certificate checks for the chart download") + f.BoolVar(&client.PlainHTTP, "plain-http", false, "use insecure HTTP connections for the chart download") + f.StringVar(&client.CaFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle") return cmd } diff --git a/cmd/helm/package_test.go b/pkg/cmd/package_test.go similarity index 98% rename from cmd/helm/package_test.go rename to pkg/cmd/package_test.go index 9093b510c..54358fc12 100644 --- a/cmd/helm/package_test.go +++ b/pkg/cmd/package_test.go @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "fmt" @@ -23,8 +23,8 @@ import ( "strings" "testing" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chart/loader" + chart "helm.sh/helm/v4/pkg/chart/v2" + "helm.sh/helm/v4/pkg/chart/v2/loader" ) func TestPackage(t *testing.T) { diff --git a/cmd/helm/plugin.go b/pkg/cmd/plugin.go similarity index 73% rename from cmd/helm/plugin.go rename to pkg/cmd/plugin.go index 23716cf1d..a2bb838df 100644 --- a/cmd/helm/plugin.go +++ b/pkg/cmd/plugin.go @@ -13,17 +13,18 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "fmt" "io" + "log/slog" "os" "os/exec" "github.com/spf13/cobra" - "helm.sh/helm/v3/pkg/plugin" + "helm.sh/helm/v4/pkg/plugin" ) const pluginHelp = ` @@ -47,19 +48,27 @@ func newPluginCmd(out io.Writer) *cobra.Command { // runHook will execute a plugin hook. func runHook(p *plugin.Plugin, event string) error { - hook := p.Metadata.Hooks[event] - if hook == "" { + plugin.SetupPluginEnv(settings, p.Metadata.Name, p.Dir) + + cmds := p.Metadata.PlatformHooks[event] + expandArgs := true + if len(cmds) == 0 && len(p.Metadata.Hooks) > 0 { + cmd := p.Metadata.Hooks[event] + if len(cmd) > 0 { + cmds = []plugin.PlatformCommand{{Command: "sh", Args: []string{"-c", cmd}}} + expandArgs = false + } + } + + main, argv, err := plugin.PrepareCommands(cmds, expandArgs, []string{}) + if err != nil { return nil } - prog := exec.Command("sh", "-c", hook) - // TODO make this work on windows - // I think its ... ¯\_(ツ)_/¯ - // prog := exec.Command("cmd", "/C", p.Metadata.Hooks.Install()) + prog := exec.Command(main, argv...) - debug("running %s hook: %s", event, prog) + slog.Debug("running hook", "event", event, "program", prog) - plugin.SetupPluginEnv(settings, p.Metadata.Name, p.Dir) prog.Stdout, prog.Stderr = os.Stdout, os.Stderr if err := prog.Run(); err != nil { if eerr, ok := err.(*exec.ExitError); ok { diff --git a/cmd/helm/plugin_install.go b/pkg/cmd/plugin_install.go similarity index 92% rename from cmd/helm/plugin_install.go rename to pkg/cmd/plugin_install.go index 9b3df38b1..945bf8ee0 100644 --- a/cmd/helm/plugin_install.go +++ b/pkg/cmd/plugin_install.go @@ -13,17 +13,18 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "fmt" "io" + "log/slog" "github.com/spf13/cobra" - "helm.sh/helm/v3/cmd/helm/require" - "helm.sh/helm/v3/pkg/plugin" - "helm.sh/helm/v3/pkg/plugin/installer" + "helm.sh/helm/v4/pkg/cmd/require" + "helm.sh/helm/v4/pkg/plugin" + "helm.sh/helm/v4/pkg/plugin/installer" ) type pluginInstallOptions struct { @@ -78,7 +79,7 @@ func (o *pluginInstallOptions) run(out io.Writer) error { return err } - debug("loading plugin from %s", i.Path()) + slog.Debug("loading plugin", "path", i.Path()) p, err := plugin.LoadDir(i.Path()) if err != nil { return fmt.Errorf("plugin is installed but unusable: %w", err) diff --git a/cmd/helm/plugin_list.go b/pkg/cmd/plugin_list.go similarity index 95% rename from cmd/helm/plugin_list.go rename to pkg/cmd/plugin_list.go index 8c73be500..fdd66ec0a 100644 --- a/cmd/helm/plugin_list.go +++ b/pkg/cmd/plugin_list.go @@ -13,16 +13,17 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "fmt" "io" + "log/slog" "github.com/gosuri/uitable" "github.com/spf13/cobra" - "helm.sh/helm/v3/pkg/plugin" + "helm.sh/helm/v4/pkg/plugin" ) func newPluginListCmd(out io.Writer) *cobra.Command { @@ -32,7 +33,7 @@ func newPluginListCmd(out io.Writer) *cobra.Command { Short: "list installed Helm plugins", ValidArgsFunction: noMoreArgsCompFunc, RunE: func(_ *cobra.Command, _ []string) error { - debug("pluginDirs: %s", settings.PluginsDirectory) + slog.Debug("pluginDirs", "directory", settings.PluginsDirectory) plugins, err := plugin.FindPlugins(settings.PluginsDirectory) if err != nil { return err diff --git a/cmd/helm/plugin_test.go b/pkg/cmd/plugin_test.go similarity index 98% rename from cmd/helm/plugin_test.go rename to pkg/cmd/plugin_test.go index e13ad26fb..7c36698b1 100644 --- a/cmd/helm/plugin_test.go +++ b/pkg/cmd/plugin_test.go @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "bytes" @@ -26,7 +26,7 @@ import ( "github.com/spf13/cobra" "github.com/spf13/pflag" - "helm.sh/helm/v3/pkg/release" + release "helm.sh/helm/v4/pkg/release/v1" ) func TestManuallyProcessArgs(t *testing.T) { @@ -143,12 +143,12 @@ func TestLoadPlugins(t *testing.T) { if runtime.GOOS != "windows" { if err := pp.RunE(pp, tt.args); err != nil { if tt.code > 0 { - perr, ok := err.(pluginError) + perr, ok := err.(PluginError) if !ok { t.Errorf("Expected %s to return pluginError: got %v(%T)", tt.use, err, err) } - if perr.code != tt.code { - t.Errorf("Expected %s to return %d: got %d", tt.use, tt.code, perr.code) + if perr.Code != tt.code { + t.Errorf("Expected %s to return %d: got %d", tt.use, tt.code, perr.Code) } } else { t.Errorf("Error running %s: %+v", tt.use, err) @@ -218,12 +218,12 @@ func TestLoadPluginsWithSpace(t *testing.T) { if runtime.GOOS != "windows" { if err := pp.RunE(pp, tt.args); err != nil { if tt.code > 0 { - perr, ok := err.(pluginError) + perr, ok := err.(PluginError) if !ok { t.Errorf("Expected %s to return pluginError: got %v(%T)", tt.use, err, err) } - if perr.code != tt.code { - t.Errorf("Expected %s to return %d: got %d", tt.use, tt.code, perr.code) + if perr.Code != tt.code { + t.Errorf("Expected %s to return %d: got %d", tt.use, tt.code, perr.Code) } } else { t.Errorf("Error running %s: %+v", tt.use, err) diff --git a/cmd/helm/plugin_uninstall.go b/pkg/cmd/plugin_uninstall.go similarity index 95% rename from cmd/helm/plugin_uninstall.go rename to pkg/cmd/plugin_uninstall.go index b9148dd86..6079e3d97 100644 --- a/cmd/helm/plugin_uninstall.go +++ b/pkg/cmd/plugin_uninstall.go @@ -13,17 +13,18 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "errors" "fmt" "io" + "log/slog" "os" "github.com/spf13/cobra" - "helm.sh/helm/v3/pkg/plugin" + "helm.sh/helm/v4/pkg/plugin" ) type pluginUninstallOptions struct { @@ -59,7 +60,7 @@ func (o *pluginUninstallOptions) complete(args []string) error { } func (o *pluginUninstallOptions) run(out io.Writer) error { - debug("loading installed plugins from %s", settings.PluginsDirectory) + slog.Debug("loading installer plugins", "dir", settings.PluginsDirectory) plugins, err := plugin.FindPlugins(settings.PluginsDirectory) if err != nil { return err diff --git a/cmd/helm/plugin_update.go b/pkg/cmd/plugin_update.go similarity index 92% rename from cmd/helm/plugin_update.go rename to pkg/cmd/plugin_update.go index 54d290597..546f50ef1 100644 --- a/cmd/helm/plugin_update.go +++ b/pkg/cmd/plugin_update.go @@ -13,18 +13,19 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "errors" "fmt" "io" + "log/slog" "path/filepath" "github.com/spf13/cobra" - "helm.sh/helm/v3/pkg/plugin" - "helm.sh/helm/v3/pkg/plugin/installer" + "helm.sh/helm/v4/pkg/plugin" + "helm.sh/helm/v4/pkg/plugin/installer" ) type pluginUpdateOptions struct { @@ -61,7 +62,7 @@ func (o *pluginUpdateOptions) complete(args []string) error { func (o *pluginUpdateOptions) run(out io.Writer) error { installer.Debug = settings.Debug - debug("loading installed plugins from %s", settings.PluginsDirectory) + slog.Debug("loading installed plugins", "path", settings.PluginsDirectory) plugins, err := plugin.FindPlugins(settings.PluginsDirectory) if err != nil { return err @@ -103,7 +104,7 @@ func updatePlugin(p *plugin.Plugin) error { return err } - debug("loading plugin from %s", i.Path()) + slog.Debug("loading plugin", "path", i.Path()) updatedPlugin, err := plugin.LoadDir(i.Path()) if err != nil { return err diff --git a/cmd/helm/printer.go b/pkg/cmd/printer.go similarity index 98% rename from cmd/helm/printer.go rename to pkg/cmd/printer.go index 7cf7bf994..30238f5bb 100644 --- a/cmd/helm/printer.go +++ b/pkg/cmd/printer.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "io" diff --git a/pkg/cmd/profiling.go b/pkg/cmd/profiling.go new file mode 100644 index 000000000..45e7b9342 --- /dev/null +++ b/pkg/cmd/profiling.go @@ -0,0 +1,91 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package cmd + +import ( + "errors" + "fmt" + "os" + "runtime" + "runtime/pprof" +) + +var ( + cpuProfileFile *os.File + cpuProfilePath string + memProfilePath string +) + +func init() { + cpuProfilePath = os.Getenv("HELM_PPROF_CPU_PROFILE") + memProfilePath = os.Getenv("HELM_PPROF_MEM_PROFILE") +} + +// startProfiling starts profiling CPU usage if HELM_PPROF_CPU_PROFILE is set +// to a file path. It returns an error if the file could not be created or +// CPU profiling could not be started. +func startProfiling() error { + if cpuProfilePath != "" { + var err error + cpuProfileFile, err = os.Create(cpuProfilePath) + if err != nil { + return fmt.Errorf("could not create CPU profile: %w", err) + } + if err := pprof.StartCPUProfile(cpuProfileFile); err != nil { + cpuProfileFile.Close() + cpuProfileFile = nil + return fmt.Errorf("could not start CPU profile: %w", err) + } + } + return nil +} + +// stopProfiling stops profiling CPU and memory usage. +// It writes memory profile to the file path specified in HELM_PPROF_MEM_PROFILE +// environment variable. +func stopProfiling() error { + errs := []error{} + + // Stop CPU profiling if it was started + if cpuProfileFile != nil { + pprof.StopCPUProfile() + err := cpuProfileFile.Close() + if err != nil { + errs = append(errs, err) + } + cpuProfileFile = nil + } + + if memProfilePath != "" { + f, err := os.Create(memProfilePath) + if err != nil { + errs = append(errs, err) + } + defer f.Close() + + runtime.GC() // get up-to-date statistics + if err := pprof.WriteHeapProfile(f); err != nil { + errs = append(errs, err) + } + } + + if err := errors.Join(errs...); err != nil { + return fmt.Errorf("error(s) while stopping profiling: %w", err) + } + + return nil +} diff --git a/cmd/helm/pull.go b/pkg/cmd/pull.go similarity index 92% rename from cmd/helm/pull.go rename to pkg/cmd/pull.go index e0dd1effe..e3d93c049 100644 --- a/cmd/helm/pull.go +++ b/pkg/cmd/pull.go @@ -14,17 +14,18 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "fmt" "io" "log" + "log/slog" "github.com/spf13/cobra" - "helm.sh/helm/v3/cmd/helm/require" - "helm.sh/helm/v3/pkg/action" + "helm.sh/helm/v4/pkg/action" + "helm.sh/helm/v4/pkg/cmd/require" ) const pullDesc = ` @@ -43,7 +44,7 @@ result in an error, and the chart will not be saved locally. ` func newPullCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { - client := action.NewPullWithOpts(action.WithConfig(cfg)) + client := action.NewPull(action.WithConfig(cfg)) cmd := &cobra.Command{ Use: "pull [chart URL | repo/chartname] [...]", @@ -60,12 +61,12 @@ func newPullCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { RunE: func(_ *cobra.Command, args []string) error { client.Settings = settings if client.Version == "" && client.Devel { - debug("setting version to >0.0.0-0") + slog.Debug("setting version to >0.0.0-0") client.Version = ">0.0.0-0" } registryClient, err := newRegistryClient(client.CertFile, client.KeyFile, client.CaFile, - client.InsecureSkipTLSverify, client.PlainHTTP) + client.InsecureSkipTLSverify, client.PlainHTTP, client.Username, client.Password) if err != nil { return fmt.Errorf("missing registry client: %w", err) } diff --git a/cmd/helm/pull_test.go b/pkg/cmd/pull_test.go similarity index 91% rename from cmd/helm/pull_test.go rename to pkg/cmd/pull_test.go index ae70595f9..c30c94b49 100644 --- a/cmd/helm/pull_test.go +++ b/pkg/cmd/pull_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "fmt" @@ -24,14 +24,14 @@ import ( "path/filepath" "testing" - "helm.sh/helm/v3/pkg/repo/repotest" + "helm.sh/helm/v4/pkg/repo/repotest" ) func TestPullCmd(t *testing.T) { - srv, err := repotest.NewTempServerWithCleanup(t, "testdata/testcharts/*.tgz*") - if err != nil { - t.Fatal(err) - } + srv := repotest.NewTempServer( + t, + repotest.WithChartSourceGlob("testdata/testcharts/*.tgz*"), + ) defer srv.Stop() ociSrv, err := repotest.NewOCIServer(t, srv.Root()) @@ -183,22 +183,27 @@ func TestPullCmd(t *testing.T) { wantError: true, }, { - name: "Fail fetching OCI chart without version specified", - args: fmt.Sprintf("oci://%s/u/ocitestuser/oci-dependent-chart:0.1.0", ociSrv.RegistryURL), - wantErrorMsg: "Error: --version flag is explicitly required for OCI registries", - wantError: true, + name: "Fetching OCI chart without version option specified", + args: fmt.Sprintf("oci://%s/u/ocitestuser/oci-dependent-chart:0.1.0", ociSrv.RegistryURL), + expectFile: "./oci-dependent-chart-0.1.0.tgz", }, { - name: "Fail fetching OCI chart without version specified", - args: fmt.Sprintf("oci://%s/u/ocitestuser/oci-dependent-chart:0.1.0 --version 0.1.0", ociSrv.RegistryURL), - wantError: true, + name: "Fetching OCI chart with version specified", + args: fmt.Sprintf("oci://%s/u/ocitestuser/oci-dependent-chart:0.1.0 --version 0.1.0", ociSrv.RegistryURL), + expectFile: "./oci-dependent-chart-0.1.0.tgz", + }, + { + name: "Fail fetching OCI chart with version mismatch", + args: fmt.Sprintf("oci://%s/u/ocitestuser/oci-dependent-chart:0.2.0 --version 0.1.0", ociSrv.RegistryURL), + wantErrorMsg: "Error: chart reference and version mismatch: 0.2.0 is not 0.1.0", + wantError: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { outdir := srv.Root() - cmd := fmt.Sprintf("fetch %s -d '%s' --repository-config %s --repository-cache %s --registry-config %s", + cmd := fmt.Sprintf("fetch %s -d '%s' --repository-config %s --repository-cache %s --registry-config %s --plain-http", tt.args, outdir, filepath.Join(outdir, "repositories.yaml"), @@ -252,19 +257,13 @@ func TestPullCmd(t *testing.T) { } func TestPullWithCredentialsCmd(t *testing.T) { - srv, err := repotest.NewTempServerWithCleanup(t, "testdata/testcharts/*.tgz*") - if err != nil { - t.Fatal(err) - } + srv := repotest.NewTempServer( + t, + repotest.WithChartSourceGlob("testdata/testcharts/*.tgz*"), + repotest.WithMiddleware(repotest.BasicAuthMiddleware(t)), + ) defer srv.Stop() - srv.WithMiddleware(http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) { - username, password, ok := r.BasicAuth() - if !ok || username != "username" || password != "password" { - t.Errorf("Expected request to use basic auth and for username == 'username' and password == 'password', got '%v', '%s', '%s'", ok, username, password) - } - })) - srv2 := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { http.FileServer(http.Dir(srv.Root())).ServeHTTP(w, r) })) diff --git a/cmd/helm/push.go b/pkg/cmd/push.go similarity index 84% rename from cmd/helm/push.go rename to pkg/cmd/push.go index 6ec26441d..94d322b9d 100644 --- a/cmd/helm/push.go +++ b/pkg/cmd/push.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "fmt" @@ -22,9 +22,9 @@ import ( "github.com/spf13/cobra" - "helm.sh/helm/v3/cmd/helm/require" - "helm.sh/helm/v3/pkg/action" - "helm.sh/helm/v3/pkg/pusher" + "helm.sh/helm/v4/pkg/action" + "helm.sh/helm/v4/pkg/cmd/require" + "helm.sh/helm/v4/pkg/pusher" ) const pushDesc = ` @@ -40,6 +40,8 @@ type registryPushOptions struct { caFile string insecureSkipTLSverify bool plainHTTP bool + password string + username string } func newPushCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { @@ -68,7 +70,10 @@ func newPushCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { return noMoreArgsComp() }, RunE: func(_ *cobra.Command, args []string) error { - registryClient, err := newRegistryClient(o.certFile, o.keyFile, o.caFile, o.insecureSkipTLSverify, o.plainHTTP) + registryClient, err := newRegistryClient( + o.certFile, o.keyFile, o.caFile, o.insecureSkipTLSverify, o.plainHTTP, o.username, o.password, + ) + if err != nil { return fmt.Errorf("missing registry client: %w", err) } @@ -96,6 +101,8 @@ func newPushCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { f.StringVar(&o.caFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle") f.BoolVar(&o.insecureSkipTLSverify, "insecure-skip-tls-verify", false, "skip tls certificate checks for the chart upload") f.BoolVar(&o.plainHTTP, "plain-http", false, "use insecure HTTP connections for the chart upload") + f.StringVar(&o.username, "username", "", "chart repository username where to locate the requested chart") + f.StringVar(&o.password, "password", "", "chart repository password where to locate the requested chart") return cmd } diff --git a/cmd/helm/push_test.go b/pkg/cmd/push_test.go similarity index 98% rename from cmd/helm/push_test.go rename to pkg/cmd/push_test.go index 8e56d99dc..80d08b48f 100644 --- a/cmd/helm/push_test.go +++ b/pkg/cmd/push_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "testing" diff --git a/cmd/helm/registry.go b/pkg/cmd/registry.go similarity index 95% rename from cmd/helm/registry.go rename to pkg/cmd/registry.go index b2b24cd14..fcd06f13b 100644 --- a/cmd/helm/registry.go +++ b/pkg/cmd/registry.go @@ -13,14 +13,14 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "io" "github.com/spf13/cobra" - "helm.sh/helm/v3/pkg/action" + "helm.sh/helm/v4/pkg/action" ) const registryHelp = ` diff --git a/cmd/helm/registry_login.go b/pkg/cmd/registry_login.go similarity index 91% rename from cmd/helm/registry_login.go rename to pkg/cmd/registry_login.go index 96020a530..3719c1c17 100644 --- a/cmd/helm/registry_login.go +++ b/pkg/cmd/registry_login.go @@ -14,21 +14,22 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "bufio" "errors" "fmt" "io" + "log/slog" "os" "strings" "github.com/moby/term" "github.com/spf13/cobra" - "helm.sh/helm/v3/cmd/helm/require" - "helm.sh/helm/v3/pkg/action" + "helm.sh/helm/v4/pkg/action" + "helm.sh/helm/v4/pkg/cmd/require" ) const registryLoginDesc = ` @@ -43,6 +44,7 @@ type registryLoginOptions struct { keyFile string caFile string insecure bool + plainHTTP bool } func newRegistryLoginCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { @@ -66,7 +68,8 @@ func newRegistryLoginCmd(cfg *action.Configuration, out io.Writer) *cobra.Comman action.WithCertFile(o.certFile), action.WithKeyFile(o.keyFile), action.WithCAFile(o.caFile), - action.WithInsecure(o.insecure)) + action.WithInsecure(o.insecure), + action.WithPlainHTTPLogin(o.plainHTTP)) }, } @@ -78,6 +81,7 @@ func newRegistryLoginCmd(cfg *action.Configuration, out io.Writer) *cobra.Comman f.StringVar(&o.certFile, "cert-file", "", "identify registry client using this SSL certificate file") f.StringVar(&o.keyFile, "key-file", "", "identify registry client using this SSL key file") f.StringVar(&o.caFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle") + f.BoolVar(&o.plainHTTP, "plain-http", false, "use insecure HTTP connections for the chart upload") return cmd } @@ -119,7 +123,7 @@ func getUsernamePassword(usernameOpt string, passwordOpt string, passwordFromStd } } } else { - warning("Using --password via the CLI is insecure. Use --password-stdin.") + slog.Warn("using --password via the CLI is insecure. Use --password-stdin") } return username, password, nil diff --git a/cmd/helm/registry_login_test.go b/pkg/cmd/registry_login_test.go similarity index 98% rename from cmd/helm/registry_login_test.go rename to pkg/cmd/registry_login_test.go index 517fe08e1..6e4f2116e 100644 --- a/cmd/helm/registry_login_test.go +++ b/pkg/cmd/registry_login_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "testing" diff --git a/cmd/helm/registry_logout.go b/pkg/cmd/registry_logout.go similarity index 93% rename from cmd/helm/registry_logout.go rename to pkg/cmd/registry_logout.go index 4ca4120c4..300453705 100644 --- a/cmd/helm/registry_logout.go +++ b/pkg/cmd/registry_logout.go @@ -14,15 +14,15 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "io" "github.com/spf13/cobra" - "helm.sh/helm/v3/cmd/helm/require" - "helm.sh/helm/v3/pkg/action" + "helm.sh/helm/v4/pkg/action" + "helm.sh/helm/v4/pkg/cmd/require" ) const registryLogoutDesc = ` diff --git a/cmd/helm/registry_logout_test.go b/pkg/cmd/registry_logout_test.go similarity index 98% rename from cmd/helm/registry_logout_test.go rename to pkg/cmd/registry_logout_test.go index 31f716725..31a21b277 100644 --- a/cmd/helm/registry_logout_test.go +++ b/pkg/cmd/registry_logout_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "testing" diff --git a/cmd/helm/release_testing.go b/pkg/cmd/release_testing.go similarity index 90% rename from cmd/helm/release_testing.go rename to pkg/cmd/release_testing.go index 136d785d1..4904aa9f1 100644 --- a/cmd/helm/release_testing.go +++ b/pkg/cmd/release_testing.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "fmt" @@ -25,9 +25,9 @@ import ( "github.com/spf13/cobra" - "helm.sh/helm/v3/cmd/helm/require" - "helm.sh/helm/v3/pkg/action" - "helm.sh/helm/v3/pkg/cli/output" + "helm.sh/helm/v4/pkg/action" + "helm.sh/helm/v4/pkg/cli/output" + "helm.sh/helm/v4/pkg/cmd/require" ) const releaseTestHelp = ` @@ -39,7 +39,7 @@ The tests to be run are defined in the chart that was installed. func newReleaseTestCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { client := action.NewReleaseTesting(cfg) - var outfmt = output.Table + outfmt := output.Table var outputLogs bool var filter []string @@ -72,7 +72,12 @@ func newReleaseTestCmd(cfg *action.Configuration, out io.Writer) *cobra.Command return runErr } - if err := outfmt.Write(out, &statusPrinter{rel, settings.Debug, false, false, false, client.HideNotes}); err != nil { + if err := outfmt.Write(out, &statusPrinter{ + release: rel, + debug: settings.Debug, + showMetadata: false, + hideNotes: client.HideNotes, + }); err != nil { return err } diff --git a/cmd/helm/release_testing_test.go b/pkg/cmd/release_testing_test.go similarity index 98% rename from cmd/helm/release_testing_test.go rename to pkg/cmd/release_testing_test.go index 680a9bd3e..43599ad0d 100644 --- a/cmd/helm/release_testing_test.go +++ b/pkg/cmd/release_testing_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "testing" diff --git a/cmd/helm/repo.go b/pkg/cmd/repo.go similarity index 96% rename from cmd/helm/repo.go rename to pkg/cmd/repo.go index 11121be5b..0dc2a7175 100644 --- a/cmd/helm/repo.go +++ b/pkg/cmd/repo.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "errors" @@ -23,7 +23,7 @@ import ( "github.com/spf13/cobra" - "helm.sh/helm/v3/cmd/helm/require" + "helm.sh/helm/v4/pkg/cmd/require" ) var repoHelm = ` diff --git a/cmd/helm/repo_add.go b/pkg/cmd/repo_add.go similarity index 95% rename from cmd/helm/repo_add.go rename to pkg/cmd/repo_add.go index 5dc7ad0b9..24c1eecab 100644 --- a/cmd/helm/repo_add.go +++ b/pkg/cmd/repo_add.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "context" @@ -32,9 +32,9 @@ import ( "golang.org/x/term" "sigs.k8s.io/yaml" - "helm.sh/helm/v3/cmd/helm/require" - "helm.sh/helm/v3/pkg/getter" - "helm.sh/helm/v3/pkg/repo" + "helm.sh/helm/v4/pkg/cmd/require" + "helm.sh/helm/v4/pkg/getter" + "helm.sh/helm/v4/pkg/repo" ) // Repositories that have been permanently deleted and no longer work @@ -60,9 +60,6 @@ type repoAddOptions struct { repoFile string repoCache string - - // Deprecated, but cannot be removed until Helm 4 - deprecatedNoUpdate bool } func newRepoAddCmd(out io.Writer) *cobra.Command { @@ -93,7 +90,6 @@ func newRepoAddCmd(out io.Writer) *cobra.Command { f.StringVar(&o.password, "password", "", "chart repository password") f.BoolVarP(&o.passwordFromStdinOpt, "password-stdin", "", false, "read chart repository password from stdin") f.BoolVar(&o.forceUpdate, "force-update", false, "replace (overwrite) the repo if it already exists") - f.BoolVar(&o.deprecatedNoUpdate, "no-update", false, "Ignored. Formerly, it would disabled forced updates. It is deprecated by force-update.") f.StringVar(&o.certFile, "cert-file", "", "identify HTTPS client using this SSL certificate file") f.StringVar(&o.keyFile, "key-file", "", "identify HTTPS client using this SSL key file") f.StringVar(&o.caFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle") diff --git a/cmd/helm/repo_add_test.go b/pkg/cmd/repo_add_test.go similarity index 82% rename from cmd/helm/repo_add_test.go rename to pkg/cmd/repo_add_test.go index fa95ae1fa..05b5ee53e 100644 --- a/cmd/helm/repo_add_test.go +++ b/pkg/cmd/repo_add_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "errors" @@ -29,29 +29,28 @@ import ( "sigs.k8s.io/yaml" - "helm.sh/helm/v3/pkg/helmpath" - "helm.sh/helm/v3/pkg/helmpath/xdg" - "helm.sh/helm/v3/pkg/repo" - "helm.sh/helm/v3/pkg/repo/repotest" + "helm.sh/helm/v4/pkg/helmpath" + "helm.sh/helm/v4/pkg/helmpath/xdg" + "helm.sh/helm/v4/pkg/repo" + "helm.sh/helm/v4/pkg/repo/repotest" ) func TestRepoAddCmd(t *testing.T) { - srv, err := repotest.NewTempServerWithCleanup(t, "testdata/testserver/*.*") - if err != nil { - t.Fatal(err) - } + srv := repotest.NewTempServer( + t, + repotest.WithChartSourceGlob("testdata/testserver/*.*"), + ) defer srv.Stop() // A second test server is setup to verify URL changing - srv2, err := repotest.NewTempServerWithCleanup(t, "testdata/testserver/*.*") - if err != nil { - t.Fatal(err) - } + srv2 := repotest.NewTempServer( + t, + repotest.WithChartSourceGlob("testdata/testserver/*.*"), + ) defer srv2.Stop() tmpdir := filepath.Join(t.TempDir(), "path-component.yaml/data") - err = os.MkdirAll(tmpdir, 0777) - if err != nil { + if err := os.MkdirAll(tmpdir, 0777); err != nil { t.Fatal(err) } repoFile := filepath.Join(tmpdir, "repositories.yaml") @@ -83,10 +82,10 @@ func TestRepoAddCmd(t *testing.T) { } func TestRepoAdd(t *testing.T) { - ts, err := repotest.NewTempServerWithCleanup(t, "testdata/testserver/*.*") - if err != nil { - t.Fatal(err) - } + ts := repotest.NewTempServer( + t, + repotest.WithChartSourceGlob("testdata/testserver/*.*"), + ) defer ts.Stop() rootDir := t.TempDir() @@ -95,11 +94,10 @@ func TestRepoAdd(t *testing.T) { const testRepoName = "test-name" o := &repoAddOptions{ - name: testRepoName, - url: ts.URL(), - forceUpdate: false, - deprecatedNoUpdate: true, - repoFile: repoFile, + name: testRepoName, + url: ts.URL(), + forceUpdate: false, + repoFile: repoFile, } os.Setenv(xdg.CacheHomeEnvVar, rootDir) @@ -137,10 +135,10 @@ func TestRepoAdd(t *testing.T) { } func TestRepoAddCheckLegalName(t *testing.T) { - ts, err := repotest.NewTempServerWithCleanup(t, "testdata/testserver/*.*") - if err != nil { - t.Fatal(err) - } + ts := repotest.NewTempServer( + t, + repotest.WithChartSourceGlob("testdata/testserver/*.*"), + ) defer ts.Stop() defer resetEnv()() @@ -150,11 +148,10 @@ func TestRepoAddCheckLegalName(t *testing.T) { repoFile := filepath.Join(t.TempDir(), "repositories.yaml") o := &repoAddOptions{ - name: testRepoName, - url: ts.URL(), - forceUpdate: false, - deprecatedNoUpdate: true, - repoFile: repoFile, + name: testRepoName, + url: ts.URL(), + forceUpdate: false, + repoFile: repoFile, } os.Setenv(xdg.CacheHomeEnvVar, rootDir) @@ -194,10 +191,10 @@ func TestRepoAddConcurrentHiddenFile(t *testing.T) { } func repoAddConcurrent(t *testing.T, testName, repoFile string) { - ts, err := repotest.NewTempServerWithCleanup(t, "testdata/testserver/*.*") - if err != nil { - t.Fatal(err) - } + ts := repotest.NewTempServer( + t, + repotest.WithChartSourceGlob("testdata/testserver/*.*"), + ) defer ts.Stop() var wg sync.WaitGroup @@ -206,11 +203,10 @@ func repoAddConcurrent(t *testing.T, testName, repoFile string) { go func(name string) { defer wg.Done() o := &repoAddOptions{ - name: name, - url: ts.URL(), - deprecatedNoUpdate: true, - forceUpdate: false, - repoFile: repoFile, + name: name, + url: ts.URL(), + forceUpdate: false, + repoFile: repoFile, } if err := o.run(io.Discard); err != nil { t.Error(err) @@ -245,7 +241,11 @@ func TestRepoAddFileCompletion(t *testing.T) { } func TestRepoAddWithPasswordFromStdin(t *testing.T) { - srv := repotest.NewTempServerWithCleanupAndBasicAuth(t, "testdata/testserver/*.*") + srv := repotest.NewTempServer( + t, + repotest.WithChartSourceGlob("testdata/testserver/*.*"), + repotest.WithMiddleware(repotest.BasicAuthMiddleware(t)), + ) defer srv.Stop() defer resetEnv()() diff --git a/cmd/helm/repo_index.go b/pkg/cmd/repo_index.go similarity index 97% rename from cmd/helm/repo_index.go rename to pkg/cmd/repo_index.go index c87d2b878..c17fd9391 100644 --- a/cmd/helm/repo_index.go +++ b/pkg/cmd/repo_index.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "errors" @@ -26,8 +26,8 @@ import ( "github.com/spf13/cobra" - "helm.sh/helm/v3/cmd/helm/require" - "helm.sh/helm/v3/pkg/repo" + "helm.sh/helm/v4/pkg/cmd/require" + "helm.sh/helm/v4/pkg/repo" ) const repoIndexDesc = ` diff --git a/cmd/helm/repo_index_test.go b/pkg/cmd/repo_index_test.go similarity index 96% rename from cmd/helm/repo_index_test.go rename to pkg/cmd/repo_index_test.go index 554a3dadf..c865c8a5d 100644 --- a/cmd/helm/repo_index_test.go +++ b/pkg/cmd/repo_index_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "bytes" @@ -24,7 +24,7 @@ import ( "path/filepath" "testing" - "helm.sh/helm/v3/pkg/repo" + "helm.sh/helm/v4/pkg/repo" ) func TestRepoIndexCmd(t *testing.T) { @@ -162,9 +162,9 @@ func TestRepoIndexCmd(t *testing.T) { } } -func linkOrCopy(old, new string) error { - if err := os.Link(old, new); err != nil { - return copyFile(old, new) +func linkOrCopy(source, target string) error { + if err := os.Link(source, target); err != nil { + return copyFile(source, target) } return nil diff --git a/cmd/helm/repo_list.go b/pkg/cmd/repo_list.go similarity index 94% rename from cmd/helm/repo_list.go rename to pkg/cmd/repo_list.go index 7ac83b489..60c879984 100644 --- a/cmd/helm/repo_list.go +++ b/pkg/cmd/repo_list.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "errors" @@ -24,9 +24,9 @@ import ( "github.com/gosuri/uitable" "github.com/spf13/cobra" - "helm.sh/helm/v3/cmd/helm/require" - "helm.sh/helm/v3/pkg/cli/output" - "helm.sh/helm/v3/pkg/repo" + "helm.sh/helm/v4/pkg/cli/output" + "helm.sh/helm/v4/pkg/cmd/require" + "helm.sh/helm/v4/pkg/repo" ) func newRepoListCmd(out io.Writer) *cobra.Command { @@ -39,7 +39,7 @@ func newRepoListCmd(out io.Writer) *cobra.Command { ValidArgsFunction: noMoreArgsCompFunc, RunE: func(_ *cobra.Command, _ []string) error { 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") } diff --git a/cmd/helm/repo_list_test.go b/pkg/cmd/repo_list_test.go similarity index 98% rename from cmd/helm/repo_list_test.go rename to pkg/cmd/repo_list_test.go index 90149ebda..1da5484cc 100644 --- a/cmd/helm/repo_list_test.go +++ b/pkg/cmd/repo_list_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "testing" diff --git a/cmd/helm/repo_remove.go b/pkg/cmd/repo_remove.go similarity index 95% rename from cmd/helm/repo_remove.go rename to pkg/cmd/repo_remove.go index 150f8dc2e..d0a3aa205 100644 --- a/cmd/helm/repo_remove.go +++ b/pkg/cmd/repo_remove.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "errors" @@ -26,9 +26,9 @@ import ( "github.com/spf13/cobra" - "helm.sh/helm/v3/cmd/helm/require" - "helm.sh/helm/v3/pkg/helmpath" - "helm.sh/helm/v3/pkg/repo" + "helm.sh/helm/v4/pkg/cmd/require" + "helm.sh/helm/v4/pkg/helmpath" + "helm.sh/helm/v4/pkg/repo" ) type repoRemoveOptions struct { diff --git a/cmd/helm/repo_remove_test.go b/pkg/cmd/repo_remove_test.go similarity index 94% rename from cmd/helm/repo_remove_test.go rename to pkg/cmd/repo_remove_test.go index e2795e738..b8bc7179a 100644 --- a/cmd/helm/repo_remove_test.go +++ b/pkg/cmd/repo_remove_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "bytes" @@ -24,16 +24,16 @@ import ( "strings" "testing" - "helm.sh/helm/v3/pkg/helmpath" - "helm.sh/helm/v3/pkg/repo" - "helm.sh/helm/v3/pkg/repo/repotest" + "helm.sh/helm/v4/pkg/helmpath" + "helm.sh/helm/v4/pkg/repo" + "helm.sh/helm/v4/pkg/repo/repotest" ) func TestRepoRemove(t *testing.T) { - ts, err := repotest.NewTempServerWithCleanup(t, "testdata/testserver/*.*") - if err != nil { - t.Fatal(err) - } + ts := repotest.NewTempServer( + t, + repotest.WithChartSourceGlob("testdata/testserver/*.*"), + ) defer ts.Stop() rootDir := t.TempDir() @@ -162,10 +162,11 @@ func testCacheFiles(t *testing.T, cacheIndexFile string, cacheChartsFile string, } func TestRepoRemoveCompletion(t *testing.T) { - ts, err := repotest.NewTempServerWithCleanup(t, "testdata/testserver/*.*") - if err != nil { - t.Fatal(err) - } + ts := repotest.NewTempServer( + t, + repotest.WithChartSourceGlob("testdata/testserver/*.*"), + ) + defer ts.Stop() rootDir := t.TempDir() diff --git a/cmd/helm/repo_test.go b/pkg/cmd/repo_test.go similarity index 98% rename from cmd/helm/repo_test.go rename to pkg/cmd/repo_test.go index 2b0df7c4c..6b89a66c3 100644 --- a/cmd/helm/repo_test.go +++ b/pkg/cmd/repo_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "testing" diff --git a/cmd/helm/repo_update.go b/pkg/cmd/repo_update.go similarity index 77% rename from cmd/helm/repo_update.go rename to pkg/cmd/repo_update.go index 186f7c3b4..9f4a603ae 100644 --- a/cmd/helm/repo_update.go +++ b/pkg/cmd/repo_update.go @@ -14,19 +14,20 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "errors" "fmt" "io" + "slices" "sync" "github.com/spf13/cobra" - "helm.sh/helm/v3/cmd/helm/require" - "helm.sh/helm/v3/pkg/getter" - "helm.sh/helm/v3/pkg/repo" + "helm.sh/helm/v4/pkg/cmd/require" + "helm.sh/helm/v4/pkg/getter" + "helm.sh/helm/v4/pkg/repo" ) const updateDesc = ` @@ -41,11 +42,10 @@ To update all the repositories, use 'helm repo update'. var errNoRepositories = errors.New("no repositories found. You must add one before updating") type repoUpdateOptions struct { - update func([]*repo.ChartRepository, io.Writer, bool) error - repoFile string - repoCache string - names []string - failOnRepoUpdateFail bool + update func([]*repo.ChartRepository, io.Writer) error + repoFile string + repoCache string + names []string } func newRepoUpdateCmd(out io.Writer) *cobra.Command { @@ -68,12 +68,6 @@ func newRepoUpdateCmd(out io.Writer) *cobra.Command { }, } - f := cmd.Flags() - - // Adding this flag for Helm 3 as stop gap functionality for https://github.com/helm/helm/issues/10016. - // This should be deprecated in Helm 4 by update to the behaviour of `helm repo update` command. - f.BoolVar(&o.failOnRepoUpdateFail, "fail-on-repo-update-fail", false, "update fails if any of the repository updates fail") - return cmd } @@ -111,29 +105,39 @@ func (o *repoUpdateOptions) run(out io.Writer) error { } } - return o.update(repos, out, o.failOnRepoUpdateFail) + return o.update(repos, out) } -func updateCharts(repos []*repo.ChartRepository, out io.Writer, failOnRepoUpdateFail bool) error { +func updateCharts(repos []*repo.ChartRepository, out io.Writer) error { fmt.Fprintln(out, "Hang tight while we grab the latest from your chart repositories...") var wg sync.WaitGroup - var repoFailList []string + failRepoURLChan := make(chan string, len(repos)) + for _, re := range repos { wg.Add(1) go func(re *repo.ChartRepository) { defer wg.Done() if _, err := re.DownloadIndexFile(); err != nil { 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) - repoFailList = append(repoFailList, re.Config.URL) + failRepoURLChan <- re.Config.URL } else { fmt.Fprintf(out, "...Successfully got an update from the %q chart repository\n", re.Config.Name) } }(re) } - wg.Wait() - if len(repoFailList) > 0 && failOnRepoUpdateFail { - return fmt.Errorf("Failed to update the following repositories: %s", + go func() { + wg.Wait() + close(failRepoURLChan) + }() + + var repoFailList []string + for url := range failRepoURLChan { + repoFailList = append(repoFailList, url) + } + + if len(repoFailList) > 0 { + return fmt.Errorf("failed to update the following repositories: %s", repoFailList) } @@ -158,10 +162,5 @@ func checkRequestedRepos(requestedRepos []string, validRepos []*repo.Entry) erro } func isRepoRequested(repoName string, requestedRepos []string) bool { - for _, requestedRepo := range requestedRepos { - if repoName == requestedRepo { - return true - } - } - return false + return slices.Contains(requestedRepos, repoName) } diff --git a/cmd/helm/repo_update_test.go b/pkg/cmd/repo_update_test.go similarity index 74% rename from cmd/helm/repo_update_test.go rename to pkg/cmd/repo_update_test.go index 1ddf0f5ed..b0deff1ae 100644 --- a/cmd/helm/repo_update_test.go +++ b/pkg/cmd/repo_update_test.go @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "bytes" @@ -24,17 +24,17 @@ import ( "strings" "testing" - "helm.sh/helm/v3/internal/test/ensure" - "helm.sh/helm/v3/pkg/getter" - "helm.sh/helm/v3/pkg/repo" - "helm.sh/helm/v3/pkg/repo/repotest" + "helm.sh/helm/v4/internal/test/ensure" + "helm.sh/helm/v4/pkg/getter" + "helm.sh/helm/v4/pkg/repo" + "helm.sh/helm/v4/pkg/repo/repotest" ) func TestUpdateCmd(t *testing.T) { var out bytes.Buffer // Instead of using the HTTP updater, we provide our own for this test. // The TestUpdateCharts test verifies the HTTP behavior independently. - updater := func(repos []*repo.ChartRepository, out io.Writer, _ bool) error { + updater := func(repos []*repo.ChartRepository, out io.Writer) error { for _, re := range repos { fmt.Fprintln(out, re.Config.Name) } @@ -59,7 +59,7 @@ func TestUpdateCmdMultiple(t *testing.T) { var out bytes.Buffer // Instead of using the HTTP updater, we provide our own for this test. // The TestUpdateCharts test verifies the HTTP behavior independently. - updater := func(repos []*repo.ChartRepository, out io.Writer, _ bool) error { + updater := func(repos []*repo.ChartRepository, out io.Writer) error { for _, re := range repos { fmt.Fprintln(out, re.Config.Name) } @@ -85,7 +85,7 @@ func TestUpdateCmdInvalid(t *testing.T) { var out bytes.Buffer // Instead of using the HTTP updater, we provide our own for this test. // The TestUpdateCharts test verifies the HTTP behavior independently. - updater := func(repos []*repo.ChartRepository, out io.Writer, _ bool) error { + updater := func(repos []*repo.ChartRepository, out io.Writer) error { for _, re := range repos { fmt.Fprintln(out, re.Config.Name) } @@ -106,10 +106,11 @@ func TestUpdateCustomCacheCmd(t *testing.T) { cachePath := filepath.Join(rootDir, "updcustomcache") os.Mkdir(cachePath, os.ModePerm) - ts, err := repotest.NewTempServerWithCleanup(t, "testdata/testserver/*.*") - if err != nil { - t.Fatal(err) - } + ts := repotest.NewTempServer( + t, + repotest.WithChartSourceGlob("testdata/testserver/*.*"), + ) + defer ts.Stop() o := &repoUpdateOptions{ @@ -130,10 +131,9 @@ func TestUpdateCharts(t *testing.T) { defer resetEnv()() ensure.HelmHome(t) - ts, err := repotest.NewTempServerWithCleanup(t, "testdata/testserver/*.*") - if err != nil { - t.Fatal(err) - } + ts := repotest.NewTempServer(t, + repotest.WithChartSourceGlob("testdata/testserver/*.*"), + ) defer ts.Stop() r, err := repo.NewChartRepository(&repo.Entry{ @@ -145,7 +145,7 @@ func TestUpdateCharts(t *testing.T) { } b := bytes.NewBuffer(nil) - updateCharts([]*repo.ChartRepository{r}, b, false) + updateCharts([]*repo.ChartRepository{r}, b) got := b.String() if strings.Contains(got, "Unable to get an update") { @@ -161,51 +161,25 @@ func TestRepoUpdateFileCompletion(t *testing.T) { checkFileCompletion(t, "repo update repo1", false) } -func TestUpdateChartsFail(t *testing.T) { +func TestUpdateChartsFailWithError(t *testing.T) { defer resetEnv()() ensure.HelmHome(t) - ts, err := repotest.NewTempServerWithCleanup(t, "testdata/testserver/*.*") - if err != nil { - t.Fatal(err) - } + ts := repotest.NewTempServer( + t, + repotest.WithChartSourceGlob("testdata/testserver/*.*"), + ) defer ts.Stop() var invalidURL = ts.URL() + "55" - r, err := repo.NewChartRepository(&repo.Entry{ + r1, err := repo.NewChartRepository(&repo.Entry{ Name: "charts", URL: invalidURL, }, getter.All(settings)) if err != nil { t.Error(err) } - - b := bytes.NewBuffer(nil) - if err := updateCharts([]*repo.ChartRepository{r}, b, false); err != nil { - t.Error("Repo update should not return error if update of repository fails") - } - - got := b.String() - if !strings.Contains(got, "Unable to get an update") { - t.Errorf("Repo should have failed update but instead got: %q", got) - } - if !strings.Contains(got, "Update Complete.") { - t.Error("Update was not successful") - } -} - -func TestUpdateChartsFailWithError(t *testing.T) { - defer resetEnv()() - ensure.HelmHome(t) - - ts, err := repotest.NewTempServerWithCleanup(t, "testdata/testserver/*.*") - if err != nil { - t.Fatal(err) - } - defer ts.Stop() - - var invalidURL = ts.URL() + "55" - r, err := repo.NewChartRepository(&repo.Entry{ + r2, err := repo.NewChartRepository(&repo.Entry{ Name: "charts", URL: invalidURL, }, getter.All(settings)) @@ -214,12 +188,12 @@ func TestUpdateChartsFailWithError(t *testing.T) { } b := bytes.NewBuffer(nil) - err = updateCharts([]*repo.ChartRepository{r}, b, true) + err = updateCharts([]*repo.ChartRepository{r1, r2}, b) if err == nil { t.Error("Repo update should return error because update of repository fails and 'fail-on-repo-update-fail' flag set") return } - var expectedErr = "Failed to update the following repositories" + var expectedErr = "failed to update the following repositories" var receivedErr = err.Error() if !strings.Contains(receivedErr, expectedErr) { t.Errorf("Expected error (%s) but got (%s) instead", expectedErr, receivedErr) diff --git a/cmd/helm/require/args.go b/pkg/cmd/require/args.go similarity index 100% rename from cmd/helm/require/args.go rename to pkg/cmd/require/args.go diff --git a/cmd/helm/require/args_test.go b/pkg/cmd/require/args_test.go similarity index 97% rename from cmd/helm/require/args_test.go rename to pkg/cmd/require/args_test.go index 5a84a42d0..cd5850650 100644 --- a/cmd/helm/require/args_test.go +++ b/pkg/cmd/require/args_test.go @@ -71,7 +71,8 @@ func runTestCases(t *testing.T, testCases []testCase) { Args: tc.validateFunc, } cmd.SetArgs(tc.args) - cmd.SetOutput(io.Discard) + cmd.SetOut(io.Discard) + cmd.SetErr(io.Discard) err := cmd.Execute() if tc.wantError == "" { diff --git a/cmd/helm/rollback.go b/pkg/cmd/rollback.go similarity index 89% rename from cmd/helm/rollback.go rename to pkg/cmd/rollback.go index 3dab4689f..1823432dc 100644 --- a/cmd/helm/rollback.go +++ b/pkg/cmd/rollback.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "fmt" @@ -24,8 +24,8 @@ import ( "github.com/spf13/cobra" - "helm.sh/helm/v3/cmd/helm/require" - "helm.sh/helm/v3/pkg/action" + "helm.sh/helm/v4/pkg/action" + "helm.sh/helm/v4/pkg/cmd/require" ) const rollbackDesc = ` @@ -81,10 +81,10 @@ func newRollbackCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { f.BoolVar(&client.Force, "force", false, "force resource update through delete/recreate if needed") f.BoolVar(&client.DisableHooks, "no-hooks", false, "prevent hooks from running during rollback") f.DurationVar(&client.Timeout, "timeout", 300*time.Second, "time to wait for any individual Kubernetes operation (like Jobs for hooks)") - f.BoolVar(&client.Wait, "wait", false, "if set, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment, StatefulSet, or ReplicaSet are in a ready state before marking the release as successful. It will wait for as long as --timeout") f.BoolVar(&client.WaitForJobs, "wait-for-jobs", false, "if set and --wait enabled, will wait until all Jobs have been completed before marking the release as successful. It will wait for as long as --timeout") f.BoolVar(&client.CleanupOnFail, "cleanup-on-fail", false, "allow deletion of new resources created in this rollback when rollback fails") f.IntVar(&client.MaxHistory, "history-max", settings.MaxHistory, "limit the maximum number of revisions saved per release. Use 0 for no limit") + AddWaitFlag(cmd, &client.WaitStrategy) return cmd } diff --git a/cmd/helm/rollback_test.go b/pkg/cmd/rollback_test.go similarity index 97% rename from cmd/helm/rollback_test.go rename to pkg/cmd/rollback_test.go index b58e4c162..53c63613e 100644 --- a/cmd/helm/rollback_test.go +++ b/pkg/cmd/rollback_test.go @@ -14,15 +14,15 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "fmt" "reflect" "testing" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/release" + chart "helm.sh/helm/v4/pkg/chart/v2" + release "helm.sh/helm/v4/pkg/release/v1" ) func TestRollbackCmd(t *testing.T) { diff --git a/cmd/helm/root.go b/pkg/cmd/root.go similarity index 73% rename from cmd/helm/root.go rename to pkg/cmd/root.go index 57738b111..ee22533f0 100644 --- a/cmd/helm/root.go +++ b/pkg/cmd/root.go @@ -14,24 +14,33 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main // import "helm.sh/helm/v3/cmd/helm" +package cmd // import "helm.sh/helm/v4/pkg/cmd" import ( "context" "fmt" "io" "log" + "log/slog" + "net/http" "os" "strings" "github.com/spf13/cobra" + "sigs.k8s.io/yaml" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/tools/clientcmd" - "helm.sh/helm/v3/pkg/action" - "helm.sh/helm/v3/pkg/registry" - "helm.sh/helm/v3/pkg/repo" + "helm.sh/helm/v4/internal/logging" + "helm.sh/helm/v4/internal/tlsutil" + "helm.sh/helm/v4/pkg/action" + "helm.sh/helm/v4/pkg/cli" + kubefake "helm.sh/helm/v4/pkg/kube/fake" + "helm.sh/helm/v4/pkg/registry" + release "helm.sh/helm/v4/pkg/release/v1" + "helm.sh/helm/v4/pkg/repo" + "helm.sh/helm/v4/pkg/storage/driver" ) var globalUsage = `The Kubernetes package manager @@ -87,18 +96,53 @@ By default, the default directories depend on the Operating System. The defaults | Windows | %TEMP%\helm | %APPDATA%\helm | %APPDATA%\helm | ` -func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string) (*cobra.Command, error) { +var settings = cli.New() + +func NewRootCmd(out io.Writer, args []string) (*cobra.Command, error) { + actionConfig := new(action.Configuration) + cmd, err := newRootCmdWithConfig(actionConfig, out, args) + if err != nil { + return nil, err + } + cobra.OnInitialize(func() { + helmDriver := os.Getenv("HELM_DRIVER") + if err := actionConfig.Init(settings.RESTClientGetter(), settings.Namespace(), helmDriver); err != nil { + log.Fatal(err) + } + if helmDriver == "memory" { + loadReleasesInMemory(actionConfig) + } + actionConfig.SetHookOutputFunc(hookOutputWriter) + }) + return cmd, nil +} + +func newRootCmdWithConfig(actionConfig *action.Configuration, out io.Writer, args []string) (*cobra.Command, error) { cmd := &cobra.Command{ Use: "helm", Short: "The Helm package manager for Kubernetes.", Long: globalUsage, SilenceUsage: true, + PersistentPreRun: func(_ *cobra.Command, _ []string) { + if err := startProfiling(); err != nil { + log.Printf("Warning: Failed to start profiling: %v", err) + } + }, + PersistentPostRun: func(_ *cobra.Command, _ []string) { + if err := stopProfiling(); err != nil { + log.Printf("Warning: Failed to stop profiling: %v", err) + } + }, } + flags := cmd.PersistentFlags() settings.AddFlags(flags) addKlogFlags(flags) + logger := logging.NewLogger(func() bool { return settings.Debug }) + slog.SetDefault(logger) + // Setup shell completion for the namespace flag err := cmd.RegisterFlagCompletionFunc("namespace", func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) { if client, err := actionConfig.KubernetesClientSet(); err == nil { @@ -153,7 +197,7 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string flags.ParseErrorsWhitelist.UnknownFlags = true flags.Parse(args) - registryClient, err := newDefaultRegistryClient(false) + registryClient, err := newDefaultRegistryClient(false, "", "") if err != nil { return nil, err } @@ -167,7 +211,7 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string newPullCmd(actionConfig, out), newShowCmd(actionConfig, out), newLintCmd(out), - newPackageCmd(actionConfig, out), + newPackageCmd(out), newRepoCmd(out), newSearchCmd(out), newVerifyCmd(out), @@ -207,6 +251,49 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string return cmd, nil } +// This function loads releases into the memory storage if the +// environment variable is properly set. +func loadReleasesInMemory(actionConfig *action.Configuration) { + filePaths := strings.Split(os.Getenv("HELM_MEMORY_DRIVER_DATA"), ":") + if len(filePaths) == 0 { + return + } + + store := actionConfig.Releases + mem, ok := store.Driver.(*driver.Memory) + if !ok { + // For an unexpected reason we are not dealing with the memory storage driver. + return + } + + actionConfig.KubeClient = &kubefake.PrintingKubeClient{Out: io.Discard} + + for _, path := range filePaths { + b, err := os.ReadFile(path) + if err != nil { + log.Fatal("Unable to read memory driver data", err) + } + + releases := []*release.Release{} + if err := yaml.Unmarshal(b, &releases); err != nil { + log.Fatal("Unable to unmarshal memory driver data: ", err) + } + + for _, rel := range releases { + if err := store.Create(rel); err != nil { + log.Fatal(err) + } + } + } + // Must reset namespace to the proper one + mem.SetNamespace(settings.Namespace()) +} + +// hookOutputWriter provides the writer for writing hook logs. +func hookOutputWriter(_, _, _ string) io.Writer { + return log.Writer() +} + func checkForExpiredRepos(repofile string) { expiredRepos := []struct { @@ -255,27 +342,30 @@ func checkForExpiredRepos(repofile string) { } -func newRegistryClient(certFile, keyFile, caFile string, insecureSkipTLSverify, plainHTTP bool) (*registry.Client, error) { +func newRegistryClient( + certFile, keyFile, caFile string, insecureSkipTLSverify, plainHTTP bool, username, password string, +) (*registry.Client, error) { if certFile != "" && keyFile != "" || caFile != "" || insecureSkipTLSverify { - registryClient, err := newRegistryClientWithTLS(certFile, keyFile, caFile, insecureSkipTLSverify) + registryClient, err := newRegistryClientWithTLS(certFile, keyFile, caFile, insecureSkipTLSverify, username, password) if err != nil { return nil, err } return registryClient, nil } - registryClient, err := newDefaultRegistryClient(plainHTTP) + registryClient, err := newDefaultRegistryClient(plainHTTP, username, password) if err != nil { return nil, err } return registryClient, nil } -func newDefaultRegistryClient(plainHTTP bool) (*registry.Client, error) { +func newDefaultRegistryClient(plainHTTP bool, username, password string) (*registry.Client, error) { opts := []registry.ClientOption{ registry.ClientOptDebug(settings.Debug), registry.ClientOptEnableCache(true), registry.ClientOptWriter(os.Stderr), registry.ClientOptCredentialsFile(settings.RegistryConfig), + registry.ClientOptBasicAuth(username, password), } if plainHTTP { opts = append(opts, registry.ClientOptPlainHTTP()) @@ -289,10 +379,32 @@ func newDefaultRegistryClient(plainHTTP bool) (*registry.Client, error) { return registryClient, nil } -func newRegistryClientWithTLS(certFile, keyFile, caFile string, insecureSkipTLSverify bool) (*registry.Client, error) { +func newRegistryClientWithTLS( + certFile, keyFile, caFile string, insecureSkipTLSverify bool, username, password string, +) (*registry.Client, error) { + tlsConf, err := tlsutil.NewTLSConfig( + tlsutil.WithInsecureSkipVerify(insecureSkipTLSverify), + tlsutil.WithCertKeyPairFiles(certFile, keyFile), + tlsutil.WithCAFile(caFile), + ) + + if err != nil { + return nil, fmt.Errorf("can't create TLS config for client: %w", err) + } + // Create a new registry client - registryClient, err := registry.NewRegistryClientWithTLS(os.Stderr, certFile, keyFile, caFile, insecureSkipTLSverify, - settings.RegistryConfig, settings.Debug, + registryClient, err := registry.NewClient( + registry.ClientOptDebug(settings.Debug), + registry.ClientOptEnableCache(true), + registry.ClientOptWriter(os.Stderr), + registry.ClientOptCredentialsFile(settings.RegistryConfig), + registry.ClientOptHTTPClient(&http.Client{ + Transport: &http.Transport{ + TLSClientConfig: tlsConf, + Proxy: http.ProxyFromEnvironment, + }, + }), + registry.ClientOptBasicAuth(username, password), ) if err != nil { return nil, err diff --git a/cmd/helm/root_test.go b/pkg/cmd/root_test.go similarity index 96% rename from cmd/helm/root_test.go rename to pkg/cmd/root_test.go index 65e6d66c7..9521a5aa2 100644 --- a/cmd/helm/root_test.go +++ b/pkg/cmd/root_test.go @@ -14,16 +14,16 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "os" "path/filepath" "testing" - "helm.sh/helm/v3/internal/test/ensure" - "helm.sh/helm/v3/pkg/helmpath" - "helm.sh/helm/v3/pkg/helmpath/xdg" + "helm.sh/helm/v4/internal/test/ensure" + "helm.sh/helm/v4/pkg/helmpath" + "helm.sh/helm/v4/pkg/helmpath/xdg" ) func TestRootCmd(t *testing.T) { diff --git a/cmd/helm/search.go b/pkg/cmd/search.go similarity index 98% rename from cmd/helm/search.go rename to pkg/cmd/search.go index 6c62d5d2e..4d110286d 100644 --- a/cmd/helm/search.go +++ b/pkg/cmd/search.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "io" diff --git a/cmd/helm/search/search.go b/pkg/cmd/search/search.go similarity index 99% rename from cmd/helm/search/search.go rename to pkg/cmd/search/search.go index ac29b27c2..f9e229154 100644 --- a/cmd/helm/search/search.go +++ b/pkg/cmd/search/search.go @@ -31,7 +31,7 @@ import ( "github.com/Masterminds/semver/v3" - "helm.sh/helm/v3/pkg/repo" + "helm.sh/helm/v4/pkg/repo" ) // Result is a search result. diff --git a/cmd/helm/search/search_test.go b/pkg/cmd/search/search_test.go similarity index 99% rename from cmd/helm/search/search_test.go rename to pkg/cmd/search/search_test.go index 415c085b4..7a4ba786b 100644 --- a/cmd/helm/search/search_test.go +++ b/pkg/cmd/search/search_test.go @@ -20,8 +20,8 @@ import ( "strings" "testing" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/repo" + chart "helm.sh/helm/v4/pkg/chart/v2" + "helm.sh/helm/v4/pkg/repo" ) func TestSortScore(t *testing.T) { diff --git a/cmd/helm/search_hub.go b/pkg/cmd/search_hub.go similarity index 97% rename from cmd/helm/search_hub.go rename to pkg/cmd/search_hub.go index 7b777328d..cfeeec59b 100644 --- a/cmd/helm/search_hub.go +++ b/pkg/cmd/search_hub.go @@ -14,18 +14,19 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "fmt" "io" + "log/slog" "strings" "github.com/gosuri/uitable" "github.com/spf13/cobra" - "helm.sh/helm/v3/internal/monocular" - "helm.sh/helm/v3/pkg/cli/output" + "helm.sh/helm/v4/internal/monocular" + "helm.sh/helm/v4/pkg/cli/output" ) const searchHubDesc = ` @@ -88,7 +89,7 @@ func (o *searchHubOptions) run(out io.Writer, args []string) error { q := strings.Join(args, " ") results, err := c.Search(q) if err != nil { - debug("%s", err) + slog.Debug("search failed", slog.Any("error", err)) return fmt.Errorf("unable to perform search against %q", o.searchEndpoint) } diff --git a/cmd/helm/search_hub_test.go b/pkg/cmd/search_hub_test.go similarity index 99% rename from cmd/helm/search_hub_test.go rename to pkg/cmd/search_hub_test.go index f3730275a..8e056f771 100644 --- a/cmd/helm/search_hub_test.go +++ b/pkg/cmd/search_hub_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "fmt" diff --git a/cmd/helm/search_repo.go b/pkg/cmd/search_repo.go similarity index 97% rename from cmd/helm/search_repo.go rename to pkg/cmd/search_repo.go index af92c5f21..dffa0d1c4 100644 --- a/cmd/helm/search_repo.go +++ b/pkg/cmd/search_repo.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "bufio" @@ -22,6 +22,7 @@ import ( "errors" "fmt" "io" + "log/slog" "os" "path/filepath" "strings" @@ -30,10 +31,10 @@ import ( "github.com/gosuri/uitable" "github.com/spf13/cobra" - "helm.sh/helm/v3/cmd/helm/search" - "helm.sh/helm/v3/pkg/cli/output" - "helm.sh/helm/v3/pkg/helmpath" - "helm.sh/helm/v3/pkg/repo" + "helm.sh/helm/v4/pkg/cli/output" + "helm.sh/helm/v4/pkg/cmd/search" + "helm.sh/helm/v4/pkg/helmpath" + "helm.sh/helm/v4/pkg/repo" ) const searchRepoDesc = ` @@ -130,17 +131,17 @@ func (o *searchRepoOptions) run(out io.Writer, args []string) error { } func (o *searchRepoOptions) setupSearchedVersion() { - debug("Original chart version: %q", o.version) + slog.Debug("original chart version", "version", o.version) if o.version != "" { return } if o.devel { // search for releases and prereleases (alpha, beta, and release candidate releases). - debug("setting version to >0.0.0-0") + slog.Debug("setting version to >0.0.0-0") o.version = ">0.0.0-0" } else { // search only for stable releases, prerelease versions will be skipped - debug("setting version to >0.0.0") + slog.Debug("setting version to >0.0.0") o.version = ">0.0.0" } } @@ -189,8 +190,7 @@ func (o *searchRepoOptions) buildIndex() (*search.Index, error) { f := filepath.Join(o.repoCacheDir, helmpath.CacheIndexFile(n)) ind, err := repo.LoadIndexFile(f) if err != nil { - warning("Repo %q is corrupt or missing. Try 'helm repo update'.", n) - warning("%s", err) + slog.Warn("repo is corrupt or missing", "repo", n, slog.Any("error", err)) continue } diff --git a/cmd/helm/search_repo_test.go b/pkg/cmd/search_repo_test.go similarity index 99% rename from cmd/helm/search_repo_test.go rename to pkg/cmd/search_repo_test.go index 9039842f0..e7f104e05 100644 --- a/cmd/helm/search_repo_test.go +++ b/pkg/cmd/search_repo_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "testing" diff --git a/cmd/helm/search_test.go b/pkg/cmd/search_test.go similarity index 98% rename from cmd/helm/search_test.go rename to pkg/cmd/search_test.go index 6cf845b06..a0e5d84cb 100644 --- a/cmd/helm/search_test.go +++ b/pkg/cmd/search_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import "testing" diff --git a/cmd/helm/show.go b/pkg/cmd/show.go similarity index 94% rename from cmd/helm/show.go rename to pkg/cmd/show.go index 7c06a2297..1c7e7be44 100644 --- a/cmd/helm/show.go +++ b/pkg/cmd/show.go @@ -14,17 +14,18 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "fmt" "io" "log" + "log/slog" "github.com/spf13/cobra" - "helm.sh/helm/v3/cmd/helm/require" - "helm.sh/helm/v3/pkg/action" + "helm.sh/helm/v4/pkg/action" + "helm.sh/helm/v4/pkg/cmd/require" ) const showDesc = ` @@ -57,7 +58,7 @@ of the CustomResourceDefinition files ` func newShowCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { - client := action.NewShowWithConfig(action.ShowAll, cfg) + client := action.NewShow(action.ShowAll, cfg) showCommand := &cobra.Command{ Use: "show", @@ -211,13 +212,13 @@ func addShowFlags(subCmd *cobra.Command, client *action.Show) { } func runShow(args []string, client *action.Show) (string, error) { - debug("Original chart version: %q", client.Version) + slog.Debug("original chart version", "version", client.Version) if client.Version == "" && client.Devel { - debug("setting version to >0.0.0-0") + slog.Debug("setting version to >0.0.0-0") client.Version = ">0.0.0-0" } - cp, err := client.ChartPathOptions.LocateChart(args[0], settings) + cp, err := client.LocateChart(args[0], settings) if err != nil { return "", err } @@ -226,7 +227,7 @@ func runShow(args []string, client *action.Show) (string, error) { func addRegistryClient(client *action.Show) error { registryClient, err := newRegistryClient(client.CertFile, client.KeyFile, client.CaFile, - client.InsecureSkipTLSverify, client.PlainHTTP) + client.InsecureSkipTLSverify, client.PlainHTTP, client.Username, client.Password) if err != nil { return fmt.Errorf("missing registry client: %w", err) } diff --git a/cmd/helm/show_test.go b/pkg/cmd/show_test.go similarity index 96% rename from cmd/helm/show_test.go rename to pkg/cmd/show_test.go index 93ec08d0f..ab8cafc37 100644 --- a/cmd/helm/show_test.go +++ b/pkg/cmd/show_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "fmt" @@ -22,14 +22,14 @@ import ( "strings" "testing" - "helm.sh/helm/v3/pkg/repo/repotest" + "helm.sh/helm/v4/pkg/repo/repotest" ) func TestShowPreReleaseChart(t *testing.T) { - srv, err := repotest.NewTempServerWithCleanup(t, "testdata/testcharts/*.tgz*") - if err != nil { - t.Fatal(err) - } + srv := repotest.NewTempServer( + t, + repotest.WithChartSourceGlob("testdata/testcharts/*.tgz*"), + ) defer srv.Stop() if err := srv.LinkIndices(); err != nil { diff --git a/cmd/helm/status.go b/pkg/cmd/status.go similarity index 86% rename from cmd/helm/status.go rename to pkg/cmd/status.go index 725b3f367..2b1138786 100644 --- a/cmd/helm/status.go +++ b/pkg/cmd/status.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "bytes" @@ -28,11 +28,11 @@ import ( "k8s.io/kubectl/pkg/cmd/get" - "helm.sh/helm/v3/cmd/helm/require" - "helm.sh/helm/v3/pkg/action" - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/cli/output" - "helm.sh/helm/v3/pkg/release" + "helm.sh/helm/v4/pkg/action" + chartutil "helm.sh/helm/v4/pkg/chart/v2/util" + "helm.sh/helm/v4/pkg/cli/output" + "helm.sh/helm/v4/pkg/cmd/require" + release "helm.sh/helm/v4/pkg/release/v1" ) // NOTE: Keep the list of statuses up-to-date with pkg/release/status.go. @@ -43,8 +43,8 @@ The status consists of: - k8s namespace in which the release lives - state of the release (can be: unknown, deployed, uninstalled, superseded, failed, uninstalling, pending-install, pending-upgrade or pending-rollback) - revision of the release -- description of the release (can be completion message or error message, need to enable --show-desc) -- list of resources that this release consists of (need to enable --show-resources) +- description of the release (can be completion message or error message) +- list of resources that this release consists of - details on last test suite run, if applicable - additional notes provided by the chart ` @@ -65,7 +65,6 @@ func newStatusCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { return compListReleases(toComplete, args, cfg) }, RunE: func(_ *cobra.Command, args []string) error { - // When the output format is a table the resources should be fetched // and displayed as a table. When YAML or JSON the resources will be // returned. This mirrors the handling in kubectl. @@ -80,7 +79,12 @@ func newStatusCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { // strip chart metadata from the output rel.Chart = nil - return outfmt.Write(out, &statusPrinter{rel, false, client.ShowDescription, client.ShowResources, false, false}) + return outfmt.Write(out, &statusPrinter{ + release: rel, + debug: false, + showMetadata: false, + hideNotes: false, + }) }, } @@ -94,26 +98,20 @@ func newStatusCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { } return nil, cobra.ShellCompDirectiveNoFileComp }) - if err != nil { log.Fatal(err) } bindOutputFlag(cmd, &outfmt) - f.BoolVar(&client.ShowDescription, "show-desc", false, "if set, display the description message of the named release") - - f.BoolVar(&client.ShowResources, "show-resources", false, "if set, display the resources of the named release") return cmd } type statusPrinter struct { - release *release.Release - debug bool - showDescription bool - showResources bool - showMetadata bool - hideNotes bool + release *release.Release + debug bool + showMetadata bool + hideNotes bool } func (s statusPrinter) WriteJSON(out io.Writer) error { @@ -140,11 +138,9 @@ func (s statusPrinter) WriteTable(out io.Writer) error { _, _ = fmt.Fprintf(out, "VERSION: %s\n", s.release.Chart.Metadata.Version) _, _ = fmt.Fprintf(out, "APP_VERSION: %s\n", s.release.Chart.Metadata.AppVersion) } - if s.showDescription { - _, _ = fmt.Fprintf(out, "DESCRIPTION: %s\n", s.release.Info.Description) - } + _, _ = fmt.Fprintf(out, "DESCRIPTION: %s\n", s.release.Info.Description) - if s.showResources && s.release.Info.Resources != nil && len(s.release.Info.Resources) > 0 { + if len(s.release.Info.Resources) > 0 { buf := new(bytes.Buffer) printFlags := get.NewHumanPrintFlags() typePrinter, _ := printFlags.ToPrinter("") diff --git a/cmd/helm/status_test.go b/pkg/cmd/status_test.go similarity index 95% rename from cmd/helm/status_test.go rename to pkg/cmd/status_test.go index 6722bf949..cb4e23c59 100644 --- a/cmd/helm/status_test.go +++ b/pkg/cmd/status_test.go @@ -14,15 +14,15 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "testing" "time" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/release" - helmtime "helm.sh/helm/v3/pkg/time" + chart "helm.sh/helm/v4/pkg/chart/v2" + release "helm.sh/helm/v4/pkg/release/v1" + helmtime "helm.sh/helm/v4/pkg/time" ) func TestStatusCmd(t *testing.T) { @@ -46,7 +46,7 @@ func TestStatusCmd(t *testing.T) { }), }, { name: "get status of a deployed release, with desc", - cmd: "status --show-desc flummoxed-chickadee", + cmd: "status flummoxed-chickadee", golden: "output/status-with-desc.txt", rels: releasesMockWithStatus(&release.Info{ Status: release.StatusDeployed, @@ -70,7 +70,7 @@ func TestStatusCmd(t *testing.T) { }), }, { name: "get status of a deployed release with resources", - cmd: "status --show-resources flummoxed-chickadee", + cmd: "status flummoxed-chickadee", golden: "output/status-with-resources.txt", rels: releasesMockWithStatus( &release.Info{ @@ -79,7 +79,7 @@ func TestStatusCmd(t *testing.T) { ), }, { name: "get status of a deployed release with resources in json", - cmd: "status --show-resources flummoxed-chickadee -o json", + cmd: "status flummoxed-chickadee -o json", golden: "output/status-with-resources.json", rels: releasesMockWithStatus( &release.Info{ diff --git a/cmd/helm/template.go b/pkg/cmd/template.go similarity index 92% rename from cmd/helm/template.go rename to pkg/cmd/template.go index b7c2cd13a..7a565ef85 100644 --- a/cmd/helm/template.go +++ b/pkg/cmd/template.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "bytes" @@ -26,18 +26,19 @@ import ( "path" "path/filepath" "regexp" + "slices" "sort" "strings" - "helm.sh/helm/v3/pkg/release" + release "helm.sh/helm/v4/pkg/release/v1" "github.com/spf13/cobra" - "helm.sh/helm/v3/cmd/helm/require" - "helm.sh/helm/v3/pkg/action" - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/cli/values" - "helm.sh/helm/v3/pkg/releaseutil" + "helm.sh/helm/v4/pkg/action" + chartutil "helm.sh/helm/v4/pkg/chart/v2/util" + "helm.sh/helm/v4/pkg/cli/values" + "helm.sh/helm/v4/pkg/cmd/require" + releaseutil "helm.sh/helm/v4/pkg/release/util" ) const templateDesc = ` @@ -76,7 +77,7 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { } registryClient, err := newRegistryClient(client.CertFile, client.KeyFile, client.CaFile, - client.InsecureSkipTLSverify, client.PlainHTTP) + client.InsecureSkipTLSverify, client.PlainHTTP, client.Username, client.Password) if err != nil { return fmt.Errorf("missing registry client: %w", err) } @@ -208,12 +209,7 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { } func isTestHook(h *release.Hook) bool { - for _, e := range h.Events { - if e == release.HookTest { - return true - } - } - return false + return slices.Contains(h.Events, release.HookTest) } // The following functions (writeToFile, createOrOpenFile, and ensureDirectoryForFile) @@ -221,7 +217,7 @@ func isTestHook(h *release.Hook) bool { // bug introduced by #8156. As part of the todo to refactor renderResources // this duplicate code should be removed. It is added here so that the API // surface area is as minimally impacted as possible in fixing the issue. -func writeToFile(outputDir string, name string, data string, append bool) error { +func writeToFile(outputDir string, name string, data string, appendData bool) error { outfileName := strings.Join([]string{outputDir, name}, string(filepath.Separator)) err := ensureDirectoryForFile(outfileName) @@ -229,14 +225,14 @@ func writeToFile(outputDir string, name string, data string, append bool) error return err } - f, err := createOrOpenFile(outfileName, append) + f, err := createOrOpenFile(outfileName, appendData) if err != nil { return err } defer f.Close() - _, err = f.WriteString(fmt.Sprintf("---\n# Source: %s\n%s\n", name, data)) + _, err = fmt.Fprintf(f, "---\n# Source: %s\n%s\n", name, data) if err != nil { return err @@ -246,8 +242,8 @@ func writeToFile(outputDir string, name string, data string, append bool) error return nil } -func createOrOpenFile(filename string, append bool) (*os.File, error) { - if append { +func createOrOpenFile(filename string, appendData bool) (*os.File, error) { + if appendData { return os.OpenFile(filename, os.O_APPEND|os.O_WRONLY, 0600) } return os.Create(filename) diff --git a/cmd/helm/template_test.go b/pkg/cmd/template_test.go similarity index 96% rename from cmd/helm/template_test.go rename to pkg/cmd/template_test.go index e5b939879..c478fced4 100644 --- a/cmd/helm/template_test.go +++ b/pkg/cmd/template_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "fmt" @@ -22,6 +22,18 @@ import ( "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" func TestTemplateCmd(t *testing.T) { diff --git a/cmd/helm/testdata/helm home with space/helm/plugins/fullenv/completion.yaml b/pkg/cmd/testdata/helm home with space/helm/plugins/fullenv/completion.yaml similarity index 100% rename from cmd/helm/testdata/helm home with space/helm/plugins/fullenv/completion.yaml rename to pkg/cmd/testdata/helm home with space/helm/plugins/fullenv/completion.yaml diff --git a/cmd/helm/testdata/helm home with space/helm/plugins/fullenv/fullenv.sh b/pkg/cmd/testdata/helm home with space/helm/plugins/fullenv/fullenv.sh similarity index 100% rename from cmd/helm/testdata/helm home with space/helm/plugins/fullenv/fullenv.sh rename to pkg/cmd/testdata/helm home with space/helm/plugins/fullenv/fullenv.sh diff --git a/cmd/helm/testdata/helm home with space/helm/plugins/fullenv/plugin.yaml b/pkg/cmd/testdata/helm home with space/helm/plugins/fullenv/plugin.yaml similarity index 100% rename from cmd/helm/testdata/helm home with space/helm/plugins/fullenv/plugin.yaml rename to pkg/cmd/testdata/helm home with space/helm/plugins/fullenv/plugin.yaml diff --git a/cmd/helm/testdata/helm home with space/helm/repositories.yaml b/pkg/cmd/testdata/helm home with space/helm/repositories.yaml similarity index 100% rename from cmd/helm/testdata/helm home with space/helm/repositories.yaml rename to pkg/cmd/testdata/helm home with space/helm/repositories.yaml diff --git a/cmd/helm/testdata/helm home with space/helm/repository/test-name-charts.txt b/pkg/cmd/testdata/helm home with space/helm/repository/test-name-charts.txt similarity index 100% rename from cmd/helm/testdata/helm home with space/helm/repository/test-name-charts.txt rename to pkg/cmd/testdata/helm home with space/helm/repository/test-name-charts.txt diff --git a/cmd/helm/testdata/helm home with space/helm/repository/test-name-index.yaml b/pkg/cmd/testdata/helm home with space/helm/repository/test-name-index.yaml similarity index 100% rename from cmd/helm/testdata/helm home with space/helm/repository/test-name-index.yaml rename to pkg/cmd/testdata/helm home with space/helm/repository/test-name-index.yaml diff --git a/cmd/helm/testdata/helm home with space/helm/repository/testing-index.yaml b/pkg/cmd/testdata/helm home with space/helm/repository/testing-index.yaml similarity index 100% rename from cmd/helm/testdata/helm home with space/helm/repository/testing-index.yaml rename to pkg/cmd/testdata/helm home with space/helm/repository/testing-index.yaml diff --git a/cmd/helm/testdata/helm-test-key.pub b/pkg/cmd/testdata/helm-test-key.pub similarity index 100% rename from cmd/helm/testdata/helm-test-key.pub rename to pkg/cmd/testdata/helm-test-key.pub diff --git a/cmd/helm/testdata/helm-test-key.secret b/pkg/cmd/testdata/helm-test-key.secret similarity index 100% rename from cmd/helm/testdata/helm-test-key.secret rename to pkg/cmd/testdata/helm-test-key.secret diff --git a/cmd/helm/testdata/helmhome/helm/plugins/args/args.sh b/pkg/cmd/testdata/helmhome/helm/plugins/args/args.sh similarity index 100% rename from cmd/helm/testdata/helmhome/helm/plugins/args/args.sh rename to pkg/cmd/testdata/helmhome/helm/plugins/args/args.sh diff --git a/cmd/helm/testdata/helmhome/helm/plugins/args/plugin.complete b/pkg/cmd/testdata/helmhome/helm/plugins/args/plugin.complete similarity index 100% rename from cmd/helm/testdata/helmhome/helm/plugins/args/plugin.complete rename to pkg/cmd/testdata/helmhome/helm/plugins/args/plugin.complete diff --git a/cmd/helm/testdata/helmhome/helm/plugins/args/plugin.yaml b/pkg/cmd/testdata/helmhome/helm/plugins/args/plugin.yaml similarity index 100% rename from cmd/helm/testdata/helmhome/helm/plugins/args/plugin.yaml rename to pkg/cmd/testdata/helmhome/helm/plugins/args/plugin.yaml diff --git a/cmd/helm/testdata/helmhome/helm/plugins/echo/completion.yaml b/pkg/cmd/testdata/helmhome/helm/plugins/echo/completion.yaml similarity index 100% rename from cmd/helm/testdata/helmhome/helm/plugins/echo/completion.yaml rename to pkg/cmd/testdata/helmhome/helm/plugins/echo/completion.yaml diff --git a/cmd/helm/testdata/helmhome/helm/plugins/echo/plugin.complete b/pkg/cmd/testdata/helmhome/helm/plugins/echo/plugin.complete similarity index 100% rename from cmd/helm/testdata/helmhome/helm/plugins/echo/plugin.complete rename to pkg/cmd/testdata/helmhome/helm/plugins/echo/plugin.complete diff --git a/cmd/helm/testdata/helmhome/helm/plugins/echo/plugin.yaml b/pkg/cmd/testdata/helmhome/helm/plugins/echo/plugin.yaml similarity index 100% rename from cmd/helm/testdata/helmhome/helm/plugins/echo/plugin.yaml rename to pkg/cmd/testdata/helmhome/helm/plugins/echo/plugin.yaml diff --git a/cmd/helm/testdata/helmhome/helm/plugins/env/completion.yaml b/pkg/cmd/testdata/helmhome/helm/plugins/env/completion.yaml similarity index 100% rename from cmd/helm/testdata/helmhome/helm/plugins/env/completion.yaml rename to pkg/cmd/testdata/helmhome/helm/plugins/env/completion.yaml diff --git a/cmd/helm/testdata/helmhome/helm/plugins/env/plugin.yaml b/pkg/cmd/testdata/helmhome/helm/plugins/env/plugin.yaml similarity index 100% rename from cmd/helm/testdata/helmhome/helm/plugins/env/plugin.yaml rename to pkg/cmd/testdata/helmhome/helm/plugins/env/plugin.yaml diff --git a/cmd/helm/testdata/helmhome/helm/plugins/exitwith/completion.yaml b/pkg/cmd/testdata/helmhome/helm/plugins/exitwith/completion.yaml similarity index 100% rename from cmd/helm/testdata/helmhome/helm/plugins/exitwith/completion.yaml rename to pkg/cmd/testdata/helmhome/helm/plugins/exitwith/completion.yaml diff --git a/cmd/helm/testdata/helmhome/helm/plugins/exitwith/exitwith.sh b/pkg/cmd/testdata/helmhome/helm/plugins/exitwith/exitwith.sh similarity index 100% rename from cmd/helm/testdata/helmhome/helm/plugins/exitwith/exitwith.sh rename to pkg/cmd/testdata/helmhome/helm/plugins/exitwith/exitwith.sh diff --git a/cmd/helm/testdata/helmhome/helm/plugins/exitwith/plugin.yaml b/pkg/cmd/testdata/helmhome/helm/plugins/exitwith/plugin.yaml similarity index 100% rename from cmd/helm/testdata/helmhome/helm/plugins/exitwith/plugin.yaml rename to pkg/cmd/testdata/helmhome/helm/plugins/exitwith/plugin.yaml diff --git a/cmd/helm/testdata/helmhome/helm/plugins/fullenv/completion.yaml b/pkg/cmd/testdata/helmhome/helm/plugins/fullenv/completion.yaml similarity index 100% rename from cmd/helm/testdata/helmhome/helm/plugins/fullenv/completion.yaml rename to pkg/cmd/testdata/helmhome/helm/plugins/fullenv/completion.yaml diff --git a/cmd/helm/testdata/helmhome/helm/plugins/fullenv/fullenv.sh b/pkg/cmd/testdata/helmhome/helm/plugins/fullenv/fullenv.sh similarity index 100% rename from cmd/helm/testdata/helmhome/helm/plugins/fullenv/fullenv.sh rename to pkg/cmd/testdata/helmhome/helm/plugins/fullenv/fullenv.sh diff --git a/cmd/helm/testdata/helmhome/helm/plugins/fullenv/plugin.yaml b/pkg/cmd/testdata/helmhome/helm/plugins/fullenv/plugin.yaml similarity index 100% rename from cmd/helm/testdata/helmhome/helm/plugins/fullenv/plugin.yaml rename to pkg/cmd/testdata/helmhome/helm/plugins/fullenv/plugin.yaml diff --git a/cmd/helm/testdata/helmhome/helm/repositories.yaml b/pkg/cmd/testdata/helmhome/helm/repositories.yaml similarity index 100% rename from cmd/helm/testdata/helmhome/helm/repositories.yaml rename to pkg/cmd/testdata/helmhome/helm/repositories.yaml diff --git a/cmd/helm/testdata/helmhome/helm/repository/test-name-charts.txt b/pkg/cmd/testdata/helmhome/helm/repository/test-name-charts.txt similarity index 100% rename from cmd/helm/testdata/helmhome/helm/repository/test-name-charts.txt rename to pkg/cmd/testdata/helmhome/helm/repository/test-name-charts.txt diff --git a/cmd/helm/testdata/helmhome/helm/repository/test-name-index.yaml b/pkg/cmd/testdata/helmhome/helm/repository/test-name-index.yaml similarity index 100% rename from cmd/helm/testdata/helmhome/helm/repository/test-name-index.yaml rename to pkg/cmd/testdata/helmhome/helm/repository/test-name-index.yaml diff --git a/cmd/helm/testdata/helmhome/helm/repository/testing-index.yaml b/pkg/cmd/testdata/helmhome/helm/repository/testing-index.yaml similarity index 100% rename from cmd/helm/testdata/helmhome/helm/repository/testing-index.yaml rename to pkg/cmd/testdata/helmhome/helm/repository/testing-index.yaml diff --git a/cmd/helm/testdata/output/chart-with-subchart-update.txt b/pkg/cmd/testdata/output/chart-with-subchart-update.txt similarity index 82% rename from cmd/helm/testdata/output/chart-with-subchart-update.txt rename to pkg/cmd/testdata/output/chart-with-subchart-update.txt index a4135c782..5b2083e1d 100644 --- a/cmd/helm/testdata/output/chart-with-subchart-update.txt +++ b/pkg/cmd/testdata/output/chart-with-subchart-update.txt @@ -3,6 +3,7 @@ LAST DEPLOYED: Fri Sep 2 22:04:05 1977 NAMESPACE: default STATUS: deployed REVISION: 1 +DESCRIPTION: Install complete TEST SUITE: None NOTES: PARENT NOTES diff --git a/cmd/helm/testdata/output/dependency-list-archive.txt b/pkg/cmd/testdata/output/dependency-list-archive.txt similarity index 100% rename from cmd/helm/testdata/output/dependency-list-archive.txt rename to pkg/cmd/testdata/output/dependency-list-archive.txt diff --git a/cmd/helm/testdata/output/dependency-list-no-chart-linux.txt b/pkg/cmd/testdata/output/dependency-list-no-chart-linux.txt similarity index 100% rename from cmd/helm/testdata/output/dependency-list-no-chart-linux.txt rename to pkg/cmd/testdata/output/dependency-list-no-chart-linux.txt diff --git a/cmd/helm/testdata/output/dependency-list-no-requirements-linux.txt b/pkg/cmd/testdata/output/dependency-list-no-requirements-linux.txt similarity index 100% rename from cmd/helm/testdata/output/dependency-list-no-requirements-linux.txt rename to pkg/cmd/testdata/output/dependency-list-no-requirements-linux.txt diff --git a/cmd/helm/testdata/output/dependency-list.txt b/pkg/cmd/testdata/output/dependency-list.txt similarity index 100% rename from cmd/helm/testdata/output/dependency-list.txt rename to pkg/cmd/testdata/output/dependency-list.txt diff --git a/cmd/helm/testdata/output/deprecated-chart.txt b/pkg/cmd/testdata/output/deprecated-chart.txt similarity index 79% rename from cmd/helm/testdata/output/deprecated-chart.txt rename to pkg/cmd/testdata/output/deprecated-chart.txt index 039d6aef6..fcf5cc0ef 100644 --- a/cmd/helm/testdata/output/deprecated-chart.txt +++ b/pkg/cmd/testdata/output/deprecated-chart.txt @@ -3,4 +3,5 @@ LAST DEPLOYED: Fri Sep 2 22:04:05 1977 NAMESPACE: default STATUS: deployed REVISION: 1 +DESCRIPTION: Install complete TEST SUITE: None diff --git a/cmd/helm/testdata/output/docs-type-comp.txt b/pkg/cmd/testdata/output/docs-type-comp.txt similarity index 100% rename from cmd/helm/testdata/output/docs-type-comp.txt rename to pkg/cmd/testdata/output/docs-type-comp.txt diff --git a/cmd/helm/testdata/output/empty_default_comp.txt b/pkg/cmd/testdata/output/empty_default_comp.txt similarity index 100% rename from cmd/helm/testdata/output/empty_default_comp.txt rename to pkg/cmd/testdata/output/empty_default_comp.txt diff --git a/cmd/helm/testdata/output/empty_nofile_comp.txt b/pkg/cmd/testdata/output/empty_nofile_comp.txt similarity index 100% rename from cmd/helm/testdata/output/empty_nofile_comp.txt rename to pkg/cmd/testdata/output/empty_nofile_comp.txt diff --git a/cmd/helm/testdata/output/env-comp.txt b/pkg/cmd/testdata/output/env-comp.txt similarity index 100% rename from cmd/helm/testdata/output/env-comp.txt rename to pkg/cmd/testdata/output/env-comp.txt diff --git a/cmd/helm/testdata/output/get-all-no-args.txt b/pkg/cmd/testdata/output/get-all-no-args.txt similarity index 100% rename from cmd/helm/testdata/output/get-all-no-args.txt rename to pkg/cmd/testdata/output/get-all-no-args.txt diff --git a/cmd/helm/testdata/output/get-hooks-no-args.txt b/pkg/cmd/testdata/output/get-hooks-no-args.txt similarity index 100% rename from cmd/helm/testdata/output/get-hooks-no-args.txt rename to pkg/cmd/testdata/output/get-hooks-no-args.txt diff --git a/cmd/helm/testdata/output/get-hooks.txt b/pkg/cmd/testdata/output/get-hooks.txt similarity index 100% rename from cmd/helm/testdata/output/get-hooks.txt rename to pkg/cmd/testdata/output/get-hooks.txt diff --git a/cmd/helm/testdata/output/get-manifest-no-args.txt b/pkg/cmd/testdata/output/get-manifest-no-args.txt similarity index 100% rename from cmd/helm/testdata/output/get-manifest-no-args.txt rename to pkg/cmd/testdata/output/get-manifest-no-args.txt diff --git a/cmd/helm/testdata/output/get-manifest.txt b/pkg/cmd/testdata/output/get-manifest.txt similarity index 100% rename from cmd/helm/testdata/output/get-manifest.txt rename to pkg/cmd/testdata/output/get-manifest.txt diff --git a/cmd/helm/testdata/output/get-metadata-args.txt b/pkg/cmd/testdata/output/get-metadata-args.txt similarity index 100% rename from cmd/helm/testdata/output/get-metadata-args.txt rename to pkg/cmd/testdata/output/get-metadata-args.txt diff --git a/pkg/cmd/testdata/output/get-metadata.json b/pkg/cmd/testdata/output/get-metadata.json new file mode 100644 index 000000000..4c015b977 --- /dev/null +++ b/pkg/cmd/testdata/output/get-metadata.json @@ -0,0 +1 @@ +{"name":"thomas-guide","chart":"foo","version":"0.1.0-beta.1","appVersion":"1.0","annotations":{"category":"web-apps","supported":"true"},"dependencies":[{"name":"cool-plugin","version":"1.0.0","repository":"https://coolplugin.io/charts","condition":"coolPlugin.enabled","enabled":true},{"name":"crds","version":"2.7.1","repository":"","condition":"crds.enabled"}],"namespace":"default","revision":1,"status":"deployed","deployedAt":"1977-09-02T22:04:05Z"} diff --git a/cmd/helm/testdata/output/get-metadata.txt b/pkg/cmd/testdata/output/get-metadata.txt similarity index 66% rename from cmd/helm/testdata/output/get-metadata.txt rename to pkg/cmd/testdata/output/get-metadata.txt index b91f1b86a..01083b333 100644 --- a/cmd/helm/testdata/output/get-metadata.txt +++ b/pkg/cmd/testdata/output/get-metadata.txt @@ -2,6 +2,8 @@ NAME: thomas-guide CHART: foo VERSION: 0.1.0-beta.1 APP_VERSION: 1.0 +ANNOTATIONS: category=web-apps,supported=true +DEPENDENCIES: cool-plugin,crds NAMESPACE: default REVISION: 1 STATUS: deployed diff --git a/pkg/cmd/testdata/output/get-metadata.yaml b/pkg/cmd/testdata/output/get-metadata.yaml new file mode 100644 index 000000000..6298436c9 --- /dev/null +++ b/pkg/cmd/testdata/output/get-metadata.yaml @@ -0,0 +1,21 @@ +annotations: + category: web-apps + supported: "true" +appVersion: "1.0" +chart: foo +dependencies: +- condition: coolPlugin.enabled + enabled: true + name: cool-plugin + repository: https://coolplugin.io/charts + version: 1.0.0 +- condition: crds.enabled + name: crds + repository: "" + version: 2.7.1 +deployedAt: "1977-09-02T22:04:05Z" +name: thomas-guide +namespace: default +revision: 1 +status: deployed +version: 0.1.0-beta.1 diff --git a/cmd/helm/testdata/output/get-notes-no-args.txt b/pkg/cmd/testdata/output/get-notes-no-args.txt similarity index 100% rename from cmd/helm/testdata/output/get-notes-no-args.txt rename to pkg/cmd/testdata/output/get-notes-no-args.txt diff --git a/cmd/helm/testdata/output/get-notes.txt b/pkg/cmd/testdata/output/get-notes.txt similarity index 100% rename from cmd/helm/testdata/output/get-notes.txt rename to pkg/cmd/testdata/output/get-notes.txt diff --git a/cmd/helm/testdata/output/get-release-template.txt b/pkg/cmd/testdata/output/get-release-template.txt similarity index 100% rename from cmd/helm/testdata/output/get-release-template.txt rename to pkg/cmd/testdata/output/get-release-template.txt diff --git a/cmd/helm/testdata/output/get-release.txt b/pkg/cmd/testdata/output/get-release.txt similarity index 94% rename from cmd/helm/testdata/output/get-release.txt rename to pkg/cmd/testdata/output/get-release.txt index 12b4a407a..dbca662c5 100644 --- a/cmd/helm/testdata/output/get-release.txt +++ b/pkg/cmd/testdata/output/get-release.txt @@ -6,6 +6,7 @@ REVISION: 1 CHART: foo VERSION: 0.1.0-beta.1 APP_VERSION: 1.0 +DESCRIPTION: Release mock TEST SUITE: None USER-SUPPLIED VALUES: name: value diff --git a/cmd/helm/testdata/output/get-values-all.txt b/pkg/cmd/testdata/output/get-values-all.txt similarity index 100% rename from cmd/helm/testdata/output/get-values-all.txt rename to pkg/cmd/testdata/output/get-values-all.txt diff --git a/cmd/helm/testdata/output/get-values-args.txt b/pkg/cmd/testdata/output/get-values-args.txt similarity index 100% rename from cmd/helm/testdata/output/get-values-args.txt rename to pkg/cmd/testdata/output/get-values-args.txt diff --git a/cmd/helm/testdata/output/get-values.txt b/pkg/cmd/testdata/output/get-values.txt similarity index 100% rename from cmd/helm/testdata/output/get-values.txt rename to pkg/cmd/testdata/output/get-values.txt diff --git a/cmd/helm/testdata/output/history-limit.txt b/pkg/cmd/testdata/output/history-limit.txt similarity index 100% rename from cmd/helm/testdata/output/history-limit.txt rename to pkg/cmd/testdata/output/history-limit.txt diff --git a/cmd/helm/testdata/output/history.json b/pkg/cmd/testdata/output/history.json similarity index 100% rename from cmd/helm/testdata/output/history.json rename to pkg/cmd/testdata/output/history.json diff --git a/cmd/helm/testdata/output/history.txt b/pkg/cmd/testdata/output/history.txt similarity index 100% rename from cmd/helm/testdata/output/history.txt rename to pkg/cmd/testdata/output/history.txt diff --git a/cmd/helm/testdata/output/history.yaml b/pkg/cmd/testdata/output/history.yaml similarity index 100% rename from cmd/helm/testdata/output/history.yaml rename to pkg/cmd/testdata/output/history.yaml diff --git a/cmd/helm/testdata/output/install-and-replace.txt b/pkg/cmd/testdata/output/install-and-replace.txt similarity index 79% rename from cmd/helm/testdata/output/install-and-replace.txt rename to pkg/cmd/testdata/output/install-and-replace.txt index 039d6aef6..fcf5cc0ef 100644 --- a/cmd/helm/testdata/output/install-and-replace.txt +++ b/pkg/cmd/testdata/output/install-and-replace.txt @@ -3,4 +3,5 @@ LAST DEPLOYED: Fri Sep 2 22:04:05 1977 NAMESPACE: default STATUS: deployed REVISION: 1 +DESCRIPTION: Install complete TEST SUITE: None diff --git a/pkg/cmd/testdata/output/install-and-take-ownership.txt b/pkg/cmd/testdata/output/install-and-take-ownership.txt new file mode 100644 index 000000000..413329ae1 --- /dev/null +++ b/pkg/cmd/testdata/output/install-and-take-ownership.txt @@ -0,0 +1,7 @@ +NAME: aeneas-take-ownership +LAST DEPLOYED: Fri Sep 2 22:04:05 1977 +NAMESPACE: default +STATUS: deployed +REVISION: 1 +DESCRIPTION: Install complete +TEST SUITE: None diff --git a/cmd/helm/testdata/output/install-chart-bad-type.txt b/pkg/cmd/testdata/output/install-chart-bad-type.txt similarity index 100% rename from cmd/helm/testdata/output/install-chart-bad-type.txt rename to pkg/cmd/testdata/output/install-chart-bad-type.txt diff --git a/cmd/helm/testdata/output/install-dry-run-with-secret-hidden.txt b/pkg/cmd/testdata/output/install-dry-run-with-secret-hidden.txt similarity index 92% rename from cmd/helm/testdata/output/install-dry-run-with-secret-hidden.txt rename to pkg/cmd/testdata/output/install-dry-run-with-secret-hidden.txt index 2f928c3dc..eb770967f 100644 --- a/cmd/helm/testdata/output/install-dry-run-with-secret-hidden.txt +++ b/pkg/cmd/testdata/output/install-dry-run-with-secret-hidden.txt @@ -3,6 +3,7 @@ LAST DEPLOYED: Fri Sep 2 22:04:05 1977 NAMESPACE: default STATUS: pending-install REVISION: 1 +DESCRIPTION: Dry run complete TEST SUITE: None HOOKS: MANIFEST: diff --git a/cmd/helm/testdata/output/install-dry-run-with-secret.txt b/pkg/cmd/testdata/output/install-dry-run-with-secret.txt similarity index 93% rename from cmd/helm/testdata/output/install-dry-run-with-secret.txt rename to pkg/cmd/testdata/output/install-dry-run-with-secret.txt index 6c094ef6a..d22c1437f 100644 --- a/cmd/helm/testdata/output/install-dry-run-with-secret.txt +++ b/pkg/cmd/testdata/output/install-dry-run-with-secret.txt @@ -3,6 +3,7 @@ LAST DEPLOYED: Fri Sep 2 22:04:05 1977 NAMESPACE: default STATUS: pending-install REVISION: 1 +DESCRIPTION: Dry run complete TEST SUITE: None HOOKS: MANIFEST: diff --git a/cmd/helm/testdata/output/install-hide-secret.txt b/pkg/cmd/testdata/output/install-hide-secret.txt similarity index 100% rename from cmd/helm/testdata/output/install-hide-secret.txt rename to pkg/cmd/testdata/output/install-hide-secret.txt diff --git a/cmd/helm/testdata/output/install-lib-chart.txt b/pkg/cmd/testdata/output/install-lib-chart.txt similarity index 100% rename from cmd/helm/testdata/output/install-lib-chart.txt rename to pkg/cmd/testdata/output/install-lib-chart.txt diff --git a/cmd/helm/testdata/output/install-name-template.txt b/pkg/cmd/testdata/output/install-name-template.txt similarity index 79% rename from cmd/helm/testdata/output/install-name-template.txt rename to pkg/cmd/testdata/output/install-name-template.txt index 19952e3c2..bcc5f87ba 100644 --- a/cmd/helm/testdata/output/install-name-template.txt +++ b/pkg/cmd/testdata/output/install-name-template.txt @@ -3,4 +3,5 @@ LAST DEPLOYED: Fri Sep 2 22:04:05 1977 NAMESPACE: default STATUS: deployed REVISION: 1 +DESCRIPTION: Install complete TEST SUITE: None diff --git a/cmd/helm/testdata/output/install-no-args.txt b/pkg/cmd/testdata/output/install-no-args.txt similarity index 100% rename from cmd/helm/testdata/output/install-no-args.txt rename to pkg/cmd/testdata/output/install-no-args.txt diff --git a/cmd/helm/testdata/output/install-no-hooks.txt b/pkg/cmd/testdata/output/install-no-hooks.txt similarity index 79% rename from cmd/helm/testdata/output/install-no-hooks.txt rename to pkg/cmd/testdata/output/install-no-hooks.txt index 039d6aef6..fcf5cc0ef 100644 --- a/cmd/helm/testdata/output/install-no-hooks.txt +++ b/pkg/cmd/testdata/output/install-no-hooks.txt @@ -3,4 +3,5 @@ LAST DEPLOYED: Fri Sep 2 22:04:05 1977 NAMESPACE: default STATUS: deployed REVISION: 1 +DESCRIPTION: Install complete TEST SUITE: None diff --git a/cmd/helm/testdata/output/install-with-multiple-values-files.txt b/pkg/cmd/testdata/output/install-with-multiple-values-files.txt similarity index 79% rename from cmd/helm/testdata/output/install-with-multiple-values-files.txt rename to pkg/cmd/testdata/output/install-with-multiple-values-files.txt index 406e522a9..1116cb907 100644 --- a/cmd/helm/testdata/output/install-with-multiple-values-files.txt +++ b/pkg/cmd/testdata/output/install-with-multiple-values-files.txt @@ -3,4 +3,5 @@ LAST DEPLOYED: Fri Sep 2 22:04:05 1977 NAMESPACE: default STATUS: deployed REVISION: 1 +DESCRIPTION: Install complete TEST SUITE: None diff --git a/cmd/helm/testdata/output/install-with-multiple-values.txt b/pkg/cmd/testdata/output/install-with-multiple-values.txt similarity index 79% rename from cmd/helm/testdata/output/install-with-multiple-values.txt rename to pkg/cmd/testdata/output/install-with-multiple-values.txt index 406e522a9..1116cb907 100644 --- a/cmd/helm/testdata/output/install-with-multiple-values.txt +++ b/pkg/cmd/testdata/output/install-with-multiple-values.txt @@ -3,4 +3,5 @@ LAST DEPLOYED: Fri Sep 2 22:04:05 1977 NAMESPACE: default STATUS: deployed REVISION: 1 +DESCRIPTION: Install complete TEST SUITE: None diff --git a/cmd/helm/testdata/output/install-with-timeout.txt b/pkg/cmd/testdata/output/install-with-timeout.txt similarity index 79% rename from cmd/helm/testdata/output/install-with-timeout.txt rename to pkg/cmd/testdata/output/install-with-timeout.txt index 19952e3c2..bcc5f87ba 100644 --- a/cmd/helm/testdata/output/install-with-timeout.txt +++ b/pkg/cmd/testdata/output/install-with-timeout.txt @@ -3,4 +3,5 @@ LAST DEPLOYED: Fri Sep 2 22:04:05 1977 NAMESPACE: default STATUS: deployed REVISION: 1 +DESCRIPTION: Install complete TEST SUITE: None diff --git a/cmd/helm/testdata/output/install-with-values-file.txt b/pkg/cmd/testdata/output/install-with-values-file.txt similarity index 79% rename from cmd/helm/testdata/output/install-with-values-file.txt rename to pkg/cmd/testdata/output/install-with-values-file.txt index 406e522a9..1116cb907 100644 --- a/cmd/helm/testdata/output/install-with-values-file.txt +++ b/pkg/cmd/testdata/output/install-with-values-file.txt @@ -3,4 +3,5 @@ LAST DEPLOYED: Fri Sep 2 22:04:05 1977 NAMESPACE: default STATUS: deployed REVISION: 1 +DESCRIPTION: Install complete TEST SUITE: None diff --git a/cmd/helm/testdata/output/install-with-values.txt b/pkg/cmd/testdata/output/install-with-values.txt similarity index 79% rename from cmd/helm/testdata/output/install-with-values.txt rename to pkg/cmd/testdata/output/install-with-values.txt index 406e522a9..1116cb907 100644 --- a/cmd/helm/testdata/output/install-with-values.txt +++ b/pkg/cmd/testdata/output/install-with-values.txt @@ -3,4 +3,5 @@ LAST DEPLOYED: Fri Sep 2 22:04:05 1977 NAMESPACE: default STATUS: deployed REVISION: 1 +DESCRIPTION: Install complete TEST SUITE: None diff --git a/cmd/helm/testdata/output/install-with-wait-for-jobs.txt b/pkg/cmd/testdata/output/install-with-wait-for-jobs.txt similarity index 79% rename from cmd/helm/testdata/output/install-with-wait-for-jobs.txt rename to pkg/cmd/testdata/output/install-with-wait-for-jobs.txt index 7ce22d4ec..c5676c610 100644 --- a/cmd/helm/testdata/output/install-with-wait-for-jobs.txt +++ b/pkg/cmd/testdata/output/install-with-wait-for-jobs.txt @@ -3,4 +3,5 @@ LAST DEPLOYED: Fri Sep 2 22:04:05 1977 NAMESPACE: default STATUS: deployed REVISION: 1 +DESCRIPTION: Install complete TEST SUITE: None diff --git a/cmd/helm/testdata/output/install-with-wait.txt b/pkg/cmd/testdata/output/install-with-wait.txt similarity index 79% rename from cmd/helm/testdata/output/install-with-wait.txt rename to pkg/cmd/testdata/output/install-with-wait.txt index 7ce22d4ec..c5676c610 100644 --- a/cmd/helm/testdata/output/install-with-wait.txt +++ b/pkg/cmd/testdata/output/install-with-wait.txt @@ -3,4 +3,5 @@ LAST DEPLOYED: Fri Sep 2 22:04:05 1977 NAMESPACE: default STATUS: deployed REVISION: 1 +DESCRIPTION: Install complete TEST SUITE: None diff --git a/cmd/helm/testdata/output/install.txt b/pkg/cmd/testdata/output/install.txt similarity index 79% rename from cmd/helm/testdata/output/install.txt rename to pkg/cmd/testdata/output/install.txt index 039d6aef6..fcf5cc0ef 100644 --- a/cmd/helm/testdata/output/install.txt +++ b/pkg/cmd/testdata/output/install.txt @@ -3,4 +3,5 @@ LAST DEPLOYED: Fri Sep 2 22:04:05 1977 NAMESPACE: default STATUS: deployed REVISION: 1 +DESCRIPTION: Install complete TEST SUITE: None diff --git a/cmd/helm/testdata/output/issue-9027.txt b/pkg/cmd/testdata/output/issue-9027.txt similarity index 100% rename from cmd/helm/testdata/output/issue-9027.txt rename to pkg/cmd/testdata/output/issue-9027.txt diff --git a/pkg/cmd/testdata/output/issue-totoml.txt b/pkg/cmd/testdata/output/issue-totoml.txt new file mode 100644 index 000000000..06cf4bb8d --- /dev/null +++ b/pkg/cmd/testdata/output/issue-totoml.txt @@ -0,0 +1,8 @@ +--- +# Source: issue-totoml/templates/configmap.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: issue-totoml +data: | + key = 13 diff --git a/cmd/helm/testdata/output/lint-chart-with-bad-subcharts-with-subcharts.txt b/pkg/cmd/testdata/output/lint-chart-with-bad-subcharts-with-subcharts.txt similarity index 100% rename from cmd/helm/testdata/output/lint-chart-with-bad-subcharts-with-subcharts.txt rename to pkg/cmd/testdata/output/lint-chart-with-bad-subcharts-with-subcharts.txt diff --git a/cmd/helm/testdata/output/lint-chart-with-bad-subcharts.txt b/pkg/cmd/testdata/output/lint-chart-with-bad-subcharts.txt similarity index 100% rename from cmd/helm/testdata/output/lint-chart-with-bad-subcharts.txt rename to pkg/cmd/testdata/output/lint-chart-with-bad-subcharts.txt diff --git a/cmd/helm/testdata/output/lint-chart-with-deprecated-api-old-k8s.txt b/pkg/cmd/testdata/output/lint-chart-with-deprecated-api-old-k8s.txt similarity index 100% rename from cmd/helm/testdata/output/lint-chart-with-deprecated-api-old-k8s.txt rename to pkg/cmd/testdata/output/lint-chart-with-deprecated-api-old-k8s.txt diff --git a/cmd/helm/testdata/output/lint-chart-with-deprecated-api-strict.txt b/pkg/cmd/testdata/output/lint-chart-with-deprecated-api-strict.txt similarity index 100% rename from cmd/helm/testdata/output/lint-chart-with-deprecated-api-strict.txt rename to pkg/cmd/testdata/output/lint-chart-with-deprecated-api-strict.txt diff --git a/cmd/helm/testdata/output/lint-chart-with-deprecated-api.txt b/pkg/cmd/testdata/output/lint-chart-with-deprecated-api.txt similarity index 100% rename from cmd/helm/testdata/output/lint-chart-with-deprecated-api.txt rename to pkg/cmd/testdata/output/lint-chart-with-deprecated-api.txt diff --git a/cmd/helm/testdata/output/lint-quiet-with-error.txt b/pkg/cmd/testdata/output/lint-quiet-with-error.txt similarity index 100% rename from cmd/helm/testdata/output/lint-quiet-with-error.txt rename to pkg/cmd/testdata/output/lint-quiet-with-error.txt diff --git a/cmd/helm/testdata/output/lint-quiet-with-warning.txt b/pkg/cmd/testdata/output/lint-quiet-with-warning.txt similarity index 100% rename from cmd/helm/testdata/output/lint-quiet-with-warning.txt rename to pkg/cmd/testdata/output/lint-quiet-with-warning.txt diff --git a/cmd/helm/testdata/output/lint-quiet.txt b/pkg/cmd/testdata/output/lint-quiet.txt similarity index 100% rename from cmd/helm/testdata/output/lint-quiet.txt rename to pkg/cmd/testdata/output/lint-quiet.txt diff --git a/cmd/helm/testdata/output/list-all.txt b/pkg/cmd/testdata/output/list-all.txt similarity index 100% rename from cmd/helm/testdata/output/list-all.txt rename to pkg/cmd/testdata/output/list-all.txt diff --git a/cmd/helm/testdata/output/list-date-reversed.txt b/pkg/cmd/testdata/output/list-date-reversed.txt similarity index 100% rename from cmd/helm/testdata/output/list-date-reversed.txt rename to pkg/cmd/testdata/output/list-date-reversed.txt diff --git a/cmd/helm/testdata/output/list-date.txt b/pkg/cmd/testdata/output/list-date.txt similarity index 100% rename from cmd/helm/testdata/output/list-date.txt rename to pkg/cmd/testdata/output/list-date.txt diff --git a/cmd/helm/testdata/output/list-failed.txt b/pkg/cmd/testdata/output/list-failed.txt similarity index 100% rename from cmd/helm/testdata/output/list-failed.txt rename to pkg/cmd/testdata/output/list-failed.txt diff --git a/cmd/helm/testdata/output/list-filter.txt b/pkg/cmd/testdata/output/list-filter.txt similarity index 100% rename from cmd/helm/testdata/output/list-filter.txt rename to pkg/cmd/testdata/output/list-filter.txt diff --git a/cmd/helm/testdata/output/list-max.txt b/pkg/cmd/testdata/output/list-max.txt similarity index 100% rename from cmd/helm/testdata/output/list-max.txt rename to pkg/cmd/testdata/output/list-max.txt diff --git a/cmd/helm/testdata/output/list-namespace.txt b/pkg/cmd/testdata/output/list-namespace.txt similarity index 100% rename from cmd/helm/testdata/output/list-namespace.txt rename to pkg/cmd/testdata/output/list-namespace.txt diff --git a/cmd/helm/testdata/output/list-no-headers.txt b/pkg/cmd/testdata/output/list-no-headers.txt similarity index 100% rename from cmd/helm/testdata/output/list-no-headers.txt rename to pkg/cmd/testdata/output/list-no-headers.txt diff --git a/cmd/helm/testdata/output/list-offset.txt b/pkg/cmd/testdata/output/list-offset.txt similarity index 100% rename from cmd/helm/testdata/output/list-offset.txt rename to pkg/cmd/testdata/output/list-offset.txt diff --git a/cmd/helm/testdata/output/list-pending.txt b/pkg/cmd/testdata/output/list-pending.txt similarity index 100% rename from cmd/helm/testdata/output/list-pending.txt rename to pkg/cmd/testdata/output/list-pending.txt diff --git a/cmd/helm/testdata/output/list-reverse.txt b/pkg/cmd/testdata/output/list-reverse.txt similarity index 100% rename from cmd/helm/testdata/output/list-reverse.txt rename to pkg/cmd/testdata/output/list-reverse.txt diff --git a/cmd/helm/testdata/output/list-short-json.txt b/pkg/cmd/testdata/output/list-short-json.txt similarity index 100% rename from cmd/helm/testdata/output/list-short-json.txt rename to pkg/cmd/testdata/output/list-short-json.txt diff --git a/cmd/helm/testdata/output/list-short-yaml.txt b/pkg/cmd/testdata/output/list-short-yaml.txt similarity index 100% rename from cmd/helm/testdata/output/list-short-yaml.txt rename to pkg/cmd/testdata/output/list-short-yaml.txt diff --git a/cmd/helm/testdata/output/list-short.txt b/pkg/cmd/testdata/output/list-short.txt similarity index 100% rename from cmd/helm/testdata/output/list-short.txt rename to pkg/cmd/testdata/output/list-short.txt diff --git a/cmd/helm/testdata/output/list-superseded.txt b/pkg/cmd/testdata/output/list-superseded.txt similarity index 100% rename from cmd/helm/testdata/output/list-superseded.txt rename to pkg/cmd/testdata/output/list-superseded.txt diff --git a/cmd/helm/testdata/output/list-uninstalled.txt b/pkg/cmd/testdata/output/list-uninstalled.txt similarity index 100% rename from cmd/helm/testdata/output/list-uninstalled.txt rename to pkg/cmd/testdata/output/list-uninstalled.txt diff --git a/cmd/helm/testdata/output/list-uninstalling.txt b/pkg/cmd/testdata/output/list-uninstalling.txt similarity index 100% rename from cmd/helm/testdata/output/list-uninstalling.txt rename to pkg/cmd/testdata/output/list-uninstalling.txt diff --git a/cmd/helm/testdata/output/list.txt b/pkg/cmd/testdata/output/list.txt similarity index 100% rename from cmd/helm/testdata/output/list.txt rename to pkg/cmd/testdata/output/list.txt diff --git a/cmd/helm/testdata/output/object-order.txt b/pkg/cmd/testdata/output/object-order.txt similarity index 100% rename from cmd/helm/testdata/output/object-order.txt rename to pkg/cmd/testdata/output/object-order.txt diff --git a/cmd/helm/testdata/output/output-comp.txt b/pkg/cmd/testdata/output/output-comp.txt similarity index 100% rename from cmd/helm/testdata/output/output-comp.txt rename to pkg/cmd/testdata/output/output-comp.txt diff --git a/cmd/helm/testdata/output/plugin_args_comp.txt b/pkg/cmd/testdata/output/plugin_args_comp.txt similarity index 100% rename from cmd/helm/testdata/output/plugin_args_comp.txt rename to pkg/cmd/testdata/output/plugin_args_comp.txt diff --git a/cmd/helm/testdata/output/plugin_args_flag_comp.txt b/pkg/cmd/testdata/output/plugin_args_flag_comp.txt similarity index 100% rename from cmd/helm/testdata/output/plugin_args_flag_comp.txt rename to pkg/cmd/testdata/output/plugin_args_flag_comp.txt diff --git a/cmd/helm/testdata/output/plugin_args_many_args_comp.txt b/pkg/cmd/testdata/output/plugin_args_many_args_comp.txt similarity index 100% rename from cmd/helm/testdata/output/plugin_args_many_args_comp.txt rename to pkg/cmd/testdata/output/plugin_args_many_args_comp.txt diff --git a/cmd/helm/testdata/output/plugin_args_ns_comp.txt b/pkg/cmd/testdata/output/plugin_args_ns_comp.txt similarity index 100% rename from cmd/helm/testdata/output/plugin_args_ns_comp.txt rename to pkg/cmd/testdata/output/plugin_args_ns_comp.txt diff --git a/cmd/helm/testdata/output/plugin_echo_no_directive.txt b/pkg/cmd/testdata/output/plugin_echo_no_directive.txt similarity index 100% rename from cmd/helm/testdata/output/plugin_echo_no_directive.txt rename to pkg/cmd/testdata/output/plugin_echo_no_directive.txt diff --git a/cmd/helm/testdata/output/plugin_list_comp.txt b/pkg/cmd/testdata/output/plugin_list_comp.txt similarity index 100% rename from cmd/helm/testdata/output/plugin_list_comp.txt rename to pkg/cmd/testdata/output/plugin_list_comp.txt diff --git a/cmd/helm/testdata/output/plugin_repeat_comp.txt b/pkg/cmd/testdata/output/plugin_repeat_comp.txt similarity index 100% rename from cmd/helm/testdata/output/plugin_repeat_comp.txt rename to pkg/cmd/testdata/output/plugin_repeat_comp.txt diff --git a/cmd/helm/testdata/output/release_list_comp.txt b/pkg/cmd/testdata/output/release_list_comp.txt similarity index 100% rename from cmd/helm/testdata/output/release_list_comp.txt rename to pkg/cmd/testdata/output/release_list_comp.txt diff --git a/cmd/helm/testdata/output/release_list_repeat_comp.txt b/pkg/cmd/testdata/output/release_list_repeat_comp.txt similarity index 100% rename from cmd/helm/testdata/output/release_list_repeat_comp.txt rename to pkg/cmd/testdata/output/release_list_repeat_comp.txt diff --git a/cmd/helm/testdata/output/repo-add.txt b/pkg/cmd/testdata/output/repo-add.txt similarity index 100% rename from cmd/helm/testdata/output/repo-add.txt rename to pkg/cmd/testdata/output/repo-add.txt diff --git a/cmd/helm/testdata/output/repo-add2.txt b/pkg/cmd/testdata/output/repo-add2.txt similarity index 100% rename from cmd/helm/testdata/output/repo-add2.txt rename to pkg/cmd/testdata/output/repo-add2.txt diff --git a/cmd/helm/testdata/output/repo_list_comp.txt b/pkg/cmd/testdata/output/repo_list_comp.txt similarity index 100% rename from cmd/helm/testdata/output/repo_list_comp.txt rename to pkg/cmd/testdata/output/repo_list_comp.txt diff --git a/cmd/helm/testdata/output/repo_repeat_comp.txt b/pkg/cmd/testdata/output/repo_repeat_comp.txt similarity index 100% rename from cmd/helm/testdata/output/repo_repeat_comp.txt rename to pkg/cmd/testdata/output/repo_repeat_comp.txt diff --git a/cmd/helm/testdata/output/revision-comp.txt b/pkg/cmd/testdata/output/revision-comp.txt similarity index 100% rename from cmd/helm/testdata/output/revision-comp.txt rename to pkg/cmd/testdata/output/revision-comp.txt diff --git a/cmd/helm/testdata/output/revision-wrong-args-comp.txt b/pkg/cmd/testdata/output/revision-wrong-args-comp.txt similarity index 100% rename from cmd/helm/testdata/output/revision-wrong-args-comp.txt rename to pkg/cmd/testdata/output/revision-wrong-args-comp.txt diff --git a/cmd/helm/testdata/output/rollback-comp.txt b/pkg/cmd/testdata/output/rollback-comp.txt similarity index 100% rename from cmd/helm/testdata/output/rollback-comp.txt rename to pkg/cmd/testdata/output/rollback-comp.txt diff --git a/cmd/helm/testdata/output/rollback-no-args.txt b/pkg/cmd/testdata/output/rollback-no-args.txt similarity index 100% rename from cmd/helm/testdata/output/rollback-no-args.txt rename to pkg/cmd/testdata/output/rollback-no-args.txt diff --git a/cmd/helm/testdata/output/rollback-no-revision.txt b/pkg/cmd/testdata/output/rollback-no-revision.txt similarity index 100% rename from cmd/helm/testdata/output/rollback-no-revision.txt rename to pkg/cmd/testdata/output/rollback-no-revision.txt diff --git a/cmd/helm/testdata/output/rollback-non-existent-version.txt b/pkg/cmd/testdata/output/rollback-non-existent-version.txt similarity index 100% rename from cmd/helm/testdata/output/rollback-non-existent-version.txt rename to pkg/cmd/testdata/output/rollback-non-existent-version.txt diff --git a/cmd/helm/testdata/output/rollback-timeout.txt b/pkg/cmd/testdata/output/rollback-timeout.txt similarity index 100% rename from cmd/helm/testdata/output/rollback-timeout.txt rename to pkg/cmd/testdata/output/rollback-timeout.txt diff --git a/cmd/helm/testdata/output/rollback-wait-for-jobs.txt b/pkg/cmd/testdata/output/rollback-wait-for-jobs.txt similarity index 100% rename from cmd/helm/testdata/output/rollback-wait-for-jobs.txt rename to pkg/cmd/testdata/output/rollback-wait-for-jobs.txt diff --git a/cmd/helm/testdata/output/rollback-wait.txt b/pkg/cmd/testdata/output/rollback-wait.txt similarity index 100% rename from cmd/helm/testdata/output/rollback-wait.txt rename to pkg/cmd/testdata/output/rollback-wait.txt diff --git a/cmd/helm/testdata/output/rollback-wrong-args-comp.txt b/pkg/cmd/testdata/output/rollback-wrong-args-comp.txt similarity index 100% rename from cmd/helm/testdata/output/rollback-wrong-args-comp.txt rename to pkg/cmd/testdata/output/rollback-wrong-args-comp.txt diff --git a/cmd/helm/testdata/output/rollback.txt b/pkg/cmd/testdata/output/rollback.txt similarity index 100% rename from cmd/helm/testdata/output/rollback.txt rename to pkg/cmd/testdata/output/rollback.txt diff --git a/cmd/helm/testdata/output/schema-negative-cli.txt b/pkg/cmd/testdata/output/schema-negative-cli.txt similarity index 73% rename from cmd/helm/testdata/output/schema-negative-cli.txt rename to pkg/cmd/testdata/output/schema-negative-cli.txt index c4a5cc516..12bcc5103 100644 --- a/cmd/helm/testdata/output/schema-negative-cli.txt +++ b/pkg/cmd/testdata/output/schema-negative-cli.txt @@ -1,4 +1,4 @@ Error: INSTALLATION FAILED: values don't meet the specifications of the schema(s) in the following chart(s): empty: -- age: Must be greater than or equal to 0 +- at '/age': minimum: got -5, want 0 diff --git a/cmd/helm/testdata/output/schema-negative.txt b/pkg/cmd/testdata/output/schema-negative.txt similarity index 59% rename from cmd/helm/testdata/output/schema-negative.txt rename to pkg/cmd/testdata/output/schema-negative.txt index 929af5518..daf132635 100644 --- a/cmd/helm/testdata/output/schema-negative.txt +++ b/pkg/cmd/testdata/output/schema-negative.txt @@ -1,5 +1,5 @@ Error: INSTALLATION FAILED: values don't meet the specifications of the schema(s) in the following chart(s): empty: -- (root): employmentInfo is required -- age: Must be greater than or equal to 0 +- at '': missing property 'employmentInfo' +- at '/age': minimum: got -5, want 0 diff --git a/cmd/helm/testdata/output/schema.txt b/pkg/cmd/testdata/output/schema.txt similarity index 79% rename from cmd/helm/testdata/output/schema.txt rename to pkg/cmd/testdata/output/schema.txt index 22a94b3f4..f5fe63768 100644 --- a/cmd/helm/testdata/output/schema.txt +++ b/pkg/cmd/testdata/output/schema.txt @@ -3,4 +3,5 @@ LAST DEPLOYED: Fri Sep 2 22:04:05 1977 NAMESPACE: default STATUS: deployed REVISION: 1 +DESCRIPTION: Install complete TEST SUITE: None diff --git a/cmd/helm/testdata/output/search-constraint-single.txt b/pkg/cmd/testdata/output/search-constraint-single.txt similarity index 100% rename from cmd/helm/testdata/output/search-constraint-single.txt rename to pkg/cmd/testdata/output/search-constraint-single.txt diff --git a/cmd/helm/testdata/output/search-constraint.txt b/pkg/cmd/testdata/output/search-constraint.txt similarity index 100% rename from cmd/helm/testdata/output/search-constraint.txt rename to pkg/cmd/testdata/output/search-constraint.txt diff --git a/cmd/helm/testdata/output/search-multiple-devel-release.txt b/pkg/cmd/testdata/output/search-multiple-devel-release.txt similarity index 100% rename from cmd/helm/testdata/output/search-multiple-devel-release.txt rename to pkg/cmd/testdata/output/search-multiple-devel-release.txt diff --git a/cmd/helm/testdata/output/search-multiple-stable-release.txt b/pkg/cmd/testdata/output/search-multiple-stable-release.txt similarity index 100% rename from cmd/helm/testdata/output/search-multiple-stable-release.txt rename to pkg/cmd/testdata/output/search-multiple-stable-release.txt diff --git a/cmd/helm/testdata/output/search-multiple-versions-constraints.txt b/pkg/cmd/testdata/output/search-multiple-versions-constraints.txt similarity index 100% rename from cmd/helm/testdata/output/search-multiple-versions-constraints.txt rename to pkg/cmd/testdata/output/search-multiple-versions-constraints.txt diff --git a/cmd/helm/testdata/output/search-multiple-versions.txt b/pkg/cmd/testdata/output/search-multiple-versions.txt similarity index 100% rename from cmd/helm/testdata/output/search-multiple-versions.txt rename to pkg/cmd/testdata/output/search-multiple-versions.txt diff --git a/cmd/helm/testdata/output/search-not-found-error.txt b/pkg/cmd/testdata/output/search-not-found-error.txt similarity index 100% rename from cmd/helm/testdata/output/search-not-found-error.txt rename to pkg/cmd/testdata/output/search-not-found-error.txt diff --git a/cmd/helm/testdata/output/search-not-found.txt b/pkg/cmd/testdata/output/search-not-found.txt similarity index 100% rename from cmd/helm/testdata/output/search-not-found.txt rename to pkg/cmd/testdata/output/search-not-found.txt diff --git a/cmd/helm/testdata/output/search-output-json.txt b/pkg/cmd/testdata/output/search-output-json.txt similarity index 100% rename from cmd/helm/testdata/output/search-output-json.txt rename to pkg/cmd/testdata/output/search-output-json.txt diff --git a/cmd/helm/testdata/output/search-output-yaml.txt b/pkg/cmd/testdata/output/search-output-yaml.txt similarity index 100% rename from cmd/helm/testdata/output/search-output-yaml.txt rename to pkg/cmd/testdata/output/search-output-yaml.txt diff --git a/cmd/helm/testdata/output/search-regex.txt b/pkg/cmd/testdata/output/search-regex.txt similarity index 100% rename from cmd/helm/testdata/output/search-regex.txt rename to pkg/cmd/testdata/output/search-regex.txt diff --git a/cmd/helm/testdata/output/search-versions-constraint.txt b/pkg/cmd/testdata/output/search-versions-constraint.txt similarity index 100% rename from cmd/helm/testdata/output/search-versions-constraint.txt rename to pkg/cmd/testdata/output/search-versions-constraint.txt diff --git a/cmd/helm/testdata/output/status-comp.txt b/pkg/cmd/testdata/output/status-comp.txt similarity index 100% rename from cmd/helm/testdata/output/status-comp.txt rename to pkg/cmd/testdata/output/status-comp.txt diff --git a/cmd/helm/testdata/output/status-with-desc.txt b/pkg/cmd/testdata/output/status-with-desc.txt similarity index 100% rename from cmd/helm/testdata/output/status-with-desc.txt rename to pkg/cmd/testdata/output/status-with-desc.txt diff --git a/cmd/helm/testdata/output/status-with-notes.txt b/pkg/cmd/testdata/output/status-with-notes.txt similarity index 91% rename from cmd/helm/testdata/output/status-with-notes.txt rename to pkg/cmd/testdata/output/status-with-notes.txt index e992ce91e..f05be6c18 100644 --- a/cmd/helm/testdata/output/status-with-notes.txt +++ b/pkg/cmd/testdata/output/status-with-notes.txt @@ -3,6 +3,7 @@ LAST DEPLOYED: Sat Jan 16 00:00:00 2016 NAMESPACE: default STATUS: deployed REVISION: 0 +DESCRIPTION: TEST SUITE: None NOTES: release notes diff --git a/cmd/helm/testdata/output/status-with-resources.json b/pkg/cmd/testdata/output/status-with-resources.json similarity index 100% rename from cmd/helm/testdata/output/status-with-resources.json rename to pkg/cmd/testdata/output/status-with-resources.json diff --git a/cmd/helm/testdata/output/status-with-resources.txt b/pkg/cmd/testdata/output/status-with-resources.txt similarity index 90% rename from cmd/helm/testdata/output/status-with-resources.txt rename to pkg/cmd/testdata/output/status-with-resources.txt index a326c3db0..20763acda 100644 --- a/cmd/helm/testdata/output/status-with-resources.txt +++ b/pkg/cmd/testdata/output/status-with-resources.txt @@ -3,4 +3,5 @@ LAST DEPLOYED: Sat Jan 16 00:00:00 2016 NAMESPACE: default STATUS: deployed REVISION: 0 +DESCRIPTION: TEST SUITE: None diff --git a/cmd/helm/testdata/output/status-with-test-suite.txt b/pkg/cmd/testdata/output/status-with-test-suite.txt similarity index 96% rename from cmd/helm/testdata/output/status-with-test-suite.txt rename to pkg/cmd/testdata/output/status-with-test-suite.txt index 58c67e103..7c1ade450 100644 --- a/cmd/helm/testdata/output/status-with-test-suite.txt +++ b/pkg/cmd/testdata/output/status-with-test-suite.txt @@ -3,6 +3,7 @@ LAST DEPLOYED: Sat Jan 16 00:00:00 2016 NAMESPACE: default STATUS: deployed REVISION: 0 +DESCRIPTION: TEST SUITE: passing-test Last Started: Mon Jan 2 15:04:05 2006 Last Completed: Mon Jan 2 15:04:07 2006 diff --git a/cmd/helm/testdata/output/status-wrong-args-comp.txt b/pkg/cmd/testdata/output/status-wrong-args-comp.txt similarity index 100% rename from cmd/helm/testdata/output/status-wrong-args-comp.txt rename to pkg/cmd/testdata/output/status-wrong-args-comp.txt diff --git a/cmd/helm/testdata/output/status.json b/pkg/cmd/testdata/output/status.json similarity index 100% rename from cmd/helm/testdata/output/status.json rename to pkg/cmd/testdata/output/status.json diff --git a/cmd/helm/testdata/output/status.txt b/pkg/cmd/testdata/output/status.txt similarity index 90% rename from cmd/helm/testdata/output/status.txt rename to pkg/cmd/testdata/output/status.txt index a326c3db0..20763acda 100644 --- a/cmd/helm/testdata/output/status.txt +++ b/pkg/cmd/testdata/output/status.txt @@ -3,4 +3,5 @@ LAST DEPLOYED: Sat Jan 16 00:00:00 2016 NAMESPACE: default STATUS: deployed REVISION: 0 +DESCRIPTION: TEST SUITE: None diff --git a/cmd/helm/testdata/output/subchart-schema-cli-negative.txt b/pkg/cmd/testdata/output/subchart-schema-cli-negative.txt similarity index 75% rename from cmd/helm/testdata/output/subchart-schema-cli-negative.txt rename to pkg/cmd/testdata/output/subchart-schema-cli-negative.txt index 7396b4bfe..179550f69 100644 --- a/cmd/helm/testdata/output/subchart-schema-cli-negative.txt +++ b/pkg/cmd/testdata/output/subchart-schema-cli-negative.txt @@ -1,4 +1,4 @@ Error: INSTALLATION FAILED: values don't meet the specifications of the schema(s) in the following chart(s): subchart-with-schema: -- age: Must be greater than or equal to 0 +- at '/age': minimum: got -25, want 0 diff --git a/cmd/helm/testdata/output/subchart-schema-cli.txt b/pkg/cmd/testdata/output/subchart-schema-cli.txt similarity index 79% rename from cmd/helm/testdata/output/subchart-schema-cli.txt rename to pkg/cmd/testdata/output/subchart-schema-cli.txt index 22a94b3f4..f5fe63768 100644 --- a/cmd/helm/testdata/output/subchart-schema-cli.txt +++ b/pkg/cmd/testdata/output/subchart-schema-cli.txt @@ -3,4 +3,5 @@ LAST DEPLOYED: Fri Sep 2 22:04:05 1977 NAMESPACE: default STATUS: deployed REVISION: 1 +DESCRIPTION: Install complete TEST SUITE: None diff --git a/cmd/helm/testdata/output/subchart-schema-negative.txt b/pkg/cmd/testdata/output/subchart-schema-negative.txt similarity index 69% rename from cmd/helm/testdata/output/subchart-schema-negative.txt rename to pkg/cmd/testdata/output/subchart-schema-negative.txt index 7b1f654a2..7522ef3e4 100644 --- a/cmd/helm/testdata/output/subchart-schema-negative.txt +++ b/pkg/cmd/testdata/output/subchart-schema-negative.txt @@ -1,6 +1,6 @@ Error: INSTALLATION FAILED: values don't meet the specifications of the schema(s) in the following chart(s): chart-without-schema: -- (root): lastname is required +- at '': missing property 'lastname' subchart-with-schema: -- (root): age is required +- at '': missing property 'age' diff --git a/cmd/helm/testdata/output/template-chart-bad-type.txt b/pkg/cmd/testdata/output/template-chart-bad-type.txt similarity index 100% rename from cmd/helm/testdata/output/template-chart-bad-type.txt rename to pkg/cmd/testdata/output/template-chart-bad-type.txt diff --git a/cmd/helm/testdata/output/template-chart-with-template-lib-archive-dep.txt b/pkg/cmd/testdata/output/template-chart-with-template-lib-archive-dep.txt similarity index 100% rename from cmd/helm/testdata/output/template-chart-with-template-lib-archive-dep.txt rename to pkg/cmd/testdata/output/template-chart-with-template-lib-archive-dep.txt diff --git a/cmd/helm/testdata/output/template-chart-with-template-lib-dep.txt b/pkg/cmd/testdata/output/template-chart-with-template-lib-dep.txt similarity index 100% rename from cmd/helm/testdata/output/template-chart-with-template-lib-dep.txt rename to pkg/cmd/testdata/output/template-chart-with-template-lib-dep.txt diff --git a/cmd/helm/testdata/output/template-lib-chart.txt b/pkg/cmd/testdata/output/template-lib-chart.txt similarity index 100% rename from cmd/helm/testdata/output/template-lib-chart.txt rename to pkg/cmd/testdata/output/template-lib-chart.txt diff --git a/cmd/helm/testdata/output/template-name-template.txt b/pkg/cmd/testdata/output/template-name-template.txt similarity index 100% rename from cmd/helm/testdata/output/template-name-template.txt rename to pkg/cmd/testdata/output/template-name-template.txt diff --git a/cmd/helm/testdata/output/template-no-args.txt b/pkg/cmd/testdata/output/template-no-args.txt similarity index 100% rename from cmd/helm/testdata/output/template-no-args.txt rename to pkg/cmd/testdata/output/template-no-args.txt diff --git a/cmd/helm/testdata/output/template-set.txt b/pkg/cmd/testdata/output/template-set.txt similarity index 100% rename from cmd/helm/testdata/output/template-set.txt rename to pkg/cmd/testdata/output/template-set.txt diff --git a/cmd/helm/testdata/output/template-show-only-glob.txt b/pkg/cmd/testdata/output/template-show-only-glob.txt similarity index 100% rename from cmd/helm/testdata/output/template-show-only-glob.txt rename to pkg/cmd/testdata/output/template-show-only-glob.txt diff --git a/cmd/helm/testdata/output/template-show-only-multiple.txt b/pkg/cmd/testdata/output/template-show-only-multiple.txt similarity index 100% rename from cmd/helm/testdata/output/template-show-only-multiple.txt rename to pkg/cmd/testdata/output/template-show-only-multiple.txt diff --git a/cmd/helm/testdata/output/template-show-only-one.txt b/pkg/cmd/testdata/output/template-show-only-one.txt similarity index 100% rename from cmd/helm/testdata/output/template-show-only-one.txt rename to pkg/cmd/testdata/output/template-show-only-one.txt diff --git a/cmd/helm/testdata/output/template-skip-tests.txt b/pkg/cmd/testdata/output/template-skip-tests.txt similarity index 100% rename from cmd/helm/testdata/output/template-skip-tests.txt rename to pkg/cmd/testdata/output/template-skip-tests.txt diff --git a/cmd/helm/testdata/output/template-subchart-cm-set-file.txt b/pkg/cmd/testdata/output/template-subchart-cm-set-file.txt similarity index 100% rename from cmd/helm/testdata/output/template-subchart-cm-set-file.txt rename to pkg/cmd/testdata/output/template-subchart-cm-set-file.txt diff --git a/cmd/helm/testdata/output/template-subchart-cm-set.txt b/pkg/cmd/testdata/output/template-subchart-cm-set.txt similarity index 100% rename from cmd/helm/testdata/output/template-subchart-cm-set.txt rename to pkg/cmd/testdata/output/template-subchart-cm-set.txt diff --git a/cmd/helm/testdata/output/template-subchart-cm.txt b/pkg/cmd/testdata/output/template-subchart-cm.txt similarity index 100% rename from cmd/helm/testdata/output/template-subchart-cm.txt rename to pkg/cmd/testdata/output/template-subchart-cm.txt diff --git a/cmd/helm/testdata/output/template-values-files.txt b/pkg/cmd/testdata/output/template-values-files.txt similarity index 100% rename from cmd/helm/testdata/output/template-values-files.txt rename to pkg/cmd/testdata/output/template-values-files.txt diff --git a/cmd/helm/testdata/output/template-with-api-version.txt b/pkg/cmd/testdata/output/template-with-api-version.txt similarity index 100% rename from cmd/helm/testdata/output/template-with-api-version.txt rename to pkg/cmd/testdata/output/template-with-api-version.txt diff --git a/cmd/helm/testdata/output/template-with-crds.txt b/pkg/cmd/testdata/output/template-with-crds.txt similarity index 100% rename from cmd/helm/testdata/output/template-with-crds.txt rename to pkg/cmd/testdata/output/template-with-crds.txt diff --git a/cmd/helm/testdata/output/template-with-invalid-yaml-debug.txt b/pkg/cmd/testdata/output/template-with-invalid-yaml-debug.txt similarity index 100% rename from cmd/helm/testdata/output/template-with-invalid-yaml-debug.txt rename to pkg/cmd/testdata/output/template-with-invalid-yaml-debug.txt diff --git a/cmd/helm/testdata/output/template-with-invalid-yaml.txt b/pkg/cmd/testdata/output/template-with-invalid-yaml.txt similarity index 100% rename from cmd/helm/testdata/output/template-with-invalid-yaml.txt rename to pkg/cmd/testdata/output/template-with-invalid-yaml.txt diff --git a/cmd/helm/testdata/output/template-with-kube-version.txt b/pkg/cmd/testdata/output/template-with-kube-version.txt similarity index 100% rename from cmd/helm/testdata/output/template-with-kube-version.txt rename to pkg/cmd/testdata/output/template-with-kube-version.txt diff --git a/cmd/helm/testdata/output/template.txt b/pkg/cmd/testdata/output/template.txt similarity index 100% rename from cmd/helm/testdata/output/template.txt rename to pkg/cmd/testdata/output/template.txt diff --git a/cmd/helm/testdata/output/uninstall-keep-history.txt b/pkg/cmd/testdata/output/uninstall-keep-history.txt similarity index 100% rename from cmd/helm/testdata/output/uninstall-keep-history.txt rename to pkg/cmd/testdata/output/uninstall-keep-history.txt diff --git a/cmd/helm/testdata/output/uninstall-multiple.txt b/pkg/cmd/testdata/output/uninstall-multiple.txt similarity index 100% rename from cmd/helm/testdata/output/uninstall-multiple.txt rename to pkg/cmd/testdata/output/uninstall-multiple.txt diff --git a/cmd/helm/testdata/output/uninstall-no-args.txt b/pkg/cmd/testdata/output/uninstall-no-args.txt similarity index 100% rename from cmd/helm/testdata/output/uninstall-no-args.txt rename to pkg/cmd/testdata/output/uninstall-no-args.txt diff --git a/cmd/helm/testdata/output/uninstall-no-hooks.txt b/pkg/cmd/testdata/output/uninstall-no-hooks.txt similarity index 100% rename from cmd/helm/testdata/output/uninstall-no-hooks.txt rename to pkg/cmd/testdata/output/uninstall-no-hooks.txt diff --git a/cmd/helm/testdata/output/uninstall-timeout.txt b/pkg/cmd/testdata/output/uninstall-timeout.txt similarity index 100% rename from cmd/helm/testdata/output/uninstall-timeout.txt rename to pkg/cmd/testdata/output/uninstall-timeout.txt diff --git a/cmd/helm/testdata/output/uninstall-wait.txt b/pkg/cmd/testdata/output/uninstall-wait.txt similarity index 100% rename from cmd/helm/testdata/output/uninstall-wait.txt rename to pkg/cmd/testdata/output/uninstall-wait.txt diff --git a/cmd/helm/testdata/output/uninstall.txt b/pkg/cmd/testdata/output/uninstall.txt similarity index 100% rename from cmd/helm/testdata/output/uninstall.txt rename to pkg/cmd/testdata/output/uninstall.txt diff --git a/pkg/cmd/testdata/output/upgrade-and-take-ownership.txt b/pkg/cmd/testdata/output/upgrade-and-take-ownership.txt new file mode 100644 index 000000000..59267651f --- /dev/null +++ b/pkg/cmd/testdata/output/upgrade-and-take-ownership.txt @@ -0,0 +1,8 @@ +Release "funny-bunny" has been upgraded. Happy Helming! +NAME: funny-bunny +LAST DEPLOYED: Fri Sep 2 22:04:05 1977 +NAMESPACE: default +STATUS: deployed +REVISION: 3 +DESCRIPTION: Upgrade complete +TEST SUITE: None diff --git a/cmd/helm/testdata/output/upgrade-uninstalled-with-keep-history.txt b/pkg/cmd/testdata/output/upgrade-uninstalled-with-keep-history.txt similarity index 85% rename from cmd/helm/testdata/output/upgrade-uninstalled-with-keep-history.txt rename to pkg/cmd/testdata/output/upgrade-uninstalled-with-keep-history.txt index f53b8715a..d5c42d15c 100644 --- a/cmd/helm/testdata/output/upgrade-uninstalled-with-keep-history.txt +++ b/pkg/cmd/testdata/output/upgrade-uninstalled-with-keep-history.txt @@ -4,4 +4,5 @@ LAST DEPLOYED: Fri Sep 2 22:04:05 1977 NAMESPACE: default STATUS: deployed REVISION: 3 +DESCRIPTION: Install complete TEST SUITE: None diff --git a/cmd/helm/testdata/output/upgrade-with-bad-dependencies.txt b/pkg/cmd/testdata/output/upgrade-with-bad-dependencies.txt similarity index 100% rename from cmd/helm/testdata/output/upgrade-with-bad-dependencies.txt rename to pkg/cmd/testdata/output/upgrade-with-bad-dependencies.txt diff --git a/cmd/helm/testdata/output/upgrade-with-bad-or-missing-existing-release.txt b/pkg/cmd/testdata/output/upgrade-with-bad-or-missing-existing-release.txt similarity index 100% rename from cmd/helm/testdata/output/upgrade-with-bad-or-missing-existing-release.txt rename to pkg/cmd/testdata/output/upgrade-with-bad-or-missing-existing-release.txt diff --git a/cmd/helm/testdata/output/upgrade-with-dependency-update.txt b/pkg/cmd/testdata/output/upgrade-with-dependency-update.txt similarity index 86% rename from cmd/helm/testdata/output/upgrade-with-dependency-update.txt rename to pkg/cmd/testdata/output/upgrade-with-dependency-update.txt index 0e7e5842e..d1517a686 100644 --- a/cmd/helm/testdata/output/upgrade-with-dependency-update.txt +++ b/pkg/cmd/testdata/output/upgrade-with-dependency-update.txt @@ -4,6 +4,7 @@ LAST DEPLOYED: Fri Sep 2 22:04:05 1977 NAMESPACE: default STATUS: deployed REVISION: 3 +DESCRIPTION: Upgrade complete TEST SUITE: None NOTES: PARENT NOTES diff --git a/cmd/helm/testdata/output/upgrade-with-install-timeout.txt b/pkg/cmd/testdata/output/upgrade-with-install-timeout.txt similarity index 85% rename from cmd/helm/testdata/output/upgrade-with-install-timeout.txt rename to pkg/cmd/testdata/output/upgrade-with-install-timeout.txt index 5d8d3a4ea..b159dc3bc 100644 --- a/cmd/helm/testdata/output/upgrade-with-install-timeout.txt +++ b/pkg/cmd/testdata/output/upgrade-with-install-timeout.txt @@ -4,4 +4,5 @@ LAST DEPLOYED: Fri Sep 2 22:04:05 1977 NAMESPACE: default STATUS: deployed REVISION: 2 +DESCRIPTION: Upgrade complete TEST SUITE: None diff --git a/cmd/helm/testdata/output/upgrade-with-install.txt b/pkg/cmd/testdata/output/upgrade-with-install.txt similarity index 85% rename from cmd/helm/testdata/output/upgrade-with-install.txt rename to pkg/cmd/testdata/output/upgrade-with-install.txt index af61212bd..7dc2fce69 100644 --- a/cmd/helm/testdata/output/upgrade-with-install.txt +++ b/pkg/cmd/testdata/output/upgrade-with-install.txt @@ -4,4 +4,5 @@ LAST DEPLOYED: Fri Sep 2 22:04:05 1977 NAMESPACE: default STATUS: deployed REVISION: 2 +DESCRIPTION: Upgrade complete TEST SUITE: None diff --git a/cmd/helm/testdata/output/upgrade-with-missing-dependencies.txt b/pkg/cmd/testdata/output/upgrade-with-missing-dependencies.txt similarity index 100% rename from cmd/helm/testdata/output/upgrade-with-missing-dependencies.txt rename to pkg/cmd/testdata/output/upgrade-with-missing-dependencies.txt diff --git a/cmd/helm/testdata/output/upgrade-with-pending-install.txt b/pkg/cmd/testdata/output/upgrade-with-pending-install.txt similarity index 100% rename from cmd/helm/testdata/output/upgrade-with-pending-install.txt rename to pkg/cmd/testdata/output/upgrade-with-pending-install.txt diff --git a/cmd/helm/testdata/output/upgrade-with-reset-values.txt b/pkg/cmd/testdata/output/upgrade-with-reset-values.txt similarity index 85% rename from cmd/helm/testdata/output/upgrade-with-reset-values.txt rename to pkg/cmd/testdata/output/upgrade-with-reset-values.txt index 01f1c0ac8..d02993a5c 100644 --- a/cmd/helm/testdata/output/upgrade-with-reset-values.txt +++ b/pkg/cmd/testdata/output/upgrade-with-reset-values.txt @@ -4,4 +4,5 @@ LAST DEPLOYED: Fri Sep 2 22:04:05 1977 NAMESPACE: default STATUS: deployed REVISION: 5 +DESCRIPTION: Upgrade complete TEST SUITE: None diff --git a/cmd/helm/testdata/output/upgrade-with-reset-values2.txt b/pkg/cmd/testdata/output/upgrade-with-reset-values2.txt similarity index 85% rename from cmd/helm/testdata/output/upgrade-with-reset-values2.txt rename to pkg/cmd/testdata/output/upgrade-with-reset-values2.txt index fdd1d2db7..7780c4fdc 100644 --- a/cmd/helm/testdata/output/upgrade-with-reset-values2.txt +++ b/pkg/cmd/testdata/output/upgrade-with-reset-values2.txt @@ -4,4 +4,5 @@ LAST DEPLOYED: Fri Sep 2 22:04:05 1977 NAMESPACE: default STATUS: deployed REVISION: 6 +DESCRIPTION: Upgrade complete TEST SUITE: None diff --git a/cmd/helm/testdata/output/upgrade-with-timeout.txt b/pkg/cmd/testdata/output/upgrade-with-timeout.txt similarity index 85% rename from cmd/helm/testdata/output/upgrade-with-timeout.txt rename to pkg/cmd/testdata/output/upgrade-with-timeout.txt index be3a42368..b1edac3af 100644 --- a/cmd/helm/testdata/output/upgrade-with-timeout.txt +++ b/pkg/cmd/testdata/output/upgrade-with-timeout.txt @@ -4,4 +4,5 @@ LAST DEPLOYED: Fri Sep 2 22:04:05 1977 NAMESPACE: default STATUS: deployed REVISION: 4 +DESCRIPTION: Upgrade complete TEST SUITE: None diff --git a/cmd/helm/testdata/output/upgrade-with-wait-for-jobs.txt b/pkg/cmd/testdata/output/upgrade-with-wait-for-jobs.txt similarity index 85% rename from cmd/helm/testdata/output/upgrade-with-wait-for-jobs.txt rename to pkg/cmd/testdata/output/upgrade-with-wait-for-jobs.txt index 500d07a11..21784413c 100644 --- a/cmd/helm/testdata/output/upgrade-with-wait-for-jobs.txt +++ b/pkg/cmd/testdata/output/upgrade-with-wait-for-jobs.txt @@ -4,4 +4,5 @@ LAST DEPLOYED: Fri Sep 2 22:04:05 1977 NAMESPACE: default STATUS: deployed REVISION: 3 +DESCRIPTION: Upgrade complete TEST SUITE: None diff --git a/cmd/helm/testdata/output/upgrade-with-wait.txt b/pkg/cmd/testdata/output/upgrade-with-wait.txt similarity index 85% rename from cmd/helm/testdata/output/upgrade-with-wait.txt rename to pkg/cmd/testdata/output/upgrade-with-wait.txt index 500d07a11..21784413c 100644 --- a/cmd/helm/testdata/output/upgrade-with-wait.txt +++ b/pkg/cmd/testdata/output/upgrade-with-wait.txt @@ -4,4 +4,5 @@ LAST DEPLOYED: Fri Sep 2 22:04:05 1977 NAMESPACE: default STATUS: deployed REVISION: 3 +DESCRIPTION: Upgrade complete TEST SUITE: None diff --git a/cmd/helm/testdata/output/upgrade.txt b/pkg/cmd/testdata/output/upgrade.txt similarity index 85% rename from cmd/helm/testdata/output/upgrade.txt rename to pkg/cmd/testdata/output/upgrade.txt index bea42db54..59267651f 100644 --- a/cmd/helm/testdata/output/upgrade.txt +++ b/pkg/cmd/testdata/output/upgrade.txt @@ -4,4 +4,5 @@ LAST DEPLOYED: Fri Sep 2 22:04:05 1977 NAMESPACE: default STATUS: deployed REVISION: 3 +DESCRIPTION: Upgrade complete TEST SUITE: None diff --git a/cmd/helm/testdata/output/values.json b/pkg/cmd/testdata/output/values.json similarity index 100% rename from cmd/helm/testdata/output/values.json rename to pkg/cmd/testdata/output/values.json diff --git a/cmd/helm/testdata/output/values.yaml b/pkg/cmd/testdata/output/values.yaml similarity index 100% rename from cmd/helm/testdata/output/values.yaml rename to pkg/cmd/testdata/output/values.yaml diff --git a/pkg/cmd/testdata/output/version-client-shorthand.txt b/pkg/cmd/testdata/output/version-client-shorthand.txt new file mode 100644 index 000000000..3b138ae77 --- /dev/null +++ b/pkg/cmd/testdata/output/version-client-shorthand.txt @@ -0,0 +1 @@ +version.BuildInfo{Version:"v4.0", GitCommit:"", GitTreeState:"", GoVersion:""} diff --git a/pkg/cmd/testdata/output/version-client.txt b/pkg/cmd/testdata/output/version-client.txt new file mode 100644 index 000000000..3b138ae77 --- /dev/null +++ b/pkg/cmd/testdata/output/version-client.txt @@ -0,0 +1 @@ +version.BuildInfo{Version:"v4.0", GitCommit:"", GitTreeState:"", GoVersion:""} diff --git a/cmd/helm/testdata/output/version-comp.txt b/pkg/cmd/testdata/output/version-comp.txt similarity index 100% rename from cmd/helm/testdata/output/version-comp.txt rename to pkg/cmd/testdata/output/version-comp.txt diff --git a/cmd/helm/testdata/output/version-invalid-comp.txt b/pkg/cmd/testdata/output/version-invalid-comp.txt similarity index 100% rename from cmd/helm/testdata/output/version-invalid-comp.txt rename to pkg/cmd/testdata/output/version-invalid-comp.txt diff --git a/pkg/cmd/testdata/output/version-short.txt b/pkg/cmd/testdata/output/version-short.txt new file mode 100644 index 000000000..1961bcc21 --- /dev/null +++ b/pkg/cmd/testdata/output/version-short.txt @@ -0,0 +1 @@ +v4.0 diff --git a/pkg/cmd/testdata/output/version-template.txt b/pkg/cmd/testdata/output/version-template.txt new file mode 100644 index 000000000..1c3c8f5d7 --- /dev/null +++ b/pkg/cmd/testdata/output/version-template.txt @@ -0,0 +1 @@ +Version: v4.0 \ No newline at end of file diff --git a/pkg/cmd/testdata/output/version.txt b/pkg/cmd/testdata/output/version.txt new file mode 100644 index 000000000..3b138ae77 --- /dev/null +++ b/pkg/cmd/testdata/output/version.txt @@ -0,0 +1 @@ +version.BuildInfo{Version:"v4.0", GitCommit:"", GitTreeState:"", GoVersion:""} diff --git a/cmd/helm/testdata/password b/pkg/cmd/testdata/password similarity index 100% rename from cmd/helm/testdata/password rename to pkg/cmd/testdata/password diff --git a/cmd/helm/testdata/plugins.yaml b/pkg/cmd/testdata/plugins.yaml similarity index 100% rename from cmd/helm/testdata/plugins.yaml rename to pkg/cmd/testdata/plugins.yaml diff --git a/cmd/helm/testdata/repositories.yaml b/pkg/cmd/testdata/repositories.yaml similarity index 100% rename from cmd/helm/testdata/repositories.yaml rename to pkg/cmd/testdata/repositories.yaml diff --git a/cmd/helm/testdata/testcharts/alpine/Chart.yaml b/pkg/cmd/testdata/testcharts/alpine/Chart.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/alpine/Chart.yaml rename to pkg/cmd/testdata/testcharts/alpine/Chart.yaml diff --git a/cmd/helm/testdata/testcharts/alpine/README.md b/pkg/cmd/testdata/testcharts/alpine/README.md similarity index 100% rename from cmd/helm/testdata/testcharts/alpine/README.md rename to pkg/cmd/testdata/testcharts/alpine/README.md diff --git a/cmd/helm/testdata/testcharts/alpine/extra_values.yaml b/pkg/cmd/testdata/testcharts/alpine/extra_values.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/alpine/extra_values.yaml rename to pkg/cmd/testdata/testcharts/alpine/extra_values.yaml diff --git a/cmd/helm/testdata/testcharts/alpine/more_values.yaml b/pkg/cmd/testdata/testcharts/alpine/more_values.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/alpine/more_values.yaml rename to pkg/cmd/testdata/testcharts/alpine/more_values.yaml diff --git a/cmd/helm/testdata/testcharts/alpine/templates/alpine-pod.yaml b/pkg/cmd/testdata/testcharts/alpine/templates/alpine-pod.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/alpine/templates/alpine-pod.yaml rename to pkg/cmd/testdata/testcharts/alpine/templates/alpine-pod.yaml diff --git a/cmd/helm/testdata/testcharts/alpine/values.yaml b/pkg/cmd/testdata/testcharts/alpine/values.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/alpine/values.yaml rename to pkg/cmd/testdata/testcharts/alpine/values.yaml diff --git a/cmd/helm/testdata/testcharts/chart-bad-requirements/.helmignore b/pkg/cmd/testdata/testcharts/chart-bad-requirements/.helmignore similarity index 100% rename from cmd/helm/testdata/testcharts/chart-bad-requirements/.helmignore rename to pkg/cmd/testdata/testcharts/chart-bad-requirements/.helmignore diff --git a/cmd/helm/testdata/testcharts/chart-bad-requirements/Chart.yaml b/pkg/cmd/testdata/testcharts/chart-bad-requirements/Chart.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/chart-bad-requirements/Chart.yaml rename to pkg/cmd/testdata/testcharts/chart-bad-requirements/Chart.yaml diff --git a/cmd/helm/testdata/testcharts/chart-bad-requirements/charts/reqsubchart/.helmignore b/pkg/cmd/testdata/testcharts/chart-bad-requirements/charts/reqsubchart/.helmignore similarity index 100% rename from cmd/helm/testdata/testcharts/chart-bad-requirements/charts/reqsubchart/.helmignore rename to pkg/cmd/testdata/testcharts/chart-bad-requirements/charts/reqsubchart/.helmignore diff --git a/cmd/helm/testdata/testcharts/chart-bad-requirements/charts/reqsubchart/Chart.yaml b/pkg/cmd/testdata/testcharts/chart-bad-requirements/charts/reqsubchart/Chart.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/chart-bad-requirements/charts/reqsubchart/Chart.yaml rename to pkg/cmd/testdata/testcharts/chart-bad-requirements/charts/reqsubchart/Chart.yaml diff --git a/cmd/helm/testdata/testcharts/chart-bad-requirements/charts/reqsubchart/values.yaml b/pkg/cmd/testdata/testcharts/chart-bad-requirements/charts/reqsubchart/values.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/chart-bad-requirements/charts/reqsubchart/values.yaml rename to pkg/cmd/testdata/testcharts/chart-bad-requirements/charts/reqsubchart/values.yaml diff --git a/cmd/helm/testdata/testcharts/chart-bad-requirements/values.yaml b/pkg/cmd/testdata/testcharts/chart-bad-requirements/values.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/chart-bad-requirements/values.yaml rename to pkg/cmd/testdata/testcharts/chart-bad-requirements/values.yaml diff --git a/cmd/helm/testdata/testcharts/chart-bad-type/Chart.yaml b/pkg/cmd/testdata/testcharts/chart-bad-type/Chart.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/chart-bad-type/Chart.yaml rename to pkg/cmd/testdata/testcharts/chart-bad-type/Chart.yaml diff --git a/cmd/helm/testdata/testcharts/chart-bad-type/README.md b/pkg/cmd/testdata/testcharts/chart-bad-type/README.md similarity index 100% rename from cmd/helm/testdata/testcharts/chart-bad-type/README.md rename to pkg/cmd/testdata/testcharts/chart-bad-type/README.md diff --git a/cmd/helm/testdata/testcharts/chart-bad-type/extra_values.yaml b/pkg/cmd/testdata/testcharts/chart-bad-type/extra_values.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/chart-bad-type/extra_values.yaml rename to pkg/cmd/testdata/testcharts/chart-bad-type/extra_values.yaml diff --git a/cmd/helm/testdata/testcharts/chart-bad-type/more_values.yaml b/pkg/cmd/testdata/testcharts/chart-bad-type/more_values.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/chart-bad-type/more_values.yaml rename to pkg/cmd/testdata/testcharts/chart-bad-type/more_values.yaml diff --git a/cmd/helm/testdata/testcharts/chart-bad-type/templates/alpine-pod.yaml b/pkg/cmd/testdata/testcharts/chart-bad-type/templates/alpine-pod.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/chart-bad-type/templates/alpine-pod.yaml rename to pkg/cmd/testdata/testcharts/chart-bad-type/templates/alpine-pod.yaml diff --git a/cmd/helm/testdata/testcharts/chart-bad-type/values.yaml b/pkg/cmd/testdata/testcharts/chart-bad-type/values.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/chart-bad-type/values.yaml rename to pkg/cmd/testdata/testcharts/chart-bad-type/values.yaml diff --git a/cmd/helm/testdata/testcharts/chart-missing-deps/.helmignore b/pkg/cmd/testdata/testcharts/chart-missing-deps/.helmignore similarity index 100% rename from cmd/helm/testdata/testcharts/chart-missing-deps/.helmignore rename to pkg/cmd/testdata/testcharts/chart-missing-deps/.helmignore diff --git a/cmd/helm/testdata/testcharts/chart-missing-deps/Chart.yaml b/pkg/cmd/testdata/testcharts/chart-missing-deps/Chart.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/chart-missing-deps/Chart.yaml rename to pkg/cmd/testdata/testcharts/chart-missing-deps/Chart.yaml diff --git a/cmd/helm/testdata/testcharts/chart-missing-deps/charts/reqsubchart/.helmignore b/pkg/cmd/testdata/testcharts/chart-missing-deps/charts/reqsubchart/.helmignore similarity index 100% rename from cmd/helm/testdata/testcharts/chart-missing-deps/charts/reqsubchart/.helmignore rename to pkg/cmd/testdata/testcharts/chart-missing-deps/charts/reqsubchart/.helmignore diff --git a/cmd/helm/testdata/testcharts/chart-missing-deps/charts/reqsubchart/Chart.yaml b/pkg/cmd/testdata/testcharts/chart-missing-deps/charts/reqsubchart/Chart.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/chart-missing-deps/charts/reqsubchart/Chart.yaml rename to pkg/cmd/testdata/testcharts/chart-missing-deps/charts/reqsubchart/Chart.yaml diff --git a/cmd/helm/testdata/testcharts/chart-missing-deps/charts/reqsubchart/values.yaml b/pkg/cmd/testdata/testcharts/chart-missing-deps/charts/reqsubchart/values.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/chart-missing-deps/charts/reqsubchart/values.yaml rename to pkg/cmd/testdata/testcharts/chart-missing-deps/charts/reqsubchart/values.yaml diff --git a/cmd/helm/testdata/testcharts/chart-missing-deps/values.yaml b/pkg/cmd/testdata/testcharts/chart-missing-deps/values.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/chart-missing-deps/values.yaml rename to pkg/cmd/testdata/testcharts/chart-missing-deps/values.yaml diff --git a/cmd/helm/testdata/testcharts/chart-with-bad-subcharts/Chart.yaml b/pkg/cmd/testdata/testcharts/chart-with-bad-subcharts/Chart.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-bad-subcharts/Chart.yaml rename to pkg/cmd/testdata/testcharts/chart-with-bad-subcharts/Chart.yaml diff --git a/cmd/helm/testdata/testcharts/chart-with-bad-subcharts/charts/bad-subchart/Chart.yaml b/pkg/cmd/testdata/testcharts/chart-with-bad-subcharts/charts/bad-subchart/Chart.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-bad-subcharts/charts/bad-subchart/Chart.yaml rename to pkg/cmd/testdata/testcharts/chart-with-bad-subcharts/charts/bad-subchart/Chart.yaml diff --git a/cmd/helm/testdata/testcharts/chart-with-bad-subcharts/charts/bad-subchart/values.yaml b/pkg/cmd/testdata/testcharts/chart-with-bad-subcharts/charts/bad-subchart/values.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-bad-subcharts/charts/bad-subchart/values.yaml rename to pkg/cmd/testdata/testcharts/chart-with-bad-subcharts/charts/bad-subchart/values.yaml diff --git a/cmd/helm/testdata/testcharts/chart-with-bad-subcharts/charts/good-subchart/Chart.yaml b/pkg/cmd/testdata/testcharts/chart-with-bad-subcharts/charts/good-subchart/Chart.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-bad-subcharts/charts/good-subchart/Chart.yaml rename to pkg/cmd/testdata/testcharts/chart-with-bad-subcharts/charts/good-subchart/Chart.yaml diff --git a/cmd/helm/testdata/testcharts/chart-with-bad-subcharts/charts/good-subchart/values.yaml b/pkg/cmd/testdata/testcharts/chart-with-bad-subcharts/charts/good-subchart/values.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-bad-subcharts/charts/good-subchart/values.yaml rename to pkg/cmd/testdata/testcharts/chart-with-bad-subcharts/charts/good-subchart/values.yaml diff --git a/cmd/helm/testdata/testcharts/chart-with-bad-subcharts/requirements.yaml b/pkg/cmd/testdata/testcharts/chart-with-bad-subcharts/requirements.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-bad-subcharts/requirements.yaml rename to pkg/cmd/testdata/testcharts/chart-with-bad-subcharts/requirements.yaml diff --git a/cmd/helm/testdata/testcharts/chart-with-bad-subcharts/values.yaml b/pkg/cmd/testdata/testcharts/chart-with-bad-subcharts/values.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-bad-subcharts/values.yaml rename to pkg/cmd/testdata/testcharts/chart-with-bad-subcharts/values.yaml diff --git a/cmd/helm/testdata/testcharts/chart-with-deprecated-api/Chart.yaml b/pkg/cmd/testdata/testcharts/chart-with-deprecated-api/Chart.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-deprecated-api/Chart.yaml rename to pkg/cmd/testdata/testcharts/chart-with-deprecated-api/Chart.yaml diff --git a/cmd/helm/testdata/testcharts/chart-with-deprecated-api/templates/horizontalpodautoscaler.yaml b/pkg/cmd/testdata/testcharts/chart-with-deprecated-api/templates/horizontalpodautoscaler.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-deprecated-api/templates/horizontalpodautoscaler.yaml rename to pkg/cmd/testdata/testcharts/chart-with-deprecated-api/templates/horizontalpodautoscaler.yaml diff --git a/cmd/helm/testdata/testcharts/chart-with-deprecated-api/values.yaml b/pkg/cmd/testdata/testcharts/chart-with-deprecated-api/values.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-deprecated-api/values.yaml rename to pkg/cmd/testdata/testcharts/chart-with-deprecated-api/values.yaml diff --git a/cmd/helm/testdata/testcharts/chart-with-lib-dep/.helmignore b/pkg/cmd/testdata/testcharts/chart-with-lib-dep/.helmignore similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-lib-dep/.helmignore rename to pkg/cmd/testdata/testcharts/chart-with-lib-dep/.helmignore diff --git a/cmd/helm/testdata/testcharts/chart-with-lib-dep/Chart.yaml b/pkg/cmd/testdata/testcharts/chart-with-lib-dep/Chart.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-lib-dep/Chart.yaml rename to pkg/cmd/testdata/testcharts/chart-with-lib-dep/Chart.yaml diff --git a/cmd/helm/testdata/testcharts/chart-with-lib-dep/charts/common-0.0.5.tgz b/pkg/cmd/testdata/testcharts/chart-with-lib-dep/charts/common-0.0.5.tgz similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-lib-dep/charts/common-0.0.5.tgz rename to pkg/cmd/testdata/testcharts/chart-with-lib-dep/charts/common-0.0.5.tgz diff --git a/cmd/helm/testdata/testcharts/chart-with-lib-dep/templates/NOTES.txt b/pkg/cmd/testdata/testcharts/chart-with-lib-dep/templates/NOTES.txt similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-lib-dep/templates/NOTES.txt rename to pkg/cmd/testdata/testcharts/chart-with-lib-dep/templates/NOTES.txt diff --git a/cmd/helm/testdata/testcharts/chart-with-lib-dep/templates/_helpers.tpl b/pkg/cmd/testdata/testcharts/chart-with-lib-dep/templates/_helpers.tpl similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-lib-dep/templates/_helpers.tpl rename to pkg/cmd/testdata/testcharts/chart-with-lib-dep/templates/_helpers.tpl diff --git a/cmd/helm/testdata/testcharts/chart-with-lib-dep/templates/deployment.yaml b/pkg/cmd/testdata/testcharts/chart-with-lib-dep/templates/deployment.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-lib-dep/templates/deployment.yaml rename to pkg/cmd/testdata/testcharts/chart-with-lib-dep/templates/deployment.yaml diff --git a/cmd/helm/testdata/testcharts/chart-with-lib-dep/templates/ingress.yaml b/pkg/cmd/testdata/testcharts/chart-with-lib-dep/templates/ingress.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-lib-dep/templates/ingress.yaml rename to pkg/cmd/testdata/testcharts/chart-with-lib-dep/templates/ingress.yaml diff --git a/cmd/helm/testdata/testcharts/chart-with-lib-dep/templates/service.yaml b/pkg/cmd/testdata/testcharts/chart-with-lib-dep/templates/service.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-lib-dep/templates/service.yaml rename to pkg/cmd/testdata/testcharts/chart-with-lib-dep/templates/service.yaml diff --git a/cmd/helm/testdata/testcharts/chart-with-lib-dep/values.yaml b/pkg/cmd/testdata/testcharts/chart-with-lib-dep/values.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-lib-dep/values.yaml rename to pkg/cmd/testdata/testcharts/chart-with-lib-dep/values.yaml diff --git a/cmd/helm/testdata/testcharts/chart-with-only-crds/.helmignore b/pkg/cmd/testdata/testcharts/chart-with-only-crds/.helmignore similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-only-crds/.helmignore rename to pkg/cmd/testdata/testcharts/chart-with-only-crds/.helmignore diff --git a/cmd/helm/testdata/testcharts/chart-with-only-crds/Chart.yaml b/pkg/cmd/testdata/testcharts/chart-with-only-crds/Chart.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-only-crds/Chart.yaml rename to pkg/cmd/testdata/testcharts/chart-with-only-crds/Chart.yaml diff --git a/cmd/helm/testdata/testcharts/chart-with-only-crds/crds/test-crd.yaml b/pkg/cmd/testdata/testcharts/chart-with-only-crds/crds/test-crd.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-only-crds/crds/test-crd.yaml rename to pkg/cmd/testdata/testcharts/chart-with-only-crds/crds/test-crd.yaml diff --git a/cmd/helm/testdata/testcharts/chart-with-schema-and-subchart/Chart.yaml b/pkg/cmd/testdata/testcharts/chart-with-schema-and-subchart/Chart.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-schema-and-subchart/Chart.yaml rename to pkg/cmd/testdata/testcharts/chart-with-schema-and-subchart/Chart.yaml diff --git a/cmd/helm/testdata/testcharts/chart-with-schema-and-subchart/charts/subchart-with-schema/Chart.yaml b/pkg/cmd/testdata/testcharts/chart-with-schema-and-subchart/charts/subchart-with-schema/Chart.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-schema-and-subchart/charts/subchart-with-schema/Chart.yaml rename to pkg/cmd/testdata/testcharts/chart-with-schema-and-subchart/charts/subchart-with-schema/Chart.yaml diff --git a/cmd/helm/testdata/testcharts/chart-with-schema-and-subchart/charts/subchart-with-schema/templates/empty.yaml b/pkg/cmd/testdata/testcharts/chart-with-schema-and-subchart/charts/subchart-with-schema/templates/empty.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-schema-and-subchart/charts/subchart-with-schema/templates/empty.yaml rename to pkg/cmd/testdata/testcharts/chart-with-schema-and-subchart/charts/subchart-with-schema/templates/empty.yaml diff --git a/cmd/helm/testdata/testcharts/chart-with-schema-and-subchart/charts/subchart-with-schema/values.schema.json b/pkg/cmd/testdata/testcharts/chart-with-schema-and-subchart/charts/subchart-with-schema/values.schema.json similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-schema-and-subchart/charts/subchart-with-schema/values.schema.json rename to pkg/cmd/testdata/testcharts/chart-with-schema-and-subchart/charts/subchart-with-schema/values.schema.json diff --git a/cmd/helm/testdata/testcharts/chart-with-schema-and-subchart/charts/subchart-with-schema/values.yaml b/pkg/cmd/testdata/testcharts/chart-with-schema-and-subchart/charts/subchart-with-schema/values.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-schema-and-subchart/charts/subchart-with-schema/values.yaml rename to pkg/cmd/testdata/testcharts/chart-with-schema-and-subchart/charts/subchart-with-schema/values.yaml diff --git a/cmd/helm/testdata/testcharts/chart-with-schema-and-subchart/templates/empty.yaml b/pkg/cmd/testdata/testcharts/chart-with-schema-and-subchart/templates/empty.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-schema-and-subchart/templates/empty.yaml rename to pkg/cmd/testdata/testcharts/chart-with-schema-and-subchart/templates/empty.yaml diff --git a/cmd/helm/testdata/testcharts/chart-with-schema-and-subchart/values.schema.json b/pkg/cmd/testdata/testcharts/chart-with-schema-and-subchart/values.schema.json similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-schema-and-subchart/values.schema.json rename to pkg/cmd/testdata/testcharts/chart-with-schema-and-subchart/values.schema.json diff --git a/cmd/helm/testdata/testcharts/chart-with-schema-and-subchart/values.yaml b/pkg/cmd/testdata/testcharts/chart-with-schema-and-subchart/values.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-schema-and-subchart/values.yaml rename to pkg/cmd/testdata/testcharts/chart-with-schema-and-subchart/values.yaml diff --git a/cmd/helm/testdata/testcharts/chart-with-schema-negative-skip-validation/Chart.yaml b/pkg/cmd/testdata/testcharts/chart-with-schema-negative-skip-validation/Chart.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-schema-negative-skip-validation/Chart.yaml rename to pkg/cmd/testdata/testcharts/chart-with-schema-negative-skip-validation/Chart.yaml diff --git a/cmd/helm/testdata/testcharts/chart-with-schema-negative-skip-validation/templates/empty.yaml b/pkg/cmd/testdata/testcharts/chart-with-schema-negative-skip-validation/templates/empty.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-schema-negative-skip-validation/templates/empty.yaml rename to pkg/cmd/testdata/testcharts/chart-with-schema-negative-skip-validation/templates/empty.yaml diff --git a/cmd/helm/testdata/testcharts/chart-with-schema-negative-skip-validation/values.schema.json b/pkg/cmd/testdata/testcharts/chart-with-schema-negative-skip-validation/values.schema.json similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-schema-negative-skip-validation/values.schema.json rename to pkg/cmd/testdata/testcharts/chart-with-schema-negative-skip-validation/values.schema.json diff --git a/cmd/helm/testdata/testcharts/chart-with-schema-negative-skip-validation/values.yaml b/pkg/cmd/testdata/testcharts/chart-with-schema-negative-skip-validation/values.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-schema-negative-skip-validation/values.yaml rename to pkg/cmd/testdata/testcharts/chart-with-schema-negative-skip-validation/values.yaml diff --git a/cmd/helm/testdata/testcharts/chart-with-schema-negative/Chart.yaml b/pkg/cmd/testdata/testcharts/chart-with-schema-negative/Chart.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-schema-negative/Chart.yaml rename to pkg/cmd/testdata/testcharts/chart-with-schema-negative/Chart.yaml diff --git a/cmd/helm/testdata/testcharts/chart-with-schema-negative/templates/empty.yaml b/pkg/cmd/testdata/testcharts/chart-with-schema-negative/templates/empty.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-schema-negative/templates/empty.yaml rename to pkg/cmd/testdata/testcharts/chart-with-schema-negative/templates/empty.yaml diff --git a/cmd/helm/testdata/testcharts/chart-with-schema-negative/values.schema.json b/pkg/cmd/testdata/testcharts/chart-with-schema-negative/values.schema.json similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-schema-negative/values.schema.json rename to pkg/cmd/testdata/testcharts/chart-with-schema-negative/values.schema.json diff --git a/cmd/helm/testdata/testcharts/chart-with-schema-negative/values.yaml b/pkg/cmd/testdata/testcharts/chart-with-schema-negative/values.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-schema-negative/values.yaml rename to pkg/cmd/testdata/testcharts/chart-with-schema-negative/values.yaml diff --git a/cmd/helm/testdata/testcharts/chart-with-schema/Chart.yaml b/pkg/cmd/testdata/testcharts/chart-with-schema/Chart.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-schema/Chart.yaml rename to pkg/cmd/testdata/testcharts/chart-with-schema/Chart.yaml diff --git a/cmd/helm/testdata/testcharts/chart-with-schema/extra-values.yaml b/pkg/cmd/testdata/testcharts/chart-with-schema/extra-values.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-schema/extra-values.yaml rename to pkg/cmd/testdata/testcharts/chart-with-schema/extra-values.yaml diff --git a/cmd/helm/testdata/testcharts/chart-with-schema/templates/empty.yaml b/pkg/cmd/testdata/testcharts/chart-with-schema/templates/empty.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-schema/templates/empty.yaml rename to pkg/cmd/testdata/testcharts/chart-with-schema/templates/empty.yaml diff --git a/cmd/helm/testdata/testcharts/chart-with-schema/values.schema.json b/pkg/cmd/testdata/testcharts/chart-with-schema/values.schema.json similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-schema/values.schema.json rename to pkg/cmd/testdata/testcharts/chart-with-schema/values.schema.json diff --git a/cmd/helm/testdata/testcharts/chart-with-schema/values.yaml b/pkg/cmd/testdata/testcharts/chart-with-schema/values.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-schema/values.yaml rename to pkg/cmd/testdata/testcharts/chart-with-schema/values.yaml diff --git a/cmd/helm/testdata/testcharts/chart-with-secret/Chart.yaml b/pkg/cmd/testdata/testcharts/chart-with-secret/Chart.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-secret/Chart.yaml rename to pkg/cmd/testdata/testcharts/chart-with-secret/Chart.yaml diff --git a/cmd/helm/testdata/testcharts/chart-with-secret/templates/configmap.yaml b/pkg/cmd/testdata/testcharts/chart-with-secret/templates/configmap.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-secret/templates/configmap.yaml rename to pkg/cmd/testdata/testcharts/chart-with-secret/templates/configmap.yaml diff --git a/cmd/helm/testdata/testcharts/chart-with-secret/templates/secret.yaml b/pkg/cmd/testdata/testcharts/chart-with-secret/templates/secret.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-secret/templates/secret.yaml rename to pkg/cmd/testdata/testcharts/chart-with-secret/templates/secret.yaml diff --git a/cmd/helm/testdata/testcharts/chart-with-subchart-notes/Chart.yaml b/pkg/cmd/testdata/testcharts/chart-with-subchart-notes/Chart.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-subchart-notes/Chart.yaml rename to pkg/cmd/testdata/testcharts/chart-with-subchart-notes/Chart.yaml diff --git a/cmd/helm/testdata/testcharts/chart-with-subchart-notes/charts/subchart-with-notes/Chart.yaml b/pkg/cmd/testdata/testcharts/chart-with-subchart-notes/charts/subchart-with-notes/Chart.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-subchart-notes/charts/subchart-with-notes/Chart.yaml rename to pkg/cmd/testdata/testcharts/chart-with-subchart-notes/charts/subchart-with-notes/Chart.yaml diff --git a/cmd/helm/testdata/testcharts/chart-with-subchart-notes/charts/subchart-with-notes/templates/NOTES.txt b/pkg/cmd/testdata/testcharts/chart-with-subchart-notes/charts/subchart-with-notes/templates/NOTES.txt similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-subchart-notes/charts/subchart-with-notes/templates/NOTES.txt rename to pkg/cmd/testdata/testcharts/chart-with-subchart-notes/charts/subchart-with-notes/templates/NOTES.txt diff --git a/cmd/helm/testdata/testcharts/chart-with-subchart-notes/templates/NOTES.txt b/pkg/cmd/testdata/testcharts/chart-with-subchart-notes/templates/NOTES.txt similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-subchart-notes/templates/NOTES.txt rename to pkg/cmd/testdata/testcharts/chart-with-subchart-notes/templates/NOTES.txt diff --git a/cmd/helm/testdata/testcharts/chart-with-subchart-update/Chart.lock b/pkg/cmd/testdata/testcharts/chart-with-subchart-update/Chart.lock similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-subchart-update/Chart.lock rename to pkg/cmd/testdata/testcharts/chart-with-subchart-update/Chart.lock diff --git a/cmd/helm/testdata/testcharts/chart-with-subchart-update/Chart.yaml b/pkg/cmd/testdata/testcharts/chart-with-subchart-update/Chart.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-subchart-update/Chart.yaml rename to pkg/cmd/testdata/testcharts/chart-with-subchart-update/Chart.yaml diff --git a/cmd/helm/testdata/testcharts/chart-with-subchart-update/charts/subchart-with-notes/Chart.yaml b/pkg/cmd/testdata/testcharts/chart-with-subchart-update/charts/subchart-with-notes/Chart.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-subchart-update/charts/subchart-with-notes/Chart.yaml rename to pkg/cmd/testdata/testcharts/chart-with-subchart-update/charts/subchart-with-notes/Chart.yaml diff --git a/cmd/helm/testdata/testcharts/chart-with-subchart-update/charts/subchart-with-notes/templates/NOTES.txt b/pkg/cmd/testdata/testcharts/chart-with-subchart-update/charts/subchart-with-notes/templates/NOTES.txt similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-subchart-update/charts/subchart-with-notes/templates/NOTES.txt rename to pkg/cmd/testdata/testcharts/chart-with-subchart-update/charts/subchart-with-notes/templates/NOTES.txt diff --git a/cmd/helm/testdata/testcharts/chart-with-subchart-update/templates/NOTES.txt b/pkg/cmd/testdata/testcharts/chart-with-subchart-update/templates/NOTES.txt similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-subchart-update/templates/NOTES.txt rename to pkg/cmd/testdata/testcharts/chart-with-subchart-update/templates/NOTES.txt diff --git a/cmd/helm/testdata/testcharts/chart-with-template-lib-archive-dep/.helmignore b/pkg/cmd/testdata/testcharts/chart-with-template-lib-archive-dep/.helmignore similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-template-lib-archive-dep/.helmignore rename to pkg/cmd/testdata/testcharts/chart-with-template-lib-archive-dep/.helmignore diff --git a/cmd/helm/testdata/testcharts/chart-with-template-lib-archive-dep/Chart.yaml b/pkg/cmd/testdata/testcharts/chart-with-template-lib-archive-dep/Chart.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-template-lib-archive-dep/Chart.yaml rename to pkg/cmd/testdata/testcharts/chart-with-template-lib-archive-dep/Chart.yaml diff --git a/cmd/helm/testdata/testcharts/chart-with-template-lib-archive-dep/charts/common-0.0.5.tgz b/pkg/cmd/testdata/testcharts/chart-with-template-lib-archive-dep/charts/common-0.0.5.tgz similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-template-lib-archive-dep/charts/common-0.0.5.tgz rename to pkg/cmd/testdata/testcharts/chart-with-template-lib-archive-dep/charts/common-0.0.5.tgz diff --git a/cmd/helm/testdata/testcharts/chart-with-template-lib-archive-dep/templates/NOTES.txt b/pkg/cmd/testdata/testcharts/chart-with-template-lib-archive-dep/templates/NOTES.txt similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-template-lib-archive-dep/templates/NOTES.txt rename to pkg/cmd/testdata/testcharts/chart-with-template-lib-archive-dep/templates/NOTES.txt diff --git a/cmd/helm/testdata/testcharts/chart-with-template-lib-archive-dep/templates/_helpers.tpl b/pkg/cmd/testdata/testcharts/chart-with-template-lib-archive-dep/templates/_helpers.tpl similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-template-lib-archive-dep/templates/_helpers.tpl rename to pkg/cmd/testdata/testcharts/chart-with-template-lib-archive-dep/templates/_helpers.tpl diff --git a/cmd/helm/testdata/testcharts/chart-with-template-lib-archive-dep/templates/deployment.yaml b/pkg/cmd/testdata/testcharts/chart-with-template-lib-archive-dep/templates/deployment.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-template-lib-archive-dep/templates/deployment.yaml rename to pkg/cmd/testdata/testcharts/chart-with-template-lib-archive-dep/templates/deployment.yaml diff --git a/cmd/helm/testdata/testcharts/chart-with-template-lib-archive-dep/templates/ingress.yaml b/pkg/cmd/testdata/testcharts/chart-with-template-lib-archive-dep/templates/ingress.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-template-lib-archive-dep/templates/ingress.yaml rename to pkg/cmd/testdata/testcharts/chart-with-template-lib-archive-dep/templates/ingress.yaml diff --git a/cmd/helm/testdata/testcharts/chart-with-template-lib-archive-dep/templates/service.yaml b/pkg/cmd/testdata/testcharts/chart-with-template-lib-archive-dep/templates/service.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-template-lib-archive-dep/templates/service.yaml rename to pkg/cmd/testdata/testcharts/chart-with-template-lib-archive-dep/templates/service.yaml diff --git a/cmd/helm/testdata/testcharts/chart-with-template-lib-archive-dep/values.yaml b/pkg/cmd/testdata/testcharts/chart-with-template-lib-archive-dep/values.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-template-lib-archive-dep/values.yaml rename to pkg/cmd/testdata/testcharts/chart-with-template-lib-archive-dep/values.yaml diff --git a/cmd/helm/testdata/testcharts/chart-with-template-lib-dep/.helmignore b/pkg/cmd/testdata/testcharts/chart-with-template-lib-dep/.helmignore similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-template-lib-dep/.helmignore rename to pkg/cmd/testdata/testcharts/chart-with-template-lib-dep/.helmignore diff --git a/cmd/helm/testdata/testcharts/chart-with-template-lib-dep/Chart.yaml b/pkg/cmd/testdata/testcharts/chart-with-template-lib-dep/Chart.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-template-lib-dep/Chart.yaml rename to pkg/cmd/testdata/testcharts/chart-with-template-lib-dep/Chart.yaml diff --git a/cmd/helm/testdata/testcharts/chart-with-template-lib-dep/charts/common/.helmignore b/pkg/cmd/testdata/testcharts/chart-with-template-lib-dep/charts/common/.helmignore similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-template-lib-dep/charts/common/.helmignore rename to pkg/cmd/testdata/testcharts/chart-with-template-lib-dep/charts/common/.helmignore diff --git a/cmd/helm/testdata/testcharts/chart-with-template-lib-dep/charts/common/Chart.yaml b/pkg/cmd/testdata/testcharts/chart-with-template-lib-dep/charts/common/Chart.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-template-lib-dep/charts/common/Chart.yaml rename to pkg/cmd/testdata/testcharts/chart-with-template-lib-dep/charts/common/Chart.yaml diff --git a/cmd/helm/testdata/testcharts/chart-with-template-lib-dep/charts/common/README.md b/pkg/cmd/testdata/testcharts/chart-with-template-lib-dep/charts/common/README.md similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-template-lib-dep/charts/common/README.md rename to pkg/cmd/testdata/testcharts/chart-with-template-lib-dep/charts/common/README.md diff --git a/cmd/helm/testdata/testcharts/chart-with-template-lib-dep/charts/common/templates/_chartref.tpl b/pkg/cmd/testdata/testcharts/chart-with-template-lib-dep/charts/common/templates/_chartref.tpl similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-template-lib-dep/charts/common/templates/_chartref.tpl rename to pkg/cmd/testdata/testcharts/chart-with-template-lib-dep/charts/common/templates/_chartref.tpl diff --git a/cmd/helm/testdata/testcharts/chart-with-template-lib-dep/charts/common/templates/_configmap.yaml b/pkg/cmd/testdata/testcharts/chart-with-template-lib-dep/charts/common/templates/_configmap.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-template-lib-dep/charts/common/templates/_configmap.yaml rename to pkg/cmd/testdata/testcharts/chart-with-template-lib-dep/charts/common/templates/_configmap.yaml diff --git a/cmd/helm/testdata/testcharts/chart-with-template-lib-dep/charts/common/templates/_container.yaml b/pkg/cmd/testdata/testcharts/chart-with-template-lib-dep/charts/common/templates/_container.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-template-lib-dep/charts/common/templates/_container.yaml rename to pkg/cmd/testdata/testcharts/chart-with-template-lib-dep/charts/common/templates/_container.yaml diff --git a/cmd/helm/testdata/testcharts/chart-with-template-lib-dep/charts/common/templates/_deployment.yaml b/pkg/cmd/testdata/testcharts/chart-with-template-lib-dep/charts/common/templates/_deployment.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-template-lib-dep/charts/common/templates/_deployment.yaml rename to pkg/cmd/testdata/testcharts/chart-with-template-lib-dep/charts/common/templates/_deployment.yaml diff --git a/cmd/helm/testdata/testcharts/chart-with-template-lib-dep/charts/common/templates/_envvar.tpl b/pkg/cmd/testdata/testcharts/chart-with-template-lib-dep/charts/common/templates/_envvar.tpl similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-template-lib-dep/charts/common/templates/_envvar.tpl rename to pkg/cmd/testdata/testcharts/chart-with-template-lib-dep/charts/common/templates/_envvar.tpl diff --git a/cmd/helm/testdata/testcharts/chart-with-template-lib-dep/charts/common/templates/_fullname.tpl b/pkg/cmd/testdata/testcharts/chart-with-template-lib-dep/charts/common/templates/_fullname.tpl similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-template-lib-dep/charts/common/templates/_fullname.tpl rename to pkg/cmd/testdata/testcharts/chart-with-template-lib-dep/charts/common/templates/_fullname.tpl diff --git a/cmd/helm/testdata/testcharts/chart-with-template-lib-dep/charts/common/templates/_ingress.yaml b/pkg/cmd/testdata/testcharts/chart-with-template-lib-dep/charts/common/templates/_ingress.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-template-lib-dep/charts/common/templates/_ingress.yaml rename to pkg/cmd/testdata/testcharts/chart-with-template-lib-dep/charts/common/templates/_ingress.yaml diff --git a/cmd/helm/testdata/testcharts/chart-with-template-lib-dep/charts/common/templates/_metadata.yaml b/pkg/cmd/testdata/testcharts/chart-with-template-lib-dep/charts/common/templates/_metadata.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-template-lib-dep/charts/common/templates/_metadata.yaml rename to pkg/cmd/testdata/testcharts/chart-with-template-lib-dep/charts/common/templates/_metadata.yaml diff --git a/cmd/helm/testdata/testcharts/chart-with-template-lib-dep/charts/common/templates/_metadata_annotations.tpl b/pkg/cmd/testdata/testcharts/chart-with-template-lib-dep/charts/common/templates/_metadata_annotations.tpl similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-template-lib-dep/charts/common/templates/_metadata_annotations.tpl rename to pkg/cmd/testdata/testcharts/chart-with-template-lib-dep/charts/common/templates/_metadata_annotations.tpl diff --git a/cmd/helm/testdata/testcharts/chart-with-template-lib-dep/charts/common/templates/_metadata_labels.tpl b/pkg/cmd/testdata/testcharts/chart-with-template-lib-dep/charts/common/templates/_metadata_labels.tpl similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-template-lib-dep/charts/common/templates/_metadata_labels.tpl rename to pkg/cmd/testdata/testcharts/chart-with-template-lib-dep/charts/common/templates/_metadata_labels.tpl diff --git a/cmd/helm/testdata/testcharts/chart-with-template-lib-dep/charts/common/templates/_name.tpl b/pkg/cmd/testdata/testcharts/chart-with-template-lib-dep/charts/common/templates/_name.tpl similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-template-lib-dep/charts/common/templates/_name.tpl rename to pkg/cmd/testdata/testcharts/chart-with-template-lib-dep/charts/common/templates/_name.tpl diff --git a/cmd/helm/testdata/testcharts/chart-with-template-lib-dep/charts/common/templates/_persistentvolumeclaim.yaml b/pkg/cmd/testdata/testcharts/chart-with-template-lib-dep/charts/common/templates/_persistentvolumeclaim.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-template-lib-dep/charts/common/templates/_persistentvolumeclaim.yaml rename to pkg/cmd/testdata/testcharts/chart-with-template-lib-dep/charts/common/templates/_persistentvolumeclaim.yaml diff --git a/cmd/helm/testdata/testcharts/chart-with-template-lib-dep/charts/common/templates/_secret.yaml b/pkg/cmd/testdata/testcharts/chart-with-template-lib-dep/charts/common/templates/_secret.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-template-lib-dep/charts/common/templates/_secret.yaml rename to pkg/cmd/testdata/testcharts/chart-with-template-lib-dep/charts/common/templates/_secret.yaml diff --git a/cmd/helm/testdata/testcharts/chart-with-template-lib-dep/charts/common/templates/_service.yaml b/pkg/cmd/testdata/testcharts/chart-with-template-lib-dep/charts/common/templates/_service.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-template-lib-dep/charts/common/templates/_service.yaml rename to pkg/cmd/testdata/testcharts/chart-with-template-lib-dep/charts/common/templates/_service.yaml diff --git a/cmd/helm/testdata/testcharts/chart-with-template-lib-dep/charts/common/templates/_util.tpl b/pkg/cmd/testdata/testcharts/chart-with-template-lib-dep/charts/common/templates/_util.tpl similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-template-lib-dep/charts/common/templates/_util.tpl rename to pkg/cmd/testdata/testcharts/chart-with-template-lib-dep/charts/common/templates/_util.tpl diff --git a/cmd/helm/testdata/testcharts/chart-with-template-lib-dep/charts/common/templates/_volume.tpl b/pkg/cmd/testdata/testcharts/chart-with-template-lib-dep/charts/common/templates/_volume.tpl similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-template-lib-dep/charts/common/templates/_volume.tpl rename to pkg/cmd/testdata/testcharts/chart-with-template-lib-dep/charts/common/templates/_volume.tpl diff --git a/cmd/helm/testdata/testcharts/chart-with-template-lib-dep/charts/common/templates/configmap.yaml b/pkg/cmd/testdata/testcharts/chart-with-template-lib-dep/charts/common/templates/configmap.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-template-lib-dep/charts/common/templates/configmap.yaml rename to pkg/cmd/testdata/testcharts/chart-with-template-lib-dep/charts/common/templates/configmap.yaml diff --git a/cmd/helm/testdata/testcharts/chart-with-template-lib-dep/charts/common/values.yaml b/pkg/cmd/testdata/testcharts/chart-with-template-lib-dep/charts/common/values.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-template-lib-dep/charts/common/values.yaml rename to pkg/cmd/testdata/testcharts/chart-with-template-lib-dep/charts/common/values.yaml diff --git a/cmd/helm/testdata/testcharts/chart-with-template-lib-dep/templates/NOTES.txt b/pkg/cmd/testdata/testcharts/chart-with-template-lib-dep/templates/NOTES.txt similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-template-lib-dep/templates/NOTES.txt rename to pkg/cmd/testdata/testcharts/chart-with-template-lib-dep/templates/NOTES.txt diff --git a/cmd/helm/testdata/testcharts/chart-with-template-lib-dep/templates/_helpers.tpl b/pkg/cmd/testdata/testcharts/chart-with-template-lib-dep/templates/_helpers.tpl similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-template-lib-dep/templates/_helpers.tpl rename to pkg/cmd/testdata/testcharts/chart-with-template-lib-dep/templates/_helpers.tpl diff --git a/cmd/helm/testdata/testcharts/chart-with-template-lib-dep/templates/deployment.yaml b/pkg/cmd/testdata/testcharts/chart-with-template-lib-dep/templates/deployment.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-template-lib-dep/templates/deployment.yaml rename to pkg/cmd/testdata/testcharts/chart-with-template-lib-dep/templates/deployment.yaml diff --git a/cmd/helm/testdata/testcharts/chart-with-template-lib-dep/templates/ingress.yaml b/pkg/cmd/testdata/testcharts/chart-with-template-lib-dep/templates/ingress.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-template-lib-dep/templates/ingress.yaml rename to pkg/cmd/testdata/testcharts/chart-with-template-lib-dep/templates/ingress.yaml diff --git a/cmd/helm/testdata/testcharts/chart-with-template-lib-dep/templates/service.yaml b/pkg/cmd/testdata/testcharts/chart-with-template-lib-dep/templates/service.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-template-lib-dep/templates/service.yaml rename to pkg/cmd/testdata/testcharts/chart-with-template-lib-dep/templates/service.yaml diff --git a/cmd/helm/testdata/testcharts/chart-with-template-lib-dep/values.yaml b/pkg/cmd/testdata/testcharts/chart-with-template-lib-dep/values.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-template-lib-dep/values.yaml rename to pkg/cmd/testdata/testcharts/chart-with-template-lib-dep/values.yaml diff --git a/cmd/helm/testdata/testcharts/chart-with-template-with-invalid-yaml/Chart.yaml b/pkg/cmd/testdata/testcharts/chart-with-template-with-invalid-yaml/Chart.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-template-with-invalid-yaml/Chart.yaml rename to pkg/cmd/testdata/testcharts/chart-with-template-with-invalid-yaml/Chart.yaml diff --git a/cmd/helm/testdata/testcharts/chart-with-template-with-invalid-yaml/README.md b/pkg/cmd/testdata/testcharts/chart-with-template-with-invalid-yaml/README.md similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-template-with-invalid-yaml/README.md rename to pkg/cmd/testdata/testcharts/chart-with-template-with-invalid-yaml/README.md diff --git a/cmd/helm/testdata/testcharts/chart-with-template-with-invalid-yaml/templates/alpine-pod.yaml b/pkg/cmd/testdata/testcharts/chart-with-template-with-invalid-yaml/templates/alpine-pod.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-template-with-invalid-yaml/templates/alpine-pod.yaml rename to pkg/cmd/testdata/testcharts/chart-with-template-with-invalid-yaml/templates/alpine-pod.yaml diff --git a/cmd/helm/testdata/testcharts/chart-with-template-with-invalid-yaml/values.yaml b/pkg/cmd/testdata/testcharts/chart-with-template-with-invalid-yaml/values.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/chart-with-template-with-invalid-yaml/values.yaml rename to pkg/cmd/testdata/testcharts/chart-with-template-with-invalid-yaml/values.yaml diff --git a/cmd/helm/testdata/testcharts/compressedchart-0.1.0.tar.gz b/pkg/cmd/testdata/testcharts/compressedchart-0.1.0.tar.gz similarity index 100% rename from cmd/helm/testdata/testcharts/compressedchart-0.1.0.tar.gz rename to pkg/cmd/testdata/testcharts/compressedchart-0.1.0.tar.gz diff --git a/cmd/helm/testdata/testcharts/compressedchart-0.1.0.tgz b/pkg/cmd/testdata/testcharts/compressedchart-0.1.0.tgz similarity index 100% rename from cmd/helm/testdata/testcharts/compressedchart-0.1.0.tgz rename to pkg/cmd/testdata/testcharts/compressedchart-0.1.0.tgz diff --git a/cmd/helm/testdata/testcharts/compressedchart-0.2.0.tgz b/pkg/cmd/testdata/testcharts/compressedchart-0.2.0.tgz similarity index 100% rename from cmd/helm/testdata/testcharts/compressedchart-0.2.0.tgz rename to pkg/cmd/testdata/testcharts/compressedchart-0.2.0.tgz diff --git a/cmd/helm/testdata/testcharts/compressedchart-0.3.0.tgz b/pkg/cmd/testdata/testcharts/compressedchart-0.3.0.tgz similarity index 100% rename from cmd/helm/testdata/testcharts/compressedchart-0.3.0.tgz rename to pkg/cmd/testdata/testcharts/compressedchart-0.3.0.tgz diff --git a/cmd/helm/testdata/testcharts/compressedchart-with-hyphens-0.1.0.tgz b/pkg/cmd/testdata/testcharts/compressedchart-with-hyphens-0.1.0.tgz similarity index 100% rename from cmd/helm/testdata/testcharts/compressedchart-with-hyphens-0.1.0.tgz rename to pkg/cmd/testdata/testcharts/compressedchart-with-hyphens-0.1.0.tgz diff --git a/cmd/helm/testdata/testcharts/deprecated/Chart.yaml b/pkg/cmd/testdata/testcharts/deprecated/Chart.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/deprecated/Chart.yaml rename to pkg/cmd/testdata/testcharts/deprecated/Chart.yaml diff --git a/cmd/helm/testdata/testcharts/deprecated/README.md b/pkg/cmd/testdata/testcharts/deprecated/README.md similarity index 100% rename from cmd/helm/testdata/testcharts/deprecated/README.md rename to pkg/cmd/testdata/testcharts/deprecated/README.md diff --git a/cmd/helm/testdata/testcharts/empty/Chart.yaml b/pkg/cmd/testdata/testcharts/empty/Chart.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/empty/Chart.yaml rename to pkg/cmd/testdata/testcharts/empty/Chart.yaml diff --git a/cmd/helm/testdata/testcharts/empty/README.md b/pkg/cmd/testdata/testcharts/empty/README.md similarity index 100% rename from cmd/helm/testdata/testcharts/empty/README.md rename to pkg/cmd/testdata/testcharts/empty/README.md diff --git a/cmd/helm/testdata/testcharts/empty/templates/empty.yaml b/pkg/cmd/testdata/testcharts/empty/templates/empty.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/empty/templates/empty.yaml rename to pkg/cmd/testdata/testcharts/empty/templates/empty.yaml diff --git a/cmd/helm/testdata/testcharts/empty/values.yaml b/pkg/cmd/testdata/testcharts/empty/values.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/empty/values.yaml rename to pkg/cmd/testdata/testcharts/empty/values.yaml diff --git a/cmd/helm/testdata/testcharts/issue-7233/.helmignore b/pkg/cmd/testdata/testcharts/issue-7233/.helmignore similarity index 100% rename from cmd/helm/testdata/testcharts/issue-7233/.helmignore rename to pkg/cmd/testdata/testcharts/issue-7233/.helmignore diff --git a/cmd/helm/testdata/testcharts/issue-7233/Chart.yaml b/pkg/cmd/testdata/testcharts/issue-7233/Chart.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/issue-7233/Chart.yaml rename to pkg/cmd/testdata/testcharts/issue-7233/Chart.yaml diff --git a/cmd/helm/testdata/testcharts/issue-7233/requirements.lock b/pkg/cmd/testdata/testcharts/issue-7233/requirements.lock similarity index 100% rename from cmd/helm/testdata/testcharts/issue-7233/requirements.lock rename to pkg/cmd/testdata/testcharts/issue-7233/requirements.lock diff --git a/cmd/helm/testdata/testcharts/issue-7233/requirements.yaml b/pkg/cmd/testdata/testcharts/issue-7233/requirements.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/issue-7233/requirements.yaml rename to pkg/cmd/testdata/testcharts/issue-7233/requirements.yaml diff --git a/cmd/helm/testdata/testcharts/issue-7233/templates/configmap.yaml b/pkg/cmd/testdata/testcharts/issue-7233/templates/configmap.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/issue-7233/templates/configmap.yaml rename to pkg/cmd/testdata/testcharts/issue-7233/templates/configmap.yaml diff --git a/cmd/helm/testdata/testcharts/issue-7233/values.yaml b/pkg/cmd/testdata/testcharts/issue-7233/values.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/issue-7233/values.yaml rename to pkg/cmd/testdata/testcharts/issue-7233/values.yaml diff --git a/cmd/helm/testdata/testcharts/issue-9027/Chart.yaml b/pkg/cmd/testdata/testcharts/issue-9027/Chart.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/issue-9027/Chart.yaml rename to pkg/cmd/testdata/testcharts/issue-9027/Chart.yaml diff --git a/cmd/helm/testdata/testcharts/issue-9027/charts/subchart/Chart.yaml b/pkg/cmd/testdata/testcharts/issue-9027/charts/subchart/Chart.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/issue-9027/charts/subchart/Chart.yaml rename to pkg/cmd/testdata/testcharts/issue-9027/charts/subchart/Chart.yaml diff --git a/cmd/helm/testdata/testcharts/issue-9027/charts/subchart/templates/values.yaml b/pkg/cmd/testdata/testcharts/issue-9027/charts/subchart/templates/values.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/issue-9027/charts/subchart/templates/values.yaml rename to pkg/cmd/testdata/testcharts/issue-9027/charts/subchart/templates/values.yaml diff --git a/cmd/helm/testdata/testcharts/issue-9027/charts/subchart/values.yaml b/pkg/cmd/testdata/testcharts/issue-9027/charts/subchart/values.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/issue-9027/charts/subchart/values.yaml rename to pkg/cmd/testdata/testcharts/issue-9027/charts/subchart/values.yaml diff --git a/cmd/helm/testdata/testcharts/issue-9027/templates/values.yaml b/pkg/cmd/testdata/testcharts/issue-9027/templates/values.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/issue-9027/templates/values.yaml rename to pkg/cmd/testdata/testcharts/issue-9027/templates/values.yaml diff --git a/cmd/helm/testdata/testcharts/issue-9027/values.yaml b/pkg/cmd/testdata/testcharts/issue-9027/values.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/issue-9027/values.yaml rename to pkg/cmd/testdata/testcharts/issue-9027/values.yaml diff --git a/pkg/cmd/testdata/testcharts/issue-totoml/Chart.yaml b/pkg/cmd/testdata/testcharts/issue-totoml/Chart.yaml new file mode 100644 index 000000000..f4be7a213 --- /dev/null +++ b/pkg/cmd/testdata/testcharts/issue-totoml/Chart.yaml @@ -0,0 +1,3 @@ +apiVersion: v2 +name: issue-totoml +version: 0.1.0 diff --git a/pkg/cmd/testdata/testcharts/issue-totoml/templates/configmap.yaml b/pkg/cmd/testdata/testcharts/issue-totoml/templates/configmap.yaml new file mode 100644 index 000000000..621e70d48 --- /dev/null +++ b/pkg/cmd/testdata/testcharts/issue-totoml/templates/configmap.yaml @@ -0,0 +1,6 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: issue-totoml +data: | + {{ .Values.global | toToml }} diff --git a/pkg/cmd/testdata/testcharts/issue-totoml/values.yaml b/pkg/cmd/testdata/testcharts/issue-totoml/values.yaml new file mode 100644 index 000000000..dd0140449 --- /dev/null +++ b/pkg/cmd/testdata/testcharts/issue-totoml/values.yaml @@ -0,0 +1,2 @@ +global: + key: 13 \ No newline at end of file diff --git a/cmd/helm/testdata/testcharts/issue1979/Chart.yaml b/pkg/cmd/testdata/testcharts/issue1979/Chart.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/issue1979/Chart.yaml rename to pkg/cmd/testdata/testcharts/issue1979/Chart.yaml diff --git a/cmd/helm/testdata/testcharts/issue1979/README.md b/pkg/cmd/testdata/testcharts/issue1979/README.md similarity index 100% rename from cmd/helm/testdata/testcharts/issue1979/README.md rename to pkg/cmd/testdata/testcharts/issue1979/README.md diff --git a/cmd/helm/testdata/testcharts/issue1979/extra_values.yaml b/pkg/cmd/testdata/testcharts/issue1979/extra_values.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/issue1979/extra_values.yaml rename to pkg/cmd/testdata/testcharts/issue1979/extra_values.yaml diff --git a/cmd/helm/testdata/testcharts/issue1979/more_values.yaml b/pkg/cmd/testdata/testcharts/issue1979/more_values.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/issue1979/more_values.yaml rename to pkg/cmd/testdata/testcharts/issue1979/more_values.yaml diff --git a/cmd/helm/testdata/testcharts/issue1979/templates/alpine-pod.yaml b/pkg/cmd/testdata/testcharts/issue1979/templates/alpine-pod.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/issue1979/templates/alpine-pod.yaml rename to pkg/cmd/testdata/testcharts/issue1979/templates/alpine-pod.yaml diff --git a/cmd/helm/testdata/testcharts/issue1979/values.yaml b/pkg/cmd/testdata/testcharts/issue1979/values.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/issue1979/values.yaml rename to pkg/cmd/testdata/testcharts/issue1979/values.yaml diff --git a/cmd/helm/testdata/testcharts/lib-chart/.helmignore b/pkg/cmd/testdata/testcharts/lib-chart/.helmignore similarity index 100% rename from cmd/helm/testdata/testcharts/lib-chart/.helmignore rename to pkg/cmd/testdata/testcharts/lib-chart/.helmignore diff --git a/cmd/helm/testdata/testcharts/lib-chart/Chart.yaml b/pkg/cmd/testdata/testcharts/lib-chart/Chart.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/lib-chart/Chart.yaml rename to pkg/cmd/testdata/testcharts/lib-chart/Chart.yaml diff --git a/cmd/helm/testdata/testcharts/lib-chart/README.md b/pkg/cmd/testdata/testcharts/lib-chart/README.md similarity index 100% rename from cmd/helm/testdata/testcharts/lib-chart/README.md rename to pkg/cmd/testdata/testcharts/lib-chart/README.md diff --git a/cmd/helm/testdata/testcharts/lib-chart/templates/_chartref.tpl b/pkg/cmd/testdata/testcharts/lib-chart/templates/_chartref.tpl similarity index 100% rename from cmd/helm/testdata/testcharts/lib-chart/templates/_chartref.tpl rename to pkg/cmd/testdata/testcharts/lib-chart/templates/_chartref.tpl diff --git a/cmd/helm/testdata/testcharts/lib-chart/templates/_configmap.yaml b/pkg/cmd/testdata/testcharts/lib-chart/templates/_configmap.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/lib-chart/templates/_configmap.yaml rename to pkg/cmd/testdata/testcharts/lib-chart/templates/_configmap.yaml diff --git a/cmd/helm/testdata/testcharts/lib-chart/templates/_container.yaml b/pkg/cmd/testdata/testcharts/lib-chart/templates/_container.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/lib-chart/templates/_container.yaml rename to pkg/cmd/testdata/testcharts/lib-chart/templates/_container.yaml diff --git a/cmd/helm/testdata/testcharts/lib-chart/templates/_deployment.yaml b/pkg/cmd/testdata/testcharts/lib-chart/templates/_deployment.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/lib-chart/templates/_deployment.yaml rename to pkg/cmd/testdata/testcharts/lib-chart/templates/_deployment.yaml diff --git a/cmd/helm/testdata/testcharts/lib-chart/templates/_envvar.tpl b/pkg/cmd/testdata/testcharts/lib-chart/templates/_envvar.tpl similarity index 100% rename from cmd/helm/testdata/testcharts/lib-chart/templates/_envvar.tpl rename to pkg/cmd/testdata/testcharts/lib-chart/templates/_envvar.tpl diff --git a/cmd/helm/testdata/testcharts/lib-chart/templates/_fullname.tpl b/pkg/cmd/testdata/testcharts/lib-chart/templates/_fullname.tpl similarity index 100% rename from cmd/helm/testdata/testcharts/lib-chart/templates/_fullname.tpl rename to pkg/cmd/testdata/testcharts/lib-chart/templates/_fullname.tpl diff --git a/cmd/helm/testdata/testcharts/lib-chart/templates/_ingress.yaml b/pkg/cmd/testdata/testcharts/lib-chart/templates/_ingress.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/lib-chart/templates/_ingress.yaml rename to pkg/cmd/testdata/testcharts/lib-chart/templates/_ingress.yaml diff --git a/cmd/helm/testdata/testcharts/lib-chart/templates/_metadata.yaml b/pkg/cmd/testdata/testcharts/lib-chart/templates/_metadata.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/lib-chart/templates/_metadata.yaml rename to pkg/cmd/testdata/testcharts/lib-chart/templates/_metadata.yaml diff --git a/cmd/helm/testdata/testcharts/lib-chart/templates/_metadata_annotations.tpl b/pkg/cmd/testdata/testcharts/lib-chart/templates/_metadata_annotations.tpl similarity index 100% rename from cmd/helm/testdata/testcharts/lib-chart/templates/_metadata_annotations.tpl rename to pkg/cmd/testdata/testcharts/lib-chart/templates/_metadata_annotations.tpl diff --git a/cmd/helm/testdata/testcharts/lib-chart/templates/_metadata_labels.tpl b/pkg/cmd/testdata/testcharts/lib-chart/templates/_metadata_labels.tpl similarity index 100% rename from cmd/helm/testdata/testcharts/lib-chart/templates/_metadata_labels.tpl rename to pkg/cmd/testdata/testcharts/lib-chart/templates/_metadata_labels.tpl diff --git a/cmd/helm/testdata/testcharts/lib-chart/templates/_name.tpl b/pkg/cmd/testdata/testcharts/lib-chart/templates/_name.tpl similarity index 100% rename from cmd/helm/testdata/testcharts/lib-chart/templates/_name.tpl rename to pkg/cmd/testdata/testcharts/lib-chart/templates/_name.tpl diff --git a/cmd/helm/testdata/testcharts/lib-chart/templates/_persistentvolumeclaim.yaml b/pkg/cmd/testdata/testcharts/lib-chart/templates/_persistentvolumeclaim.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/lib-chart/templates/_persistentvolumeclaim.yaml rename to pkg/cmd/testdata/testcharts/lib-chart/templates/_persistentvolumeclaim.yaml diff --git a/cmd/helm/testdata/testcharts/lib-chart/templates/_secret.yaml b/pkg/cmd/testdata/testcharts/lib-chart/templates/_secret.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/lib-chart/templates/_secret.yaml rename to pkg/cmd/testdata/testcharts/lib-chart/templates/_secret.yaml diff --git a/cmd/helm/testdata/testcharts/lib-chart/templates/_service.yaml b/pkg/cmd/testdata/testcharts/lib-chart/templates/_service.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/lib-chart/templates/_service.yaml rename to pkg/cmd/testdata/testcharts/lib-chart/templates/_service.yaml diff --git a/cmd/helm/testdata/testcharts/lib-chart/templates/_util.tpl b/pkg/cmd/testdata/testcharts/lib-chart/templates/_util.tpl similarity index 100% rename from cmd/helm/testdata/testcharts/lib-chart/templates/_util.tpl rename to pkg/cmd/testdata/testcharts/lib-chart/templates/_util.tpl diff --git a/cmd/helm/testdata/testcharts/lib-chart/templates/_volume.tpl b/pkg/cmd/testdata/testcharts/lib-chart/templates/_volume.tpl similarity index 100% rename from cmd/helm/testdata/testcharts/lib-chart/templates/_volume.tpl rename to pkg/cmd/testdata/testcharts/lib-chart/templates/_volume.tpl diff --git a/cmd/helm/testdata/testcharts/lib-chart/values.yaml b/pkg/cmd/testdata/testcharts/lib-chart/values.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/lib-chart/values.yaml rename to pkg/cmd/testdata/testcharts/lib-chart/values.yaml diff --git a/cmd/helm/testdata/testcharts/object-order/Chart.yaml b/pkg/cmd/testdata/testcharts/object-order/Chart.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/object-order/Chart.yaml rename to pkg/cmd/testdata/testcharts/object-order/Chart.yaml diff --git a/cmd/helm/testdata/testcharts/object-order/templates/01-a.yml b/pkg/cmd/testdata/testcharts/object-order/templates/01-a.yml similarity index 100% rename from cmd/helm/testdata/testcharts/object-order/templates/01-a.yml rename to pkg/cmd/testdata/testcharts/object-order/templates/01-a.yml diff --git a/cmd/helm/testdata/testcharts/object-order/templates/02-b.yml b/pkg/cmd/testdata/testcharts/object-order/templates/02-b.yml similarity index 100% rename from cmd/helm/testdata/testcharts/object-order/templates/02-b.yml rename to pkg/cmd/testdata/testcharts/object-order/templates/02-b.yml diff --git a/cmd/helm/testdata/testcharts/object-order/values.yaml b/pkg/cmd/testdata/testcharts/object-order/values.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/object-order/values.yaml rename to pkg/cmd/testdata/testcharts/object-order/values.yaml diff --git a/cmd/helm/testdata/testcharts/oci-dependent-chart-0.1.0.tgz b/pkg/cmd/testdata/testcharts/oci-dependent-chart-0.1.0.tgz similarity index 100% rename from cmd/helm/testdata/testcharts/oci-dependent-chart-0.1.0.tgz rename to pkg/cmd/testdata/testcharts/oci-dependent-chart-0.1.0.tgz diff --git a/cmd/helm/testdata/testcharts/pre-release-chart-0.1.0-alpha.tgz b/pkg/cmd/testdata/testcharts/pre-release-chart-0.1.0-alpha.tgz similarity index 100% rename from cmd/helm/testdata/testcharts/pre-release-chart-0.1.0-alpha.tgz rename to pkg/cmd/testdata/testcharts/pre-release-chart-0.1.0-alpha.tgz diff --git a/cmd/helm/testdata/testcharts/reqtest-0.1.0.tgz b/pkg/cmd/testdata/testcharts/reqtest-0.1.0.tgz similarity index 100% rename from cmd/helm/testdata/testcharts/reqtest-0.1.0.tgz rename to pkg/cmd/testdata/testcharts/reqtest-0.1.0.tgz diff --git a/cmd/helm/testdata/testcharts/reqtest/.helmignore b/pkg/cmd/testdata/testcharts/reqtest/.helmignore similarity index 100% rename from cmd/helm/testdata/testcharts/reqtest/.helmignore rename to pkg/cmd/testdata/testcharts/reqtest/.helmignore diff --git a/cmd/helm/testdata/testcharts/reqtest/Chart.lock b/pkg/cmd/testdata/testcharts/reqtest/Chart.lock similarity index 100% rename from cmd/helm/testdata/testcharts/reqtest/Chart.lock rename to pkg/cmd/testdata/testcharts/reqtest/Chart.lock diff --git a/cmd/helm/testdata/testcharts/reqtest/Chart.yaml b/pkg/cmd/testdata/testcharts/reqtest/Chart.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/reqtest/Chart.yaml rename to pkg/cmd/testdata/testcharts/reqtest/Chart.yaml diff --git a/cmd/helm/testdata/testcharts/reqtest/charts/reqsubchart/.helmignore b/pkg/cmd/testdata/testcharts/reqtest/charts/reqsubchart/.helmignore similarity index 100% rename from cmd/helm/testdata/testcharts/reqtest/charts/reqsubchart/.helmignore rename to pkg/cmd/testdata/testcharts/reqtest/charts/reqsubchart/.helmignore diff --git a/cmd/helm/testdata/testcharts/reqtest/charts/reqsubchart/Chart.yaml b/pkg/cmd/testdata/testcharts/reqtest/charts/reqsubchart/Chart.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/reqtest/charts/reqsubchart/Chart.yaml rename to pkg/cmd/testdata/testcharts/reqtest/charts/reqsubchart/Chart.yaml diff --git a/cmd/helm/testdata/testcharts/reqtest/charts/reqsubchart/values.yaml b/pkg/cmd/testdata/testcharts/reqtest/charts/reqsubchart/values.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/reqtest/charts/reqsubchart/values.yaml rename to pkg/cmd/testdata/testcharts/reqtest/charts/reqsubchart/values.yaml diff --git a/cmd/helm/testdata/testcharts/reqtest/charts/reqsubchart2/.helmignore b/pkg/cmd/testdata/testcharts/reqtest/charts/reqsubchart2/.helmignore similarity index 100% rename from cmd/helm/testdata/testcharts/reqtest/charts/reqsubchart2/.helmignore rename to pkg/cmd/testdata/testcharts/reqtest/charts/reqsubchart2/.helmignore diff --git a/cmd/helm/testdata/testcharts/reqtest/charts/reqsubchart2/Chart.yaml b/pkg/cmd/testdata/testcharts/reqtest/charts/reqsubchart2/Chart.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/reqtest/charts/reqsubchart2/Chart.yaml rename to pkg/cmd/testdata/testcharts/reqtest/charts/reqsubchart2/Chart.yaml diff --git a/cmd/helm/testdata/testcharts/reqtest/charts/reqsubchart2/values.yaml b/pkg/cmd/testdata/testcharts/reqtest/charts/reqsubchart2/values.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/reqtest/charts/reqsubchart2/values.yaml rename to pkg/cmd/testdata/testcharts/reqtest/charts/reqsubchart2/values.yaml diff --git a/cmd/helm/testdata/testcharts/reqtest/charts/reqsubchart3-0.2.0.tgz b/pkg/cmd/testdata/testcharts/reqtest/charts/reqsubchart3-0.2.0.tgz similarity index 100% rename from cmd/helm/testdata/testcharts/reqtest/charts/reqsubchart3-0.2.0.tgz rename to pkg/cmd/testdata/testcharts/reqtest/charts/reqsubchart3-0.2.0.tgz diff --git a/cmd/helm/testdata/testcharts/reqtest/values.yaml b/pkg/cmd/testdata/testcharts/reqtest/values.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/reqtest/values.yaml rename to pkg/cmd/testdata/testcharts/reqtest/values.yaml diff --git a/cmd/helm/testdata/testcharts/signtest-0.1.0.tgz b/pkg/cmd/testdata/testcharts/signtest-0.1.0.tgz similarity index 100% rename from cmd/helm/testdata/testcharts/signtest-0.1.0.tgz rename to pkg/cmd/testdata/testcharts/signtest-0.1.0.tgz diff --git a/cmd/helm/testdata/testcharts/signtest-0.1.0.tgz.prov b/pkg/cmd/testdata/testcharts/signtest-0.1.0.tgz.prov similarity index 100% rename from cmd/helm/testdata/testcharts/signtest-0.1.0.tgz.prov rename to pkg/cmd/testdata/testcharts/signtest-0.1.0.tgz.prov diff --git a/cmd/helm/testdata/testcharts/signtest/.helmignore b/pkg/cmd/testdata/testcharts/signtest/.helmignore similarity index 100% rename from cmd/helm/testdata/testcharts/signtest/.helmignore rename to pkg/cmd/testdata/testcharts/signtest/.helmignore diff --git a/cmd/helm/testdata/testcharts/signtest/Chart.yaml b/pkg/cmd/testdata/testcharts/signtest/Chart.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/signtest/Chart.yaml rename to pkg/cmd/testdata/testcharts/signtest/Chart.yaml diff --git a/cmd/helm/testdata/testcharts/signtest/alpine/Chart.yaml b/pkg/cmd/testdata/testcharts/signtest/alpine/Chart.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/signtest/alpine/Chart.yaml rename to pkg/cmd/testdata/testcharts/signtest/alpine/Chart.yaml diff --git a/cmd/helm/testdata/testcharts/signtest/alpine/README.md b/pkg/cmd/testdata/testcharts/signtest/alpine/README.md similarity index 100% rename from cmd/helm/testdata/testcharts/signtest/alpine/README.md rename to pkg/cmd/testdata/testcharts/signtest/alpine/README.md diff --git a/pkg/chartutil/testdata/frobnitz/charts/alpine/templates/alpine-pod.yaml b/pkg/cmd/testdata/testcharts/signtest/alpine/templates/alpine-pod.yaml similarity index 100% rename from pkg/chartutil/testdata/frobnitz/charts/alpine/templates/alpine-pod.yaml rename to pkg/cmd/testdata/testcharts/signtest/alpine/templates/alpine-pod.yaml diff --git a/cmd/helm/testdata/testcharts/signtest/alpine/values.yaml b/pkg/cmd/testdata/testcharts/signtest/alpine/values.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/signtest/alpine/values.yaml rename to pkg/cmd/testdata/testcharts/signtest/alpine/values.yaml diff --git a/cmd/helm/testdata/testcharts/signtest/templates/pod.yaml b/pkg/cmd/testdata/testcharts/signtest/templates/pod.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/signtest/templates/pod.yaml rename to pkg/cmd/testdata/testcharts/signtest/templates/pod.yaml diff --git a/cmd/helm/testdata/testcharts/signtest/values.yaml b/pkg/cmd/testdata/testcharts/signtest/values.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/signtest/values.yaml rename to pkg/cmd/testdata/testcharts/signtest/values.yaml diff --git a/cmd/helm/testdata/testcharts/subchart/Chart.yaml b/pkg/cmd/testdata/testcharts/subchart/Chart.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/subchart/Chart.yaml rename to pkg/cmd/testdata/testcharts/subchart/Chart.yaml diff --git a/pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartA/Chart.yaml b/pkg/cmd/testdata/testcharts/subchart/charts/subchartA/Chart.yaml similarity index 100% rename from pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartA/Chart.yaml rename to pkg/cmd/testdata/testcharts/subchart/charts/subchartA/Chart.yaml diff --git a/pkg/chartutil/testdata/subpop/charts/subchart2/templates/service.yaml b/pkg/cmd/testdata/testcharts/subchart/charts/subchartA/templates/service.yaml similarity index 100% rename from pkg/chartutil/testdata/subpop/charts/subchart2/templates/service.yaml rename to pkg/cmd/testdata/testcharts/subchart/charts/subchartA/templates/service.yaml diff --git a/pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartA/values.yaml b/pkg/cmd/testdata/testcharts/subchart/charts/subchartA/values.yaml similarity index 100% rename from pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartA/values.yaml rename to pkg/cmd/testdata/testcharts/subchart/charts/subchartA/values.yaml diff --git a/pkg/chartutil/testdata/subpop/charts/subchart2/charts/subchartB/Chart.yaml b/pkg/cmd/testdata/testcharts/subchart/charts/subchartB/Chart.yaml similarity index 100% rename from pkg/chartutil/testdata/subpop/charts/subchart2/charts/subchartB/Chart.yaml rename to pkg/cmd/testdata/testcharts/subchart/charts/subchartB/Chart.yaml diff --git a/pkg/chartutil/testdata/subpop/noreqs/templates/service.yaml b/pkg/cmd/testdata/testcharts/subchart/charts/subchartB/templates/service.yaml similarity index 100% rename from pkg/chartutil/testdata/subpop/noreqs/templates/service.yaml rename to pkg/cmd/testdata/testcharts/subchart/charts/subchartB/templates/service.yaml diff --git a/cmd/helm/testdata/testcharts/subchart/charts/subchartB/values.yaml b/pkg/cmd/testdata/testcharts/subchart/charts/subchartB/values.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/subchart/charts/subchartB/values.yaml rename to pkg/cmd/testdata/testcharts/subchart/charts/subchartB/values.yaml diff --git a/cmd/helm/testdata/testcharts/subchart/crds/crdA.yaml b/pkg/cmd/testdata/testcharts/subchart/crds/crdA.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/subchart/crds/crdA.yaml rename to pkg/cmd/testdata/testcharts/subchart/crds/crdA.yaml diff --git a/cmd/helm/testdata/testcharts/subchart/extra_values.yaml b/pkg/cmd/testdata/testcharts/subchart/extra_values.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/subchart/extra_values.yaml rename to pkg/cmd/testdata/testcharts/subchart/extra_values.yaml diff --git a/pkg/chartutil/testdata/subpop/charts/subchart1/templates/NOTES.txt b/pkg/cmd/testdata/testcharts/subchart/templates/NOTES.txt similarity index 100% rename from pkg/chartutil/testdata/subpop/charts/subchart1/templates/NOTES.txt rename to pkg/cmd/testdata/testcharts/subchart/templates/NOTES.txt diff --git a/pkg/chartutil/testdata/subpop/charts/subchart1/templates/service.yaml b/pkg/cmd/testdata/testcharts/subchart/templates/service.yaml similarity index 100% rename from pkg/chartutil/testdata/subpop/charts/subchart1/templates/service.yaml rename to pkg/cmd/testdata/testcharts/subchart/templates/service.yaml diff --git a/cmd/helm/testdata/testcharts/subchart/templates/subdir/configmap.yaml b/pkg/cmd/testdata/testcharts/subchart/templates/subdir/configmap.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/subchart/templates/subdir/configmap.yaml rename to pkg/cmd/testdata/testcharts/subchart/templates/subdir/configmap.yaml diff --git a/cmd/helm/testdata/testcharts/subchart/templates/subdir/role.yaml b/pkg/cmd/testdata/testcharts/subchart/templates/subdir/role.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/subchart/templates/subdir/role.yaml rename to pkg/cmd/testdata/testcharts/subchart/templates/subdir/role.yaml diff --git a/pkg/chartutil/testdata/subpop/charts/subchart1/templates/subdir/rolebinding.yaml b/pkg/cmd/testdata/testcharts/subchart/templates/subdir/rolebinding.yaml similarity index 100% rename from pkg/chartutil/testdata/subpop/charts/subchart1/templates/subdir/rolebinding.yaml rename to pkg/cmd/testdata/testcharts/subchart/templates/subdir/rolebinding.yaml diff --git a/pkg/chartutil/testdata/subpop/charts/subchart1/templates/subdir/serviceaccount.yaml b/pkg/cmd/testdata/testcharts/subchart/templates/subdir/serviceaccount.yaml similarity index 100% rename from pkg/chartutil/testdata/subpop/charts/subchart1/templates/subdir/serviceaccount.yaml rename to pkg/cmd/testdata/testcharts/subchart/templates/subdir/serviceaccount.yaml diff --git a/cmd/helm/testdata/testcharts/subchart/templates/tests/test-config.yaml b/pkg/cmd/testdata/testcharts/subchart/templates/tests/test-config.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/subchart/templates/tests/test-config.yaml rename to pkg/cmd/testdata/testcharts/subchart/templates/tests/test-config.yaml diff --git a/cmd/helm/testdata/testcharts/subchart/templates/tests/test-nothing.yaml b/pkg/cmd/testdata/testcharts/subchart/templates/tests/test-nothing.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/subchart/templates/tests/test-nothing.yaml rename to pkg/cmd/testdata/testcharts/subchart/templates/tests/test-nothing.yaml diff --git a/cmd/helm/testdata/testcharts/subchart/values.yaml b/pkg/cmd/testdata/testcharts/subchart/values.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/subchart/values.yaml rename to pkg/cmd/testdata/testcharts/subchart/values.yaml diff --git a/cmd/helm/testdata/testcharts/upgradetest/templates/configmap.yaml b/pkg/cmd/testdata/testcharts/upgradetest/templates/configmap.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/upgradetest/templates/configmap.yaml rename to pkg/cmd/testdata/testcharts/upgradetest/templates/configmap.yaml diff --git a/cmd/helm/testdata/testcharts/upgradetest/values.yaml b/pkg/cmd/testdata/testcharts/upgradetest/values.yaml similarity index 100% rename from cmd/helm/testdata/testcharts/upgradetest/values.yaml rename to pkg/cmd/testdata/testcharts/upgradetest/values.yaml diff --git a/cmd/helm/testdata/testplugin/plugin.yaml b/pkg/cmd/testdata/testplugin/plugin.yaml similarity index 100% rename from cmd/helm/testdata/testplugin/plugin.yaml rename to pkg/cmd/testdata/testplugin/plugin.yaml diff --git a/cmd/helm/testdata/testserver/index.yaml b/pkg/cmd/testdata/testserver/index.yaml similarity index 100% rename from cmd/helm/testdata/testserver/index.yaml rename to pkg/cmd/testdata/testserver/index.yaml diff --git a/cmd/helm/testdata/testserver/repository/repositories.yaml b/pkg/cmd/testdata/testserver/repository/repositories.yaml similarity index 100% rename from cmd/helm/testdata/testserver/repository/repositories.yaml rename to pkg/cmd/testdata/testserver/repository/repositories.yaml diff --git a/cmd/helm/uninstall.go b/pkg/cmd/uninstall.go similarity index 93% rename from cmd/helm/uninstall.go rename to pkg/cmd/uninstall.go index 3c4517b50..4680c324a 100644 --- a/cmd/helm/uninstall.go +++ b/pkg/cmd/uninstall.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "fmt" @@ -23,8 +23,8 @@ import ( "github.com/spf13/cobra" - "helm.sh/helm/v3/cmd/helm/require" - "helm.sh/helm/v3/pkg/action" + "helm.sh/helm/v4/pkg/action" + "helm.sh/helm/v4/pkg/cmd/require" ) const uninstallDesc = ` @@ -76,10 +76,10 @@ func newUninstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { f.BoolVar(&client.DisableHooks, "no-hooks", false, "prevent hooks from running during uninstallation") f.BoolVar(&client.IgnoreNotFound, "ignore-not-found", false, `Treat "release not found" as a successful uninstall`) f.BoolVar(&client.KeepHistory, "keep-history", false, "remove all associated resources and mark the release as deleted, but retain the release history") - f.BoolVar(&client.Wait, "wait", false, "if set, will wait until all the resources are deleted before returning. It will wait for as long as --timeout") f.StringVar(&client.DeletionPropagation, "cascade", "background", "Must be \"background\", \"orphan\", or \"foreground\". Selects the deletion cascading strategy for the dependents. Defaults to background.") f.DurationVar(&client.Timeout, "timeout", 300*time.Second, "time to wait for any individual Kubernetes operation (like Jobs for hooks)") f.StringVar(&client.Description, "description", "", "add a custom description") + AddWaitFlag(cmd, &client.WaitStrategy) return cmd } diff --git a/cmd/helm/uninstall_test.go b/pkg/cmd/uninstall_test.go similarity index 97% rename from cmd/helm/uninstall_test.go rename to pkg/cmd/uninstall_test.go index 23b61058e..1123f449b 100644 --- a/cmd/helm/uninstall_test.go +++ b/pkg/cmd/uninstall_test.go @@ -14,12 +14,12 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "testing" - "helm.sh/helm/v3/pkg/release" + release "helm.sh/helm/v4/pkg/release/v1" ) func TestUninstall(t *testing.T) { diff --git a/cmd/helm/upgrade.go b/pkg/cmd/upgrade.go similarity index 89% rename from cmd/helm/upgrade.go rename to pkg/cmd/upgrade.go index ed25bc7d7..eb4dc92c0 100644 --- a/cmd/helm/upgrade.go +++ b/pkg/cmd/upgrade.go @@ -14,13 +14,14 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "context" "fmt" "io" "log" + "log/slog" "os" "os/signal" "syscall" @@ -28,15 +29,15 @@ import ( "github.com/spf13/cobra" - "helm.sh/helm/v3/cmd/helm/require" - "helm.sh/helm/v3/pkg/action" - "helm.sh/helm/v3/pkg/chart/loader" - "helm.sh/helm/v3/pkg/cli/output" - "helm.sh/helm/v3/pkg/cli/values" - "helm.sh/helm/v3/pkg/downloader" - "helm.sh/helm/v3/pkg/getter" - "helm.sh/helm/v3/pkg/release" - "helm.sh/helm/v3/pkg/storage/driver" + "helm.sh/helm/v4/pkg/action" + "helm.sh/helm/v4/pkg/chart/v2/loader" + "helm.sh/helm/v4/pkg/cli/output" + "helm.sh/helm/v4/pkg/cli/values" + "helm.sh/helm/v4/pkg/cmd/require" + "helm.sh/helm/v4/pkg/downloader" + "helm.sh/helm/v4/pkg/getter" + release "helm.sh/helm/v4/pkg/release/v1" + "helm.sh/helm/v4/pkg/storage/driver" ) const upgradeDesc = ` @@ -52,7 +53,7 @@ or use the '--set' flag and pass configuration from the command line, to force s values, use '--set-string'. You can use '--set-file' to set individual values from a file when the value itself is too long for the command line or is dynamically generated. You can also use '--set-json' to set json values -(scalars/objects/arrays) from the command line. +(scalars/objects/arrays) from the command line. Additionally, you can use '--set-json' and passing json object as a string. You can specify the '--values'/'-f' flag multiple times. The priority will be given to the last (right-most) file specified. For example, if both myvalues.yaml and override.yaml @@ -102,7 +103,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { client.Namespace = settings.Namespace() registryClient, err := newRegistryClient(client.CertFile, client.KeyFile, client.CaFile, - client.InsecureSkipTLSverify, client.PlainHTTP) + client.InsecureSkipTLSverify, client.PlainHTTP, client.Username, client.Password) if err != nil { return fmt.Errorf("missing registry client: %w", err) } @@ -135,7 +136,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { instClient.DisableHooks = client.DisableHooks instClient.SkipCRDs = client.SkipCRDs instClient.Timeout = client.Timeout - instClient.Wait = client.Wait + instClient.WaitStrategy = client.WaitStrategy instClient.WaitForJobs = client.WaitForJobs instClient.Devel = client.Devel instClient.Namespace = client.Namespace @@ -150,6 +151,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { instClient.Labels = client.Labels instClient.EnableDNS = client.EnableDNS instClient.HideSecret = client.HideSecret + instClient.TakeOwnership = client.TakeOwnership if isReleaseUninstalled(versions) { instClient.Replace = true @@ -159,18 +161,23 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { if err != nil { return err } - return outfmt.Write(out, &statusPrinter{rel, settings.Debug, false, false, false, instClient.HideNotes}) + return outfmt.Write(out, &statusPrinter{ + release: rel, + debug: settings.Debug, + showMetadata: false, + hideNotes: instClient.HideNotes, + }) } else if err != nil { return err } } if client.Version == "" && client.Devel { - debug("setting version to >0.0.0-0") + slog.Debug("setting version to >0.0.0-0") client.Version = ">0.0.0-0" } - chartPath, err := client.ChartPathOptions.LocateChart(args[1], settings) + chartPath, err := client.LocateChart(args[1], settings) if err != nil { return err } @@ -197,7 +204,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { man := &downloader.Manager{ Out: out, ChartPath: chartPath, - Keyring: client.ChartPathOptions.Keyring, + Keyring: client.Keyring, SkipUpdate: false, Getters: p, RepositoryConfig: settings.RepositoryConfig, @@ -218,7 +225,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { } if ch.Metadata.Deprecated { - warning("This chart is deprecated") + slog.Warn("this chart is deprecated") } // Create context and prepare the handle of SIGTERM @@ -237,7 +244,6 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { }() rel, err := client.RunWithContext(ctx, args[0], ch, vals) - if err != nil { return fmt.Errorf("UPGRADE FAILED: %w", err) } @@ -246,7 +252,12 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { fmt.Fprintf(out, "Release %q has been upgraded. Happy Helming!\n", args[0]) } - return outfmt.Write(out, &statusPrinter{rel, settings.Debug, false, false, false, client.HideNotes}) + return outfmt.Write(out, &statusPrinter{ + release: rel, + debug: settings.Debug, + showMetadata: false, + hideNotes: client.HideNotes, + }) }, } @@ -267,9 +278,8 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { f.BoolVar(&client.ResetValues, "reset-values", false, "when upgrading, reset the values to the ones built into the chart") f.BoolVar(&client.ReuseValues, "reuse-values", false, "when upgrading, reuse the last release's values and merge in any overrides from the command line via --set and -f. If '--reset-values' is specified, this is ignored") f.BoolVar(&client.ResetThenReuseValues, "reset-then-reuse-values", false, "when upgrading, reset the values to the ones built into the chart, apply the last release's values and merge in any overrides from the command line via --set and -f. If '--reset-values' or '--reuse-values' is specified, this is ignored") - f.BoolVar(&client.Wait, "wait", false, "if set, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment, StatefulSet, or ReplicaSet are in a ready state before marking the release as successful. It will wait for as long as --timeout") f.BoolVar(&client.WaitForJobs, "wait-for-jobs", false, "if set and --wait enabled, will wait until all Jobs have been completed before marking the release as successful. It will wait for as long as --timeout") - f.BoolVar(&client.Atomic, "atomic", false, "if set, upgrade process rolls back changes made in case of failed upgrade. The --wait flag will be set automatically if --atomic is used") + f.BoolVar(&client.Atomic, "atomic", false, "if set, upgrade process rolls back changes made in case of failed upgrade. The --wait flag will be set automatically to \"watcher\" if --atomic is used") f.IntVar(&client.MaxHistory, "history-max", settings.MaxHistory, "limit the maximum number of revisions saved per release. Use 0 for no limit") f.BoolVar(&client.CleanupOnFail, "cleanup-on-fail", false, "allow deletion of new resources created in this upgrade when upgrade fails") f.BoolVar(&client.SubNotes, "render-subchart-notes", false, "if set, render subchart notes along with the parent") @@ -279,10 +289,12 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { f.StringVar(&client.Description, "description", "", "add a custom description") f.BoolVar(&client.DependencyUpdate, "dependency-update", false, "update dependencies if they are missing before installing the chart") f.BoolVar(&client.EnableDNS, "enable-dns", false, "enable DNS lookups when rendering templates") + f.BoolVar(&client.TakeOwnership, "take-ownership", false, "if set, upgrade will ignore the check for helm annotations and take ownership of the existing resources") addChartPathOptionsFlags(f, &client.ChartPathOptions) addValueOptionsFlags(f, valueOpts) bindOutputFlag(cmd, &outfmt) bindPostRenderFlag(cmd, &client.PostRenderer) + AddWaitFlag(cmd, &client.WaitStrategy) err := cmd.RegisterFlagCompletionFunc("version", func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { if len(args) != 2 { @@ -290,7 +302,6 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { } return compVersionFlag(args[1], toComplete) }) - if err != nil { log.Fatal(err) } diff --git a/cmd/helm/upgrade_test.go b/pkg/cmd/upgrade_test.go similarity index 97% rename from cmd/helm/upgrade_test.go rename to pkg/cmd/upgrade_test.go index 497c78d71..8a840f149 100644 --- a/cmd/helm/upgrade_test.go +++ b/pkg/cmd/upgrade_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "fmt" @@ -24,10 +24,10 @@ import ( "strings" "testing" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chart/loader" - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/release" + chart "helm.sh/helm/v4/pkg/chart/v2" + "helm.sh/helm/v4/pkg/chart/v2/loader" + chartutil "helm.sh/helm/v4/pkg/chart/v2/util" + release "helm.sh/helm/v4/pkg/release/v1" ) func TestUpgradeCmd(t *testing.T) { @@ -114,6 +114,12 @@ func TestUpgradeCmd(t *testing.T) { golden: "output/upgrade-with-reset-values2.txt", rels: []*release.Release{relMock("funny-bunny", 5, ch2)}, }, + { + name: "upgrade a release with --take-ownership", + cmd: fmt.Sprintf("upgrade funny-bunny '%s' --take-ownership", chartPath), + golden: "output/upgrade-and-take-ownership.txt", + rels: []*release.Release{relMock("funny-bunny", 2, ch)}, + }, { name: "install a release with 'upgrade --install'", cmd: fmt.Sprintf("upgrade zany-bunny -i '%s'", chartPath), diff --git a/cmd/helm/verify.go b/pkg/cmd/verify.go similarity index 96% rename from cmd/helm/verify.go rename to pkg/cmd/verify.go index 42d8bf8dc..50f1ea914 100644 --- a/cmd/helm/verify.go +++ b/pkg/cmd/verify.go @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "fmt" @@ -21,8 +21,8 @@ import ( "github.com/spf13/cobra" - "helm.sh/helm/v3/cmd/helm/require" - "helm.sh/helm/v3/pkg/action" + "helm.sh/helm/v4/pkg/action" + "helm.sh/helm/v4/pkg/cmd/require" ) const verifyDesc = ` diff --git a/cmd/helm/verify_test.go b/pkg/cmd/verify_test.go similarity index 99% rename from cmd/helm/verify_test.go rename to pkg/cmd/verify_test.go index 23b793557..ae373afd2 100644 --- a/cmd/helm/verify_test.go +++ b/pkg/cmd/verify_test.go @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "fmt" diff --git a/cmd/helm/version.go b/pkg/cmd/version.go similarity index 97% rename from cmd/helm/version.go rename to pkg/cmd/version.go index 12d0777f5..0211716fe 100644 --- a/cmd/helm/version.go +++ b/pkg/cmd/version.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "fmt" @@ -23,8 +23,8 @@ import ( "github.com/spf13/cobra" - "helm.sh/helm/v3/cmd/helm/require" - "helm.sh/helm/v3/internal/version" + "helm.sh/helm/v4/internal/version" + "helm.sh/helm/v4/pkg/cmd/require" ) const versionDesc = ` diff --git a/cmd/helm/version_test.go b/pkg/cmd/version_test.go similarity index 98% rename from cmd/helm/version_test.go rename to pkg/cmd/version_test.go index aa3cbfb7d..c06c72309 100644 --- a/cmd/helm/version_test.go +++ b/pkg/cmd/version_test.go @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cmd import ( "testing" diff --git a/pkg/downloader/chart_downloader.go b/pkg/downloader/chart_downloader.go index 0b13365c6..04c56e614 100644 --- a/pkg/downloader/chart_downloader.go +++ b/pkg/downloader/chart_downloader.go @@ -25,15 +25,13 @@ import ( "path/filepath" "strings" - "github.com/Masterminds/semver/v3" - - "helm.sh/helm/v3/internal/fileutil" - "helm.sh/helm/v3/internal/urlutil" - "helm.sh/helm/v3/pkg/getter" - "helm.sh/helm/v3/pkg/helmpath" - "helm.sh/helm/v3/pkg/provenance" - "helm.sh/helm/v3/pkg/registry" - "helm.sh/helm/v3/pkg/repo" + "helm.sh/helm/v4/internal/fileutil" + "helm.sh/helm/v4/internal/urlutil" + "helm.sh/helm/v4/pkg/getter" + "helm.sh/helm/v4/pkg/helmpath" + "helm.sh/helm/v4/pkg/provenance" + "helm.sh/helm/v4/pkg/registry" + "helm.sh/helm/v4/pkg/repo" ) // VerificationStrategy describes a strategy for determining whether to verify a chart. @@ -144,39 +142,6 @@ func (c *ChartDownloader) DownloadTo(ref, version, dest string) (string, *proven return destfile, ver, nil } -func (c *ChartDownloader) getOciURI(ref, version string, u *url.URL) (*url.URL, error) { - var tag string - var err error - - // Evaluate whether an explicit version has been provided. Otherwise, determine version to use - _, errSemVer := semver.NewVersion(version) - if errSemVer == nil { - tag = version - } else { - // Retrieve list of repository tags - tags, err := c.RegistryClient.Tags(strings.TrimPrefix(ref, fmt.Sprintf("%s://", registry.OCIScheme))) - if err != nil { - return nil, err - } - if len(tags) == 0 { - return nil, fmt.Errorf("Unable to locate any tags in provided repository: %s", ref) - } - - // Determine if version provided - // If empty, try to get the highest available tag - // If exact version, try to find it - // If semver constraint string, try to find a match - tag, err = registry.GetTagMatchingVersionOrConstraint(tags, version) - if err != nil { - return nil, err - } - } - - u.Path = fmt.Sprintf("%s:%s", u.Path, tag) - - return u, err -} - // ResolveChartVersion resolves a chart reference to a URL. // // It returns the URL and sets the ChartDownloader's Options that can fetch @@ -199,7 +164,7 @@ func (c *ChartDownloader) ResolveChartVersion(ref, version string) (*url.URL, er } if registry.IsOCI(u.String()) { - return c.getOciURI(ref, version, u) + return c.RegistryClient.ValidateReference(ref, version, u) } rf, err := loadRepoConfig(c.RepositoryConfig) diff --git a/pkg/downloader/chart_downloader_test.go b/pkg/downloader/chart_downloader_test.go index 131e21306..26dcc58ff 100644 --- a/pkg/downloader/chart_downloader_test.go +++ b/pkg/downloader/chart_downloader_test.go @@ -20,11 +20,11 @@ import ( "path/filepath" "testing" - "helm.sh/helm/v3/internal/test/ensure" - "helm.sh/helm/v3/pkg/cli" - "helm.sh/helm/v3/pkg/getter" - "helm.sh/helm/v3/pkg/repo" - "helm.sh/helm/v3/pkg/repo/repotest" + "helm.sh/helm/v4/internal/test/ensure" + "helm.sh/helm/v4/pkg/cli" + "helm.sh/helm/v4/pkg/getter" + "helm.sh/helm/v4/pkg/repo" + "helm.sh/helm/v4/pkg/repo/repotest" ) const ( @@ -53,6 +53,10 @@ func TestResolveChartRef(t *testing.T) { {name: "full URL, file", ref: "file:///foo-1.2.3.tgz", fail: true}, {name: "invalid", ref: "invalid-1.2.3", fail: true}, {name: "not found", ref: "nosuchthing/invalid-1.2.3", fail: true}, + {name: "ref with tag", ref: "oci://example.com/helm-charts/nginx:15.4.2", expect: "oci://example.com/helm-charts/nginx:15.4.2"}, + {name: "no repository", ref: "oci://", fail: true}, + {name: "oci ref", ref: "oci://example.com/helm-charts/nginx", version: "15.4.2", expect: "oci://example.com/helm-charts/nginx:15.4.2"}, + {name: "oci ref with sha256 and version mismatch", ref: "oci://example.com/install/by/sha:0.1.1@sha256:d234555386402a5867ef0169fefe5486858b6d8d209eaf32fd26d29b16807fd6", version: "0.1.2", fail: true}, } c := ChartDownloader{ @@ -171,7 +175,11 @@ func TestIsTar(t *testing.T) { } func TestDownloadTo(t *testing.T) { - srv := repotest.NewTempServerWithCleanupAndBasicAuth(t, "testdata/*.tgz*") + srv := repotest.NewTempServer( + t, + repotest.WithChartSourceGlob("testdata/*.tgz*"), + repotest.WithMiddleware(repotest.BasicAuthMiddleware(t)), + ) defer srv.Stop() if err := srv.CreateIndex(); err != nil { t.Fatal(err) @@ -218,12 +226,11 @@ func TestDownloadTo(t *testing.T) { func TestDownloadTo_TLS(t *testing.T) { // Set up mock server w/ tls enabled - srv, err := repotest.NewTempServerWithCleanup(t, "testdata/*.tgz*") - srv.Stop() - if err != nil { - t.Fatal(err) - } - srv.StartTLS() + srv := repotest.NewTempServer( + t, + repotest.WithChartSourceGlob("testdata/*.tgz*"), + repotest.WithTLSConfig(repotest.MakeTestTLSConfig(t, "../../testdata")), + ) defer srv.Stop() if err := srv.CreateIndex(); err != nil { t.Fatal(err) @@ -245,7 +252,13 @@ func TestDownloadTo_TLS(t *testing.T) { RepositoryConfig: repoConfig, RepositoryCache: repoCache, }), - Options: []getter.Option{}, + Options: []getter.Option{ + getter.WithTLSClientConfig( + "", + "", + filepath.Join("../../testdata/rootca.crt"), + ), + }, } cname := "test/signtest" dest := srv.Root() @@ -274,10 +287,10 @@ func TestDownloadTo_VerifyLater(t *testing.T) { dest := t.TempDir() // Set up a fake repo - srv, err := repotest.NewTempServerWithCleanup(t, "testdata/*.tgz*") - if err != nil { - t.Fatal(err) - } + srv := repotest.NewTempServer( + t, + repotest.WithChartSourceGlob("testdata/*.tgz*"), + ) defer srv.Stop() if err := srv.LinkIndices(); err != nil { t.Fatal(err) diff --git a/pkg/downloader/manager.go b/pkg/downloader/manager.go index 8d402e784..e884e12d4 100644 --- a/pkg/downloader/manager.go +++ b/pkg/downloader/manager.go @@ -34,16 +34,16 @@ import ( "github.com/Masterminds/semver/v3" "sigs.k8s.io/yaml" - "helm.sh/helm/v3/internal/resolver" - "helm.sh/helm/v3/internal/third_party/dep/fs" - "helm.sh/helm/v3/internal/urlutil" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chart/loader" - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/getter" - "helm.sh/helm/v3/pkg/helmpath" - "helm.sh/helm/v3/pkg/registry" - "helm.sh/helm/v3/pkg/repo" + "helm.sh/helm/v4/internal/resolver" + "helm.sh/helm/v4/internal/third_party/dep/fs" + "helm.sh/helm/v4/internal/urlutil" + chart "helm.sh/helm/v4/pkg/chart/v2" + "helm.sh/helm/v4/pkg/chart/v2/loader" + chartutil "helm.sh/helm/v4/pkg/chart/v2/util" + "helm.sh/helm/v4/pkg/getter" + "helm.sh/helm/v4/pkg/helmpath" + "helm.sh/helm/v4/pkg/registry" + "helm.sh/helm/v4/pkg/repo" ) // ErrRepoNotFound indicates that chart repositories can't be found in local repo cache. @@ -386,7 +386,7 @@ func parseOCIRef(chartRef string) (string, string, error) { return chartRef, tag, nil } -// safeMoveDep moves all dependencies in the source and moves them into dest. +// safeMoveDeps moves all dependencies in the source and moves them into dest. // // It does this by first matching the file name to an expected pattern, then loading // the file to verify that it is a chart. @@ -660,10 +660,28 @@ func (m *Manager) UpdateRepositories() error { return nil } +// Filter out duplicate repos by URL, including those with trailing slashes. +func dedupeRepos(repos []*repo.Entry) []*repo.Entry { + seen := make(map[string]*repo.Entry) + for _, r := range repos { + // Normalize URL by removing trailing slashes. + seenURL := strings.TrimSuffix(r.URL, "/") + seen[seenURL] = r + } + var unique []*repo.Entry + for _, r := range seen { + unique = append(unique, r) + } + return unique +} + func (m *Manager) parallelRepoUpdate(repos []*repo.Entry) error { var wg sync.WaitGroup - for _, c := range repos { + + localRepos := dedupeRepos(repos) + + for _, c := range localRepos { r, err := repo.NewChartRepository(c, m.Getters) if err != nil { return err @@ -743,7 +761,7 @@ func (m *Manager) findChartURL(name, version, repoURL string, repos map[string]* return } } - url, err = repo.FindChartInRepoURL(repoURL, name, version, certFile, keyFile, caFile, m.Getters) + url, err = repo.FindChartInRepoURL(repoURL, name, m.Getters, repo.WithChartVersion(version), repo.WithClientTLS(certFile, keyFile, caFile)) if err == nil { return url, username, password, false, false, "", "", "", err } diff --git a/pkg/downloader/manager_test.go b/pkg/downloader/manager_test.go index 805b78bf7..fecc8fbef 100644 --- a/pkg/downloader/manager_test.go +++ b/pkg/downloader/manager_test.go @@ -24,11 +24,14 @@ import ( "reflect" "testing" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chart/loader" - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/getter" - "helm.sh/helm/v3/pkg/repo/repotest" + "github.com/stretchr/testify/assert" + + chart "helm.sh/helm/v4/pkg/chart/v2" + "helm.sh/helm/v4/pkg/chart/v2/loader" + chartutil "helm.sh/helm/v4/pkg/chart/v2/util" + "helm.sh/helm/v4/pkg/getter" + "helm.sh/helm/v4/pkg/repo" + "helm.sh/helm/v4/pkg/repo/repotest" ) func TestVersionEquals(t *testing.T) { @@ -294,10 +297,10 @@ version: 0.1.0` func TestUpdateBeforeBuild(t *testing.T) { // Set up a fake repo - srv, err := repotest.NewTempServerWithCleanup(t, "testdata/*.tgz*") - if err != nil { - t.Fatal(err) - } + srv := repotest.NewTempServer( + t, + repotest.WithChartSourceGlob("testdata/*.tgz*"), + ) defer srv.Stop() if err := srv.LinkIndices(); err != nil { t.Fatal(err) @@ -349,13 +352,11 @@ func TestUpdateBeforeBuild(t *testing.T) { } // Update before Build. see issue: https://github.com/helm/helm/issues/7101 - err = m.Update() - if err != nil { + if err := m.Update(); err != nil { t.Fatal(err) } - err = m.Build() - if err != nil { + if err := m.Build(); err != nil { t.Fatal(err) } } @@ -365,10 +366,10 @@ func TestUpdateBeforeBuild(t *testing.T) { // to be fetched. func TestUpdateWithNoRepo(t *testing.T) { // Set up a fake repo - srv, err := repotest.NewTempServerWithCleanup(t, "testdata/*.tgz*") - if err != nil { - t.Fatal(err) - } + srv := repotest.NewTempServer( + t, + repotest.WithChartSourceGlob("testdata/*.tgz*"), + ) defer srv.Stop() if err := srv.LinkIndices(); err != nil { t.Fatal(err) @@ -424,8 +425,7 @@ func TestUpdateWithNoRepo(t *testing.T) { } // Test the update - err = m.Update() - if err != nil { + if err := m.Update(); err != nil { t.Fatal(err) } } @@ -438,10 +438,10 @@ func TestUpdateWithNoRepo(t *testing.T) { // 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) { // Set up a fake repo - srv, err := repotest.NewTempServerWithCleanup(t, "testdata/*.tgz*") - if err != nil { - t.Fatal(err) - } + srv := repotest.NewTempServer( + t, + repotest.WithChartSourceGlob("testdata/*.tgz*"), + ) defer srv.Stop() if err := srv.LinkIndices(); err != nil { t.Fatal(err) @@ -489,14 +489,12 @@ func checkBuildWithOptionalFields(t *testing.T, chartName string, dep chart.Depe } // First build will update dependencies and create Chart.lock file. - err = m.Build() - if err != nil { + if err := m.Build(); err != nil { t.Fatal(err) } // Second build should be passed. See PR #6655. - err = m.Build() - if err != nil { + if err := m.Build(); err != nil { t.Fatal(err) } } @@ -600,3 +598,71 @@ func TestKey(t *testing.T) { } } } + +// Test dedupeRepos tests that the dedupeRepos function correctly deduplicates +func TestDedupeRepos(t *testing.T) { + tests := []struct { + name string + repos []*repo.Entry + want []*repo.Entry + }{ + { + name: "no duplicates", + repos: []*repo.Entry{ + { + URL: "https://example.com/charts", + }, + { + URL: "https://example.com/charts2", + }, + }, + want: []*repo.Entry{ + { + URL: "https://example.com/charts", + }, + { + URL: "https://example.com/charts2", + }, + }, + }, + { + name: "duplicates", + repos: []*repo.Entry{ + { + URL: "https://example.com/charts", + }, + { + URL: "https://example.com/charts", + }, + }, + want: []*repo.Entry{ + { + URL: "https://example.com/charts", + }, + }, + }, + { + name: "duplicates with trailing slash", + repos: []*repo.Entry{ + { + URL: "https://example.com/charts", + }, + { + URL: "https://example.com/charts/", + }, + }, + want: []*repo.Entry{ + { + // the last one wins + URL: "https://example.com/charts/", + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := dedupeRepos(tt.repos) + assert.ElementsMatch(t, tt.want, got) + }) + } +} diff --git a/pkg/engine/doc.go b/pkg/engine/doc.go index 6b3443aaf..e764a829a 100644 --- a/pkg/engine/doc.go +++ b/pkg/engine/doc.go @@ -21,4 +21,4 @@ When Helm renders templates it does so with additional functions and different modes (e.g., strict, lint mode). This package handles the helm specific implementation. */ -package engine // import "helm.sh/helm/v3/pkg/engine" +package engine // import "helm.sh/helm/v4/pkg/engine" diff --git a/pkg/engine/engine.go b/pkg/engine/engine.go index c48840f29..839ad4a31 100644 --- a/pkg/engine/engine.go +++ b/pkg/engine/engine.go @@ -19,7 +19,7 @@ package engine import ( "errors" "fmt" - "log" + "log/slog" "path" "path/filepath" "regexp" @@ -29,8 +29,8 @@ import ( "k8s.io/client-go/rest" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chartutil" + chart "helm.sh/helm/v4/pkg/chart/v2" + chartutil "helm.sh/helm/v4/pkg/chart/v2/util" ) // Engine is an implementation of the Helm rendering implementation for templates. @@ -44,6 +44,8 @@ type Engine struct { clientProvider *ClientProvider // EnableDNS tells the engine to allow DNS lookups when rendering templates EnableDNS bool + // CustomTemplateFuncs is defined by users to provide custom template funcs + CustomTemplateFuncs template.FuncMap } // New creates a new instance of Engine using the passed in rest config. @@ -205,7 +207,7 @@ func (e Engine) initFunMap(t *template.Template) { if val == nil { if e.LintMode { // Don't fail on missing required values when linting - log.Printf("[INFO] Missing required value: %s", warn) + slog.Warn("missing required value", "message", warn) return "", nil } return val, errors.New(warnWrap(warn)) @@ -213,7 +215,7 @@ func (e Engine) initFunMap(t *template.Template) { if val == "" { if e.LintMode { // Don't fail on missing required values when linting - log.Printf("[INFO] Missing required value: %s", warn) + slog.Warn("missing required values", "message", warn) return "", nil } return val, errors.New(warnWrap(warn)) @@ -226,7 +228,7 @@ func (e Engine) initFunMap(t *template.Template) { funcMap["fail"] = func(msg string) (string, error) { if e.LintMode { // Don't fail when linting - log.Printf("[INFO] Fail: %s", msg) + slog.Info("funcMap fail", "message", msg) return "", nil } return "", errors.New(warnWrap(msg)) @@ -246,6 +248,11 @@ func (e Engine) initFunMap(t *template.Template) { } } + // Set custom template funcs + for k, v := range e.CustomTemplateFuncs { + funcMap[k] = v + } + t.Funcs(funcMap) } diff --git a/pkg/engine/engine_test.go b/pkg/engine/engine_test.go index de1896a47..68e0158fa 100644 --- a/pkg/engine/engine_test.go +++ b/pkg/engine/engine_test.go @@ -30,8 +30,8 @@ import ( "k8s.io/client-go/dynamic" "k8s.io/client-go/dynamic/fake" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chartutil" + chart "helm.sh/helm/v4/pkg/chart/v2" + chartutil "helm.sh/helm/v4/pkg/chart/v2/util" ) func TestSortTemplates(t *testing.T) { @@ -1300,3 +1300,63 @@ func TestRenderTplMissingKeyString(t *testing.T) { t.Fatal(err) } } + +func TestRenderCustomTemplateFuncs(t *testing.T) { + // Create a chart with two templates that use custom functions + c := &chart.Chart{ + Metadata: &chart.Metadata{Name: "CustomFunc"}, + Templates: []*chart.File{ + { + Name: "templates/manifest", + Data: []byte(`{{exclaim .Values.message}}`), + }, + { + Name: "templates/override", + Data: []byte(`{{ upper .Values.message }}`), + }, + }, + } + v := chartutil.Values{ + "Values": chartutil.Values{ + "message": "hello", + }, + "Chart": c.Metadata, + "Release": chartutil.Values{ + "Name": "TestRelease", + }, + } + + // Define a custom template function "exclaim" that appends "!!!" to a string and override "upper" function + customFuncs := template.FuncMap{ + "exclaim": func(input string) string { + return input + "!!!" + }, + "upper": func(s string) string { + return "custom:" + s + }, + } + + // Create an engine instance and set the CustomTemplateFuncs. + e := new(Engine) + e.CustomTemplateFuncs = customFuncs + + // Render the chart. + out, err := e.Render(c, v) + if err != nil { + t.Fatal(err) + } + + // Expected output should be "hello!!!". + expected := "hello!!!" + key := "CustomFunc/templates/manifest" + if rendered, ok := out[key]; !ok || rendered != expected { + t.Errorf("Expected %q, got %q", expected, rendered) + } + + // Verify that the rendered template used the custom "upper" function. + expected = "custom:hello" + key = "CustomFunc/templates/override" + if rendered, ok := out[key]; !ok || rendered != expected { + t.Errorf("Expected %q, got %q", expected, rendered) + } +} diff --git a/pkg/engine/files.go b/pkg/engine/files.go index f2cfdb3f3..87166728c 100644 --- a/pkg/engine/files.go +++ b/pkg/engine/files.go @@ -23,7 +23,7 @@ import ( "github.com/gobwas/glob" - "helm.sh/helm/v3/pkg/chart" + chart "helm.sh/helm/v4/pkg/chart/v2" ) // files is a map of files in a chart that can be accessed from a template. diff --git a/pkg/engine/funcs_test.go b/pkg/engine/funcs_test.go index a4f4d604f..a7e2506a3 100644 --- a/pkg/engine/funcs_test.go +++ b/pkg/engine/funcs_test.go @@ -63,7 +63,7 @@ keyInElement0 = "valueInElement0" keyInElement1 = "valueInElement1"`, }, { tpl: `{{ fromToml . }}`, - expect: "map[Error:toml: line 0: unexpected EOF; expected key separator '=']", + expect: "map[Error:toml: line 1: unexpected EOF; expected key separator '=']", vars: "one", }, { tpl: `{{ toJson . }}`, diff --git a/pkg/engine/lookup_func.go b/pkg/engine/lookup_func.go index 3bbc6d85b..605b43a48 100644 --- a/pkg/engine/lookup_func.go +++ b/pkg/engine/lookup_func.go @@ -19,7 +19,7 @@ package engine import ( "context" "fmt" - "log" + "log/slog" "strings" apierrors "k8s.io/apimachinery/pkg/api/errors" @@ -35,9 +35,6 @@ type lookupFunc = func(apiversion string, resource string, namespace string, nam // NewLookupFunction returns a function for looking up objects in the cluster. // // If the resource does not exist, no error is raised. -// -// This function is considered deprecated, and will be renamed in Helm 4. It will no -// longer be a public function. func NewLookupFunction(config *rest.Config) lookupFunc { return newLookupFunction(clientProviderFromConfig{config: config}) } @@ -101,7 +98,7 @@ func getDynamicClientOnKind(apiversion string, kind string, config *rest.Config) gvk := schema.FromAPIVersionAndKind(apiversion, kind) apiRes, err := getAPIResourceForGVK(gvk, config) if err != nil { - log.Printf("[ERROR] unable to get apiresource from unstructured: %s , error %s", gvk.String(), err) + slog.Error("unable to get apiresource", "groupVersionKind", gvk.String(), slog.Any("error", err)) return nil, false, fmt.Errorf("unable to get apiresource from unstructured: %s: %w", gvk.String(), err) } gvr := schema.GroupVersionResource{ @@ -111,7 +108,7 @@ func getDynamicClientOnKind(apiversion string, kind string, config *rest.Config) } intf, err := dynamic.NewForConfig(config) if err != nil { - log.Printf("[ERROR] unable to get dynamic client %s", err) + slog.Error("unable to get dynamic client", slog.Any("error", err)) return nil, false, err } res := intf.Resource(gvr) @@ -122,12 +119,12 @@ func getAPIResourceForGVK(gvk schema.GroupVersionKind, config *rest.Config) (met res := metav1.APIResource{} discoveryClient, err := discovery.NewDiscoveryClientForConfig(config) if err != nil { - log.Printf("[ERROR] unable to create discovery client %s", err) + slog.Error("unable to create discovery client", slog.Any("error", err)) return res, err } resList, err := discoveryClient.ServerResourcesForGroupVersion(gvk.GroupVersion().String()) if err != nil { - log.Printf("[ERROR] unable to retrieve resource list for: %s , error: %s", gvk.GroupVersion().String(), err) + slog.Error("unable to retrieve resource list", "GroupVersion", gvk.GroupVersion().String(), slog.Any("error", err)) return res, err } for _, resource := range resList.APIResources { diff --git a/pkg/getter/getter.go b/pkg/getter/getter.go index 2225d5f85..743ac569b 100644 --- a/pkg/getter/getter.go +++ b/pkg/getter/getter.go @@ -22,8 +22,8 @@ import ( "net/http" "time" - "helm.sh/helm/v3/pkg/cli" - "helm.sh/helm/v3/pkg/registry" + "helm.sh/helm/v4/pkg/cli" + "helm.sh/helm/v4/pkg/registry" ) // options are generic parameters to be provided to the getter during instantiation. diff --git a/pkg/getter/getter_test.go b/pkg/getter/getter_test.go index ab14784ab..a14301900 100644 --- a/pkg/getter/getter_test.go +++ b/pkg/getter/getter_test.go @@ -18,7 +18,7 @@ package getter import ( "testing" - "helm.sh/helm/v3/pkg/cli" + "helm.sh/helm/v4/pkg/cli" ) const pluginDir = "testdata/plugins" diff --git a/pkg/getter/httpgetter.go b/pkg/getter/httpgetter.go index 6eda754cd..4348fed27 100644 --- a/pkg/getter/httpgetter.go +++ b/pkg/getter/httpgetter.go @@ -24,9 +24,8 @@ import ( "net/url" "sync" - "helm.sh/helm/v3/internal/tlsutil" - "helm.sh/helm/v3/internal/urlutil" - "helm.sh/helm/v3/internal/version" + "helm.sh/helm/v4/internal/tlsutil" + "helm.sh/helm/v4/internal/version" ) // HTTPGetter is the default HTTP(/S) backend handler @@ -127,17 +126,15 @@ func (g *HTTPGetter) httpClient() (*http.Client, error) { }) if (g.opts.certFile != "" && g.opts.keyFile != "") || g.opts.caFile != "" || g.opts.insecureSkipVerifyTLS { - tlsConf, err := tlsutil.NewClientTLS(g.opts.certFile, g.opts.keyFile, g.opts.caFile, g.opts.insecureSkipVerifyTLS) + tlsConf, err := tlsutil.NewTLSConfig( + tlsutil.WithInsecureSkipVerify(g.opts.insecureSkipVerifyTLS), + tlsutil.WithCertKeyPairFiles(g.opts.certFile, g.opts.keyFile), + tlsutil.WithCAFile(g.opts.caFile), + ) if err != nil { return nil, fmt.Errorf("can't create TLS config for client: %w", err) } - sni, err := urlutil.ExtractHostname(g.opts.url) - if err != nil { - return nil, err - } - tlsConf.ServerName = sni - g.transport.TLSClientConfig = tlsConf } diff --git a/pkg/getter/httpgetter_test.go b/pkg/getter/httpgetter_test.go index b1755fdb4..510fffd13 100644 --- a/pkg/getter/httpgetter_test.go +++ b/pkg/getter/httpgetter_test.go @@ -28,9 +28,9 @@ import ( "testing" "time" - "helm.sh/helm/v3/internal/tlsutil" - "helm.sh/helm/v3/internal/version" - "helm.sh/helm/v3/pkg/cli" + "helm.sh/helm/v4/internal/tlsutil" + "helm.sh/helm/v4/internal/version" + "helm.sh/helm/v4/pkg/cli" ) func TestHTTPGetter(t *testing.T) { @@ -309,7 +309,11 @@ func TestDownloadTLS(t *testing.T) { insecureSkipTLSverify := false tlsSrv := httptest.NewUnstartedServer(http.HandlerFunc(func(_ http.ResponseWriter, _ *http.Request) {})) - tlsConf, err := tlsutil.NewClientTLS(pub, priv, ca, insecureSkipTLSverify) + tlsConf, err := tlsutil.NewTLSConfig( + tlsutil.WithInsecureSkipVerify(insecureSkipTLSverify), + tlsutil.WithCertKeyPairFiles(pub, priv), + tlsutil.WithCAFile(ca), + ) if err != nil { t.Fatal(fmt.Errorf("can't create TLS config for client: %w", err)) } @@ -352,6 +356,131 @@ func TestDownloadTLS(t *testing.T) { } } +func TestDownloadTLSWithRedirect(t *testing.T) { + cd := "../../testdata" + srv2Resp := "hello" + insecureSkipTLSverify := false + + // Server 2 that will actually fulfil the request. + ca, pub, priv := filepath.Join(cd, "rootca.crt"), filepath.Join(cd, "localhost-crt.pem"), filepath.Join(cd, "key.pem") + tlsConf, err := tlsutil.NewTLSConfig( + tlsutil.WithCAFile(ca), + tlsutil.WithCertKeyPairFiles(pub, priv), + tlsutil.WithInsecureSkipVerify(insecureSkipTLSverify), + ) + + if err != nil { + t.Fatal(fmt.Errorf("can't create TLS config for client: %w", err)) + } + + tlsSrv2 := httptest.NewUnstartedServer(http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) { + rw.Header().Set("Content-Type", "text/plain") + rw.Write([]byte(srv2Resp)) + })) + + tlsSrv2.TLS = tlsConf + tlsSrv2.StartTLS() + defer tlsSrv2.Close() + + // Server 1 responds with a redirect to Server 2. + ca, pub, priv = filepath.Join(cd, "rootca.crt"), filepath.Join(cd, "crt.pem"), filepath.Join(cd, "key.pem") + tlsConf, err = tlsutil.NewTLSConfig( + tlsutil.WithCAFile(ca), + tlsutil.WithCertKeyPairFiles(pub, priv), + tlsutil.WithInsecureSkipVerify(insecureSkipTLSverify), + ) + + if err != nil { + t.Fatal(fmt.Errorf("can't create TLS config for client: %w", err)) + } + + tlsSrv1 := httptest.NewUnstartedServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { + u, _ := url.ParseRequestURI(tlsSrv2.URL) + + // Make the request using the hostname 'localhost' (to which 'localhost-crt.pem' is issued) + // to verify that a successful TLS connection is made even if the client doesn't specify + // the hostname (SNI) in `tls.Config.ServerName`. By default the hostname is derived from the + // request URL for every request (including redirects). Setting `tls.Config.ServerName` on the + // client just overrides the remote endpoint's hostname. + // See https://github.com/golang/go/blob/3979fb9/src/net/http/transport.go#L1505-L1513. + u.Host = fmt.Sprintf("localhost:%s", u.Port()) + + http.Redirect(rw, r, u.String(), http.StatusTemporaryRedirect) + })) + + tlsSrv1.TLS = tlsConf + tlsSrv1.StartTLS() + defer tlsSrv1.Close() + + u, _ := url.ParseRequestURI(tlsSrv1.URL) + + t.Run("Test with TLS", func(t *testing.T) { + g, err := NewHTTPGetter( + WithURL(u.String()), + WithTLSClientConfig(pub, priv, ca), + ) + if err != nil { + t.Fatal(err) + } + + buf, err := g.Get(u.String()) + if err != nil { + t.Error(err) + } + + b, err := io.ReadAll(buf) + if err != nil { + t.Error(err) + } + + if string(b) != srv2Resp { + t.Errorf("expected response from Server2 to be '%s', instead got: %s", srv2Resp, string(b)) + } + }) + + t.Run("Test with TLS config being passed along in .Get (see #6635)", func(t *testing.T) { + g, err := NewHTTPGetter() + if err != nil { + t.Fatal(err) + } + + buf, err := g.Get(u.String(), WithURL(u.String()), WithTLSClientConfig(pub, priv, ca)) + if err != nil { + t.Error(err) + } + + b, err := io.ReadAll(buf) + if err != nil { + t.Error(err) + } + + if string(b) != srv2Resp { + t.Errorf("expected response from Server2 to be '%s', instead got: %s", srv2Resp, string(b)) + } + }) + + t.Run("Test with only the CA file (see also #6635)", func(t *testing.T) { + g, err := NewHTTPGetter() + if err != nil { + t.Fatal(err) + } + + buf, err := g.Get(u.String(), WithURL(u.String()), WithTLSClientConfig("", "", ca)) + if err != nil { + t.Error(err) + } + + b, err := io.ReadAll(buf) + if err != nil { + t.Error(err) + } + + if string(b) != srv2Resp { + t.Errorf("expected response from Server2 to be '%s', instead got: %s", srv2Resp, string(b)) + } + }) +} + func TestDownloadInsecureSkipTLSVerify(t *testing.T) { ts := httptest.NewTLSServer(http.HandlerFunc(func(_ http.ResponseWriter, _ *http.Request) {})) defer ts.Close() @@ -444,9 +573,6 @@ func TestHttpClientInsecureSkipVerify(t *testing.T) { if len(transport.TLSClientConfig.Certificates) <= 0 { t.Fatal("transport.TLSClientConfig.Certificates is not present") } - if transport.TLSClientConfig.ServerName == "" { - t.Fatal("TLSClientConfig.ServerName is blank") - } } func verifyInsecureSkipVerify(t *testing.T, g *HTTPGetter, caseName string, expectedValue bool) *http.Transport { diff --git a/pkg/getter/ocigetter.go b/pkg/getter/ocigetter.go index 0547cdcbb..2a611e13a 100644 --- a/pkg/getter/ocigetter.go +++ b/pkg/getter/ocigetter.go @@ -20,13 +20,14 @@ import ( "fmt" "net" "net/http" + "path" "strings" "sync" "time" - "helm.sh/helm/v3/internal/tlsutil" - "helm.sh/helm/v3/internal/urlutil" - "helm.sh/helm/v3/pkg/registry" + "helm.sh/helm/v4/internal/tlsutil" + "helm.sh/helm/v4/internal/urlutil" + "helm.sh/helm/v4/pkg/registry" ) // OCIGetter is the default HTTP(/S) backend handler @@ -58,6 +59,9 @@ func (g *OCIGetter) get(href string) (*bytes.Buffer, error) { ref := strings.TrimPrefix(href, fmt.Sprintf("%s://", registry.OCIScheme)) + if version := g.opts.version; version != "" && !strings.Contains(path.Base(ref), ":") { + ref = fmt.Sprintf("%s:%s", ref, version) + } var pullOpts []registry.PullOption requestingProv := strings.HasSuffix(ref, ".prov") if requestingProv { @@ -124,7 +128,11 @@ func (g *OCIGetter) newRegistryClient() (*registry.Client, error) { }) if (g.opts.certFile != "" && g.opts.keyFile != "") || g.opts.caFile != "" || g.opts.insecureSkipVerifyTLS { - tlsConf, err := tlsutil.NewClientTLS(g.opts.certFile, g.opts.keyFile, g.opts.caFile, g.opts.insecureSkipVerifyTLS) + tlsConf, err := tlsutil.NewTLSConfig( + tlsutil.WithInsecureSkipVerify(g.opts.insecureSkipVerifyTLS), + tlsutil.WithCertKeyPairFiles(g.opts.certFile, g.opts.keyFile), + tlsutil.WithCAFile(g.opts.caFile), + ) if err != nil { return nil, fmt.Errorf("can't create TLS config for client: %w", err) } diff --git a/pkg/getter/ocigetter_test.go b/pkg/getter/ocigetter_test.go index d0834d9fc..e3d9278a5 100644 --- a/pkg/getter/ocigetter_test.go +++ b/pkg/getter/ocigetter_test.go @@ -21,7 +21,7 @@ import ( "testing" "time" - "helm.sh/helm/v3/pkg/registry" + "helm.sh/helm/v4/pkg/registry" ) func TestOCIGetter(t *testing.T) { diff --git a/pkg/getter/plugingetter.go b/pkg/getter/plugingetter.go index 86f666207..3b8185543 100644 --- a/pkg/getter/plugingetter.go +++ b/pkg/getter/plugingetter.go @@ -23,8 +23,8 @@ import ( "path/filepath" "strings" - "helm.sh/helm/v3/pkg/cli" - "helm.sh/helm/v3/pkg/plugin" + "helm.sh/helm/v4/pkg/cli" + "helm.sh/helm/v4/pkg/plugin" ) // collectPlugins scans for getter plugins. diff --git a/pkg/getter/plugingetter_test.go b/pkg/getter/plugingetter_test.go index a18fa302b..310ab9e07 100644 --- a/pkg/getter/plugingetter_test.go +++ b/pkg/getter/plugingetter_test.go @@ -20,7 +20,7 @@ import ( "strings" "testing" - "helm.sh/helm/v3/pkg/cli" + "helm.sh/helm/v4/pkg/cli" ) func TestCollectPlugins(t *testing.T) { diff --git a/pkg/helmpath/home_unix_test.go b/pkg/helmpath/home_unix_test.go index 977002549..6e4189bc9 100644 --- a/pkg/helmpath/home_unix_test.go +++ b/pkg/helmpath/home_unix_test.go @@ -20,7 +20,7 @@ import ( "runtime" "testing" - "helm.sh/helm/v3/pkg/helmpath/xdg" + "helm.sh/helm/v4/pkg/helmpath/xdg" ) func TestHelmHome(t *testing.T) { diff --git a/pkg/helmpath/home_windows_test.go b/pkg/helmpath/home_windows_test.go index 073e6347f..38fe5e4f1 100644 --- a/pkg/helmpath/home_windows_test.go +++ b/pkg/helmpath/home_windows_test.go @@ -19,7 +19,7 @@ import ( "os" "testing" - "helm.sh/helm/v3/pkg/helmpath/xdg" + "helm.sh/helm/v4/pkg/helmpath/xdg" ) func TestHelmHome(t *testing.T) { diff --git a/pkg/helmpath/lazypath.go b/pkg/helmpath/lazypath.go index 6b4f1fc77..c1f868754 100644 --- a/pkg/helmpath/lazypath.go +++ b/pkg/helmpath/lazypath.go @@ -17,7 +17,7 @@ import ( "os" "path/filepath" - "helm.sh/helm/v3/pkg/helmpath/xdg" + "helm.sh/helm/v4/pkg/helmpath/xdg" ) const ( diff --git a/pkg/helmpath/lazypath_darwin_test.go b/pkg/helmpath/lazypath_darwin_test.go index d0503e0e1..e04e20756 100644 --- a/pkg/helmpath/lazypath_darwin_test.go +++ b/pkg/helmpath/lazypath_darwin_test.go @@ -22,7 +22,7 @@ import ( "k8s.io/client-go/util/homedir" - "helm.sh/helm/v3/pkg/helmpath/xdg" + "helm.sh/helm/v4/pkg/helmpath/xdg" ) const ( diff --git a/pkg/helmpath/lazypath_unix_test.go b/pkg/helmpath/lazypath_unix_test.go index 657982b2d..534735d10 100644 --- a/pkg/helmpath/lazypath_unix_test.go +++ b/pkg/helmpath/lazypath_unix_test.go @@ -22,7 +22,7 @@ import ( "k8s.io/client-go/util/homedir" - "helm.sh/helm/v3/pkg/helmpath/xdg" + "helm.sh/helm/v4/pkg/helmpath/xdg" ) const ( diff --git a/pkg/helmpath/lazypath_windows_test.go b/pkg/helmpath/lazypath_windows_test.go index dedfd5720..ebd95e812 100644 --- a/pkg/helmpath/lazypath_windows_test.go +++ b/pkg/helmpath/lazypath_windows_test.go @@ -22,7 +22,7 @@ import ( "k8s.io/client-go/util/homedir" - "helm.sh/helm/v3/pkg/helmpath/xdg" + "helm.sh/helm/v4/pkg/helmpath/xdg" ) const ( diff --git a/pkg/ignore/doc.go b/pkg/ignore/doc.go index 1f5e91847..a66066eb2 100644 --- a/pkg/ignore/doc.go +++ b/pkg/ignore/doc.go @@ -65,4 +65,4 @@ Notable differences from .gitignore: - The evaluation of escape sequences has not been tested for compatibility - There is no support for '\!' as a special leading sequence. */ -package ignore // import "helm.sh/helm/v3/pkg/ignore" +package ignore // import "helm.sh/helm/v4/pkg/ignore" diff --git a/pkg/ignore/rules.go b/pkg/ignore/rules.go index 0a5ba544d..5281c3d59 100644 --- a/pkg/ignore/rules.go +++ b/pkg/ignore/rules.go @@ -21,7 +21,7 @@ import ( "bytes" "errors" "io" - "log" + "log/slog" "os" "path/filepath" "strings" @@ -101,7 +101,7 @@ func (r *Rules) Ignore(path string, fi os.FileInfo) bool { } for _, p := range r.patterns { if p.match == nil { - log.Printf("ignore: no matcher supplied for %q", p.raw) + slog.Info("this will be ignored no matcher supplied", "patterns", p.raw) return false } @@ -176,7 +176,7 @@ func (r *Rules) parseRule(rule string) error { rule = strings.TrimPrefix(rule, "/") ok, err := filepath.Match(rule, n) if err != nil { - log.Printf("Failed to compile %q: %s", rule, err) + slog.Error("failed to compile", "rule", rule, slog.Any("error", err)) return false } return ok @@ -186,7 +186,7 @@ func (r *Rules) parseRule(rule string) error { p.match = func(n string, _ os.FileInfo) bool { ok, err := filepath.Match(rule, n) if err != nil { - log.Printf("Failed to compile %q: %s", rule, err) + slog.Error("failed to compile", "rule", rule, slog.Any("error", err)) return false } return ok @@ -198,7 +198,7 @@ func (r *Rules) parseRule(rule string) error { n = filepath.Base(n) ok, err := filepath.Match(rule, n) if err != nil { - log.Printf("Failed to compile %q: %s", rule, err) + slog.Error("failed to compile", "rule", rule, slog.Any("error", err)) return false } return ok diff --git a/pkg/kube/client.go b/pkg/kube/client.go index 69e53f5d3..7b29bfba4 100644 --- a/pkg/kube/client.go +++ b/pkg/kube/client.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package kube // import "helm.sh/helm/v3/pkg/kube" +package kube // import "helm.sh/helm/v4/pkg/kube" import ( "bytes" @@ -23,6 +23,7 @@ import ( "errors" "fmt" "io" + "log/slog" "os" "path/filepath" "reflect" @@ -31,30 +32,27 @@ import ( "time" jsonpatch "github.com/evanphx/json-patch" - batch "k8s.io/api/batch/v1" v1 "k8s.io/api/core/v1" apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" apiextv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/client/apiutil" - multierror "github.com/hashicorp/go-multierror" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1" - "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/jsonmergepatch" + "k8s.io/apimachinery/pkg/util/mergepatch" "k8s.io/apimachinery/pkg/util/strategicpatch" - "k8s.io/apimachinery/pkg/watch" "k8s.io/cli-runtime/pkg/genericclioptions" "k8s.io/cli-runtime/pkg/resource" "k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" - cachetools "k8s.io/client-go/tools/cache" - watchtools "k8s.io/client-go/tools/watch" "k8s.io/client-go/util/retry" cmdutil "k8s.io/kubectl/pkg/cmd/util" ) @@ -79,13 +77,21 @@ type Client struct { // needs. The smaller surface area of the interface means there is a lower // chance of it changing. Factory Factory - Log func(string, ...interface{}) // Namespace allows to bypass the kubeconfig file for the choice of the namespace Namespace string - kubeClient *kubernetes.Clientset + Waiter + kubeClient kubernetes.Interface } +type WaitStrategy string + +const ( + StatusWatcherStrategy WaitStrategy = "watcher" + LegacyStrategy WaitStrategy = "legacy" + HookOnlyStrategy WaitStrategy = "hookOnly" +) + func init() { // Add CRDs to the scheme. They are missing by default. if err := apiextv1.AddToScheme(scheme.Scheme); err != nil { @@ -97,21 +103,73 @@ func init() { } } +func (c *Client) newStatusWatcher() (*statusWaiter, error) { + cfg, err := c.Factory.ToRESTConfig() + if err != nil { + return nil, err + } + dynamicClient, err := c.Factory.DynamicClient() + if err != nil { + return nil, err + } + httpClient, err := rest.HTTPClientFor(cfg) + if err != nil { + return nil, err + } + restMapper, err := apiutil.NewDynamicRESTMapper(cfg, httpClient) + if err != nil { + return nil, err + } + return &statusWaiter{ + restMapper: restMapper, + client: dynamicClient, + }, nil +} + +func (c *Client) GetWaiter(strategy WaitStrategy) (Waiter, error) { + switch strategy { + case LegacyStrategy: + kc, err := c.Factory.KubernetesClientSet() + if err != nil { + return nil, err + } + return &legacyWaiter{kubeClient: kc}, nil + case StatusWatcherStrategy: + return c.newStatusWatcher() + case HookOnlyStrategy: + sw, err := c.newStatusWatcher() + if err != nil { + return nil, err + } + return &hookOnlyWaiter{sw: sw}, nil + default: + return nil, errors.New("unknown wait strategy") + } +} + +func (c *Client) SetWaiter(ws WaitStrategy) error { + var err error + c.Waiter, err = c.GetWaiter(ws) + if err != nil { + return err + } + return nil +} + // New creates a new Client. func New(getter genericclioptions.RESTClientGetter) *Client { if getter == nil { getter = genericclioptions.NewConfigFlags(true) } - return &Client{ - Factory: cmdutil.NewFactory(getter), - Log: nopLogger, + factory := cmdutil.NewFactory(getter) + c := &Client{ + Factory: factory, } + return c } -var nopLogger = func(_ string, _ ...interface{}) {} - // getKubeClient get or create a new KubernetesClientSet -func (c *Client) getKubeClient() (*kubernetes.Clientset, error) { +func (c *Client) getKubeClient() (kubernetes.Interface, error) { var err error if c.kubeClient == nil { c.kubeClient, err = c.Factory.KubernetesClientSet() @@ -131,7 +189,7 @@ func (c *Client) IsReachable() error { if err != nil { return fmt.Errorf("Kubernetes cluster unreachable: %w", err) } - if _, err := client.ServerVersion(); err != nil { + if _, err := client.Discovery().ServerVersion(); err != nil { return fmt.Errorf("Kubernetes cluster unreachable: %w", err) } return nil @@ -139,7 +197,7 @@ func (c *Client) IsReachable() error { // Create creates Kubernetes resources specified in the resource list. func (c *Client) Create(resources ResourceList) (*Result, error) { - c.Log("creating %d resource(s)", len(resources)) + slog.Debug("creating resource(s)", "resources", len(resources)) if err := perform(resources, createResource); err != nil { return nil, err } @@ -191,7 +249,7 @@ func (c *Client) Get(resources ResourceList, related bool) (map[string][]runtime objs, err = c.getSelectRelationPod(info, objs, isTable, &podSelectors) if err != nil { - c.Log("Warning: get the relation pod is failed, err:%s", err.Error()) + slog.Warn("get the relation pod is failed", slog.Any("error", err)) } } } @@ -209,7 +267,7 @@ func (c *Client) getSelectRelationPod(info *resource.Info, objs map[string][]run if info == nil { return objs, nil } - c.Log("get relation pod of object: %s/%s/%s", info.Namespace, info.Mapping.GroupVersionKind.Kind, info.Name) + slog.Debug("get relation pod of object", "namespace", info.Namespace, "name", info.Name, "kind", info.Mapping.GroupVersionKind.Kind) selector, ok, _ := getSelectorFromObject(info.Object) if !ok { return objs, nil @@ -281,45 +339,6 @@ func getResource(info *resource.Info) (runtime.Object, error) { return obj, nil } -// Wait waits up to the given timeout for the specified resources to be ready. -func (c *Client) Wait(resources ResourceList, timeout time.Duration) error { - cs, err := c.getKubeClient() - if err != nil { - return err - } - checker := NewReadyChecker(cs, c.Log, PausedAsReady(true)) - w := waiter{ - c: checker, - log: c.Log, - timeout: timeout, - } - return w.waitForResources(resources) -} - -// WaitWithJobs wait up to the given timeout for the specified resources to be ready, including jobs. -func (c *Client) WaitWithJobs(resources ResourceList, timeout time.Duration) error { - cs, err := c.getKubeClient() - if err != nil { - return err - } - checker := NewReadyChecker(cs, c.Log, PausedAsReady(true), CheckJobs(true)) - w := waiter{ - c: checker, - log: c.Log, - timeout: timeout, - } - return w.waitForResources(resources) -} - -// WaitForDelete wait up to the given timeout for the specified resources to be deleted. -func (c *Client) WaitForDelete(resources ResourceList, timeout time.Duration) error { - w := waiter{ - log: c.Log, - timeout: timeout, - } - return w.waitForDeletedResources(resources) -} - func (c *Client) namespace() string { if c.Namespace != "" { return c.Namespace @@ -379,18 +398,11 @@ func (c *Client) BuildTable(reader io.Reader, validate bool) (ResourceList, erro return result, scrubValidationError(err) } -// Update takes the current list of objects and target list of objects and -// creates resources that don't already exist, updates resources that have been -// modified in the target configuration, and deletes resources from the current -// configuration that are not present in the target configuration. If an error -// occurs, a Result will still be returned with the error, containing all -// resource updates, creations, and deletions that were attempted. These can be -// used for cleanup or other logging purposes. -func (c *Client) Update(original, target ResourceList, force bool) (*Result, error) { +func (c *Client) update(original, target ResourceList, force, threeWayMerge bool) (*Result, error) { updateErrors := []error{} res := &Result{} - c.Log("checking %d resources for changes", len(target)) + slog.Debug("checking resources for changes", "resources", len(target)) err := target.Visit(func(info *resource.Info, err error) error { if err != nil { return err @@ -411,7 +423,7 @@ func (c *Client) Update(original, target ResourceList, force bool) (*Result, err } kind := info.Mapping.GroupVersionKind.Kind - c.Log("Created a new %s called %q in %s\n", kind, info.Name, info.Namespace) + slog.Debug("created a new resource", "namespace", info.Namespace, "name", info.Name, "kind", kind) return nil } @@ -421,8 +433,8 @@ func (c *Client) Update(original, target ResourceList, force bool) (*Result, err return fmt.Errorf("no %s with the name %q found", kind, info.Name) } - if err := updateResource(c, info, originalInfo.Object, force); err != nil { - c.Log("error updating the resource %q:\n\t %v", info.Name, err) + if err := updateResource(c, info, originalInfo.Object, force, threeWayMerge); err != nil { + slog.Debug("error updating the resource", "namespace", info.Namespace, "name", info.Name, "kind", info.Mapping.GroupVersionKind.Kind, slog.Any("error", err)) updateErrors = append(updateErrors, err) } // Because we check for errors later, append the info regardless @@ -439,22 +451,22 @@ func (c *Client) Update(original, target ResourceList, force bool) (*Result, err } for _, info := range original.Difference(target) { - c.Log("Deleting %s %q in namespace %s...", info.Mapping.GroupVersionKind.Kind, info.Name, info.Namespace) + slog.Debug("deleting resource", "namespace", info.Namespace, "name", info.Name, "kind", info.Mapping.GroupVersionKind.Kind) if err := info.Get(); err != nil { - c.Log("Unable to get obj %q, err: %s", info.Name, err) + slog.Debug("unable to get object", "namespace", info.Namespace, "name", info.Name, "kind", info.Mapping.GroupVersionKind.Kind, slog.Any("error", err)) continue } annotations, err := metadataAccessor.Annotations(info.Object) if err != nil { - c.Log("Unable to get annotations on %q, err: %s", info.Name, err) + slog.Debug("unable to get annotations", "namespace", info.Namespace, "name", info.Name, "kind", info.Mapping.GroupVersionKind.Kind, slog.Any("error", err)) } if annotations != nil && annotations[ResourcePolicyAnno] == KeepPolicy { - c.Log("Skipping delete of %q due to annotation [%s=%s]", info.Name, ResourcePolicyAnno, KeepPolicy) + slog.Debug("skipping delete due to annotation", "namespace", info.Namespace, "name", info.Name, "kind", info.Mapping.GroupVersionKind.Kind, "annotation", ResourcePolicyAnno, "value", KeepPolicy) continue } if err := deleteResource(info, metav1.DeletePropagationBackground); err != nil { - c.Log("Failed to delete %q, err: %s", info.ObjectName(), err) + slog.Debug("failed to delete resource", "namespace", info.Namespace, "name", info.Name, "kind", info.Mapping.GroupVersionKind.Kind, slog.Any("error", err)) continue } res.Deleted = append(res.Deleted, info) @@ -462,6 +474,31 @@ func (c *Client) Update(original, target ResourceList, force bool) (*Result, err return res, nil } +// Update takes the current list of objects and target list of objects and +// creates resources that don't already exist, updates resources that have been +// modified in the target configuration, and deletes resources from the current +// configuration that are not present in the target configuration. If an error +// occurs, a Result will still be returned with the error, containing all +// resource updates, creations, and deletions that were attempted. These can be +// used for cleanup or other logging purposes. +// +// The difference to Update is that UpdateThreeWayMerge does a three-way-merge +// for unstructured objects. +func (c *Client) UpdateThreeWayMerge(original, target ResourceList, force bool) (*Result, error) { + return c.update(original, target, force, true) +} + +// Update takes the current list of objects and target list of objects and +// creates resources that don't already exist, updates resources that have been +// modified in the target configuration, and deletes resources from the current +// configuration that are not present in the target configuration. If an error +// occurs, a Result will still be returned with the error, containing all +// resource updates, creations, and deletions that were attempted. These can be +// used for cleanup or other logging purposes. +func (c *Client) Update(original, target ResourceList, force bool) (*Result, error) { + return c.update(original, target, force, false) +} + // Delete deletes Kubernetes resources specified in the resources list with // background cascade deletion. It will attempt to delete all resources even // if one or more fail and collect any errors. All successfully deleted items @@ -478,16 +515,16 @@ func (c *Client) DeleteWithPropagationPolicy(resources ResourceList, policy meta return rdelete(c, resources, policy) } -func rdelete(c *Client, resources ResourceList, propagation metav1.DeletionPropagation) (*Result, []error) { +func rdelete(_ *Client, resources ResourceList, propagation metav1.DeletionPropagation) (*Result, []error) { var errs []error res := &Result{} mtx := sync.Mutex{} err := perform(resources, func(info *resource.Info) error { - c.Log("Starting delete for %q %s", info.Name, info.Mapping.GroupVersionKind.Kind) + slog.Debug("starting delete resource", "namespace", info.Namespace, "name", info.Name, "kind", info.Mapping.GroupVersionKind.Kind) err := deleteResource(info, propagation) if err == nil || apierrors.IsNotFound(err) { if err != nil { - c.Log("Ignoring delete failure for %q %s: %v", info.Name, info.Mapping.GroupVersionKind, err) + slog.Debug("ignoring delete failure", "namespace", info.Namespace, "name", info.Name, "kind", info.Mapping.GroupVersionKind.Kind, slog.Any("error", err)) } mtx.Lock() defer mtx.Unlock() @@ -512,52 +549,6 @@ func rdelete(c *Client, resources ResourceList, propagation metav1.DeletionPropa return res, nil } -func (c *Client) watchTimeout(t time.Duration) func(*resource.Info) error { - return func(info *resource.Info) error { - return c.watchUntilReady(t, info) - } -} - -// WatchUntilReady watches the resources given and waits until it is ready. -// -// This method is mainly for hook implementations. It watches for a resource to -// hit a particular milestone. The milestone depends on the Kind. -// -// For most kinds, it checks to see if the resource is marked as Added or Modified -// by the Kubernetes event stream. For some kinds, it does more: -// -// - Jobs: A job is marked "Ready" when it has successfully completed. This is -// ascertained by watching the Status fields in a job's output. -// - Pods: A pod is marked "Ready" when it has successfully completed. This is -// ascertained by watching the status.phase field in a pod's output. -// -// Handling for other kinds will be added as necessary. -func (c *Client) WatchUntilReady(resources ResourceList, timeout time.Duration) error { - // For jobs, there's also the option to do poll c.Jobs(namespace).Get(): - // https://github.com/adamreese/kubernetes/blob/master/test/e2e/job.go#L291-L300 - return perform(resources, c.watchTimeout(timeout)) -} - -func perform(infos ResourceList, fn func(*resource.Info) error) error { - var result error - - if len(infos) == 0 { - return ErrNoObjectsVisited - } - - errs := make(chan error) - go batchPerform(infos, fn, errs) - - for range infos { - err := <-errs - if err != nil { - result = multierror.Append(result, err) - } - } - - return result -} - // getManagedFieldsManager returns the manager string. If one was set it will be returned. // Otherwise, one is calculated based on the name of the binary. func getManagedFieldsManager() string { @@ -617,7 +608,7 @@ func deleteResource(info *resource.Info, policy metav1.DeletionPropagation) erro }) } -func createPatch(target *resource.Info, current runtime.Object) ([]byte, types.PatchType, error) { +func createPatch(target *resource.Info, current runtime.Object, threeWayMergeForUnstructured bool) ([]byte, types.PatchType, error) { oldData, err := json.Marshal(current) if err != nil { return nil, types.StrategicMergePatchType, fmt.Errorf("serializing current configuration: %w", err) @@ -645,7 +636,7 @@ func createPatch(target *resource.Info, current runtime.Object) ([]byte, types.P // Unstructured objects, such as CRDs, may not have a not registered error // returned from ConvertToVersion. Anything that's unstructured should - // use the jsonpatch.CreateMergePatch. Strategic Merge Patch is not supported + // use generic JSON merge patch. Strategic Merge Patch is not supported // on objects like CRDs. _, isUnstructured := versionedObject.(runtime.Unstructured) @@ -653,6 +644,19 @@ func createPatch(target *resource.Info, current runtime.Object) ([]byte, types.P _, isCRD := versionedObject.(*apiextv1beta1.CustomResourceDefinition) if isUnstructured || isCRD { + if threeWayMergeForUnstructured { + // from https://github.com/kubernetes/kubectl/blob/b83b2ec7d15f286720bccf7872b5c72372cb8e80/pkg/cmd/apply/patcher.go#L129 + preconditions := []mergepatch.PreconditionFunc{ + mergepatch.RequireKeyUnchanged("apiVersion"), + mergepatch.RequireKeyUnchanged("kind"), + mergepatch.RequireMetadataKeyUnchanged("name"), + } + patch, err := jsonmergepatch.CreateThreeWayJSONMergePatch(oldData, newData, currentData, preconditions...) + if err != nil && mergepatch.IsPreconditionFailed(err) { + err = fmt.Errorf("%w: at least one field was changed: apiVersion, kind or name", err) + } + return patch, types.MergePatchType, err + } // fall back to generic JSON merge patch patch, err := jsonpatch.CreateMergePatch(oldData, newData) return patch, types.MergePatchType, err @@ -667,7 +671,7 @@ func createPatch(target *resource.Info, current runtime.Object) ([]byte, types.P return patch, types.StrategicMergePatchType, err } -func updateResource(c *Client, target *resource.Info, currentObj runtime.Object, force bool) error { +func updateResource(_ *Client, target *resource.Info, currentObj runtime.Object, force, threeWayMergeForUnstructured bool) error { var ( obj runtime.Object helper = resource.NewHelper(target.Client, target.Mapping).WithFieldManager(getManagedFieldsManager()) @@ -681,15 +685,15 @@ func updateResource(c *Client, target *resource.Info, currentObj runtime.Object, if err != nil { return fmt.Errorf("failed to replace object: %w", err) } - c.Log("Replaced %q with kind %s for kind %s", target.Name, currentObj.GetObjectKind().GroupVersionKind().Kind, kind) + slog.Debug("replace succeeded", "name", target.Name, "initialKind", currentObj.GetObjectKind().GroupVersionKind().Kind, "kind", kind) } else { - patch, patchType, err := createPatch(target, currentObj) + patch, patchType, err := createPatch(target, currentObj, threeWayMergeForUnstructured) if err != nil { return fmt.Errorf("failed to create patch: %w", err) } if patch == nil || string(patch) == "{}" { - c.Log("Looks like there are no changes for %s %q", kind, target.Name) + slog.Debug("no changes detected", "kind", kind, "name", target.Name) // This needs to happen to make sure that Helm has the latest info from the API // Otherwise there will be no labels and other functions that use labels will panic if err := target.Get(); err != nil { @@ -698,7 +702,7 @@ func updateResource(c *Client, target *resource.Info, currentObj runtime.Object, return nil } // send patch to server - c.Log("Patch %s %q in namespace %s", kind, target.Name, target.Namespace) + slog.Debug("patching resource", "kind", kind, "name", target.Name, "namespace", target.Namespace) obj, err = helper.Patch(target.Namespace, target.Name, patchType, patch, nil) if err != nil { return fmt.Errorf("cannot patch %q with kind %s: %w", target.Name, kind, err) @@ -709,107 +713,43 @@ func updateResource(c *Client, target *resource.Info, currentObj runtime.Object, return nil } -func (c *Client) watchUntilReady(timeout time.Duration, info *resource.Info) error { - kind := info.Mapping.GroupVersionKind.Kind - switch kind { - case "Job", "Pod": - default: - return nil - } - - c.Log("Watching for changes to %s %s with timeout of %v", kind, info.Name, timeout) - - // Use a selector on the name of the resource. This should be unique for the - // given version and kind - selector, err := fields.ParseSelector(fmt.Sprintf("metadata.name=%s", info.Name)) +// GetPodList uses the kubernetes interface to get the list of pods filtered by listOptions +func (c *Client) GetPodList(namespace string, listOptions metav1.ListOptions) (*v1.PodList, error) { + podList, err := c.kubeClient.CoreV1().Pods(namespace).List(context.Background(), listOptions) if err != nil { - return err + return nil, fmt.Errorf("failed to get pod list with options: %+v with error: %v", listOptions, err) } - lw := cachetools.NewListWatchFromClient(info.Client, info.Mapping.Resource.Resource, info.Namespace, selector) - - // What we watch for depends on the Kind. - // - For a Job, we watch for completion. - // - For all else, we watch until Ready. - // In the future, we might want to add some special logic for types - // like Ingress, Volume, etc. - - ctx, cancel := watchtools.ContextWithOptionalTimeout(context.Background(), timeout) - defer cancel() - _, err = watchtools.UntilWithSync(ctx, lw, &unstructured.Unstructured{}, nil, func(e watch.Event) (bool, error) { - // Make sure the incoming object is versioned as we use unstructured - // objects when we build manifests - obj := convertWithMapper(e.Object, info.Mapping) - switch e.Type { - case watch.Added, watch.Modified: - // For things like a secret or a config map, this is the best indicator - // we get. We care mostly about jobs, where what we want to see is - // the status go into a good state. For other types, like ReplicaSet - // we don't really do anything to support these as hooks. - c.Log("Add/Modify event for %s: %v", info.Name, e.Type) - switch kind { - case "Job": - return c.waitForJob(obj, info.Name) - case "Pod": - return c.waitForPodSuccess(obj, info.Name) - } - return true, nil - case watch.Deleted: - c.Log("Deleted event for %s", info.Name) - return true, nil - case watch.Error: - // Handle error and return with an error. - c.Log("Error event for %s", info.Name) - return true, fmt.Errorf("failed to deploy %s", info.Name) - default: - return false, nil - } - }) - return err + return podList, nil } -// waitForJob is a helper that waits for a job to complete. -// -// This operates on an event returned from a watcher. -func (c *Client) waitForJob(obj runtime.Object, name string) (bool, error) { - o, ok := obj.(*batch.Job) - if !ok { - return true, fmt.Errorf("expected %s to be a *batch.Job, got %T", name, obj) - } - - for _, c := range o.Status.Conditions { - if c.Type == batch.JobComplete && c.Status == "True" { - return true, nil - } else if c.Type == batch.JobFailed && c.Status == "True" { - return true, fmt.Errorf("job %s failed: %s", name, c.Reason) +// OutputContainerLogsForPodList is a helper that outputs logs for a list of pods +func (c *Client) OutputContainerLogsForPodList(podList *v1.PodList, namespace string, writerFunc func(namespace, pod, container string) io.Writer) error { + for _, pod := range podList.Items { + for _, container := range pod.Spec.Containers { + options := &v1.PodLogOptions{ + Container: container.Name, + } + request := c.kubeClient.CoreV1().Pods(namespace).GetLogs(pod.Name, options) + err2 := copyRequestStreamToWriter(request, pod.Name, container.Name, writerFunc(namespace, pod.Name, container.Name)) + if err2 != nil { + return err2 + } } } - - c.Log("%s: Jobs active: %d, jobs failed: %d, jobs succeeded: %d", name, o.Status.Active, o.Status.Failed, o.Status.Succeeded) - return false, nil + return nil } -// waitForPodSuccess is a helper that waits for a pod to complete. -// -// This operates on an event returned from a watcher. -func (c *Client) waitForPodSuccess(obj runtime.Object, name string) (bool, error) { - o, ok := obj.(*v1.Pod) - if !ok { - return true, fmt.Errorf("expected %s to be a *v1.Pod, got %T", name, obj) +func copyRequestStreamToWriter(request *rest.Request, podName, containerName string, writer io.Writer) error { + readCloser, err := request.Stream(context.Background()) + if err != nil { + return fmt.Errorf("Failed to stream pod logs for pod: %s, container: %s", podName, containerName) } - - switch o.Status.Phase { - case v1.PodSucceeded: - c.Log("Pod %s succeeded", o.Name) - return true, nil - case v1.PodFailed: - return true, fmt.Errorf("pod %s failed", o.Name) - case v1.PodPending: - c.Log("Pod %s pending", o.Name) - case v1.PodRunning: - c.Log("Pod %s running", o.Name) + defer readCloser.Close() + _, err = io.Copy(writer, readCloser) + if err != nil { + return fmt.Errorf("Failed to copy IO from logs for pod: %s, container: %s", podName, containerName) } - - return false, nil + return nil } // scrubValidationError removes kubectl info from the message. diff --git a/pkg/kube/client_test.go b/pkg/kube/client_test.go index f2d6bcb59..56c7eebc9 100644 --- a/pkg/kube/client_test.go +++ b/pkg/kube/client_test.go @@ -24,10 +24,18 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + jsonserializer "k8s.io/apimachinery/pkg/runtime/serializer/json" + "k8s.io/apimachinery/pkg/types" "k8s.io/cli-runtime/pkg/resource" + k8sfake "k8s.io/client-go/kubernetes/fake" "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest/fake" cmdtesting "k8s.io/kubectl/pkg/cmd/testing" @@ -104,7 +112,6 @@ func newTestClient(t *testing.T) *Client { return &Client{ Factory: testFactory.WithNamespace("default"), - Log: nopLogger, } } @@ -205,7 +212,7 @@ func TestCreate(t *testing.T) { }) } -func TestUpdate(t *testing.T) { +func testUpdate(t *testing.T, threeWayMerge bool) { listA := newPodList("starfish", "otter", "squid") listB := newPodList("starfish", "otter", "dolphin") listC := newPodList("starfish", "otter", "dolphin") @@ -276,7 +283,12 @@ func TestUpdate(t *testing.T) { t.Fatal(err) } - result, err := c.Update(first, second, false) + var result *Result + if threeWayMerge { + result, err = c.UpdateThreeWayMerge(first, second, false) + } else { + result, err = c.Update(first, second, false) + } if err != nil { t.Fatal(err) } @@ -325,6 +337,14 @@ func TestUpdate(t *testing.T) { } } +func TestUpdate(t *testing.T) { + testUpdate(t, false) +} + +func TestUpdateThreeWayMerge(t *testing.T) { + testUpdate(t, true) +} + func TestBuild(t *testing.T) { tests := []struct { name string @@ -512,6 +532,11 @@ func TestWait(t *testing.T) { } }), } + var err error + c.Waiter, err = c.GetWaiter(LegacyStrategy) + if err != nil { + t.Fatal(err) + } resources, err := c.Build(objBody(&podList), false) if err != nil { t.Fatal(err) @@ -564,6 +589,11 @@ func TestWaitJob(t *testing.T) { } }), } + var err error + c.Waiter, err = c.GetWaiter(LegacyStrategy) + if err != nil { + t.Fatal(err) + } resources, err := c.Build(objBody(job), false) if err != nil { t.Fatal(err) @@ -618,6 +648,11 @@ func TestWaitDelete(t *testing.T) { } }), } + var err error + c.Waiter, err = c.GetWaiter(LegacyStrategy) + if err != nil { + t.Fatal(err) + } resources, err := c.Build(objBody(&pod), false) if err != nil { t.Fatal(err) @@ -682,6 +717,39 @@ func TestReal(t *testing.T) { } } +func TestGetPodList(t *testing.T) { + + namespace := "some-namespace" + names := []string{"dave", "jimmy"} + var responsePodList v1.PodList + for _, name := range names { + responsePodList.Items = append(responsePodList.Items, newPodWithStatus(name, v1.PodStatus{}, namespace)) + } + + kubeClient := k8sfake.NewSimpleClientset(&responsePodList) + c := Client{Namespace: namespace, kubeClient: kubeClient} + + podList, err := c.GetPodList(namespace, metav1.ListOptions{}) + clientAssertions := assert.New(t) + clientAssertions.NoError(err) + clientAssertions.Equal(&responsePodList, podList) + +} + +func TestOutputContainerLogsForPodList(t *testing.T) { + namespace := "some-namespace" + somePodList := newPodList("jimmy", "three", "structs") + + kubeClient := k8sfake.NewSimpleClientset(&somePodList) + c := Client{Namespace: namespace, kubeClient: kubeClient} + outBuffer := &bytes.Buffer{} + outBufferFunc := func(_, _, _ string) io.Writer { return outBuffer } + err := c.OutputContainerLogsForPodList(&somePodList, namespace, outBufferFunc) + clientAssertions := assert.New(t) + clientAssertions.NoError(err) + clientAssertions.Equal("fake logsfake logsfake logs", outBuffer.String()) +} + const testServiceManifest = ` kind: Service apiVersion: v1 @@ -862,3 +930,150 @@ spec: var resourceQuotaConflict = []byte(` {"kind":"Status","apiVersion":"v1","metadata":{},"status":"Failure","message":"Operation cannot be fulfilled on resourcequotas \"quota\": the object has been modified; please apply your changes to the latest version and try again","reason":"Conflict","details":{"name":"quota","kind":"resourcequotas"},"code":409}`) + +type createPatchTestCase struct { + name string + + // The target state. + target *unstructured.Unstructured + // The current state as it exists in the release. + current *unstructured.Unstructured + // The actual state as it exists in the cluster. + actual *unstructured.Unstructured + + threeWayMergeForUnstructured bool + // The patch is supposed to transfer the current state to the target state, + // thereby preserving the actual state, wherever possible. + expectedPatch string + expectedPatchType types.PatchType +} + +func (c createPatchTestCase) run(t *testing.T) { + scheme := runtime.NewScheme() + v1.AddToScheme(scheme) + encoder := jsonserializer.NewSerializerWithOptions( + jsonserializer.DefaultMetaFactory, scheme, scheme, jsonserializer.SerializerOptions{ + Yaml: false, Pretty: false, Strict: true, + }, + ) + objBody := func(obj runtime.Object) io.ReadCloser { + return io.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(encoder, obj)))) + } + header := make(http.Header) + header.Set("Content-Type", runtime.ContentTypeJSON) + restClient := &fake.RESTClient{ + NegotiatedSerializer: unstructuredSerializer, + Resp: &http.Response{ + StatusCode: 200, + Body: objBody(c.actual), + Header: header, + }, + } + + targetInfo := &resource.Info{ + Client: restClient, + Namespace: "default", + Name: "test-obj", + Object: c.target, + Mapping: &meta.RESTMapping{ + Resource: schema.GroupVersionResource{ + Group: "crd.com", + Version: "v1", + Resource: "datas", + }, + Scope: meta.RESTScopeNamespace, + }, + } + + patch, patchType, err := createPatch(targetInfo, c.current, c.threeWayMergeForUnstructured) + if err != nil { + t.Fatalf("Failed to create patch: %v", err) + } + + if c.expectedPatch != string(patch) { + t.Errorf("Unexpected patch.\nTarget:\n%s\nCurrent:\n%s\nActual:\n%s\n\nExpected:\n%s\nGot:\n%s", + c.target, + c.current, + c.actual, + c.expectedPatch, + string(patch), + ) + } + + if patchType != types.MergePatchType { + t.Errorf("Expected patch type %s, got %s", types.MergePatchType, patchType) + } +} + +func newTestCustomResourceData(metadata map[string]string, spec map[string]interface{}) *unstructured.Unstructured { + if metadata == nil { + metadata = make(map[string]string) + } + if _, ok := metadata["name"]; !ok { + metadata["name"] = "test-obj" + } + if _, ok := metadata["namespace"]; !ok { + metadata["namespace"] = "default" + } + o := map[string]interface{}{ + "apiVersion": "crd.com/v1", + "kind": "Data", + "metadata": metadata, + } + if len(spec) > 0 { + o["spec"] = spec + } + return &unstructured.Unstructured{ + Object: o, + } +} + +func TestCreatePatchCustomResourceMetadata(t *testing.T) { + target := newTestCustomResourceData(map[string]string{ + "meta.helm.sh/release-name": "foo-simple", + "meta.helm.sh/release-namespace": "default", + "objectset.rio.cattle.io/id": "default-foo-simple", + }, nil) + testCase := createPatchTestCase{ + name: "take ownership of resource", + target: target, + current: target, + actual: newTestCustomResourceData(nil, map[string]interface{}{ + "color": "red", + }), + threeWayMergeForUnstructured: true, + expectedPatch: `{"metadata":{"meta.helm.sh/release-name":"foo-simple","meta.helm.sh/release-namespace":"default","objectset.rio.cattle.io/id":"default-foo-simple"}}`, + expectedPatchType: types.MergePatchType, + } + t.Run(testCase.name, testCase.run) + + // Previous behavior. + testCase.threeWayMergeForUnstructured = false + testCase.expectedPatch = `{}` + t.Run(testCase.name, testCase.run) +} + +func TestCreatePatchCustomResourceSpec(t *testing.T) { + target := newTestCustomResourceData(nil, map[string]interface{}{ + "color": "red", + "size": "large", + }) + testCase := createPatchTestCase{ + name: "merge with spec of existing custom resource", + target: target, + current: target, + actual: newTestCustomResourceData(nil, map[string]interface{}{ + "color": "red", + "weight": "heavy", + }), + threeWayMergeForUnstructured: true, + expectedPatch: `{"spec":{"size":"large"}}`, + expectedPatchType: types.MergePatchType, + } + t.Run(testCase.name, testCase.run) + + // Previous behavior. + testCase.threeWayMergeForUnstructured = false + testCase.expectedPatch = `{}` + t.Run(testCase.name, testCase.run) +} diff --git a/pkg/kube/converter.go b/pkg/kube/converter.go index 3bf0e358c..ac6d95fb4 100644 --- a/pkg/kube/converter.go +++ b/pkg/kube/converter.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package kube // import "helm.sh/helm/v3/pkg/kube" +package kube // import "helm.sh/helm/v4/pkg/kube" import ( "sync" diff --git a/pkg/kube/factory.go b/pkg/kube/factory.go index f19d62dc3..1d237c307 100644 --- a/pkg/kube/factory.go +++ b/pkg/kube/factory.go @@ -14,12 +14,13 @@ See the License for the specific language governing permissions and limitations under the License. */ -package kube // import "helm.sh/helm/v3/pkg/kube" +package kube // import "helm.sh/helm/v4/pkg/kube" import ( "k8s.io/cli-runtime/pkg/resource" "k8s.io/client-go/dynamic" "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" "k8s.io/kubectl/pkg/validation" ) @@ -33,6 +34,9 @@ import ( // Helm does not need are not impacted or exposed. This minimizes the impact of Kubernetes changes // being exposed. type Factory interface { + // ToRESTConfig returns restconfig + ToRESTConfig() (*rest.Config, error) + // ToRawKubeConfigLoader return kubeconfig loader as-is ToRawKubeConfigLoader() clientcmd.ClientConfig diff --git a/pkg/kube/fake/fake.go b/pkg/kube/fake/fake.go index 267020d57..a543a0f73 100644 --- a/pkg/kube/fake/fake.go +++ b/pkg/kube/fake/fake.go @@ -21,12 +21,11 @@ import ( "io" "time" - v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/cli-runtime/pkg/resource" - "helm.sh/helm/v3/pkg/kube" + "helm.sh/helm/v4/pkg/kube" ) // FailingKubeClient implements KubeClient for testing purposes. It also has @@ -34,19 +33,30 @@ import ( // delegates all its calls to `PrintingKubeClient` type FailingKubeClient struct { PrintingKubeClient - CreateError error - GetError error - WaitError error - DeleteError error - DeleteWithPropagationError error - WatchUntilReadyError error - UpdateError error - BuildError error - BuildTableError error - BuildDummy bool - BuildUnstructuredError error - WaitAndGetCompletedPodPhaseError error - WaitDuration time.Duration + CreateError error + GetError error + DeleteError error + DeleteWithPropagationError error + UpdateError error + BuildError error + BuildTableError error + BuildDummy bool + DummyResources kube.ResourceList + BuildUnstructuredError error + WaitError error + WaitForDeleteError error + WatchUntilReadyError error + WaitDuration time.Duration +} + +// FailingKubeWaiter implements kube.Waiter for testing purposes. +// It also has additional errors you can set to fail different functions, otherwise it delegates all its calls to `PrintingKubeWaiter` +type FailingKubeWaiter struct { + *PrintingKubeWaiter + waitError error + waitForDeleteError error + watchUntilReadyError error + waitDuration time.Duration } // Create returns the configured error if set or prints @@ -66,28 +76,28 @@ func (f *FailingKubeClient) Get(resources kube.ResourceList, related bool) (map[ } // Waits the amount of time defined on f.WaitDuration, then returns the configured error if set or prints. -func (f *FailingKubeClient) Wait(resources kube.ResourceList, d time.Duration) error { - time.Sleep(f.WaitDuration) - if f.WaitError != nil { - return f.WaitError +func (f *FailingKubeWaiter) Wait(resources kube.ResourceList, d time.Duration) error { + time.Sleep(f.waitDuration) + if f.waitError != nil { + return f.waitError } - return f.PrintingKubeClient.Wait(resources, d) + return f.PrintingKubeWaiter.Wait(resources, d) } // WaitWithJobs returns the configured error if set or prints -func (f *FailingKubeClient) WaitWithJobs(resources kube.ResourceList, d time.Duration) error { - if f.WaitError != nil { - return f.WaitError +func (f *FailingKubeWaiter) WaitWithJobs(resources kube.ResourceList, d time.Duration) error { + if f.waitError != nil { + return f.waitError } - return f.PrintingKubeClient.WaitWithJobs(resources, d) + return f.PrintingKubeWaiter.WaitWithJobs(resources, d) } // WaitForDelete returns the configured error if set or prints -func (f *FailingKubeClient) WaitForDelete(resources kube.ResourceList, d time.Duration) error { - if f.WaitError != nil { - return f.WaitError +func (f *FailingKubeWaiter) WaitForDelete(resources kube.ResourceList, d time.Duration) error { + if f.waitForDeleteError != nil { + return f.waitForDeleteError } - return f.PrintingKubeClient.WaitForDelete(resources, d) + return f.PrintingKubeWaiter.WaitForDelete(resources, d) } // Delete returns the configured error if set or prints @@ -99,11 +109,11 @@ func (f *FailingKubeClient) Delete(resources kube.ResourceList) (*kube.Result, [ } // WatchUntilReady returns the configured error if set or prints -func (f *FailingKubeClient) WatchUntilReady(resources kube.ResourceList, d time.Duration) error { - if f.WatchUntilReadyError != nil { - return f.WatchUntilReadyError +func (f *FailingKubeWaiter) WatchUntilReady(resources kube.ResourceList, d time.Duration) error { + if f.watchUntilReadyError != nil { + return f.watchUntilReadyError } - return f.PrintingKubeClient.WatchUntilReady(resources, d) + return f.PrintingKubeWaiter.WatchUntilReady(resources, d) } // Update returns the configured error if set or prints @@ -114,11 +124,22 @@ func (f *FailingKubeClient) Update(r, modified kube.ResourceList, ignoreMe bool) return f.PrintingKubeClient.Update(r, modified, ignoreMe) } +// Update returns the configured error if set or prints +func (f *FailingKubeClient) UpdateThreeWayMerge(r, modified kube.ResourceList, ignoreMe bool) (*kube.Result, error) { + if f.UpdateError != nil { + return &kube.Result{}, f.UpdateError + } + return f.PrintingKubeClient.Update(r, modified, ignoreMe) +} + // Build returns the configured error if set or prints func (f *FailingKubeClient) Build(r io.Reader, _ bool) (kube.ResourceList, error) { if f.BuildError != nil { return []*resource.Info{}, f.BuildError } + if f.DummyResources != nil { + return f.DummyResources, nil + } if f.BuildDummy { return createDummyResourceList(), nil } @@ -133,14 +154,6 @@ func (f *FailingKubeClient) BuildTable(r io.Reader, _ bool) (kube.ResourceList, return f.PrintingKubeClient.BuildTable(r, false) } -// WaitAndGetCompletedPodPhase returns the configured error if set or prints -func (f *FailingKubeClient) WaitAndGetCompletedPodPhase(s string, d time.Duration) (v1.PodPhase, error) { - if f.WaitAndGetCompletedPodPhaseError != nil { - return v1.PodSucceeded, f.WaitAndGetCompletedPodPhaseError - } - return f.PrintingKubeClient.WaitAndGetCompletedPodPhase(s, d) -} - // DeleteWithPropagationPolicy returns the configured error if set or prints func (f *FailingKubeClient) DeleteWithPropagationPolicy(resources kube.ResourceList, policy metav1.DeletionPropagation) (*kube.Result, []error) { if f.DeleteWithPropagationError != nil { @@ -149,6 +162,18 @@ func (f *FailingKubeClient) DeleteWithPropagationPolicy(resources kube.ResourceL return f.PrintingKubeClient.DeleteWithPropagationPolicy(resources, policy) } +func (f *FailingKubeClient) GetWaiter(ws kube.WaitStrategy) (kube.Waiter, error) { + waiter, _ := f.PrintingKubeClient.GetWaiter(ws) + printingKubeWaiter, _ := waiter.(*PrintingKubeWaiter) + return &FailingKubeWaiter{ + PrintingKubeWaiter: printingKubeWaiter, + waitError: f.WaitError, + waitForDeleteError: f.WaitForDeleteError, + watchUntilReadyError: f.WatchUntilReadyError, + waitDuration: f.WaitDuration, + }, nil +} + func createDummyResourceList() kube.ResourceList { var resInfo resource.Info resInfo.Name = "dummyName" @@ -156,5 +181,4 @@ func createDummyResourceList() kube.ResourceList { var resourceList kube.ResourceList resourceList.Append(&resInfo) return resourceList - } diff --git a/pkg/kube/fake/printer.go b/pkg/kube/fake/printer.go index cc2c84b40..f6659a904 100644 --- a/pkg/kube/fake/printer.go +++ b/pkg/kube/fake/printer.go @@ -17,6 +17,7 @@ limitations under the License. package fake import ( + "fmt" "io" "strings" "time" @@ -26,13 +27,20 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/cli-runtime/pkg/resource" - "helm.sh/helm/v3/pkg/kube" + "helm.sh/helm/v4/pkg/kube" ) // PrintingKubeClient implements KubeClient, but simply prints the reader to // the given output. type PrintingKubeClient struct { - Out io.Writer + Out io.Writer + LogOutput io.Writer +} + +// PrintingKubeWaiter implements kube.Waiter, but simply prints the reader to the given output +type PrintingKubeWaiter struct { + Out io.Writer + LogOutput io.Writer } // IsReachable checks if the cluster is reachable @@ -57,17 +65,23 @@ func (p *PrintingKubeClient) Get(resources kube.ResourceList, _ bool) (map[strin return make(map[string][]runtime.Object), nil } -func (p *PrintingKubeClient) Wait(resources kube.ResourceList, _ time.Duration) error { +func (p *PrintingKubeWaiter) Wait(resources kube.ResourceList, _ time.Duration) error { + _, err := io.Copy(p.Out, bufferize(resources)) + return err +} + +func (p *PrintingKubeWaiter) WaitWithJobs(resources kube.ResourceList, _ time.Duration) error { _, err := io.Copy(p.Out, bufferize(resources)) return err } -func (p *PrintingKubeClient) WaitWithJobs(resources kube.ResourceList, _ time.Duration) error { +func (p *PrintingKubeWaiter) WaitForDelete(resources kube.ResourceList, _ time.Duration) error { _, err := io.Copy(p.Out, bufferize(resources)) return err } -func (p *PrintingKubeClient) WaitForDelete(resources kube.ResourceList, _ time.Duration) error { +// WatchUntilReady implements KubeClient WatchUntilReady. +func (p *PrintingKubeWaiter) WatchUntilReady(resources kube.ResourceList, _ time.Duration) error { _, err := io.Copy(p.Out, bufferize(resources)) return err } @@ -83,12 +97,6 @@ func (p *PrintingKubeClient) Delete(resources kube.ResourceList) (*kube.Result, return &kube.Result{Deleted: resources}, nil } -// WatchUntilReady implements KubeClient WatchUntilReady. -func (p *PrintingKubeClient) WatchUntilReady(resources kube.ResourceList, _ time.Duration) error { - _, err := io.Copy(p.Out, bufferize(resources)) - return err -} - // Update implements KubeClient Update. func (p *PrintingKubeClient) Update(_, modified kube.ResourceList, _ bool) (*kube.Result, error) { _, err := io.Copy(p.Out, bufferize(modified)) @@ -116,6 +124,17 @@ func (p *PrintingKubeClient) WaitAndGetCompletedPodPhase(_ string, _ time.Durati return v1.PodSucceeded, nil } +// GetPodList implements KubeClient GetPodList. +func (p *PrintingKubeClient) GetPodList(_ string, _ metav1.ListOptions) (*v1.PodList, error) { + return &v1.PodList{}, nil +} + +// OutputContainerLogsForPodList implements KubeClient OutputContainerLogsForPodList. +func (p *PrintingKubeClient) OutputContainerLogsForPodList(_ *v1.PodList, someNamespace string, _ func(namespace, pod, container string) io.Writer) error { + _, err := io.Copy(p.LogOutput, strings.NewReader(fmt.Sprintf("attempted to output logs for namespace: %s", someNamespace))) + return err +} + // DeleteWithPropagationPolicy implements KubeClient delete. // // It only prints out the content to be deleted. @@ -127,6 +146,10 @@ func (p *PrintingKubeClient) DeleteWithPropagationPolicy(resources kube.Resource return &kube.Result{Deleted: resources}, nil } +func (p *PrintingKubeClient) GetWaiter(_ kube.WaitStrategy) (kube.Waiter, error) { + return &PrintingKubeWaiter{Out: p.Out, LogOutput: p.LogOutput}, nil +} + func bufferize(resources kube.ResourceList) io.Reader { var builder strings.Builder for _, info := range resources { diff --git a/pkg/kube/interface.go b/pkg/kube/interface.go index ce42ed950..6b945088e 100644 --- a/pkg/kube/interface.go +++ b/pkg/kube/interface.go @@ -32,26 +32,9 @@ type Interface interface { // Create creates one or more resources. Create(resources ResourceList) (*Result, error) - // Wait waits up to the given timeout for the specified resources to be ready. - Wait(resources ResourceList, timeout time.Duration) error - - // WaitWithJobs wait up to the given timeout for the specified resources to be ready, including jobs. - WaitWithJobs(resources ResourceList, timeout time.Duration) error - // Delete destroys one or more resources. Delete(resources ResourceList) (*Result, []error) - // WatchUntilReady watches the resources given and waits until it is ready. - // - // This method is mainly for hook implementations. It watches for a resource to - // hit a particular milestone. The milestone depends on the Kind. - // - // For Jobs, "ready" means the Job ran to completion (exited without error). - // For Pods, "ready" means the Pod phase is marked "succeeded". - // For all other kinds, it means the kind was created or modified without - // error. - WatchUntilReady(resources ResourceList, timeout time.Duration) error - // Update updates one or more resources or creates the resource // if it doesn't exist. Update(original, target ResourceList, force bool) (*Result, error) @@ -63,28 +46,59 @@ type Interface interface { // // Validates against OpenAPI schema if validate is true. Build(reader io.Reader, validate bool) (ResourceList, error) - - // WaitAndGetCompletedPodPhase waits up to a timeout until a pod enters a completed phase - // and returns said phase (PodSucceeded or PodFailed qualify). - WaitAndGetCompletedPodPhase(name string, timeout time.Duration) (v1.PodPhase, error) - // IsReachable checks whether the client is able to connect to the cluster. IsReachable() error + + // Get Waiter gets the Kube.Waiter + GetWaiter(ws WaitStrategy) (Waiter, error) } -// InterfaceExt is introduced to avoid breaking backwards compatibility for Interface implementers. +// InterfaceThreeWayMerge was introduced to avoid breaking backwards compatibility for Interface implementers. // -// TODO Helm 4: Remove InterfaceExt and integrate its method(s) into the Interface. -type InterfaceExt interface { +// TODO Helm 4: Remove InterfaceThreeWayMerge and integrate its method(s) into the Interface. +type InterfaceThreeWayMerge interface { + UpdateThreeWayMerge(original, target ResourceList, force bool) (*Result, error) +} + +// Waiter defines methods related to waiting for resource states. +type Waiter interface { + // Wait waits up to the given timeout for the specified resources to be ready. + Wait(resources ResourceList, timeout time.Duration) error + + // WaitWithJobs wait up to the given timeout for the specified resources to be ready, including jobs. + WaitWithJobs(resources ResourceList, timeout time.Duration) error + // WaitForDelete wait up to the given timeout for the specified resources to be deleted. WaitForDelete(resources ResourceList, timeout time.Duration) error + + // WatchUntilReady watches the resources given and waits until it is ready. + // + // This method is mainly for hook implementations. It watches for a resource to + // hit a particular milestone. The milestone depends on the Kind. + // + // For Jobs, "ready" means the Job ran to completion (exited without error). + // For Pods, "ready" means the Pod phase is marked "succeeded". + // For all other kinds, it means the kind was created or modified without + // error. + WatchUntilReady(resources ResourceList, timeout time.Duration) error +} + +// InterfaceLogs was introduced to avoid breaking backwards compatibility for Interface implementers. +// +// TODO Helm 4: Remove InterfaceLogs and integrate its method(s) into the Interface. +type InterfaceLogs interface { + // GetPodList list all pods that match the specified listOptions + GetPodList(namespace string, listOptions metav1.ListOptions) (*v1.PodList, error) + + // OutputContainerLogsForPodList output the logs for a pod list + OutputContainerLogsForPodList(podList *v1.PodList, namespace string, writerFunc func(namespace, pod, container string) io.Writer) error } // InterfaceDeletionPropagation is introduced to avoid breaking backwards compatibility for Interface implementers. // // TODO Helm 4: Remove InterfaceDeletionPropagation and integrate its method(s) into the Interface. type InterfaceDeletionPropagation interface { - // Delete destroys one or more resources. The deletion propagation is handled as per the given deletion propagation value. + // DeleteWithPropagationPolicy destroys one or more resources. The deletion propagation is handled as per the given deletion propagation value. DeleteWithPropagationPolicy(resources ResourceList, policy metav1.DeletionPropagation) (*Result, []error) } @@ -111,6 +125,7 @@ type InterfaceResources interface { } var _ Interface = (*Client)(nil) -var _ InterfaceExt = (*Client)(nil) +var _ InterfaceThreeWayMerge = (*Client)(nil) +var _ InterfaceLogs = (*Client)(nil) var _ InterfaceDeletionPropagation = (*Client)(nil) var _ InterfaceResources = (*Client)(nil) diff --git a/pkg/kube/ready.go b/pkg/kube/ready.go index 55c4a39bf..7a06c72f9 100644 --- a/pkg/kube/ready.go +++ b/pkg/kube/ready.go @@ -14,18 +14,16 @@ See the License for the specific language governing permissions and limitations under the License. */ -package kube // import "helm.sh/helm/v3/pkg/kube" +package kube // import "helm.sh/helm/v4/pkg/kube" import ( "context" "fmt" + "log/slog" appsv1 "k8s.io/api/apps/v1" - appsv1beta1 "k8s.io/api/apps/v1beta1" - appsv1beta2 "k8s.io/api/apps/v1beta2" batchv1 "k8s.io/api/batch/v1" corev1 "k8s.io/api/core/v1" - extensionsv1beta1 "k8s.io/api/extensions/v1beta1" apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" apiextv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -35,7 +33,7 @@ import ( "k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes/scheme" - deploymentutil "helm.sh/helm/v3/internal/third_party/k8s.io/kubernetes/deployment/util" + deploymentutil "helm.sh/helm/v4/internal/third_party/k8s.io/kubernetes/deployment/util" ) // ReadyCheckerOption is a function that configures a ReadyChecker. @@ -60,13 +58,9 @@ func CheckJobs(checkJobs bool) ReadyCheckerOption { // NewReadyChecker creates a new checker. Passed ReadyCheckerOptions can // be used to override defaults. -func NewReadyChecker(cl kubernetes.Interface, log func(string, ...interface{}), opts ...ReadyCheckerOption) ReadyChecker { +func NewReadyChecker(cl kubernetes.Interface, opts ...ReadyCheckerOption) ReadyChecker { c := ReadyChecker{ client: cl, - log: log, - } - if c.log == nil { - c.log = nopLogger } for _, opt := range opts { opt(&c) @@ -77,7 +71,6 @@ func NewReadyChecker(cl kubernetes.Interface, log func(string, ...interface{}), // ReadyChecker is a type that can check core Kubernetes types for readiness. type ReadyChecker struct { client kubernetes.Interface - log func(string, ...interface{}) checkJobs bool pausedAsReady bool } @@ -105,7 +98,7 @@ func (c *ReadyChecker) IsReady(ctx context.Context, v *resource.Info) (bool, err ready, err := c.jobReady(job) return ready, err } - case *appsv1.Deployment, *appsv1beta1.Deployment, *appsv1beta2.Deployment, *extensionsv1beta1.Deployment: + case *appsv1.Deployment: currentDeployment, err := c.client.AppsV1().Deployments(v.Namespace).Get(ctx, v.Name, metav1.GetOptions{}) if err != nil { return false, err @@ -138,7 +131,7 @@ func (c *ReadyChecker) IsReady(ctx context.Context, v *resource.Info) (bool, err if !c.serviceReady(svc) { return false, nil } - case *extensionsv1beta1.DaemonSet, *appsv1.DaemonSet, *appsv1beta2.DaemonSet: + case *appsv1.DaemonSet: ds, err := c.client.AppsV1().DaemonSets(v.Namespace).Get(ctx, v.Name, metav1.GetOptions{}) if err != nil { return false, err @@ -168,7 +161,7 @@ func (c *ReadyChecker) IsReady(ctx context.Context, v *resource.Info) (bool, err if !c.crdReady(*crd) { return false, nil } - case *appsv1.StatefulSet, *appsv1beta1.StatefulSet, *appsv1beta2.StatefulSet: + case *appsv1.StatefulSet: sts, err := c.client.AppsV1().StatefulSets(v.Namespace).Get(ctx, v.Name, metav1.GetOptions{}) if err != nil { return false, err @@ -188,7 +181,7 @@ func (c *ReadyChecker) IsReady(ctx context.Context, v *resource.Info) (bool, err if !ready || err != nil { return false, err } - case *extensionsv1beta1.ReplicaSet, *appsv1beta2.ReplicaSet, *appsv1.ReplicaSet: + case *appsv1.ReplicaSet: rs, err := c.client.AppsV1().ReplicaSets(v.Namespace).Get(ctx, v.Name, metav1.GetOptions{}) if err != nil { return false, err @@ -233,20 +226,21 @@ func (c *ReadyChecker) isPodReady(pod *corev1.Pod) bool { return true } } - c.log("Pod is not ready: %s/%s", pod.GetNamespace(), pod.GetName()) + slog.Debug("Pod is not ready", "namespace", pod.GetNamespace(), "name", pod.GetName()) return false } func (c *ReadyChecker) jobReady(job *batchv1.Job) (bool, error) { if job.Status.Failed > *job.Spec.BackoffLimit { - c.log("Job is failed: %s/%s", job.GetNamespace(), job.GetName()) + slog.Debug("Job is failed", "namespace", job.GetNamespace(), "name", job.GetName()) // If a job is failed, it can't recover, so throw an error return false, fmt.Errorf("job is failed: %s/%s", job.GetNamespace(), job.GetName()) } if job.Spec.Completions != nil && job.Status.Succeeded < *job.Spec.Completions { - c.log("Job is not completed: %s/%s", job.GetNamespace(), job.GetName()) + slog.Debug("Job is not completed", "namespace", job.GetNamespace(), "name", job.GetName()) return false, nil } + slog.Debug("Job is completed", "namespace", job.GetNamespace(), "name", job.GetName()) return true, nil } @@ -258,7 +252,7 @@ func (c *ReadyChecker) serviceReady(s *corev1.Service) bool { // Ensure that the service cluster IP is not empty if s.Spec.ClusterIP == "" { - c.log("Service does not have cluster IP address: %s/%s", s.GetNamespace(), s.GetName()) + slog.Debug("Service does not have cluster IP address", "namespace", s.GetNamespace(), "name", s.GetName()) return false } @@ -266,24 +260,25 @@ func (c *ReadyChecker) serviceReady(s *corev1.Service) bool { if s.Spec.Type == corev1.ServiceTypeLoadBalancer { // do not wait when at least 1 external IP is set if len(s.Spec.ExternalIPs) > 0 { - c.log("Service %s/%s has external IP addresses (%v), marking as ready", s.GetNamespace(), s.GetName(), s.Spec.ExternalIPs) + slog.Debug("Service has external IP addresses", "namespace", s.GetNamespace(), "name", s.GetName(), "externalIPs", s.Spec.ExternalIPs) return true } if s.Status.LoadBalancer.Ingress == nil { - c.log("Service does not have load balancer ingress IP address: %s/%s", s.GetNamespace(), s.GetName()) + slog.Debug("Service does not have load balancer ingress IP address", "namespace", s.GetNamespace(), "name", s.GetName()) return false } } - + slog.Debug("Service is ready", "namespace", s.GetNamespace(), "name", s.GetName(), "clusterIP", s.Spec.ClusterIP, "externalIPs", s.Spec.ExternalIPs) return true } func (c *ReadyChecker) volumeReady(v *corev1.PersistentVolumeClaim) bool { if v.Status.Phase != corev1.ClaimBound { - c.log("PersistentVolumeClaim is not bound: %s/%s", v.GetNamespace(), v.GetName()) + slog.Debug("PersistentVolumeClaim is not bound", "namespace", v.GetNamespace(), "name", v.GetName()) return false } + slog.Debug("PersistentVolumeClaim is bound", "namespace", v.GetNamespace(), "name", v.GetName(), "phase", v.Status.Phase) return true } @@ -293,23 +288,24 @@ func (c *ReadyChecker) deploymentReady(rs *appsv1.ReplicaSet, dep *appsv1.Deploy return false } // Verify the generation observed by the deployment controller matches the spec generation - if dep.Status.ObservedGeneration != dep.ObjectMeta.Generation { - c.log("Deployment is not ready: %s/%s. observedGeneration (%d) does not match spec generation (%d).", dep.Namespace, dep.Name, dep.Status.ObservedGeneration, dep.ObjectMeta.Generation) + if dep.Status.ObservedGeneration != dep.Generation { + slog.Debug("Deployment is not ready, observedGeneration does not match spec generation", "namespace", dep.GetNamespace(), "name", dep.GetName(), "actualGeneration", dep.Status.ObservedGeneration, "expectedGeneration", dep.Generation) return false } expectedReady := *dep.Spec.Replicas - deploymentutil.MaxUnavailable(*dep) - if !(rs.Status.ReadyReplicas >= expectedReady) { - c.log("Deployment is not ready: %s/%s. %d out of %d expected pods are ready", dep.Namespace, dep.Name, rs.Status.ReadyReplicas, expectedReady) + if rs.Status.ReadyReplicas < expectedReady { + slog.Debug("Deployment does not have enough pods ready", "namespace", dep.GetNamespace(), "name", dep.GetName(), "readyPods", rs.Status.ReadyReplicas, "totalPods", expectedReady) return false } + slog.Debug("Deployment is ready", "namespace", dep.GetNamespace(), "name", dep.GetName(), "readyPods", rs.Status.ReadyReplicas, "totalPods", expectedReady) return true } func (c *ReadyChecker) daemonSetReady(ds *appsv1.DaemonSet) bool { // Verify the generation observed by the daemonSet controller matches the spec generation - if ds.Status.ObservedGeneration != ds.ObjectMeta.Generation { - c.log("DaemonSet is not ready: %s/%s. observedGeneration (%d) does not match spec generation (%d).", ds.Namespace, ds.Name, ds.Status.ObservedGeneration, ds.ObjectMeta.Generation) + if ds.Status.ObservedGeneration != ds.Generation { + slog.Debug("DaemonSet is not ready, observedGeneration does not match spec generation", "namespace", ds.GetNamespace(), "name", ds.GetName(), "observedGeneration", ds.Status.ObservedGeneration, "expectedGeneration", ds.Generation) return false } @@ -320,7 +316,7 @@ func (c *ReadyChecker) daemonSetReady(ds *appsv1.DaemonSet) bool { // Make sure all the updated pods have been scheduled if ds.Status.UpdatedNumberScheduled != ds.Status.DesiredNumberScheduled { - c.log("DaemonSet is not ready: %s/%s. %d out of %d expected pods have been scheduled", ds.Namespace, ds.Name, ds.Status.UpdatedNumberScheduled, ds.Status.DesiredNumberScheduled) + slog.Debug("DaemonSet does not have enough Pods scheduled", "namespace", ds.GetNamespace(), "name", ds.GetName(), "scheduledPods", ds.Status.UpdatedNumberScheduled, "totalPods", ds.Status.DesiredNumberScheduled) return false } maxUnavailable, err := intstr.GetScaledValueFromIntOrPercent(ds.Spec.UpdateStrategy.RollingUpdate.MaxUnavailable, int(ds.Status.DesiredNumberScheduled), true) @@ -332,10 +328,11 @@ func (c *ReadyChecker) daemonSetReady(ds *appsv1.DaemonSet) bool { } expectedReady := int(ds.Status.DesiredNumberScheduled) - maxUnavailable - if !(int(ds.Status.NumberReady) >= expectedReady) { - c.log("DaemonSet is not ready: %s/%s. %d out of %d expected pods are ready", ds.Namespace, ds.Name, ds.Status.NumberReady, expectedReady) + if int(ds.Status.NumberReady) < expectedReady { + slog.Debug("DaemonSet does not have enough Pods ready", "namespace", ds.GetNamespace(), "name", ds.GetName(), "readyPods", ds.Status.NumberReady, "totalPods", expectedReady) return false } + slog.Debug("DaemonSet is ready", "namespace", ds.GetNamespace(), "name", ds.GetName(), "readyPods", ds.Status.NumberReady, "totalPods", expectedReady) return true } @@ -384,14 +381,14 @@ func (c *ReadyChecker) crdReady(crd apiextv1.CustomResourceDefinition) bool { func (c *ReadyChecker) statefulSetReady(sts *appsv1.StatefulSet) bool { // Verify the generation observed by the statefulSet controller matches the spec generation - if sts.Status.ObservedGeneration != sts.ObjectMeta.Generation { - c.log("StatefulSet is not ready: %s/%s. observedGeneration (%d) does not match spec generation (%d).", sts.Namespace, sts.Name, sts.Status.ObservedGeneration, sts.ObjectMeta.Generation) + if sts.Status.ObservedGeneration != sts.Generation { + slog.Debug("StatefulSet is not ready, observedGeneration doest not match spec generation", "namespace", sts.GetNamespace(), "name", sts.GetName(), "actualGeneration", sts.Status.ObservedGeneration, "expectedGeneration", sts.Generation) return false } // If the update strategy is not a rolling update, there will be nothing to wait for if sts.Spec.UpdateStrategy.Type != appsv1.RollingUpdateStatefulSetStrategyType { - c.log("StatefulSet skipped ready check: %s/%s. updateStrategy is %v", sts.Namespace, sts.Name, sts.Spec.UpdateStrategy.Type) + slog.Debug("StatefulSet skipped ready check", "namespace", sts.GetNamespace(), "name", sts.GetName(), "updateStrategy", sts.Spec.UpdateStrategy.Type) return true } @@ -417,30 +414,29 @@ func (c *ReadyChecker) statefulSetReady(sts *appsv1.StatefulSet) bool { // Make sure all the updated pods have been scheduled if int(sts.Status.UpdatedReplicas) < expectedReplicas { - c.log("StatefulSet is not ready: %s/%s. %d out of %d expected pods have been scheduled", sts.Namespace, sts.Name, sts.Status.UpdatedReplicas, expectedReplicas) + slog.Debug("StatefulSet does not have enough Pods scheduled", "namespace", sts.GetNamespace(), "name", sts.GetName(), "readyPods", sts.Status.UpdatedReplicas, "totalPods", expectedReplicas) return false } if int(sts.Status.ReadyReplicas) != replicas { - c.log("StatefulSet is not ready: %s/%s. %d out of %d expected pods are ready", sts.Namespace, sts.Name, sts.Status.ReadyReplicas, replicas) + slog.Debug("StatefulSet does not have enough Pods ready", "namespace", sts.GetNamespace(), "name", sts.GetName(), "readyPods", sts.Status.ReadyReplicas, "totalPods", replicas) return false } // This check only makes sense when all partitions are being upgraded otherwise during a // partitioned rolling upgrade, this condition will never evaluate to true, leading to // error. if partition == 0 && sts.Status.CurrentRevision != sts.Status.UpdateRevision { - c.log("StatefulSet is not ready: %s/%s. currentRevision %s does not yet match updateRevision %s", sts.Namespace, sts.Name, sts.Status.CurrentRevision, sts.Status.UpdateRevision) + slog.Debug("StatefulSet is not ready, currentRevision does not match updateRevision", "namespace", sts.GetNamespace(), "name", sts.GetName(), "currentRevision", sts.Status.CurrentRevision, "updateRevision", sts.Status.UpdateRevision) return false } - - c.log("StatefulSet is ready: %s/%s. %d out of %d expected pods are ready", sts.Namespace, sts.Name, sts.Status.ReadyReplicas, replicas) + slog.Debug("StatefulSet is ready", "namespace", sts.GetNamespace(), "name", sts.GetName(), "readyPods", sts.Status.ReadyReplicas, "totalPods", replicas) return true } func (c *ReadyChecker) replicationControllerReady(rc *corev1.ReplicationController) bool { // Verify the generation observed by the replicationController controller matches the spec generation - if rc.Status.ObservedGeneration != rc.ObjectMeta.Generation { - c.log("ReplicationController is not ready: %s/%s. observedGeneration (%d) does not match spec generation (%d).", rc.Namespace, rc.Name, rc.Status.ObservedGeneration, rc.ObjectMeta.Generation) + if rc.Status.ObservedGeneration != rc.Generation { + slog.Debug("ReplicationController is not ready, observedGeneration doest not match spec generation", "namespace", rc.GetNamespace(), "name", rc.GetName(), "actualGeneration", rc.Status.ObservedGeneration, "expectedGeneration", rc.Generation) return false } return true @@ -448,8 +444,8 @@ func (c *ReadyChecker) replicationControllerReady(rc *corev1.ReplicationControll func (c *ReadyChecker) replicaSetReady(rs *appsv1.ReplicaSet) bool { // Verify the generation observed by the replicaSet controller matches the spec generation - if rs.Status.ObservedGeneration != rs.ObjectMeta.Generation { - c.log("ReplicaSet is not ready: %s/%s. observedGeneration (%d) does not match spec generation (%d).", rs.Namespace, rs.Name, rs.Status.ObservedGeneration, rs.ObjectMeta.Generation) + if rs.Status.ObservedGeneration != rs.Generation { + slog.Debug("ReplicaSet is not ready, observedGeneration doest not match spec generation", "namespace", rs.GetNamespace(), "name", rs.GetName(), "actualGeneration", rs.Status.ObservedGeneration, "expectedGeneration", rs.Generation) return false } return true diff --git a/pkg/kube/ready_test.go b/pkg/kube/ready_test.go index 14bf8588b..9d1dfd272 100644 --- a/pkg/kube/ready_test.go +++ b/pkg/kube/ready_test.go @@ -13,17 +13,15 @@ See the License for the specific language governing permissions and limitations under the License. */ -package kube // import "helm.sh/helm/v3/pkg/kube" +package kube // import "helm.sh/helm/v4/pkg/kube" import ( "context" "testing" appsv1 "k8s.io/api/apps/v1" - appsv1beta1 "k8s.io/api/apps/v1beta1" batchv1 "k8s.io/api/batch/v1" corev1 "k8s.io/api/core/v1" - extensionsv1beta1 "k8s.io/api/extensions/v1beta1" apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" apiextv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -39,7 +37,6 @@ const defaultNamespace = metav1.NamespaceDefault func Test_ReadyChecker_IsReady_Pod(t *testing.T) { type fields struct { client kubernetes.Interface - log func(string, ...interface{}) checkJobs bool pausedAsReady bool } @@ -58,8 +55,7 @@ func Test_ReadyChecker_IsReady_Pod(t *testing.T) { { name: "IsReady Pod", fields: fields{ - client: fake.NewSimpleClientset(), - log: func(string, ...interface{}) {}, + client: fake.NewClientset(), checkJobs: true, pausedAsReady: false, }, @@ -74,8 +70,7 @@ func Test_ReadyChecker_IsReady_Pod(t *testing.T) { { name: "IsReady Pod returns error", fields: fields{ - client: fake.NewSimpleClientset(), - log: func(string, ...interface{}) {}, + client: fake.NewClientset(), checkJobs: true, pausedAsReady: false, }, @@ -92,7 +87,6 @@ func Test_ReadyChecker_IsReady_Pod(t *testing.T) { t.Run(tt.name, func(t *testing.T) { c := &ReadyChecker{ client: tt.fields.client, - log: tt.fields.log, checkJobs: tt.fields.checkJobs, pausedAsReady: tt.fields.pausedAsReady, } @@ -115,7 +109,6 @@ func Test_ReadyChecker_IsReady_Pod(t *testing.T) { func Test_ReadyChecker_IsReady_Job(t *testing.T) { type fields struct { client kubernetes.Interface - log func(string, ...interface{}) checkJobs bool pausedAsReady bool } @@ -134,8 +127,7 @@ func Test_ReadyChecker_IsReady_Job(t *testing.T) { { name: "IsReady Job error while getting job", fields: fields{ - client: fake.NewSimpleClientset(), - log: func(string, ...interface{}) {}, + client: fake.NewClientset(), checkJobs: true, pausedAsReady: false, }, @@ -150,8 +142,7 @@ func Test_ReadyChecker_IsReady_Job(t *testing.T) { { name: "IsReady Job", fields: fields{ - client: fake.NewSimpleClientset(), - log: func(string, ...interface{}) {}, + client: fake.NewClientset(), checkJobs: true, pausedAsReady: false, }, @@ -168,7 +159,6 @@ func Test_ReadyChecker_IsReady_Job(t *testing.T) { t.Run(tt.name, func(t *testing.T) { c := &ReadyChecker{ client: tt.fields.client, - log: tt.fields.log, checkJobs: tt.fields.checkJobs, pausedAsReady: tt.fields.pausedAsReady, } @@ -190,7 +180,6 @@ func Test_ReadyChecker_IsReady_Job(t *testing.T) { func Test_ReadyChecker_IsReady_Deployment(t *testing.T) { type fields struct { client kubernetes.Interface - log func(string, ...interface{}) checkJobs bool pausedAsReady bool } @@ -210,8 +199,7 @@ func Test_ReadyChecker_IsReady_Deployment(t *testing.T) { { name: "IsReady Deployments error while getting current Deployment", fields: fields{ - client: fake.NewSimpleClientset(), - log: func(string, ...interface{}) {}, + client: fake.NewClientset(), checkJobs: true, pausedAsReady: false, }, @@ -227,8 +215,7 @@ func Test_ReadyChecker_IsReady_Deployment(t *testing.T) { { name: "IsReady Deployments", //TODO fix this one fields: fields{ - client: fake.NewSimpleClientset(), - log: func(string, ...interface{}) {}, + client: fake.NewClientset(), checkJobs: true, pausedAsReady: false, }, @@ -246,7 +233,6 @@ func Test_ReadyChecker_IsReady_Deployment(t *testing.T) { t.Run(tt.name, func(t *testing.T) { c := &ReadyChecker{ client: tt.fields.client, - log: tt.fields.log, checkJobs: tt.fields.checkJobs, pausedAsReady: tt.fields.pausedAsReady, } @@ -272,7 +258,6 @@ func Test_ReadyChecker_IsReady_Deployment(t *testing.T) { func Test_ReadyChecker_IsReady_PersistentVolumeClaim(t *testing.T) { type fields struct { client kubernetes.Interface - log func(string, ...interface{}) checkJobs bool pausedAsReady bool } @@ -291,8 +276,7 @@ func Test_ReadyChecker_IsReady_PersistentVolumeClaim(t *testing.T) { { name: "IsReady PersistentVolumeClaim", fields: fields{ - client: fake.NewSimpleClientset(), - log: func(string, ...interface{}) {}, + client: fake.NewClientset(), checkJobs: true, pausedAsReady: false, }, @@ -307,8 +291,7 @@ func Test_ReadyChecker_IsReady_PersistentVolumeClaim(t *testing.T) { { name: "IsReady PersistentVolumeClaim with error", fields: fields{ - client: fake.NewSimpleClientset(), - log: func(string, ...interface{}) {}, + client: fake.NewClientset(), checkJobs: true, pausedAsReady: false, }, @@ -325,7 +308,6 @@ func Test_ReadyChecker_IsReady_PersistentVolumeClaim(t *testing.T) { t.Run(tt.name, func(t *testing.T) { c := &ReadyChecker{ client: tt.fields.client, - log: tt.fields.log, checkJobs: tt.fields.checkJobs, pausedAsReady: tt.fields.pausedAsReady, } @@ -347,7 +329,6 @@ func Test_ReadyChecker_IsReady_PersistentVolumeClaim(t *testing.T) { func Test_ReadyChecker_IsReady_Service(t *testing.T) { type fields struct { client kubernetes.Interface - log func(string, ...interface{}) checkJobs bool pausedAsReady bool } @@ -366,8 +347,7 @@ func Test_ReadyChecker_IsReady_Service(t *testing.T) { { name: "IsReady Service", fields: fields{ - client: fake.NewSimpleClientset(), - log: func(string, ...interface{}) {}, + client: fake.NewClientset(), checkJobs: true, pausedAsReady: false, }, @@ -382,8 +362,7 @@ func Test_ReadyChecker_IsReady_Service(t *testing.T) { { name: "IsReady Service with error", fields: fields{ - client: fake.NewSimpleClientset(), - log: func(string, ...interface{}) {}, + client: fake.NewClientset(), checkJobs: true, pausedAsReady: false, }, @@ -400,7 +379,6 @@ func Test_ReadyChecker_IsReady_Service(t *testing.T) { t.Run(tt.name, func(t *testing.T) { c := &ReadyChecker{ client: tt.fields.client, - log: tt.fields.log, checkJobs: tt.fields.checkJobs, pausedAsReady: tt.fields.pausedAsReady, } @@ -422,7 +400,6 @@ func Test_ReadyChecker_IsReady_Service(t *testing.T) { func Test_ReadyChecker_IsReady_DaemonSet(t *testing.T) { type fields struct { client kubernetes.Interface - log func(string, ...interface{}) checkJobs bool pausedAsReady bool } @@ -441,8 +418,7 @@ func Test_ReadyChecker_IsReady_DaemonSet(t *testing.T) { { name: "IsReady DaemonSet", fields: fields{ - client: fake.NewSimpleClientset(), - log: func(string, ...interface{}) {}, + client: fake.NewClientset(), checkJobs: true, pausedAsReady: false, }, @@ -457,8 +433,7 @@ func Test_ReadyChecker_IsReady_DaemonSet(t *testing.T) { { name: "IsReady DaemonSet with error", fields: fields{ - client: fake.NewSimpleClientset(), - log: func(string, ...interface{}) {}, + client: fake.NewClientset(), checkJobs: true, pausedAsReady: false, }, @@ -475,7 +450,6 @@ func Test_ReadyChecker_IsReady_DaemonSet(t *testing.T) { t.Run(tt.name, func(t *testing.T) { c := &ReadyChecker{ client: tt.fields.client, - log: tt.fields.log, checkJobs: tt.fields.checkJobs, pausedAsReady: tt.fields.pausedAsReady, } @@ -497,7 +471,6 @@ func Test_ReadyChecker_IsReady_DaemonSet(t *testing.T) { func Test_ReadyChecker_IsReady_StatefulSet(t *testing.T) { type fields struct { client kubernetes.Interface - log func(string, ...interface{}) checkJobs bool pausedAsReady bool } @@ -516,14 +489,13 @@ func Test_ReadyChecker_IsReady_StatefulSet(t *testing.T) { { name: "IsReady StatefulSet", fields: fields{ - client: fake.NewSimpleClientset(), - log: func(string, ...interface{}) {}, + client: fake.NewClientset(), checkJobs: true, pausedAsReady: false, }, args: args{ ctx: context.TODO(), - resource: &resource.Info{Object: &appsv1beta1.StatefulSet{}, Name: "foo", Namespace: defaultNamespace}, + resource: &resource.Info{Object: &appsv1.StatefulSet{}, Name: "foo", Namespace: defaultNamespace}, }, ss: newStatefulSet("foo", 1, 0, 0, 1, true), want: false, @@ -532,14 +504,13 @@ func Test_ReadyChecker_IsReady_StatefulSet(t *testing.T) { { name: "IsReady StatefulSet with error", fields: fields{ - client: fake.NewSimpleClientset(), - log: func(string, ...interface{}) {}, + client: fake.NewClientset(), checkJobs: true, pausedAsReady: false, }, args: args{ ctx: context.TODO(), - resource: &resource.Info{Object: &appsv1beta1.StatefulSet{}, Name: "foo", Namespace: defaultNamespace}, + resource: &resource.Info{Object: &appsv1.StatefulSet{}, Name: "foo", Namespace: defaultNamespace}, }, ss: newStatefulSet("bar", 1, 0, 1, 1, true), want: false, @@ -550,7 +521,6 @@ func Test_ReadyChecker_IsReady_StatefulSet(t *testing.T) { t.Run(tt.name, func(t *testing.T) { c := &ReadyChecker{ client: tt.fields.client, - log: tt.fields.log, checkJobs: tt.fields.checkJobs, pausedAsReady: tt.fields.pausedAsReady, } @@ -572,7 +542,6 @@ func Test_ReadyChecker_IsReady_StatefulSet(t *testing.T) { func Test_ReadyChecker_IsReady_ReplicationController(t *testing.T) { type fields struct { client kubernetes.Interface - log func(string, ...interface{}) checkJobs bool pausedAsReady bool } @@ -591,8 +560,7 @@ func Test_ReadyChecker_IsReady_ReplicationController(t *testing.T) { { name: "IsReady ReplicationController", fields: fields{ - client: fake.NewSimpleClientset(), - log: func(string, ...interface{}) {}, + client: fake.NewClientset(), checkJobs: true, pausedAsReady: false, }, @@ -607,8 +575,7 @@ func Test_ReadyChecker_IsReady_ReplicationController(t *testing.T) { { name: "IsReady ReplicationController with error", fields: fields{ - client: fake.NewSimpleClientset(), - log: func(string, ...interface{}) {}, + client: fake.NewClientset(), checkJobs: true, pausedAsReady: false, }, @@ -623,8 +590,7 @@ func Test_ReadyChecker_IsReady_ReplicationController(t *testing.T) { { name: "IsReady ReplicationController and pods not ready for object", fields: fields{ - client: fake.NewSimpleClientset(), - log: func(string, ...interface{}) {}, + client: fake.NewClientset(), checkJobs: true, pausedAsReady: false, }, @@ -641,7 +607,6 @@ func Test_ReadyChecker_IsReady_ReplicationController(t *testing.T) { t.Run(tt.name, func(t *testing.T) { c := &ReadyChecker{ client: tt.fields.client, - log: tt.fields.log, checkJobs: tt.fields.checkJobs, pausedAsReady: tt.fields.pausedAsReady, } @@ -663,7 +628,6 @@ func Test_ReadyChecker_IsReady_ReplicationController(t *testing.T) { func Test_ReadyChecker_IsReady_ReplicaSet(t *testing.T) { type fields struct { client kubernetes.Interface - log func(string, ...interface{}) checkJobs bool pausedAsReady bool } @@ -682,14 +646,13 @@ func Test_ReadyChecker_IsReady_ReplicaSet(t *testing.T) { { name: "IsReady ReplicaSet", fields: fields{ - client: fake.NewSimpleClientset(), - log: func(string, ...interface{}) {}, + client: fake.NewClientset(), checkJobs: true, pausedAsReady: false, }, args: args{ ctx: context.TODO(), - resource: &resource.Info{Object: &extensionsv1beta1.ReplicaSet{}, Name: "foo", Namespace: defaultNamespace}, + resource: &resource.Info{Object: &appsv1.ReplicaSet{}, Name: "foo", Namespace: defaultNamespace}, }, rs: newReplicaSet("foo", 1, 1, true), want: false, @@ -698,14 +661,13 @@ func Test_ReadyChecker_IsReady_ReplicaSet(t *testing.T) { { name: "IsReady ReplicaSet not ready", fields: fields{ - client: fake.NewSimpleClientset(), - log: func(string, ...interface{}) {}, + client: fake.NewClientset(), checkJobs: true, pausedAsReady: false, }, args: args{ ctx: context.TODO(), - resource: &resource.Info{Object: &extensionsv1beta1.ReplicaSet{}, Name: "foo", Namespace: defaultNamespace}, + resource: &resource.Info{Object: &appsv1.ReplicaSet{}, Name: "foo", Namespace: defaultNamespace}, }, rs: newReplicaSet("bar", 1, 1, false), want: false, @@ -716,7 +678,6 @@ func Test_ReadyChecker_IsReady_ReplicaSet(t *testing.T) { t.Run(tt.name, func(t *testing.T) { c := &ReadyChecker{ client: tt.fields.client, - log: tt.fields.log, checkJobs: tt.fields.checkJobs, pausedAsReady: tt.fields.pausedAsReady, } @@ -793,7 +754,7 @@ func Test_ReadyChecker_deploymentReady(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - c := NewReadyChecker(fake.NewSimpleClientset(), nil) + c := NewReadyChecker(fake.NewClientset()) if got := c.deploymentReady(tt.args.rs, tt.args.dep); got != tt.want { t.Errorf("deploymentReady() = %v, want %v", got, tt.want) } @@ -827,7 +788,7 @@ func Test_ReadyChecker_replicaSetReady(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - c := NewReadyChecker(fake.NewSimpleClientset(), nil) + c := NewReadyChecker(fake.NewClientset()) if got := c.replicaSetReady(tt.args.rs); got != tt.want { t.Errorf("replicaSetReady() = %v, want %v", got, tt.want) } @@ -861,7 +822,7 @@ func Test_ReadyChecker_replicationControllerReady(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - c := NewReadyChecker(fake.NewSimpleClientset(), nil) + c := NewReadyChecker(fake.NewClientset()) if got := c.replicationControllerReady(tt.args.rc); got != tt.want { t.Errorf("replicationControllerReady() = %v, want %v", got, tt.want) } @@ -916,7 +877,7 @@ func Test_ReadyChecker_daemonSetReady(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - c := NewReadyChecker(fake.NewSimpleClientset(), nil) + c := NewReadyChecker(fake.NewClientset()) if got := c.daemonSetReady(tt.args.ds); got != tt.want { t.Errorf("daemonSetReady() = %v, want %v", got, tt.want) } @@ -992,7 +953,7 @@ func Test_ReadyChecker_statefulSetReady(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - c := NewReadyChecker(fake.NewSimpleClientset(), nil) + c := NewReadyChecker(fake.NewClientset()) if got := c.statefulSetReady(tt.args.sts); got != tt.want { t.Errorf("statefulSetReady() = %v, want %v", got, tt.want) } @@ -1051,7 +1012,7 @@ func Test_ReadyChecker_podsReadyForObject(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - c := NewReadyChecker(fake.NewSimpleClientset(), nil) + c := NewReadyChecker(fake.NewClientset()) for _, pod := range tt.existPods { if _, err := c.client.CoreV1().Pods(defaultNamespace).Create(context.TODO(), &pod, metav1.CreateOptions{}); err != nil { t.Errorf("Failed to create Pod error: %v", err) @@ -1130,7 +1091,7 @@ func Test_ReadyChecker_jobReady(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - c := NewReadyChecker(fake.NewSimpleClientset(), nil) + c := NewReadyChecker(fake.NewClientset()) got, err := c.jobReady(tt.args.job) if (err != nil) != tt.wantErr { t.Errorf("jobReady() error = %v, wantErr %v", err, tt.wantErr) @@ -1169,7 +1130,7 @@ func Test_ReadyChecker_volumeReady(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - c := NewReadyChecker(fake.NewSimpleClientset(), nil) + c := NewReadyChecker(fake.NewClientset()) if got := c.volumeReady(tt.args.v); got != tt.want { t.Errorf("volumeReady() = %v, want %v", got, tt.want) } @@ -1214,7 +1175,7 @@ func Test_ReadyChecker_serviceReady(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - c := NewReadyChecker(fake.NewSimpleClientset(), nil) + c := NewReadyChecker(fake.NewClientset()) got := c.serviceReady(tt.args.service) if got != tt.want { t.Errorf("serviceReady() = %v, want %v", got, tt.want) @@ -1283,7 +1244,7 @@ func Test_ReadyChecker_crdBetaReady(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - c := NewReadyChecker(fake.NewSimpleClientset(), nil) + c := NewReadyChecker(fake.NewClientset()) got := c.crdBetaReady(tt.args.crdBeta) if got != tt.want { t.Errorf("crdBetaReady() = %v, want %v", got, tt.want) @@ -1352,7 +1313,7 @@ func Test_ReadyChecker_crdReady(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - c := NewReadyChecker(fake.NewSimpleClientset(), nil) + c := NewReadyChecker(fake.NewClientset()) got := c.crdReady(tt.args.crdBeta) if got != tt.want { t.Errorf("crdBetaReady() = %v, want %v", got, tt.want) diff --git a/pkg/kube/resource.go b/pkg/kube/resource.go index d441db8a7..600f256b3 100644 --- a/pkg/kube/resource.go +++ b/pkg/kube/resource.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package kube // import "helm.sh/helm/v3/pkg/kube" +package kube // import "helm.sh/helm/v4/pkg/kube" import "k8s.io/cli-runtime/pkg/resource" @@ -81,5 +81,5 @@ func (r ResourceList) Intersect(rs ResourceList) ResourceList { // isMatchingInfo returns true if infos match on Name and GroupVersionKind. func isMatchingInfo(a, b *resource.Info) bool { - return a.Name == b.Name && a.Namespace == b.Namespace && a.Mapping.GroupVersionKind.Kind == b.Mapping.GroupVersionKind.Kind + 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 } diff --git a/pkg/kube/resource_policy.go b/pkg/kube/resource_policy.go index 46b8680dd..fb1089785 100644 --- a/pkg/kube/resource_policy.go +++ b/pkg/kube/resource_policy.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package kube // import "helm.sh/helm/v3/pkg/kube" +package kube // import "helm.sh/helm/v4/pkg/kube" // ResourcePolicyAnno is the annotation name for a resource policy const ResourcePolicyAnno = "helm.sh/resource-policy" diff --git a/pkg/kube/resource_test.go b/pkg/kube/resource_test.go index 3c906ceca..c405ca382 100644 --- a/pkg/kube/resource_test.go +++ b/pkg/kube/resource_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package kube // import "helm.sh/helm/v3/pkg/kube" +package kube // import "helm.sh/helm/v4/pkg/kube" import ( "testing" diff --git a/pkg/kube/roundtripper.go b/pkg/kube/roundtripper.go index fdb103529..52cb5bad2 100644 --- a/pkg/kube/roundtripper.go +++ b/pkg/kube/roundtripper.go @@ -49,7 +49,7 @@ func (rt *RetryingRoundTripper) roundTrip(req *http.Request, retry int, prevResp b, err := io.ReadAll(resp.Body) resp.Body.Close() if err != nil { - return resp, rtErr + return resp, err } var ke kubernetesError @@ -58,10 +58,10 @@ func (rt *RetryingRoundTripper) roundTrip(req *http.Request, retry int, prevResp r.Seek(0, io.SeekStart) resp.Body = io.NopCloser(r) if err != nil { - return resp, rtErr + return resp, err } if ke.Code < 500 { - return resp, rtErr + return resp, nil } // Matches messages like "etcdserver: leader changed" if strings.HasSuffix(ke.Message, "etcdserver: leader changed") { @@ -71,7 +71,7 @@ func (rt *RetryingRoundTripper) roundTrip(req *http.Request, retry int, prevResp if strings.HasSuffix(ke.Message, "raft proposal dropped") { return rt.roundTrip(req, retry-1, resp) } - return resp, rtErr + return resp, nil } type kubernetesError struct { diff --git a/pkg/kube/statuswait.go b/pkg/kube/statuswait.go new file mode 100644 index 000000000..2d7cfe971 --- /dev/null +++ b/pkg/kube/statuswait.go @@ -0,0 +1,235 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package kube // import "helm.sh/helm/v3/pkg/kube" + +import ( + "context" + "errors" + "fmt" + "log/slog" + "sort" + "time" + + "github.com/fluxcd/cli-utils/pkg/kstatus/polling/aggregator" + "github.com/fluxcd/cli-utils/pkg/kstatus/polling/collector" + "github.com/fluxcd/cli-utils/pkg/kstatus/polling/engine" + "github.com/fluxcd/cli-utils/pkg/kstatus/polling/event" + "github.com/fluxcd/cli-utils/pkg/kstatus/polling/statusreaders" + "github.com/fluxcd/cli-utils/pkg/kstatus/status" + "github.com/fluxcd/cli-utils/pkg/kstatus/watcher" + "github.com/fluxcd/cli-utils/pkg/object" + appsv1 "k8s.io/api/apps/v1" + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/client-go/dynamic" + + helmStatusReaders "helm.sh/helm/v4/internal/statusreaders" +) + +type statusWaiter struct { + client dynamic.Interface + restMapper meta.RESTMapper +} + +func alwaysReady(_ *unstructured.Unstructured) (*status.Result, error) { + return &status.Result{ + Status: status.CurrentStatus, + Message: "Resource is current", + }, nil +} + +func (w *statusWaiter) WatchUntilReady(resourceList ResourceList, timeout time.Duration) error { + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + slog.Debug("waiting for resources", "count", len(resourceList), "timeout", timeout) + sw := watcher.NewDefaultStatusWatcher(w.client, w.restMapper) + jobSR := helmStatusReaders.NewCustomJobStatusReader(w.restMapper) + podSR := helmStatusReaders.NewCustomPodStatusReader(w.restMapper) + // We don't want to wait on any other resources as watchUntilReady is only for Helm hooks + genericSR := statusreaders.NewGenericStatusReader(w.restMapper, alwaysReady) + + sr := &statusreaders.DelegatingStatusReader{ + StatusReaders: []engine.StatusReader{ + jobSR, + podSR, + genericSR, + }, + } + sw.StatusReader = sr + return w.wait(ctx, resourceList, sw) +} + +func (w *statusWaiter) Wait(resourceList ResourceList, timeout time.Duration) error { + ctx, cancel := context.WithTimeout(context.TODO(), timeout) + defer cancel() + slog.Debug("waiting for resources", "count", len(resourceList), "timeout", timeout) + sw := watcher.NewDefaultStatusWatcher(w.client, w.restMapper) + return w.wait(ctx, resourceList, sw) +} + +func (w *statusWaiter) WaitWithJobs(resourceList ResourceList, timeout time.Duration) error { + ctx, cancel := context.WithTimeout(context.TODO(), timeout) + defer cancel() + slog.Debug("waiting for resources", "count", len(resourceList), "timeout", timeout) + sw := watcher.NewDefaultStatusWatcher(w.client, w.restMapper) + newCustomJobStatusReader := helmStatusReaders.NewCustomJobStatusReader(w.restMapper) + customSR := statusreaders.NewStatusReader(w.restMapper, newCustomJobStatusReader) + sw.StatusReader = customSR + return w.wait(ctx, resourceList, sw) +} + +func (w *statusWaiter) WaitForDelete(resourceList ResourceList, timeout time.Duration) error { + ctx, cancel := context.WithTimeout(context.TODO(), timeout) + defer cancel() + slog.Debug("waiting for resources to be deleted", "count", len(resourceList), "timeout", timeout) + sw := watcher.NewDefaultStatusWatcher(w.client, w.restMapper) + return w.waitForDelete(ctx, resourceList, sw) +} + +func (w *statusWaiter) waitForDelete(ctx context.Context, resourceList ResourceList, sw watcher.StatusWatcher) error { + cancelCtx, cancel := context.WithCancel(ctx) + defer cancel() + resources := []object.ObjMetadata{} + for _, resource := range resourceList { + obj, err := object.RuntimeToObjMeta(resource.Object) + if err != nil { + return err + } + resources = append(resources, obj) + } + eventCh := sw.Watch(cancelCtx, resources, watcher.Options{}) + statusCollector := collector.NewResourceStatusCollector(resources) + done := statusCollector.ListenWithObserver(eventCh, statusObserver(cancel, status.NotFoundStatus)) + <-done + + if statusCollector.Error != nil { + return statusCollector.Error + } + + // Only check parent context error, otherwise we would error when desired status is achieved. + if ctx.Err() != nil { + errs := []error{} + for _, id := range resources { + rs := statusCollector.ResourceStatuses[id] + if rs.Status == status.NotFoundStatus { + continue + } + errs = append(errs, fmt.Errorf("resource still exists, name: %s, kind: %s, status: %s", rs.Identifier.Name, rs.Identifier.GroupKind.Kind, rs.Status)) + } + errs = append(errs, ctx.Err()) + return errors.Join(errs...) + } + return nil +} + +func (w *statusWaiter) wait(ctx context.Context, resourceList ResourceList, sw watcher.StatusWatcher) error { + cancelCtx, cancel := context.WithCancel(ctx) + defer cancel() + resources := []object.ObjMetadata{} + for _, resource := range resourceList { + switch value := AsVersioned(resource).(type) { + case *appsv1.Deployment: + if value.Spec.Paused { + continue + } + } + obj, err := object.RuntimeToObjMeta(resource.Object) + if err != nil { + return err + } + resources = append(resources, obj) + } + + eventCh := sw.Watch(cancelCtx, resources, watcher.Options{}) + statusCollector := collector.NewResourceStatusCollector(resources) + done := statusCollector.ListenWithObserver(eventCh, statusObserver(cancel, status.CurrentStatus)) + <-done + + if statusCollector.Error != nil { + return statusCollector.Error + } + + // Only check parent context error, otherwise we would error when desired status is achieved. + if ctx.Err() != nil { + errs := []error{} + for _, id := range resources { + rs := statusCollector.ResourceStatuses[id] + if rs.Status == status.CurrentStatus { + continue + } + errs = append(errs, fmt.Errorf("resource not ready, name: %s, kind: %s, status: %s", rs.Identifier.Name, rs.Identifier.GroupKind.Kind, rs.Status)) + } + errs = append(errs, ctx.Err()) + return errors.Join(errs...) + } + return nil +} + +func statusObserver(cancel context.CancelFunc, desired status.Status) collector.ObserverFunc { + return func(statusCollector *collector.ResourceStatusCollector, _ event.Event) { + var rss []*event.ResourceStatus + var nonDesiredResources []*event.ResourceStatus + for _, rs := range statusCollector.ResourceStatuses { + if rs == nil { + continue + } + // If a resource is already deleted before waiting has started, it will show as unknown + // this check ensures we don't wait forever for a resource that is already deleted + if rs.Status == status.UnknownStatus && desired == status.NotFoundStatus { + continue + } + rss = append(rss, rs) + if rs.Status != desired { + nonDesiredResources = append(nonDesiredResources, rs) + } + } + + if aggregator.AggregateStatus(rss, desired) == desired { + cancel() + return + } + + if len(nonDesiredResources) > 0 { + // Log a single resource so the user knows what they're waiting for without an overwhelming amount of output + sort.Slice(nonDesiredResources, func(i, j int) bool { + return nonDesiredResources[i].Identifier.Name < nonDesiredResources[j].Identifier.Name + }) + first := nonDesiredResources[0] + slog.Debug("waiting for resource", "name", first.Identifier.Name, "kind", first.Identifier.GroupKind.Kind, "expectedStatus", desired, "actualStatus", first.Status) + } + } +} + +type hookOnlyWaiter struct { + sw *statusWaiter +} + +func (w *hookOnlyWaiter) WatchUntilReady(resourceList ResourceList, timeout time.Duration) error { + return w.sw.WatchUntilReady(resourceList, timeout) +} + +func (w *hookOnlyWaiter) Wait(_ ResourceList, _ time.Duration) error { + return nil +} + +func (w *hookOnlyWaiter) WaitWithJobs(_ ResourceList, _ time.Duration) error { + return nil +} + +func (w *hookOnlyWaiter) WaitForDelete(_ ResourceList, _ time.Duration) error { + return nil +} diff --git a/pkg/kube/statuswait_test.go b/pkg/kube/statuswait_test.go new file mode 100644 index 000000000..0b309b22d --- /dev/null +++ b/pkg/kube/statuswait_test.go @@ -0,0 +1,447 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package kube // import "helm.sh/helm/v3/pkg/kube" + +import ( + "errors" + "testing" + "time" + + "github.com/fluxcd/cli-utils/pkg/testutil" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + appsv1 "k8s.io/api/apps/v1" + batchv1 "k8s.io/api/batch/v1" + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/yaml" + dynamicfake "k8s.io/client-go/dynamic/fake" + "k8s.io/kubectl/pkg/scheme" +) + +var podCurrentManifest = ` +apiVersion: v1 +kind: Pod +metadata: + name: current-pod + namespace: ns +status: + conditions: + - type: Ready + status: "True" + phase: Running +` + +var podNoStatusManifest = ` +apiVersion: v1 +kind: Pod +metadata: + name: in-progress-pod + namespace: ns +` + +var jobNoStatusManifest = ` +apiVersion: batch/v1 +kind: Job +metadata: + name: test + namespace: qual + generation: 1 +` + +var jobReadyManifest = ` +apiVersion: batch/v1 +kind: Job +metadata: + name: ready-not-complete + namespace: default + generation: 1 +status: + startTime: 2025-02-06T16:34:20-05:00 + active: 1 + ready: 1 +` + +var jobCompleteManifest = ` +apiVersion: batch/v1 +kind: Job +metadata: + name: test + namespace: qual + generation: 1 +status: + succeeded: 1 + active: 0 + conditions: + - type: Complete + status: "True" +` + +var podCompleteManifest = ` +apiVersion: v1 +kind: Pod +metadata: + name: good-pod + namespace: ns +status: + phase: Succeeded +` + +var pausedDeploymentManifest = ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: paused + namespace: ns-1 + generation: 1 +spec: + paused: true + replicas: 1 + selector: + matchLabels: + app: nginx + template: + metadata: + labels: + app: nginx + spec: + containers: + - name: nginx + image: nginx:1.19.6 + ports: + - containerPort: 80 +` + +var notReadyDeploymentManifest = ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: not-ready + namespace: ns-1 + generation: 1 +spec: + replicas: 1 + selector: + matchLabels: + app: nginx + template: + metadata: + labels: + app: nginx + spec: + containers: + - name: nginx + image: nginx:1.19.6 + ports: + - containerPort: 80 +` + +func getGVR(t *testing.T, mapper meta.RESTMapper, obj *unstructured.Unstructured) schema.GroupVersionResource { + gvk := obj.GroupVersionKind() + mapping, err := mapper.RESTMapping(gvk.GroupKind(), gvk.Version) + require.NoError(t, err) + return mapping.Resource +} + +func getRuntimeObjFromManifests(t *testing.T, manifests []string) []runtime.Object { + objects := []runtime.Object{} + for _, manifest := range manifests { + m := make(map[string]interface{}) + err := yaml.Unmarshal([]byte(manifest), &m) + assert.NoError(t, err) + resource := &unstructured.Unstructured{Object: m} + objects = append(objects, resource) + } + return objects +} + +func getResourceListFromRuntimeObjs(t *testing.T, c *Client, objs []runtime.Object) ResourceList { + resourceList := ResourceList{} + for _, obj := range objs { + list, err := c.Build(objBody(obj), false) + assert.NoError(t, err) + resourceList = append(resourceList, list...) + } + return resourceList +} + +func TestStatusWaitForDelete(t *testing.T) { + t.Parallel() + tests := []struct { + name string + manifestsToCreate []string + manifestsToDelete []string + expectErrs []error + }{ + { + name: "wait for pod to be deleted", + manifestsToCreate: []string{podCurrentManifest}, + manifestsToDelete: []string{podCurrentManifest}, + expectErrs: nil, + }, + { + name: "error when not all objects are deleted", + manifestsToCreate: []string{jobCompleteManifest, podCurrentManifest}, + manifestsToDelete: []string{jobCompleteManifest}, + expectErrs: []error{errors.New("resource still exists, name: current-pod, kind: Pod, status: Current"), errors.New("context deadline exceeded")}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + c := newTestClient(t) + timeout := time.Second + timeUntilPodDelete := time.Millisecond * 500 + fakeClient := dynamicfake.NewSimpleDynamicClient(scheme.Scheme) + fakeMapper := testutil.NewFakeRESTMapper( + v1.SchemeGroupVersion.WithKind("Pod"), + batchv1.SchemeGroupVersion.WithKind("Job"), + ) + statusWaiter := statusWaiter{ + restMapper: fakeMapper, + client: fakeClient, + } + objsToCreate := getRuntimeObjFromManifests(t, tt.manifestsToCreate) + for _, objToCreate := range objsToCreate { + u := objToCreate.(*unstructured.Unstructured) + gvr := getGVR(t, fakeMapper, u) + err := fakeClient.Tracker().Create(gvr, u, u.GetNamespace()) + assert.NoError(t, err) + } + objsToDelete := getRuntimeObjFromManifests(t, tt.manifestsToDelete) + for _, objToDelete := range objsToDelete { + u := objToDelete.(*unstructured.Unstructured) + gvr := getGVR(t, fakeMapper, u) + go func() { + time.Sleep(timeUntilPodDelete) + err := fakeClient.Tracker().Delete(gvr, u.GetNamespace(), u.GetName()) + assert.NoError(t, err) + }() + } + resourceList := getResourceListFromRuntimeObjs(t, c, objsToCreate) + err := statusWaiter.WaitForDelete(resourceList, timeout) + if tt.expectErrs != nil { + assert.EqualError(t, err, errors.Join(tt.expectErrs...).Error()) + return + } + assert.NoError(t, err) + }) + } +} + +func TestStatusWaitForDeleteNonExistentObject(t *testing.T) { + t.Parallel() + c := newTestClient(t) + timeout := time.Second + fakeClient := dynamicfake.NewSimpleDynamicClient(scheme.Scheme) + fakeMapper := testutil.NewFakeRESTMapper( + v1.SchemeGroupVersion.WithKind("Pod"), + ) + statusWaiter := statusWaiter{ + restMapper: fakeMapper, + client: fakeClient, + } + // Don't create the object to test that the wait for delete works when the object doesn't exist + objManifest := getRuntimeObjFromManifests(t, []string{podCurrentManifest}) + resourceList := getResourceListFromRuntimeObjs(t, c, objManifest) + err := statusWaiter.WaitForDelete(resourceList, timeout) + assert.NoError(t, err) +} + +func TestStatusWait(t *testing.T) { + t.Parallel() + tests := []struct { + name string + objManifests []string + expectErrs []error + waitForJobs bool + }{ + { + name: "Job is not complete", + objManifests: []string{jobNoStatusManifest}, + expectErrs: []error{errors.New("resource not ready, name: test, kind: Job, status: InProgress"), errors.New("context deadline exceeded")}, + waitForJobs: true, + }, + { + name: "Job is ready but not complete", + objManifests: []string{jobReadyManifest}, + expectErrs: nil, + waitForJobs: false, + }, + { + name: "Pod is ready", + objManifests: []string{podCurrentManifest}, + expectErrs: nil, + }, + { + name: "one of the pods never becomes ready", + objManifests: []string{podNoStatusManifest, podCurrentManifest}, + expectErrs: []error{errors.New("resource not ready, name: in-progress-pod, kind: Pod, status: InProgress"), errors.New("context deadline exceeded")}, + }, + { + name: "paused deployment passes", + objManifests: []string{pausedDeploymentManifest}, + expectErrs: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + c := newTestClient(t) + fakeClient := dynamicfake.NewSimpleDynamicClient(scheme.Scheme) + fakeMapper := testutil.NewFakeRESTMapper( + v1.SchemeGroupVersion.WithKind("Pod"), + appsv1.SchemeGroupVersion.WithKind("Deployment"), + batchv1.SchemeGroupVersion.WithKind("Job"), + ) + statusWaiter := statusWaiter{ + client: fakeClient, + restMapper: fakeMapper, + } + objs := getRuntimeObjFromManifests(t, tt.objManifests) + for _, obj := range objs { + u := obj.(*unstructured.Unstructured) + gvr := getGVR(t, fakeMapper, u) + err := fakeClient.Tracker().Create(gvr, u, u.GetNamespace()) + assert.NoError(t, err) + } + resourceList := getResourceListFromRuntimeObjs(t, c, objs) + err := statusWaiter.Wait(resourceList, time.Second*3) + if tt.expectErrs != nil { + assert.EqualError(t, err, errors.Join(tt.expectErrs...).Error()) + return + } + assert.NoError(t, err) + }) + } +} + +func TestWaitForJobComplete(t *testing.T) { + t.Parallel() + tests := []struct { + name string + objManifests []string + expectErrs []error + }{ + { + name: "Job is complete", + objManifests: []string{jobCompleteManifest}, + }, + { + name: "Job is not ready", + objManifests: []string{jobNoStatusManifest}, + expectErrs: []error{errors.New("resource not ready, name: test, kind: Job, status: InProgress"), errors.New("context deadline exceeded")}, + }, + { + name: "Job is ready but not complete", + objManifests: []string{jobReadyManifest}, + expectErrs: []error{errors.New("resource not ready, name: ready-not-complete, kind: Job, status: InProgress"), errors.New("context deadline exceeded")}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + c := newTestClient(t) + fakeClient := dynamicfake.NewSimpleDynamicClient(scheme.Scheme) + fakeMapper := testutil.NewFakeRESTMapper( + batchv1.SchemeGroupVersion.WithKind("Job"), + ) + statusWaiter := statusWaiter{ + client: fakeClient, + restMapper: fakeMapper, + } + objs := getRuntimeObjFromManifests(t, tt.objManifests) + for _, obj := range objs { + u := obj.(*unstructured.Unstructured) + gvr := getGVR(t, fakeMapper, u) + err := fakeClient.Tracker().Create(gvr, u, u.GetNamespace()) + assert.NoError(t, err) + } + resourceList := getResourceListFromRuntimeObjs(t, c, objs) + err := statusWaiter.WaitWithJobs(resourceList, time.Second*3) + if tt.expectErrs != nil { + assert.EqualError(t, err, errors.Join(tt.expectErrs...).Error()) + return + } + assert.NoError(t, err) + }) + } +} + +func TestWatchForReady(t *testing.T) { + t.Parallel() + tests := []struct { + name string + objManifests []string + expectErrs []error + }{ + { + name: "succeeds if pod and job are complete", + objManifests: []string{jobCompleteManifest, podCompleteManifest}, + }, + { + name: "succeeds when a resource that's not a pod or job is not ready", + objManifests: []string{notReadyDeploymentManifest}, + }, + { + name: "Fails if job is not complete", + objManifests: []string{jobReadyManifest}, + expectErrs: []error{errors.New("resource not ready, name: ready-not-complete, kind: Job, status: InProgress"), errors.New("context deadline exceeded")}, + }, + { + name: "Fails if pod is not complete", + objManifests: []string{podCurrentManifest}, + expectErrs: []error{errors.New("resource not ready, name: current-pod, kind: Pod, status: InProgress"), errors.New("context deadline exceeded")}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + c := newTestClient(t) + fakeClient := dynamicfake.NewSimpleDynamicClient(scheme.Scheme) + fakeMapper := testutil.NewFakeRESTMapper( + v1.SchemeGroupVersion.WithKind("Pod"), + appsv1.SchemeGroupVersion.WithKind("Deployment"), + batchv1.SchemeGroupVersion.WithKind("Job"), + ) + statusWaiter := statusWaiter{ + client: fakeClient, + restMapper: fakeMapper, + } + objs := getRuntimeObjFromManifests(t, tt.objManifests) + for _, obj := range objs { + u := obj.(*unstructured.Unstructured) + gvr := getGVR(t, fakeMapper, u) + err := fakeClient.Tracker().Create(gvr, u, u.GetNamespace()) + assert.NoError(t, err) + } + resourceList := getResourceListFromRuntimeObjs(t, c, objs) + err := statusWaiter.WatchUntilReady(resourceList, time.Second*3) + if tt.expectErrs != nil { + assert.EqualError(t, err, errors.Join(tt.expectErrs...).Error()) + return + } + assert.NoError(t, err) + }) + } +} diff --git a/pkg/kube/wait.go b/pkg/kube/wait.go index 74799edea..ebb5b3257 100644 --- a/pkg/kube/wait.go +++ b/pkg/kube/wait.go @@ -14,11 +14,13 @@ See the License for the specific language governing permissions and limitations under the License. */ -package kube // import "helm.sh/helm/v3/pkg/kube" +package kube // import "helm.sh/helm/v4/pkg/kube" import ( "context" + "errors" "fmt" + "log/slog" "net/http" "time" @@ -30,25 +32,42 @@ import ( extensionsv1beta1 "k8s.io/api/extensions/v1beta1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/watch" "k8s.io/cli-runtime/pkg/resource" + "k8s.io/client-go/kubernetes" + cachetools "k8s.io/client-go/tools/cache" + watchtools "k8s.io/client-go/tools/watch" "k8s.io/apimachinery/pkg/util/wait" ) -type waiter struct { - c ReadyChecker - timeout time.Duration - log func(string, ...interface{}) +// legacyWaiter is the legacy implementation of the Waiter interface. This logic was used by default in Helm 3 +// Helm 4 now uses the StatusWaiter implementation instead +type legacyWaiter struct { + c ReadyChecker + kubeClient *kubernetes.Clientset +} + +func (hw *legacyWaiter) Wait(resources ResourceList, timeout time.Duration) error { + hw.c = NewReadyChecker(hw.kubeClient, PausedAsReady(true)) + return hw.waitForResources(resources, timeout) +} + +func (hw *legacyWaiter) WaitWithJobs(resources ResourceList, timeout time.Duration) error { + hw.c = NewReadyChecker(hw.kubeClient, PausedAsReady(true), CheckJobs(true)) + return hw.waitForResources(resources, timeout) } // waitForResources polls to get the current status of all pods, PVCs, Services and // Jobs(optional) until all are ready or a timeout is reached -func (w *waiter) waitForResources(created ResourceList) error { - w.log("beginning wait for %d resources with timeout of %v", len(created), w.timeout) +func (hw *legacyWaiter) waitForResources(created ResourceList, timeout time.Duration) error { + slog.Debug("beginning wait for resources", "count", len(created), "timeout", timeout) - ctx, cancel := context.WithTimeout(context.Background(), w.timeout) + ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() numberOfErrors := make([]int, len(created)) @@ -59,15 +78,15 @@ func (w *waiter) waitForResources(created ResourceList) error { return wait.PollUntilContextCancel(ctx, 2*time.Second, true, func(ctx context.Context) (bool, error) { waitRetries := 30 for i, v := range created { - ready, err := w.c.IsReady(ctx, v) + ready, err := hw.c.IsReady(ctx, v) - if waitRetries > 0 && w.isRetryableError(err, v) { + if waitRetries > 0 && hw.isRetryableError(err, v) { numberOfErrors[i]++ if numberOfErrors[i] > waitRetries { - w.log("Max number of retries reached") + slog.Debug("max number of retries reached", "resource", v.Name, "retries", numberOfErrors[i]) return false, err } - w.log("Retrying as current number of retries %d less than max number of retries %d", numberOfErrors[i]-1, waitRetries) + slog.Debug("retrying resource readiness", "resource", v.Name, "currentRetries", numberOfErrors[i]-1, "maxRetries", waitRetries) return false, nil } numberOfErrors[i] = 0 @@ -79,33 +98,34 @@ func (w *waiter) waitForResources(created ResourceList) error { }) } -func (w *waiter) isRetryableError(err error, resource *resource.Info) bool { +func (hw *legacyWaiter) isRetryableError(err error, resource *resource.Info) bool { if err == nil { return false } - w.log("Error received when checking status of resource %s. Error: '%s', Resource details: '%s'", resource.Name, err, resource) + slog.Debug("error received when checking resource status", "resource", resource.Name, slog.Any("error", err)) if ev, ok := err.(*apierrors.StatusError); ok { statusCode := ev.Status().Code - retryable := w.isRetryableHTTPStatusCode(statusCode) - w.log("Status code received: %d. Retryable error? %t", statusCode, retryable) + retryable := hw.isRetryableHTTPStatusCode(statusCode) + slog.Debug("status code received", "resource", resource.Name, "statusCode", statusCode, "retryable", retryable) return retryable } - w.log("Retryable error? %t", true) + slog.Debug("retryable error assumed", "resource", resource.Name) return true } -func (w *waiter) isRetryableHTTPStatusCode(httpStatusCode int32) bool { +func (hw *legacyWaiter) isRetryableHTTPStatusCode(httpStatusCode int32) bool { 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 -func (w *waiter) waitForDeletedResources(deleted ResourceList) error { - w.log("beginning wait for %d resources to be deleted with timeout of %v", len(deleted), w.timeout) +func (hw *legacyWaiter) WaitForDelete(deleted ResourceList, timeout time.Duration) error { + slog.Debug("beginning wait for resources to be deleted", "count", len(deleted), "timeout", timeout) - ctx, cancel := context.WithTimeout(context.Background(), w.timeout) + startTime := time.Now() + ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() - return wait.PollUntilContextCancel(ctx, 2*time.Second, true, func(_ context.Context) (bool, error) { + err := wait.PollUntilContextCancel(ctx, 2*time.Second, true, func(_ context.Context) (bool, error) { for _, v := range deleted { err := v.Get() if err == nil || !apierrors.IsNotFound(err) { @@ -114,6 +134,15 @@ func (w *waiter) waitForDeletedResources(deleted ResourceList) error { } return true, nil }) + + elapsed := time.Since(startTime).Round(time.Second) + if err != nil { + slog.Debug("wait for resources failed", "elapsed", elapsed, slog.Any("error", err)) + } else { + slog.Debug("wait for resources succeeded", "elapsed", elapsed) + } + + return err } // SelectorsForObject returns the pod label selector for a given object @@ -152,7 +181,7 @@ func SelectorsForObject(object runtime.Object) (selector labels.Selector, err er case *batchv1.Job: selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector) case *corev1.Service: - if t.Spec.Selector == nil || len(t.Spec.Selector) == 0 { + if len(t.Spec.Selector) == 0 { return nil, fmt.Errorf("invalid service '%s': Service is defined without a selector", t.Name) } selector = labels.SelectorFromSet(t.Spec.Selector) @@ -167,3 +196,155 @@ func SelectorsForObject(object runtime.Object) (selector labels.Selector, err er return selector, nil } + +func (hw *legacyWaiter) watchTimeout(t time.Duration) func(*resource.Info) error { + return func(info *resource.Info) error { + return hw.watchUntilReady(t, info) + } +} + +// WatchUntilReady watches the resources given and waits until it is ready. +// +// This method is mainly for hook implementations. It watches for a resource to +// hit a particular milestone. The milestone depends on the Kind. +// +// For most kinds, it checks to see if the resource is marked as Added or Modified +// by the Kubernetes event stream. For some kinds, it does more: +// +// - Jobs: A job is marked "Ready" when it has successfully completed. This is +// ascertained by watching the Status fields in a job's output. +// - Pods: A pod is marked "Ready" when it has successfully completed. This is +// ascertained by watching the status.phase field in a pod's output. +// +// Handling for other kinds will be added as necessary. +func (hw *legacyWaiter) WatchUntilReady(resources ResourceList, timeout time.Duration) error { + // For jobs, there's also the option to do poll c.Jobs(namespace).Get(): + // https://github.com/adamreese/kubernetes/blob/master/test/e2e/job.go#L291-L300 + return perform(resources, hw.watchTimeout(timeout)) +} + +func perform(infos ResourceList, fn func(*resource.Info) error) error { + var result error + + if len(infos) == 0 { + return ErrNoObjectsVisited + } + + errs := make(chan error) + go batchPerform(infos, fn, errs) + + for range infos { + err := <-errs + if err != nil { + result = errors.Join(result, err) + } + } + + return result +} + +func (hw *legacyWaiter) watchUntilReady(timeout time.Duration, info *resource.Info) error { + kind := info.Mapping.GroupVersionKind.Kind + switch kind { + case "Job", "Pod": + default: + return nil + } + + slog.Debug("watching for resource changes", "kind", kind, "resource", info.Name, "timeout", timeout) + + // Use a selector on the name of the resource. This should be unique for the + // given version and kind + selector, err := fields.ParseSelector(fmt.Sprintf("metadata.name=%s", info.Name)) + if err != nil { + return err + } + lw := cachetools.NewListWatchFromClient(info.Client, info.Mapping.Resource.Resource, info.Namespace, selector) + + // What we watch for depends on the Kind. + // - For a Job, we watch for completion. + // - For all else, we watch until Ready. + // In the future, we might want to add some special logic for types + // like Ingress, Volume, etc. + + ctx, cancel := watchtools.ContextWithOptionalTimeout(context.Background(), timeout) + defer cancel() + _, err = watchtools.UntilWithSync(ctx, lw, &unstructured.Unstructured{}, nil, func(e watch.Event) (bool, error) { + // Make sure the incoming object is versioned as we use unstructured + // objects when we build manifests + obj := convertWithMapper(e.Object, info.Mapping) + switch e.Type { + case watch.Added, watch.Modified: + // For things like a secret or a config map, this is the best indicator + // we get. We care mostly about jobs, where what we want to see is + // the status go into a good state. For other types, like ReplicaSet + // we don't really do anything to support these as hooks. + slog.Debug("add/modify event received", "resource", info.Name, "eventType", e.Type) + + switch kind { + case "Job": + return hw.waitForJob(obj, info.Name) + case "Pod": + return hw.waitForPodSuccess(obj, info.Name) + } + return true, nil + case watch.Deleted: + slog.Debug("deleted event received", "resource", info.Name) + return true, nil + case watch.Error: + // Handle error and return with an error. + slog.Error("error event received", "resource", info.Name) + return true, fmt.Errorf("failed to deploy %s", info.Name) + default: + return false, nil + } + }) + return err +} + +// waitForJob is a helper that waits for a job to complete. +// +// This operates on an event returned from a watcher. +func (hw *legacyWaiter) waitForJob(obj runtime.Object, name string) (bool, error) { + o, ok := obj.(*batchv1.Job) + if !ok { + return true, fmt.Errorf("expected %s to be a *batch.Job, got %T", name, obj) + } + + for _, c := range o.Status.Conditions { + if c.Type == batchv1.JobComplete && c.Status == "True" { + return true, nil + } else if c.Type == batchv1.JobFailed && c.Status == "True" { + slog.Error("job failed", "job", name, "reason", c.Reason) + return true, fmt.Errorf("job %s failed: %s", name, c.Reason) + } + } + + slog.Debug("job status update", "job", name, "active", o.Status.Active, "failed", o.Status.Failed, "succeeded", o.Status.Succeeded) + return false, nil +} + +// waitForPodSuccess is a helper that waits for a pod to complete. +// +// This operates on an event returned from a watcher. +func (hw *legacyWaiter) waitForPodSuccess(obj runtime.Object, name string) (bool, error) { + o, ok := obj.(*corev1.Pod) + if !ok { + return true, fmt.Errorf("expected %s to be a *v1.Pod, got %T", name, obj) + } + + switch o.Status.Phase { + case corev1.PodSucceeded: + slog.Debug("pod succeeded", "pod", o.Name) + return true, nil + case corev1.PodFailed: + slog.Error("pod failed", "pod", o.Name) + return true, fmt.Errorf("pod %s failed", o.Name) + case corev1.PodPending: + slog.Debug("pod pending", "pod", o.Name) + case corev1.PodRunning: + slog.Debug("pod running", "pod", o.Name) + } + + return false, nil +} diff --git a/pkg/lint/lint.go b/pkg/lint/lint.go index ef23ee7c8..a61d5e43f 100644 --- a/pkg/lint/lint.go +++ b/pkg/lint/lint.go @@ -14,35 +14,52 @@ See the License for the specific language governing permissions and limitations under the License. */ -package lint // import "helm.sh/helm/v3/pkg/lint" +package lint // import "helm.sh/helm/v4/pkg/lint" import ( "path/filepath" - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/lint/rules" - "helm.sh/helm/v3/pkg/lint/support" + chartutil "helm.sh/helm/v4/pkg/chart/v2/util" + "helm.sh/helm/v4/pkg/lint/rules" + "helm.sh/helm/v4/pkg/lint/support" ) -// All runs all the available linters on the given base directory. -func All(basedir string, values map[string]interface{}, namespace string, _ bool) support.Linter { - return AllWithKubeVersion(basedir, values, namespace, nil) +type linterOptions struct { + KubeVersion *chartutil.KubeVersion + SkipSchemaValidation bool } -// AllWithKubeVersion runs all the available linters on the given base directory, allowing to specify the kubernetes version. -func AllWithKubeVersion(basedir string, values map[string]interface{}, namespace string, kubeVersion *chartutil.KubeVersion) support.Linter { - return AllWithKubeVersionAndSchemaValidation(basedir, values, namespace, kubeVersion, false) +type LinterOption func(lo *linterOptions) + +func WithKubeVersion(kubeVersion *chartutil.KubeVersion) LinterOption { + return func(lo *linterOptions) { + lo.KubeVersion = kubeVersion + } +} + +func WithSkipSchemaValidation(skipSchemaValidation bool) LinterOption { + return func(lo *linterOptions) { + lo.SkipSchemaValidation = skipSchemaValidation + } } -// AllWithKubeVersionAndSchemaValidation runs all the available linters on the given base directory, allowing to specify the kubernetes version and if schema validation is enabled or not. -func AllWithKubeVersionAndSchemaValidation(basedir string, values map[string]interface{}, namespace string, kubeVersion *chartutil.KubeVersion, skipSchemaValidation bool) support.Linter { - // Using abs path to get directory context - chartDir, _ := filepath.Abs(basedir) - - linter := support.Linter{ChartDir: chartDir} - rules.Chartfile(&linter) - rules.ValuesWithOverrides(&linter, values) - rules.TemplatesWithSkipSchemaValidation(&linter, values, namespace, kubeVersion, skipSchemaValidation) - rules.Dependencies(&linter) - return linter +func RunAll(baseDir string, values map[string]interface{}, namespace string, options ...LinterOption) support.Linter { + + chartDir, _ := filepath.Abs(baseDir) + + lo := linterOptions{} + for _, option := range options { + option(&lo) + } + + result := support.Linter{ + ChartDir: chartDir, + } + + rules.Chartfile(&result) + rules.ValuesWithOverrides(&result, values) + rules.TemplatesWithSkipSchemaValidation(&result, values, namespace, lo.KubeVersion, lo.SkipSchemaValidation) + rules.Dependencies(&result) + + return result } diff --git a/pkg/lint/lint_test.go b/pkg/lint/lint_test.go index 8f03ed1b1..888d3dfe6 100644 --- a/pkg/lint/lint_test.go +++ b/pkg/lint/lint_test.go @@ -21,14 +21,13 @@ import ( "testing" "time" - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/lint/support" + chartutil "helm.sh/helm/v4/pkg/chart/v2/util" + "helm.sh/helm/v4/pkg/lint/support" ) var values map[string]interface{} const namespace = "testNamespace" -const strict = false const badChartDir = "rules/testdata/badchartfile" const badValuesFileDir = "rules/testdata/badvaluesfile" @@ -36,9 +35,10 @@ const badYamlFileDir = "rules/testdata/albatross" const goodChartDir = "rules/testdata/goodone" const subChartValuesDir = "rules/testdata/withsubchart" const malformedTemplate = "rules/testdata/malformed-template" +const invalidChartFileDir = "rules/testdata/invalidchartfile" func TestBadChart(t *testing.T) { - m := All(badChartDir, values, namespace, strict).Messages + m := RunAll(badChartDir, values, namespace).Messages if len(m) != 8 { t.Errorf("Number of errors %v", len(m)) t.Errorf("All didn't fail with expected errors, got %#v", m) @@ -82,7 +82,7 @@ func TestBadChart(t *testing.T) { } func TestInvalidYaml(t *testing.T) { - m := All(badYamlFileDir, values, namespace, strict).Messages + m := RunAll(badYamlFileDir, values, namespace).Messages if len(m) != 1 { t.Fatalf("All didn't fail with expected errors, got %#v", m) } @@ -91,8 +91,18 @@ func TestInvalidYaml(t *testing.T) { } } +func TestInvalidChartYaml(t *testing.T) { + m := RunAll(invalidChartFileDir, values, namespace).Messages + if len(m) != 1 { + t.Fatalf("All didn't fail with expected errors, got %#v", m) + } + if !strings.Contains(m[0].Err.Error(), "failed to strictly parse chart metadata file") { + t.Errorf("All didn't have the error for duplicate YAML keys") + } +} + func TestBadValues(t *testing.T) { - m := All(badValuesFileDir, values, namespace, strict).Messages + m := RunAll(badValuesFileDir, values, namespace).Messages if len(m) < 1 { t.Fatalf("All didn't fail with expected errors, got %#v", m) } @@ -102,7 +112,7 @@ func TestBadValues(t *testing.T) { } func TestGoodChart(t *testing.T) { - m := All(goodChartDir, values, namespace, strict).Messages + m := RunAll(goodChartDir, values, namespace).Messages if len(m) != 0 { t.Error("All returned linter messages when it shouldn't have") for i, msg := range m { @@ -126,7 +136,7 @@ func TestHelmCreateChart(t *testing.T) { // Note: we test with strict=true here, even though others have // strict = false. - m := All(createdChart, values, namespace, true).Messages + m := RunAll(createdChart, values, namespace, WithSkipSchemaValidation(true)).Messages if ll := len(m); ll != 1 { t.Errorf("All should have had exactly 1 error. Got %d", ll) for i, msg := range m { @@ -147,8 +157,8 @@ func TestHelmCreateChart(t *testing.T) { // // Note: This test requires the following ldflags to be set per the current Kubernetes version to avoid false-positive // results. -// 1. -X helm.sh/helm/v3/pkg/lint/rules.k8sVersionMajor= -// 2. -X helm.sh/helm/v3/pkg/lint/rules.k8sVersionMinor= +// 1. -X helm.sh/helm/v4/pkg/lint/rules.k8sVersionMajor= +// 2. -X helm.sh/helm/v4/pkg/lint/rules.k8sVersionMinor= // or directly use '$(LDFLAGS)' in Makefile. // // When run without ldflags, the test passes giving a false-positive result. This is because the variables @@ -173,7 +183,7 @@ func TestHelmCreateChart_CheckDeprecatedWarnings(t *testing.T) { }, } - linterRunDetails := All(createdChart, updatedValues, namespace, true) + linterRunDetails := RunAll(createdChart, updatedValues, namespace, WithSkipSchemaValidation(true)) for _, msg := range linterRunDetails.Messages { if strings.HasPrefix(msg.Error(), "[WARNING]") && strings.Contains(msg.Error(), "deprecated") { @@ -187,7 +197,7 @@ func TestHelmCreateChart_CheckDeprecatedWarnings(t *testing.T) { // lint ignores import-values // See https://github.com/helm/helm/issues/9658 func TestSubChartValuesChart(t *testing.T) { - m := All(subChartValuesDir, values, namespace, strict).Messages + m := RunAll(subChartValuesDir, values, namespace).Messages if len(m) != 0 { t.Error("All returned linter messages when it shouldn't have") for i, msg := range m { @@ -203,7 +213,7 @@ func TestMalformedTemplate(t *testing.T) { ch := make(chan int, 1) var m []support.Message go func() { - m = All(malformedTemplate, values, namespace, strict).Messages + m = RunAll(malformedTemplate, values, namespace).Messages ch <- 1 }() select { diff --git a/pkg/lint/rules/chartfile.go b/pkg/lint/rules/chartfile.go index 49ab7b9bc..94dae78fa 100644 --- a/pkg/lint/rules/chartfile.go +++ b/pkg/lint/rules/chartfile.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package rules // import "helm.sh/helm/v3/pkg/lint/rules" +package rules // import "helm.sh/helm/v4/pkg/lint/rules" import ( "errors" @@ -26,9 +26,9 @@ import ( "github.com/asaskevich/govalidator" "sigs.k8s.io/yaml" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/lint/support" + chart "helm.sh/helm/v4/pkg/chart/v2" + chartutil "helm.sh/helm/v4/pkg/chart/v2/util" + "helm.sh/helm/v4/pkg/lint/support" ) // Chartfile runs a set of linter rules related to Chart.yaml file @@ -46,6 +46,9 @@ func Chartfile(linter *support.Linter) { return } + _, err = chartutil.StrictLoadChartfile(chartPath) + linter.RunLinterRule(support.WarningSev, chartFileName, validateChartYamlStrictFormat(err)) + // type check for Chart.yaml . ignoring error as any parse // errors would already be caught in the above load function chartFileForTypeCheck, _ := loadChartFileForTypeCheck(chartPath) @@ -102,6 +105,13 @@ func validateChartYamlFormat(chartFileError error) error { return nil } +func validateChartYamlStrictFormat(chartFileError error) error { + if chartFileError != nil { + return errors.Errorf("failed to strictly parse chart metadata file\n\t%s", chartFileError.Error()) + } + return nil +} + func validateChartName(cf *chart.Metadata) error { if cf.Name == "" { return errors.New("name is required") diff --git a/pkg/lint/rules/chartfile_test.go b/pkg/lint/rules/chartfile_test.go index ac4a73f67..1c63a6d94 100644 --- a/pkg/lint/rules/chartfile_test.go +++ b/pkg/lint/rules/chartfile_test.go @@ -23,9 +23,9 @@ import ( "strings" "testing" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/lint/support" + chart "helm.sh/helm/v4/pkg/chart/v2" + chartutil "helm.sh/helm/v4/pkg/chart/v2/util" + "helm.sh/helm/v4/pkg/lint/support" ) const ( diff --git a/pkg/lint/rules/dependencies.go b/pkg/lint/rules/dependencies.go index 65fff649d..16c9d6435 100644 --- a/pkg/lint/rules/dependencies.go +++ b/pkg/lint/rules/dependencies.go @@ -14,15 +14,15 @@ See the License for the specific language governing permissions and limitations under the License. */ -package rules // import "helm.sh/helm/v3/pkg/lint/rules" +package rules // import "helm.sh/helm/v4/pkg/lint/rules" import ( "fmt" "strings" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chart/loader" - "helm.sh/helm/v3/pkg/lint/support" + chart "helm.sh/helm/v4/pkg/chart/v2" + "helm.sh/helm/v4/pkg/chart/v2/loader" + "helm.sh/helm/v4/pkg/lint/support" ) // Dependencies runs lints against a chart's dependencies diff --git a/pkg/lint/rules/dependencies_test.go b/pkg/lint/rules/dependencies_test.go index c0afff133..1369b2372 100644 --- a/pkg/lint/rules/dependencies_test.go +++ b/pkg/lint/rules/dependencies_test.go @@ -19,9 +19,9 @@ import ( "path/filepath" "testing" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/lint/support" + chart "helm.sh/helm/v4/pkg/chart/v2" + chartutil "helm.sh/helm/v4/pkg/chart/v2/util" + "helm.sh/helm/v4/pkg/lint/support" ) func chartWithBadDependencies() chart.Chart { diff --git a/pkg/lint/rules/deprecations.go b/pkg/lint/rules/deprecations.go index 90e7748a4..bd4a4436a 100644 --- a/pkg/lint/rules/deprecations.go +++ b/pkg/lint/rules/deprecations.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package rules // import "helm.sh/helm/v3/pkg/lint/rules" +package rules // import "helm.sh/helm/v4/pkg/lint/rules" import ( "fmt" @@ -25,7 +25,7 @@ import ( "k8s.io/apiserver/pkg/endpoints/deprecation" kscheme "k8s.io/client-go/kubernetes/scheme" - "helm.sh/helm/v3/pkg/chartutil" + chartutil "helm.sh/helm/v4/pkg/chart/v2/util" ) var ( @@ -73,16 +73,16 @@ func validateNoDeprecations(resource *K8sYamlStruct, kubeVersion *chartutil.Kube return err } - maj, err := strconv.Atoi(majorVersion) + major, err := strconv.Atoi(majorVersion) if err != nil { return err } - min, err := strconv.Atoi(minorVersion) + minor, err := strconv.Atoi(minorVersion) if err != nil { return err } - if !deprecation.IsDeprecated(runtimeObject, maj, min) { + if !deprecation.IsDeprecated(runtimeObject, major, minor) { return nil } gvk := fmt.Sprintf("%s %s", resource.APIVersion, resource.Kind) diff --git a/pkg/lint/rules/deprecations_test.go b/pkg/lint/rules/deprecations_test.go index cf2409007..c0e64d04f 100644 --- a/pkg/lint/rules/deprecations_test.go +++ b/pkg/lint/rules/deprecations_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package rules // import "helm.sh/helm/v3/pkg/lint/rules" +package rules // import "helm.sh/helm/v4/pkg/lint/rules" import "testing" diff --git a/pkg/lint/rules/template.go b/pkg/lint/rules/template.go index 5358309e4..4a8c47db5 100644 --- a/pkg/lint/rules/template.go +++ b/pkg/lint/rules/template.go @@ -25,7 +25,6 @@ import ( "os" "path" "path/filepath" - "regexp" "strings" "k8s.io/apimachinery/pkg/api/validation" @@ -33,15 +32,10 @@ import ( "k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/apimachinery/pkg/util/yaml" - "helm.sh/helm/v3/pkg/chart/loader" - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/engine" - "helm.sh/helm/v3/pkg/lint/support" -) - -var ( - crdHookSearch = regexp.MustCompile(`"?helm\.sh/hook"?:\s+crd-install`) - releaseTimeSearch = regexp.MustCompile(`\.Release\.Time`) + "helm.sh/helm/v4/pkg/chart/v2/loader" + chartutil "helm.sh/helm/v4/pkg/chart/v2/util" + "helm.sh/helm/v4/pkg/engine" + "helm.sh/helm/v4/pkg/lint/support" ) // Templates lints the templates in the Linter. @@ -87,7 +81,7 @@ func TemplatesWithSkipSchemaValidation(linter *support.Linter, values map[string // lint ignores import-values // See https://github.com/helm/helm/issues/9658 - if err := chartutil.ProcessDependenciesWithMerge(chart, values); err != nil { + if err := chartutil.ProcessDependencies(chart, values); err != nil { return } @@ -119,14 +113,10 @@ func TemplatesWithSkipSchemaValidation(linter *support.Linter, values map[string - Metadata.Namespace is not set */ for _, template := range chart.Templates { - fileName, data := template.Name, template.Data + fileName := template.Name fpath = fileName linter.RunLinterRule(support.ErrorSev, fpath, validateAllowedExtension(fileName)) - // These are v3 specific checks to make sure and warn people if their - // chart is not compatible with v3 - linter.RunLinterRule(support.WarningSev, fpath, validateNoCRDHooks(data)) - linter.RunLinterRule(support.ErrorSev, fpath, validateNoReleaseTime(data)) // We only apply the following lint rules to yaml files if filepath.Ext(fileName) != ".yaml" || filepath.Ext(fileName) == ".yml" { @@ -294,27 +284,13 @@ func validateMetadataNameFunc(obj *K8sYamlStruct) validation.ValidateNameFunc { } } -func validateNoCRDHooks(manifest []byte) error { - if crdHookSearch.Match(manifest) { - return errors.New("manifest is a crd-install hook. This hook is no longer supported in v3 and all CRDs should also exist the crds/ directory at the top level of the chart") - } - return nil -} - -func validateNoReleaseTime(manifest []byte) error { - if releaseTimeSearch.Match(manifest) { - return errors.New(".Release.Time has been removed in v3, please replace with the `now` function in your templates") - } - return nil -} - // validateMatchSelector ensures that template specs have a selector declared. // See https://github.com/helm/helm/issues/1990 func validateMatchSelector(yamlStruct *K8sYamlStruct, manifest string) error { switch yamlStruct.Kind { case "Deployment", "ReplicaSet", "DaemonSet", "StatefulSet": // verify that matchLabels or matchExpressions is present - if !(strings.Contains(manifest, "matchLabels") || strings.Contains(manifest, "matchExpressions")) { + if !strings.Contains(manifest, "matchLabels") && !strings.Contains(manifest, "matchExpressions") { return fmt.Errorf("a %s must contain matchLabels or matchExpressions, and %q does not", yamlStruct.Kind, yamlStruct.Metadata.Name) } } diff --git a/pkg/lint/rules/template_test.go b/pkg/lint/rules/template_test.go index 80f9b28ed..bd503368d 100644 --- a/pkg/lint/rules/template_test.go +++ b/pkg/lint/rules/template_test.go @@ -23,9 +23,9 @@ import ( "strings" "testing" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/lint/support" + chart "helm.sh/helm/v4/pkg/chart/v2" + chartutil "helm.sh/helm/v4/pkg/chart/v2/util" + "helm.sh/helm/v4/pkg/lint/support" ) const templateTestBasedir = "./testdata/albatross" @@ -85,26 +85,6 @@ func TestTemplateIntegrationHappyPath(t *testing.T) { } } -func TestV3Fail(t *testing.T) { - linter := support.Linter{ChartDir: "./testdata/v3-fail"} - Templates(&linter, values, namespace, strict) - res := linter.Messages - - if len(res) != 3 { - t.Fatalf("Expected 3 errors, got %d, %v", len(res), res) - } - - if !strings.Contains(res[0].Err.Error(), ".Release.Time has been removed in v3") { - t.Errorf("Unexpected error: %s", res[0].Err) - } - if !strings.Contains(res[1].Err.Error(), "manifest is a crd-install hook") { - t.Errorf("Unexpected error: %s", res[1].Err) - } - if !strings.Contains(res[2].Err.Error(), "manifest is a crd-install hook") { - t.Errorf("Unexpected error: %s", res[2].Err) - } -} - func TestMultiTemplateFail(t *testing.T) { linter := support.Linter{ChartDir: "./testdata/multi-template-fail"} Templates(&linter, values, namespace, strict) diff --git a/pkg/lint/rules/testdata/invalidchartfile/Chart.yaml b/pkg/lint/rules/testdata/invalidchartfile/Chart.yaml new file mode 100644 index 000000000..0fd58d1d4 --- /dev/null +++ b/pkg/lint/rules/testdata/invalidchartfile/Chart.yaml @@ -0,0 +1,6 @@ +name: some-chart +apiVersion: v2 +apiVersion: v1 +description: A Helm chart for Kubernetes +version: 1.3.0 +icon: http://example.com diff --git a/pkg/lint/rules/testdata/invalidchartfile/values.yaml b/pkg/lint/rules/testdata/invalidchartfile/values.yaml new file mode 100644 index 000000000..e69de29bb diff --git a/pkg/lint/rules/values.go b/pkg/lint/rules/values.go index 5a051fdac..019e74fa7 100644 --- a/pkg/lint/rules/values.go +++ b/pkg/lint/rules/values.go @@ -21,24 +21,17 @@ import ( "os" "path/filepath" - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/lint/support" + chartutil "helm.sh/helm/v4/pkg/chart/v2/util" + "helm.sh/helm/v4/pkg/lint/support" ) -// Values lints a chart's values.yaml file. -// -// This function is deprecated and will be removed in Helm 4. -func Values(linter *support.Linter) { - ValuesWithOverrides(linter, map[string]interface{}{}) -} - // ValuesWithOverrides tests the values.yaml file. // // If a schema is present in the chart, values are tested against that. Otherwise, // they are only tested for well-formedness. // // If additional values are supplied, they are coalesced into the values in values.yaml. -func ValuesWithOverrides(linter *support.Linter, values map[string]interface{}) { +func ValuesWithOverrides(linter *support.Linter, valueOverrides map[string]interface{}) { file := "values.yaml" vf := filepath.Join(linter.ChartDir, file) fileExists := linter.RunLinterRule(support.InfoSev, file, validateValuesFileExistence(vf)) @@ -47,7 +40,7 @@ func ValuesWithOverrides(linter *support.Linter, values map[string]interface{}) return } - linter.RunLinterRule(support.ErrorSev, file, validateValuesFile(vf, values)) + linter.RunLinterRule(support.ErrorSev, file, validateValuesFile(vf, valueOverrides)) } func validateValuesFileExistence(valuesPath string) error { diff --git a/pkg/lint/rules/values_test.go b/pkg/lint/rules/values_test.go index faa29d48a..348695785 100644 --- a/pkg/lint/rules/values_test.go +++ b/pkg/lint/rules/values_test.go @@ -23,7 +23,7 @@ import ( "github.com/stretchr/testify/assert" - "helm.sh/helm/v3/internal/test/ensure" + "helm.sh/helm/v4/internal/test/ensure" ) var nonExistingValuesFilePath = filepath.Join("/fake/dir", "values.yaml") @@ -96,7 +96,7 @@ func TestValidateValuesFileSchemaFailure(t *testing.T) { t.Fatal("expected values file to fail parsing") } - assert.Contains(t, err.Error(), "Expected: string, given: integer", "integer should be caught by schema") + assert.Contains(t, err.Error(), "- at '/username': got number, want string") } func TestValidateValuesFileSchemaOverrides(t *testing.T) { @@ -129,7 +129,7 @@ func TestValidateValuesFile(t *testing.T) { name: "value not overridden", yaml: "username: admin\npassword:", overrides: map[string]interface{}{"username": "anotherUser"}, - errorMessage: "Expected: string, given: null", + errorMessage: "- at '/password': got null, want string", }, { name: "value overridden", diff --git a/pkg/lint/support/doc.go b/pkg/lint/support/doc.go index bffefe8ff..b007804dc 100644 --- a/pkg/lint/support/doc.go +++ b/pkg/lint/support/doc.go @@ -20,4 +20,4 @@ Package support contains tools for linting charts. Linting is the process of testing charts for errors or warnings regarding formatting, compilation, or standards compliance. */ -package support // import "helm.sh/helm/v3/pkg/lint/support" +package support // import "helm.sh/helm/v4/pkg/lint/support" diff --git a/pkg/plugin/cache/cache.go b/pkg/plugin/cache/cache.go index 5f3345b63..f3e847374 100644 --- a/pkg/plugin/cache/cache.go +++ b/pkg/plugin/cache/cache.go @@ -14,7 +14,7 @@ limitations under the License. */ // Package cache provides a key generator for vcs urls. -package cache // import "helm.sh/helm/v3/pkg/plugin/cache" +package cache // import "helm.sh/helm/v4/pkg/plugin/cache" import ( "net/url" diff --git a/pkg/plugin/hooks.go b/pkg/plugin/hooks.go index e3481515f..10dc8580e 100644 --- a/pkg/plugin/hooks.go +++ b/pkg/plugin/hooks.go @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package plugin // import "helm.sh/helm/v3/pkg/plugin" +package plugin // import "helm.sh/helm/v4/pkg/plugin" // Types of hooks const ( @@ -25,5 +25,8 @@ const ( Update = "update" ) +// PlatformHooks is a map of events to a command for a particular operating system and architecture. +type PlatformHooks map[string][]PlatformCommand + // Hooks is a map of events to commands. type Hooks map[string]string diff --git a/pkg/plugin/installer/base.go b/pkg/plugin/installer/base.go index ba6a55d55..3738246ee 100644 --- a/pkg/plugin/installer/base.go +++ b/pkg/plugin/installer/base.go @@ -13,12 +13,12 @@ See the License for the specific language governing permissions and limitations under the License. */ -package installer // import "helm.sh/helm/v3/pkg/plugin/installer" +package installer // import "helm.sh/helm/v4/pkg/plugin/installer" import ( "path/filepath" - "helm.sh/helm/v3/pkg/cli" + "helm.sh/helm/v4/pkg/cli" ) type base struct { diff --git a/pkg/plugin/installer/base_test.go b/pkg/plugin/installer/base_test.go index 38ef28c3e..f4dd6d6be 100644 --- a/pkg/plugin/installer/base_test.go +++ b/pkg/plugin/installer/base_test.go @@ -11,7 +11,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package installer // import "helm.sh/helm/v3/pkg/plugin/installer" +package installer // import "helm.sh/helm/v4/pkg/plugin/installer" import ( "os" diff --git a/pkg/plugin/installer/doc.go b/pkg/plugin/installer/doc.go index 3e3b2ebeb..b927dbd37 100644 --- a/pkg/plugin/installer/doc.go +++ b/pkg/plugin/installer/doc.go @@ -14,4 +14,4 @@ limitations under the License. */ // Package installer provides an interface for installing Helm plugins. -package installer // import "helm.sh/helm/v3/pkg/plugin/installer" +package installer // import "helm.sh/helm/v4/pkg/plugin/installer" diff --git a/pkg/plugin/installer/http_installer.go b/pkg/plugin/installer/http_installer.go index 8e1eb2398..7b6f28db1 100644 --- a/pkg/plugin/installer/http_installer.go +++ b/pkg/plugin/installer/http_installer.go @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package installer // import "helm.sh/helm/v3/pkg/plugin/installer" +package installer // import "helm.sh/helm/v4/pkg/plugin/installer" import ( "archive/tar" @@ -22,6 +22,7 @@ import ( "errors" "fmt" "io" + "log/slog" "os" "path" "path/filepath" @@ -30,11 +31,11 @@ import ( securejoin "github.com/cyphar/filepath-securejoin" - "helm.sh/helm/v3/internal/third_party/dep/fs" - "helm.sh/helm/v3/pkg/cli" - "helm.sh/helm/v3/pkg/getter" - "helm.sh/helm/v3/pkg/helmpath" - "helm.sh/helm/v3/pkg/plugin/cache" + "helm.sh/helm/v4/internal/third_party/dep/fs" + "helm.sh/helm/v4/pkg/cli" + "helm.sh/helm/v4/pkg/getter" + "helm.sh/helm/v4/pkg/helmpath" + "helm.sh/helm/v4/pkg/plugin/cache" ) // HTTPInstaller installs plugins from an archive served by a web server. @@ -145,7 +146,7 @@ func (i *HTTPInstaller) Install() error { return err } - debug("copying %s to %s", src, i.Path()) + slog.Debug("copying", "source", src, "path", i.Path()) return fs.CopyDir(src, i.Path()) } @@ -157,7 +158,7 @@ func (i *HTTPInstaller) Update() error { // Path is overridden because we want to join on the plugin name not the file name func (i HTTPInstaller) Path() string { - if i.base.Source == "" { + if i.Source == "" { return "" } return helmpath.DataPath("plugins", i.PluginName) @@ -207,6 +208,9 @@ func cleanJoin(root, dest string) (string, error) { } // SecureJoin will do some cleaning, as well as some rudimentary checking of symlinks. + // The directory needs to be cleaned prior to passing to SecureJoin or the location may end up + // being wrong or returning an error. This was introduced in v0.4.0. + root = filepath.Clean(root) newpath, err := securejoin.SecureJoin(root, dest) if err != nil { return "", err diff --git a/pkg/plugin/installer/http_installer_test.go b/pkg/plugin/installer/http_installer_test.go index 31d6b90e2..ed4b73b35 100644 --- a/pkg/plugin/installer/http_installer_test.go +++ b/pkg/plugin/installer/http_installer_test.go @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package installer // import "helm.sh/helm/v3/pkg/plugin/installer" +package installer // import "helm.sh/helm/v4/pkg/plugin/installer" import ( "archive/tar" @@ -31,9 +31,9 @@ import ( "syscall" "testing" - "helm.sh/helm/v3/internal/test/ensure" - "helm.sh/helm/v3/pkg/getter" - "helm.sh/helm/v3/pkg/helmpath" + "helm.sh/helm/v4/internal/test/ensure" + "helm.sh/helm/v4/pkg/getter" + "helm.sh/helm/v4/pkg/helmpath" ) var _ Installer = new(HTTPInstaller) diff --git a/pkg/plugin/installer/installer.go b/pkg/plugin/installer/installer.go index a738311ad..d88737ebf 100644 --- a/pkg/plugin/installer/installer.go +++ b/pkg/plugin/installer/installer.go @@ -17,14 +17,12 @@ package installer import ( "errors" - "fmt" - "log" "net/http" "os" "path/filepath" "strings" - "helm.sh/helm/v3/pkg/plugin" + "helm.sh/helm/v4/pkg/plugin" ) // ErrMissingMetadata indicates that plugin.yaml is missing. @@ -124,11 +122,3 @@ func isPlugin(dirname string) bool { _, err := os.Stat(filepath.Join(dirname, plugin.PluginFileName)) return err == nil } - -var logger = log.New(os.Stderr, "[debug] ", log.Lshortfile) - -func debug(format string, args ...interface{}) { - if Debug { - logger.Output(2, fmt.Sprintf(format, args...)) - } -} diff --git a/pkg/plugin/installer/local_installer.go b/pkg/plugin/installer/local_installer.go index 89de204b3..109f4f236 100644 --- a/pkg/plugin/installer/local_installer.go +++ b/pkg/plugin/installer/local_installer.go @@ -13,11 +13,12 @@ See the License for the specific language governing permissions and limitations under the License. */ -package installer // import "helm.sh/helm/v3/pkg/plugin/installer" +package installer // import "helm.sh/helm/v4/pkg/plugin/installer" import ( "errors" "fmt" + "log/slog" "os" "path/filepath" ) @@ -57,12 +58,12 @@ func (i *LocalInstaller) Install() error { if !isPlugin(i.Source) { return ErrMissingMetadata } - debug("symlinking %s to %s", i.Source, i.Path()) + slog.Debug("symlinking", "source", i.Source, "path", i.Path()) return os.Symlink(i.Source, i.Path()) } // Update updates a local repository func (i *LocalInstaller) Update() error { - debug("local repository is auto-updated") + slog.Debug("local repository is auto-updated") return nil } diff --git a/pkg/plugin/installer/local_installer_test.go b/pkg/plugin/installer/local_installer_test.go index 51408f128..b28920af4 100644 --- a/pkg/plugin/installer/local_installer_test.go +++ b/pkg/plugin/installer/local_installer_test.go @@ -13,14 +13,14 @@ See the License for the specific language governing permissions and limitations under the License. */ -package installer // import "helm.sh/helm/v3/pkg/plugin/installer" +package installer // import "helm.sh/helm/v4/pkg/plugin/installer" import ( "os" "path/filepath" "testing" - "helm.sh/helm/v3/pkg/helmpath" + "helm.sh/helm/v4/pkg/helmpath" ) var _ Installer = new(LocalInstaller) diff --git a/pkg/plugin/installer/vcs_installer.go b/pkg/plugin/installer/vcs_installer.go index 23364a89a..3e53cbf11 100644 --- a/pkg/plugin/installer/vcs_installer.go +++ b/pkg/plugin/installer/vcs_installer.go @@ -13,21 +13,22 @@ See the License for the specific language governing permissions and limitations under the License. */ -package installer // import "helm.sh/helm/v3/pkg/plugin/installer" +package installer // import "helm.sh/helm/v4/pkg/plugin/installer" import ( "errors" "fmt" stdfs "io/fs" + "log/slog" "os" "sort" "github.com/Masterminds/semver/v3" "github.com/Masterminds/vcs" - "helm.sh/helm/v3/internal/third_party/dep/fs" - "helm.sh/helm/v3/pkg/helmpath" - "helm.sh/helm/v3/pkg/plugin/cache" + "helm.sh/helm/v4/internal/third_party/dep/fs" + "helm.sh/helm/v4/pkg/helmpath" + "helm.sh/helm/v4/pkg/plugin/cache" ) // VCSInstaller installs plugins from remote a repository. @@ -65,7 +66,7 @@ func NewVCSInstaller(source, version string) (*VCSInstaller, error) { Version: version, base: newBase(source), } - return i, err + return i, nil } // Install clones a remote repository and installs into the plugin directory. @@ -90,13 +91,13 @@ func (i *VCSInstaller) Install() error { return ErrMissingMetadata } - debug("copying %s to %s", i.Repo.LocalPath(), i.Path()) + slog.Debug("copying files", "source", i.Repo.LocalPath(), "destination", i.Path()) return fs.CopyDir(i.Repo.LocalPath(), i.Path()) } // Update updates a remote repository func (i *VCSInstaller) Update() error { - debug("updating %s", i.Repo.Remote()) + slog.Debug("updating", "source", i.Repo.Remote()) if i.Repo.IsDirty() { return errors.New("plugin repo was modified") } @@ -130,7 +131,7 @@ func (i *VCSInstaller) solveVersion(repo vcs.Repo) (string, error) { if err != nil { return "", err } - debug("found refs: %s", refs) + slog.Debug("found refs", "refs", refs) // Convert and filter the list to semver.Version instances semvers := getSemVers(refs) @@ -141,7 +142,7 @@ func (i *VCSInstaller) solveVersion(repo vcs.Repo) (string, error) { if constraint.Check(v) { // If the constraint passes get the original reference ver := v.Original() - debug("setting to %s", ver) + slog.Debug("setting to version", "version", ver) return ver, nil } } @@ -151,17 +152,17 @@ func (i *VCSInstaller) solveVersion(repo vcs.Repo) (string, error) { // setVersion attempts to checkout the version func (i *VCSInstaller) setVersion(repo vcs.Repo, ref string) error { - debug("setting version to %q", i.Version) + slog.Debug("setting version", "version", i.Version) return repo.UpdateVersion(ref) } // sync will clone or update a remote repo. func (i *VCSInstaller) sync(repo vcs.Repo) error { if _, err := os.Stat(repo.LocalPath()); errors.Is(err, stdfs.ErrNotExist) { - debug("cloning %s to %s", repo.Remote(), repo.LocalPath()) + slog.Debug("cloning", "source", repo.Remote(), "destination", repo.LocalPath()) return repo.Get() } - debug("updating %s", repo.Remote()) + slog.Debug("updating", "source", repo.Remote(), "destination", repo.LocalPath()) return repo.Update() } diff --git a/pkg/plugin/installer/vcs_installer_test.go b/pkg/plugin/installer/vcs_installer_test.go index 0bb0b6780..fbb5d354e 100644 --- a/pkg/plugin/installer/vcs_installer_test.go +++ b/pkg/plugin/installer/vcs_installer_test.go @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package installer // import "helm.sh/helm/v3/pkg/plugin/installer" +package installer // import "helm.sh/helm/v4/pkg/plugin/installer" import ( "fmt" @@ -23,8 +23,8 @@ import ( "github.com/Masterminds/vcs" - "helm.sh/helm/v3/internal/test/ensure" - "helm.sh/helm/v3/pkg/helmpath" + "helm.sh/helm/v4/internal/test/ensure" + "helm.sh/helm/v4/pkg/helmpath" ) var _ Installer = new(VCSInstaller) diff --git a/pkg/plugin/plugin.go b/pkg/plugin/plugin.go index 47d5628ba..9d79ab4fc 100644 --- a/pkg/plugin/plugin.go +++ b/pkg/plugin/plugin.go @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package plugin // import "helm.sh/helm/v3/pkg/plugin" +package plugin // import "helm.sh/helm/v4/pkg/plugin" import ( "fmt" @@ -26,7 +26,7 @@ import ( "sigs.k8s.io/yaml" - "helm.sh/helm/v3/pkg/cli" + "helm.sh/helm/v4/pkg/cli" ) const PluginFileName = "plugin.yaml" @@ -43,9 +43,10 @@ type Downloaders struct { // PlatformCommand represents a command for a particular operating system and architecture type PlatformCommand struct { - OperatingSystem string `json:"os"` - Architecture string `json:"arch"` - Command string `json:"command"` + OperatingSystem string `json:"os"` + Architecture string `json:"arch"` + Command string `json:"command"` + Args []string `json:"args"` } // Metadata describes a plugin. @@ -64,23 +65,35 @@ type Metadata struct { // Description is a long description shown in places like `helm help` Description string `json:"description"` - // Command is the command, as a single string. + // PlatformCommand is the plugin command, with a platform selector and support for args. // - // The command will be passed through environment expansion, so env vars can + // The command and args will be passed through environment expansion, so env vars can // be present in this command. Unless IgnoreFlags is set, this will // also merge the flags passed from Helm. // - // Note that command is not executed in a shell. To do so, we suggest + // Note that the command is not executed in a shell. To do so, we suggest // pointing the command to a shell script. // - // The following rules will apply to processing commands: - // - If platformCommand is present, it will be searched first + // The following rules will apply to processing platform commands: + // - If PlatformCommand is present, it will be used // - If both OS and Arch match the current platform, search will stop and the command will be executed - // - If OS matches and there is no more specific match, the command will be executed + // - If OS matches and Arch is empty, the command will be executed // - If no OS/Arch match is found, the default command will be executed - // - If no command is present and no matches are found in platformCommand, Helm will exit with an error + // - If no matches are found in platformCommand, Helm will exit with an error PlatformCommand []PlatformCommand `json:"platformCommand"` - Command string `json:"command"` + + // Command is the plugin command, as a single string. + // Providing a command will result in an error if PlatformCommand is also set. + // + // The command will be passed through environment expansion, so env vars can + // be present in this command. Unless IgnoreFlags is set, this will + // also merge the flags passed from Helm. + // + // Note that command is not executed in a shell. To do so, we suggest + // pointing the command to a shell script. + // + // DEPRECATED: Use PlatformCommand instead. Remove in Helm 4. + Command string `json:"command"` // IgnoreFlags ignores any flags passed in from Helm // @@ -89,7 +102,31 @@ type Metadata struct { // the `--debug` flag will be discarded. IgnoreFlags bool `json:"ignoreFlags"` - // Hooks are commands that will run on events. + // PlatformHooks are commands that will run on plugin events, with a platform selector and support for args. + // + // The command and args will be passed through environment expansion, so env vars can + // be present in the command. + // + // Note that the command is not executed in a shell. To do so, we suggest + // pointing the command to a shell script. + // + // The following rules will apply to processing platform hooks: + // - If PlatformHooks is present, it will be used + // - If both OS and Arch match the current platform, search will stop and the command will be executed + // - If OS matches and Arch is empty, the command will be executed + // - If no OS/Arch match is found, the default command will be executed + // - If no matches are found in platformHooks, Helm will skip the event + PlatformHooks PlatformHooks `json:"platformHooks"` + + // Hooks are commands that will run on plugin events, as a single string. + // Providing a hooks will result in an error if PlatformHooks is also set. + // + // The command will be passed through environment expansion, so env vars can + // be present in this command. + // + // Note that the command is executed in the sh shell. + // + // DEPRECATED: Use PlatformHooks instead. Remove in Helm 4. Hooks Hooks // Downloaders field is used if the plugin supply downloader mechanism @@ -111,62 +148,106 @@ type Plugin struct { Dir string } -// The following rules will apply to processing the Plugin.PlatformCommand.Command: -// - If both OS and Arch match the current platform, search will stop and the command will be prepared for execution -// - If OS matches and there is no more specific match, the command will be prepared for execution -// - If no OS/Arch match is found, return nil -func getPlatformCommand(cmds []PlatformCommand) []string { - var command []string +// Returns command and args strings based on the following rules in priority order: +// - From the PlatformCommand where OS and Arch match the current platform +// - From the PlatformCommand where OS matches the current platform and Arch is empty/unspecified +// - From the PlatformCommand where OS is empty/unspecified and Arch matches the current platform +// - From the PlatformCommand where OS and Arch are both empty/unspecified +// - Return nil, nil +func getPlatformCommand(cmds []PlatformCommand) ([]string, []string) { + var command, args []string + found := false + foundOs := false + eq := strings.EqualFold for _, c := range cmds { - if eq(c.OperatingSystem, runtime.GOOS) { - command = strings.Split(c.Command, " ") - } if eq(c.OperatingSystem, runtime.GOOS) && eq(c.Architecture, runtime.GOARCH) { - return strings.Split(c.Command, " ") + // Return early for an exact match + return strings.Split(c.Command, " "), c.Args + } + + if (len(c.OperatingSystem) > 0 && !eq(c.OperatingSystem, runtime.GOOS)) || len(c.Architecture) > 0 { + // Skip if OS is not empty and doesn't match or if arch is set as a set arch requires an OS match + continue + } + + if !foundOs && len(c.OperatingSystem) > 0 && eq(c.OperatingSystem, runtime.GOOS) { + // First OS match with empty arch, can only be overridden by a direct match + command = strings.Split(c.Command, " ") + args = c.Args + found = true + foundOs = true + } else if !found { + // First empty match, can be overridden by a direct match or an OS match + command = strings.Split(c.Command, " ") + args = c.Args + found = true } } - return command + + return command, args } -// PrepareCommand takes a Plugin.PlatformCommand.Command, a Plugin.Command and will applying the following processing: -// - If platformCommand is present, it will be searched first -// - If both OS and Arch match the current platform, search will stop and the command will be prepared for execution -// - If OS matches and there is no more specific match, the command will be prepared for execution -// - If no OS/Arch match is found, the default command will be prepared for execution -// - If no command is present and no matches are found in platformCommand, will exit with an error +// PrepareCommands takes a []Plugin.PlatformCommand +// and prepares the command and arguments for execution. // // It merges extraArgs into any arguments supplied in the plugin. It -// returns the name of the command and an args array. +// returns the main command and an args array. // // The result is suitable to pass to exec.Command. -func (p *Plugin) PrepareCommand(extraArgs []string) (string, []string, error) { - var parts []string - platCmdLen := len(p.Metadata.PlatformCommand) - if platCmdLen > 0 { - parts = getPlatformCommand(p.Metadata.PlatformCommand) - } - if platCmdLen == 0 || parts == nil { - parts = strings.Split(p.Metadata.Command, " ") - } - if len(parts) == 0 || parts[0] == "" { +func PrepareCommands(cmds []PlatformCommand, expandArgs bool, extraArgs []string) (string, []string, error) { + cmdParts, args := getPlatformCommand(cmds) + if len(cmdParts) == 0 || cmdParts[0] == "" { return "", nil, fmt.Errorf("no plugin command is applicable") } - main := os.ExpandEnv(parts[0]) + main := os.ExpandEnv(cmdParts[0]) baseArgs := []string{} - if len(parts) > 1 { - for _, cmdpart := range parts[1:] { - cmdexp := os.ExpandEnv(cmdpart) - baseArgs = append(baseArgs, cmdexp) + if len(cmdParts) > 1 { + for _, cmdPart := range cmdParts[1:] { + if expandArgs { + baseArgs = append(baseArgs, os.ExpandEnv(cmdPart)) + } else { + baseArgs = append(baseArgs, cmdPart) + } } } - if !p.Metadata.IgnoreFlags { + + for _, arg := range args { + if expandArgs { + baseArgs = append(baseArgs, os.ExpandEnv(arg)) + } else { + baseArgs = append(baseArgs, arg) + } + } + + if len(extraArgs) > 0 { baseArgs = append(baseArgs, extraArgs...) } + return main, baseArgs, nil } +// PrepareCommand gets the correct command and arguments for a plugin. +// +// It merges extraArgs into any arguments supplied in the plugin. It returns the name of the command and an args array. +// +// The result is suitable to pass to exec.Command. +func (p *Plugin) PrepareCommand(extraArgs []string) (string, []string, error) { + var extraArgsIn []string + + if !p.Metadata.IgnoreFlags { + extraArgsIn = extraArgs + } + + cmds := p.Metadata.PlatformCommand + if len(cmds) == 0 && len(p.Metadata.Command) > 0 { + cmds = []PlatformCommand{{Command: p.Metadata.Command}} + } + + return PrepareCommands(cmds, true, extraArgsIn) +} + // validPluginName is a regular expression that validates plugin names. // // Plugin names can only contain the ASCII characters a-z, A-Z, 0-9, ​_​ and ​-. @@ -183,6 +264,14 @@ func validatePluginData(plug *Plugin, filepath string) error { } plug.Metadata.Usage = sanitizeString(plug.Metadata.Usage) + if len(plug.Metadata.PlatformCommand) > 0 && len(plug.Metadata.Command) > 0 { + return fmt.Errorf("both platformCommand and command are set in %q", filepath) + } + + if len(plug.Metadata.PlatformHooks) > 0 && len(plug.Metadata.Hooks) > 0 { + return fmt.Errorf("both platformHooks and hooks are set in %q", filepath) + } + // We could also validate SemVer, executable, and other fields should we so choose. return nil } diff --git a/pkg/plugin/plugin_test.go b/pkg/plugin/plugin_test.go index b81797390..b96428f6b 100644 --- a/pkg/plugin/plugin_test.go +++ b/pkg/plugin/plugin_test.go @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package plugin // import "helm.sh/helm/v3/pkg/plugin" +package plugin // import "helm.sh/helm/v4/pkg/plugin" import ( "fmt" @@ -23,166 +23,259 @@ import ( "runtime" "testing" - "helm.sh/helm/v3/pkg/cli" + "helm.sh/helm/v4/pkg/cli" ) -func checkCommand(p *Plugin, extraArgs []string, osStrCmp string, t *testing.T) { - cmd, args, err := p.PrepareCommand(extraArgs) - if err != nil { - t.Fatal(err) - } - if cmd != "echo" { - t.Fatalf("Expected echo, got %q", cmd) - } - - if l := len(args); l != 5 { - t.Fatalf("expected 5 args, got %d", l) - } +func TestPrepareCommand(t *testing.T) { + cmdMain := "sh" + cmdArgs := []string{"-c", "echo \"test\""} - expect := []string{"-n", osStrCmp, "--debug", "--foo", "bar"} - for i := 0; i < len(args); i++ { - if expect[i] != args[i] { - t.Errorf("Expected arg=%q, got %q", expect[i], args[i]) - } + p := &Plugin{ + Dir: "/tmp", // Unused + Metadata: &Metadata{ + Name: "test", + Command: "echo \"error\"", + PlatformCommand: []PlatformCommand{ + {OperatingSystem: "no-os", Architecture: "no-arch", Command: "pwsh", Args: []string{"-c", "echo \"error\""}}, + {OperatingSystem: runtime.GOOS, Architecture: "no-arch", Command: "pwsh", Args: []string{"-c", "echo \"error\""}}, + {OperatingSystem: runtime.GOOS, Architecture: "", Command: "pwsh", Args: []string{"-c", "echo \"error\""}}, + {OperatingSystem: runtime.GOOS, Architecture: runtime.GOARCH, Command: cmdMain, Args: cmdArgs}, + }, + }, } - // Test with IgnoreFlags. This should omit --debug, --foo, bar - p.Metadata.IgnoreFlags = true - cmd, args, err = p.PrepareCommand(extraArgs) + cmd, args, err := p.PrepareCommand([]string{}) if err != nil { t.Fatal(err) } - if cmd != "echo" { - t.Fatalf("Expected echo, got %q", cmd) + if cmd != cmdMain { + t.Fatalf("Expected %q, got %q", cmdMain, cmd) } - if l := len(args); l != 2 { - t.Fatalf("expected 2 args, got %d", l) - } - expect = []string{"-n", osStrCmp} - for i := 0; i < len(args); i++ { - if expect[i] != args[i] { - t.Errorf("Expected arg=%q, got %q", expect[i], args[i]) - } + if !reflect.DeepEqual(args, cmdArgs) { + t.Fatalf("Expected %v, got %v", cmdArgs, args) } } -func TestPrepareCommand(t *testing.T) { +func TestPrepareCommandExtraArgs(t *testing.T) { + cmdMain := "sh" + cmdArgs := []string{"-c", "echo \"test\""} + extraArgs := []string{"--debug", "--foo", "bar"} + p := &Plugin{ Dir: "/tmp", // Unused Metadata: &Metadata{ Name: "test", - Command: "echo -n foo", + Command: "echo \"error\"", + PlatformCommand: []PlatformCommand{ + {OperatingSystem: "no-os", Architecture: "no-arch", Command: "pwsh", Args: []string{"-c", "echo \"error\""}}, + {OperatingSystem: runtime.GOOS, Architecture: runtime.GOARCH, Command: cmdMain, Args: cmdArgs}, + {OperatingSystem: runtime.GOOS, Architecture: "no-arch", Command: "pwsh", Args: []string{"-c", "echo \"error\""}}, + {OperatingSystem: runtime.GOOS, Architecture: "", Command: "pwsh", Args: []string{"-c", "echo \"error\""}}, + }, }, } - argv := []string{"--debug", "--foo", "bar"} - checkCommand(p, argv, "foo", t) + expectedArgs := append(cmdArgs, extraArgs...) + + cmd, args, err := p.PrepareCommand(extraArgs) + if err != nil { + t.Fatal(err) + } + if cmd != cmdMain { + t.Fatalf("Expected %q, got %q", cmdMain, cmd) + } + if !reflect.DeepEqual(args, expectedArgs) { + t.Fatalf("Expected %v, got %v", expectedArgs, args) + } } -func TestPlatformPrepareCommand(t *testing.T) { +func TestPrepareCommandExtraArgsIgnored(t *testing.T) { + cmdMain := "sh" + cmdArgs := []string{"-c", "echo \"test\""} + extraArgs := []string{"--debug", "--foo", "bar"} + p := &Plugin{ Dir: "/tmp", // Unused Metadata: &Metadata{ Name: "test", - Command: "echo -n os-arch", + Command: "echo \"error\"", PlatformCommand: []PlatformCommand{ - {OperatingSystem: "linux", Architecture: "386", Command: "echo -n linux-386"}, - {OperatingSystem: "linux", Architecture: "amd64", Command: "echo -n linux-amd64"}, - {OperatingSystem: "linux", Architecture: "arm64", Command: "echo -n linux-arm64"}, - {OperatingSystem: "linux", Architecture: "ppc64le", Command: "echo -n linux-ppc64le"}, - {OperatingSystem: "linux", Architecture: "s390x", Command: "echo -n linux-s390x"}, - {OperatingSystem: "linux", Architecture: "riscv64", Command: "echo -n linux-riscv64"}, - {OperatingSystem: "linux", Architecture: "loong64", Command: "echo -n linux-loong64"}, - {OperatingSystem: "windows", Architecture: "amd64", Command: "echo -n win-64"}, + {OperatingSystem: "no-os", Architecture: "no-arch", Command: "pwsh", Args: []string{"-c", "echo \"error\""}}, + {OperatingSystem: runtime.GOOS, Architecture: runtime.GOARCH, Command: cmdMain, Args: cmdArgs}, + {OperatingSystem: runtime.GOOS, Architecture: "no-arch", Command: "pwsh", Args: []string{"-c", "echo \"error\""}}, + {OperatingSystem: runtime.GOOS, Architecture: "", Command: "pwsh", Args: []string{"-c", "echo \"error\""}}, }, + IgnoreFlags: true, }, } - var osStrCmp string - os := runtime.GOOS - arch := runtime.GOARCH - if os == "linux" && arch == "386" { - osStrCmp = "linux-386" - } else if os == "linux" && arch == "amd64" { - osStrCmp = "linux-amd64" - } else if os == "linux" && arch == "arm64" { - osStrCmp = "linux-arm64" - } else if os == "linux" && arch == "ppc64le" { - osStrCmp = "linux-ppc64le" - } else if os == "linux" && arch == "s390x" { - osStrCmp = "linux-s390x" - } else if os == "linux" && arch == "riscv64" { - osStrCmp = "linux-riscv64" - } else if os == "linux" && arch == "loong64" { - osStrCmp = "linux-loong64" - } else if os == "windows" && arch == "amd64" { - osStrCmp = "win-64" - } else { - osStrCmp = "os-arch" - } - - argv := []string{"--debug", "--foo", "bar"} - checkCommand(p, argv, osStrCmp, t) + + cmd, args, err := p.PrepareCommand(extraArgs) + if err != nil { + t.Fatal(err) + } + if cmd != cmdMain { + t.Fatalf("Expected %q, got %q", cmdMain, cmd) + } + if !reflect.DeepEqual(args, cmdArgs) { + t.Fatalf("Expected %v, got %v", cmdArgs, args) + } } -func TestPartialPlatformPrepareCommand(t *testing.T) { - p := &Plugin{ - Dir: "/tmp", // Unused - Metadata: &Metadata{ - Name: "test", - Command: "echo -n os-arch", - PlatformCommand: []PlatformCommand{ - {OperatingSystem: "linux", Architecture: "386", Command: "echo -n linux-386"}, - {OperatingSystem: "windows", Architecture: "amd64", Command: "echo -n win-64"}, - }, - }, +func TestPrepareCommands(t *testing.T) { + cmdMain := "sh" + cmdArgs := []string{"-c", "echo \"test\""} + + cmds := []PlatformCommand{ + {OperatingSystem: "no-os", Architecture: "no-arch", Command: "pwsh", Args: []string{"-c", "echo \"error\""}}, + {OperatingSystem: runtime.GOOS, Architecture: runtime.GOARCH, Command: cmdMain, Args: cmdArgs}, + {OperatingSystem: runtime.GOOS, Architecture: "no-arch", Command: "pwsh", Args: []string{"-c", "echo \"error\""}}, + {OperatingSystem: runtime.GOOS, Architecture: "", Command: "pwsh", Args: []string{"-c", "echo \"error\""}}, + } + + cmd, args, err := PrepareCommands(cmds, true, []string{}) + if err != nil { + t.Fatal(err) + } + if cmd != cmdMain { + t.Fatalf("Expected %q, got %q", cmdMain, cmd) } - var osStrCmp string - os := runtime.GOOS - arch := runtime.GOARCH - if os == "linux" { - osStrCmp = "linux-386" - } else if os == "windows" && arch == "amd64" { - osStrCmp = "win-64" - } else { - osStrCmp = "os-arch" + if !reflect.DeepEqual(args, cmdArgs) { + t.Fatalf("Expected %v, got %v", cmdArgs, args) + } +} + +func TestPrepareCommandsExtraArgs(t *testing.T) { + cmdMain := "sh" + cmdArgs := []string{"-c", "echo \"test\""} + extraArgs := []string{"--debug", "--foo", "bar"} + + cmds := []PlatformCommand{ + {OperatingSystem: "no-os", Architecture: "no-arch", Command: "pwsh", Args: []string{"-c", "echo \"error\""}}, + {OperatingSystem: runtime.GOOS, Architecture: runtime.GOARCH, Command: "sh", Args: []string{"-c", "echo \"test\""}}, + {OperatingSystem: runtime.GOOS, Architecture: "no-arch", Command: "pwsh", Args: []string{"-c", "echo \"error\""}}, + {OperatingSystem: runtime.GOOS, Architecture: "", Command: "pwsh", Args: []string{"-c", "echo \"error\""}}, } - argv := []string{"--debug", "--foo", "bar"} - checkCommand(p, argv, osStrCmp, t) + expectedArgs := append(cmdArgs, extraArgs...) + + cmd, args, err := PrepareCommands(cmds, true, extraArgs) + if err != nil { + t.Fatal(err) + } + if cmd != cmdMain { + t.Fatalf("Expected %q, got %q", cmdMain, cmd) + } + if !reflect.DeepEqual(args, expectedArgs) { + t.Fatalf("Expected %v, got %v", expectedArgs, args) + } } -func TestNoPrepareCommand(t *testing.T) { - p := &Plugin{ - Dir: "/tmp", // Unused - Metadata: &Metadata{ - Name: "test", - }, +func TestPrepareCommandsNoArch(t *testing.T) { + cmdMain := "sh" + cmdArgs := []string{"-c", "echo \"test\""} + + cmds := []PlatformCommand{ + {OperatingSystem: "no-os", Architecture: "no-arch", Command: "pwsh", Args: []string{"-c", "echo \"error\""}}, + {OperatingSystem: runtime.GOOS, Architecture: "", Command: "sh", Args: []string{"-c", "echo \"test\""}}, + {OperatingSystem: runtime.GOOS, Architecture: "no-arch", Command: "pwsh", Args: []string{"-c", "echo \"error\""}}, } - argv := []string{"--debug", "--foo", "bar"} - _, _, err := p.PrepareCommand(argv) - if err == nil { - t.Fatalf("Expected error to be returned") + cmd, args, err := PrepareCommands(cmds, true, []string{}) + if err != nil { + t.Fatal(err) + } + if cmd != cmdMain { + t.Fatalf("Expected %q, got %q", cmdMain, cmd) + } + if !reflect.DeepEqual(args, cmdArgs) { + t.Fatalf("Expected %v, got %v", cmdArgs, args) } } -func TestNoMatchPrepareCommand(t *testing.T) { - p := &Plugin{ - Dir: "/tmp", // Unused - Metadata: &Metadata{ - Name: "test", - PlatformCommand: []PlatformCommand{ - {OperatingSystem: "no-os", Architecture: "amd64", Command: "echo -n linux-386"}, - }, - }, +func TestPrepareCommandsNoOsNoArch(t *testing.T) { + cmdMain := "sh" + cmdArgs := []string{"-c", "echo \"test\""} + + cmds := []PlatformCommand{ + {OperatingSystem: "no-os", Architecture: "no-arch", Command: "pwsh", Args: []string{"-c", "echo \"error\""}}, + {OperatingSystem: "", Architecture: "", Command: "sh", Args: []string{"-c", "echo \"test\""}}, + {OperatingSystem: runtime.GOOS, Architecture: "no-arch", Command: "pwsh", Args: []string{"-c", "echo \"error\""}}, } - argv := []string{"--debug", "--foo", "bar"} - if _, _, err := p.PrepareCommand(argv); err == nil { + cmd, args, err := PrepareCommands(cmds, true, []string{}) + if err != nil { + t.Fatal(err) + } + if cmd != cmdMain { + t.Fatalf("Expected %q, got %q", cmdMain, cmd) + } + if !reflect.DeepEqual(args, cmdArgs) { + t.Fatalf("Expected %v, got %v", cmdArgs, args) + } +} + +func TestPrepareCommandsNoMatch(t *testing.T) { + cmds := []PlatformCommand{ + {OperatingSystem: "no-os", Architecture: "no-arch", Command: "sh", Args: []string{"-c", "echo \"test\""}}, + {OperatingSystem: runtime.GOOS, Architecture: "no-arch", Command: "sh", Args: []string{"-c", "echo \"test\""}}, + {OperatingSystem: "no-os", Architecture: runtime.GOARCH, Command: "sh", Args: []string{"-c", "echo \"test\""}}, + } + + if _, _, err := PrepareCommands(cmds, true, []string{}); err == nil { t.Fatalf("Expected error to be returned") } } +func TestPrepareCommandsNoCommands(t *testing.T) { + cmds := []PlatformCommand{} + + if _, _, err := PrepareCommands(cmds, true, []string{}); err == nil { + t.Fatalf("Expected error to be returned") + } +} + +func TestPrepareCommandsExpand(t *testing.T) { + t.Setenv("TEST", "test") + cmdMain := "sh" + cmdArgs := []string{"-c", "echo \"${TEST}\""} + cmds := []PlatformCommand{ + {OperatingSystem: "", Architecture: "", Command: cmdMain, Args: cmdArgs}, + } + + expectedArgs := []string{"-c", "echo \"test\""} + + cmd, args, err := PrepareCommands(cmds, true, []string{}) + if err != nil { + t.Fatal(err) + } + if cmd != cmdMain { + t.Fatalf("Expected %q, got %q", cmdMain, cmd) + } + if !reflect.DeepEqual(args, expectedArgs) { + t.Fatalf("Expected %v, got %v", expectedArgs, args) + } +} + +func TestPrepareCommandsNoExpand(t *testing.T) { + t.Setenv("TEST", "test") + cmdMain := "sh" + cmdArgs := []string{"-c", "echo \"${TEST}\""} + cmds := []PlatformCommand{ + {OperatingSystem: "", Architecture: "", Command: cmdMain, Args: cmdArgs}, + } + + cmd, args, err := PrepareCommands(cmds, false, []string{}) + if err != nil { + t.Fatal(err) + } + if cmd != cmdMain { + t.Fatalf("Expected %q, got %q", cmdMain, cmd) + } + if !reflect.DeepEqual(args, cmdArgs) { + t.Fatalf("Expected %v, got %v", cmdArgs, args) + } +} + func TestLoadDir(t *testing.T) { dirname := "testdata/plugdir/good/hello" plug, err := LoadDir(dirname) @@ -199,10 +292,16 @@ func TestLoadDir(t *testing.T) { Version: "0.1.0", Usage: "usage", Description: "description", - Command: "$HELM_PLUGIN_DIR/hello.sh", + PlatformCommand: []PlatformCommand{ + {OperatingSystem: "linux", Architecture: "", Command: "sh", Args: []string{"-c", "${HELM_PLUGIN_DIR}/hello.sh"}}, + {OperatingSystem: "windows", Architecture: "", Command: "pwsh", Args: []string{"-c", "${HELM_PLUGIN_DIR}/hello.ps1"}}, + }, IgnoreFlags: true, - Hooks: map[string]string{ - Install: "echo installing...", + PlatformHooks: map[string][]PlatformCommand{ + Install: { + {OperatingSystem: "linux", Architecture: "", Command: "sh", Args: []string{"-c", "echo \"installing...\""}}, + {OperatingSystem: "windows", Architecture: "", Command: "pwsh", Args: []string{"-c", "echo \"installing...\""}}, + }, }, } @@ -249,7 +348,6 @@ func TestDownloader(t *testing.T) { } func TestLoadAll(t *testing.T) { - // Verify that empty dir loads: if plugs, err := LoadAll("testdata"); err != nil { t.Fatalf("error loading dir with no plugins: %s", err) @@ -361,6 +459,30 @@ func TestValidatePluginData(t *testing.T) { Dir: "no-such-dir", } + // A mock plugin with no commands + mockNoCommand := mockPlugin("foo") + mockNoCommand.Metadata.PlatformCommand = []PlatformCommand{} + mockNoCommand.Metadata.PlatformHooks = map[string][]PlatformCommand{} + + // A mock plugin with legacy commands + mockLegacyCommand := mockPlugin("foo") + mockLegacyCommand.Metadata.PlatformCommand = []PlatformCommand{} + mockLegacyCommand.Metadata.Command = "echo \"mock plugin\"" + mockLegacyCommand.Metadata.PlatformHooks = map[string][]PlatformCommand{} + mockLegacyCommand.Metadata.Hooks = map[string]string{ + Install: "echo installing...", + } + + // A mock plugin with a command also set + mockWithCommand := mockPlugin("foo") + mockWithCommand.Metadata.Command = "echo \"mock plugin\"" + + // A mock plugin with a hooks also set + mockWithHooks := mockPlugin("foo") + mockWithHooks.Metadata.Hooks = map[string]string{ + Install: "echo installing...", + } + for i, item := range []struct { pass bool plug *Plugin @@ -372,6 +494,10 @@ func TestValidatePluginData(t *testing.T) { {false, mockPlugin("foo -bar ")}, // Test trailing chars {false, mockPlugin("foo\nbar")}, // Test newline {false, mockMissingMeta}, // Test if the metadata section missing + {true, mockNoCommand}, // Test no command metadata works + {true, mockLegacyCommand}, // Test legacy command metadata works + {false, mockWithCommand}, // Test platformCommand and command both set fails + {false, mockWithHooks}, // Test platformHooks and hooks both set fails } { err := validatePluginData(item.plug, fmt.Sprintf("test-%d", i)) if item.pass && err != nil { @@ -403,7 +529,16 @@ func mockPlugin(name string) *Plugin { Version: "v0.1.2", Usage: "Mock plugin", Description: "Mock plugin for testing", - Command: "echo mock plugin", + PlatformCommand: []PlatformCommand{ + {OperatingSystem: "linux", Architecture: "", Command: "sh", Args: []string{"-c", "echo \"mock plugin\""}}, + {OperatingSystem: "windows", Architecture: "", Command: "pwsh", Args: []string{"-c", "echo \"mock plugin\""}}, + }, + PlatformHooks: map[string][]PlatformCommand{ + Install: { + {OperatingSystem: "linux", Architecture: "", Command: "sh", Args: []string{"-c", "echo \"installing...\""}}, + {OperatingSystem: "windows", Architecture: "", Command: "pwsh", Args: []string{"-c", "echo \"installing...\""}}, + }, + }, }, Dir: "no-such-dir", } diff --git a/pkg/plugin/testdata/plugdir/good/hello/hello.ps1 b/pkg/plugin/testdata/plugdir/good/hello/hello.ps1 new file mode 100644 index 000000000..bee61f27d --- /dev/null +++ b/pkg/plugin/testdata/plugdir/good/hello/hello.ps1 @@ -0,0 +1,3 @@ +#!/usr/bin/env pwsh + +Write-Host "Hello, world!" diff --git a/pkg/plugin/testdata/plugdir/good/hello/plugin.yaml b/pkg/plugin/testdata/plugdir/good/hello/plugin.yaml index b857b55ee..71dc88259 100644 --- a/pkg/plugin/testdata/plugdir/good/hello/plugin.yaml +++ b/pkg/plugin/testdata/plugdir/good/hello/plugin.yaml @@ -3,7 +3,23 @@ version: "0.1.0" usage: "usage" description: |- description -command: "$HELM_PLUGIN_DIR/hello.sh" +platformCommand: + - os: linux + arch: + command: "sh" + args: ["-c", "${HELM_PLUGIN_DIR}/hello.sh"] + - os: windows + arch: + command: "pwsh" + args: ["-c", "${HELM_PLUGIN_DIR}/hello.ps1"] ignoreFlags: true -hooks: - install: "echo installing..." +platformHooks: + install: + - os: linux + arch: "" + command: "sh" + args: ["-c", 'echo "installing..."'] + - os: windows + arch: "" + command: "pwsh" + args: ["-c", 'echo "installing..."'] diff --git a/pkg/postrender/exec.go b/pkg/postrender/exec.go index 90e5473f3..3a6e20b82 100644 --- a/pkg/postrender/exec.go +++ b/pkg/postrender/exec.go @@ -63,6 +63,12 @@ func (p *execRender) Run(renderedManifests *bytes.Buffer) (*bytes.Buffer, error) return nil, fmt.Errorf("error while running command %s. error output:\n%s: %w", p.binaryPath, stderr.String(), err) } + // If the binary returned almost nothing, it's likely that it didn't + // successfully render anything + if len(bytes.TrimSpace(postRendered.Bytes())) == 0 { + return nil, errors.Errorf("post-renderer %q produced empty output", p.binaryPath) + } + return postRendered, nil } diff --git a/pkg/postrender/exec_test.go b/pkg/postrender/exec_test.go index 19a6ec6c4..2b091cc12 100644 --- a/pkg/postrender/exec_test.go +++ b/pkg/postrender/exec_test.go @@ -121,6 +121,21 @@ func TestExecRun(t *testing.T) { is.Contains(output.String(), "BARTEST") } +func TestExecRunWithNoOutput(t *testing.T) { + if runtime.GOOS == "windows" { + // the actual Run test uses a basic sed example, so skip this test on windows + t.Skip("skipping on windows") + } + is := assert.New(t) + testpath := setupTestingScript(t) + + renderer, err := NewExec(testpath) + require.NoError(t, err) + + _, err = renderer.Run(bytes.NewBufferString("")) + is.Error(err) +} + func TestNewExecWithOneArgsRun(t *testing.T) { if runtime.GOOS == "windows" { // the actual Run test uses a basic sed example, so skip this test on windows diff --git a/pkg/provenance/doc.go b/pkg/provenance/doc.go index 0c7ae0618..883c0e724 100644 --- a/pkg/provenance/doc.go +++ b/pkg/provenance/doc.go @@ -35,4 +35,4 @@ and using `gpg --verify`, `keybase pgp verify`, or similar: gpg: Signature made Mon Jul 25 17:23:44 2016 MDT using RSA key ID 1FC18762 gpg: Good signature from "Helm Testing (This key should only be used for testing. DO NOT TRUST.) " [ultimate] */ -package provenance // import "helm.sh/helm/v3/pkg/provenance" +package provenance // import "helm.sh/helm/v4/pkg/provenance" diff --git a/pkg/provenance/sign.go b/pkg/provenance/sign.go index 240d54af9..504bc6aa1 100644 --- a/pkg/provenance/sign.go +++ b/pkg/provenance/sign.go @@ -31,8 +31,8 @@ import ( "golang.org/x/crypto/openpgp/packet" //nolint "sigs.k8s.io/yaml" - hapi "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chart/loader" + hapi "helm.sh/helm/v4/pkg/chart/v2" + "helm.sh/helm/v4/pkg/chart/v2/loader" ) var defaultPGPConfig = packet.Config{ diff --git a/pkg/provenance/sign_test.go b/pkg/provenance/sign_test.go index bf6848368..69a6dad5b 100644 --- a/pkg/provenance/sign_test.go +++ b/pkg/provenance/sign_test.go @@ -34,7 +34,7 @@ const ( // phrase. Use `gpg --export-secret-keys helm-test` to export the secret. testKeyfile = "testdata/helm-test-key.secret" - // testPasswordKeyFile is a keyfile with a password. + // testPasswordKeyfile is a keyfile with a password. testPasswordKeyfile = "testdata/helm-password-key.secret" // testPubfile is the public key file. diff --git a/pkg/pusher/ocipusher.go b/pkg/pusher/ocipusher.go index 29ffb0afb..699d27caf 100644 --- a/pkg/pusher/ocipusher.go +++ b/pkg/pusher/ocipusher.go @@ -26,10 +26,10 @@ import ( "strings" "time" - "helm.sh/helm/v3/internal/tlsutil" - "helm.sh/helm/v3/pkg/chart/loader" - "helm.sh/helm/v3/pkg/registry" - "helm.sh/helm/v3/pkg/time/ctime" + "helm.sh/helm/v4/internal/tlsutil" + "helm.sh/helm/v4/pkg/chart/v2/loader" + "helm.sh/helm/v4/pkg/registry" + "helm.sh/helm/v4/pkg/time/ctime" ) // OCIPusher is the default OCI backend handler @@ -111,7 +111,11 @@ func NewOCIPusher(ops ...Option) (Pusher, error) { func (pusher *OCIPusher) newRegistryClient() (*registry.Client, error) { if (pusher.opts.certFile != "" && pusher.opts.keyFile != "") || pusher.opts.caFile != "" || pusher.opts.insecureSkipTLSverify { - tlsConf, err := tlsutil.NewClientTLS(pusher.opts.certFile, pusher.opts.keyFile, pusher.opts.caFile, pusher.opts.insecureSkipTLSverify) + tlsConf, err := tlsutil.NewTLSConfig( + tlsutil.WithInsecureSkipVerify(pusher.opts.insecureSkipTLSverify), + tlsutil.WithCertKeyPairFiles(pusher.opts.certFile, pusher.opts.keyFile), + tlsutil.WithCAFile(pusher.opts.caFile), + ) if err != nil { return nil, fmt.Errorf("can't create TLS config for client: %w", err) } diff --git a/pkg/pusher/ocipusher_test.go b/pkg/pusher/ocipusher_test.go index 11842b4ae..760da8404 100644 --- a/pkg/pusher/ocipusher_test.go +++ b/pkg/pusher/ocipusher_test.go @@ -19,7 +19,7 @@ import ( "path/filepath" "testing" - "helm.sh/helm/v3/pkg/registry" + "helm.sh/helm/v4/pkg/registry" ) func TestNewOCIPusher(t *testing.T) { diff --git a/pkg/pusher/pusher.go b/pkg/pusher/pusher.go index 7b8b4312a..c4c766748 100644 --- a/pkg/pusher/pusher.go +++ b/pkg/pusher/pusher.go @@ -19,8 +19,8 @@ package pusher import ( "fmt" - "helm.sh/helm/v3/pkg/cli" - "helm.sh/helm/v3/pkg/registry" + "helm.sh/helm/v4/pkg/cli" + "helm.sh/helm/v4/pkg/registry" ) // options are generic parameters to be provided to the pusher during instantiation. diff --git a/pkg/pusher/pusher_test.go b/pkg/pusher/pusher_test.go index d43e6c9ec..71fab8694 100644 --- a/pkg/pusher/pusher_test.go +++ b/pkg/pusher/pusher_test.go @@ -18,8 +18,8 @@ package pusher import ( "testing" - "helm.sh/helm/v3/pkg/cli" - "helm.sh/helm/v3/pkg/registry" + "helm.sh/helm/v4/pkg/cli" + "helm.sh/helm/v4/pkg/registry" ) func TestProvider(t *testing.T) { diff --git a/pkg/registry/client.go b/pkg/registry/client.go index 309efc9cc..1a58df0e1 100644 --- a/pkg/registry/client.go +++ b/pkg/registry/client.go @@ -14,32 +14,38 @@ See the License for the specific language governing permissions and limitations under the License. */ -package registry // import "helm.sh/helm/v3/pkg/registry" +package registry // import "helm.sh/helm/v4/pkg/registry" import ( "context" + "crypto/tls" + "crypto/x509" "encoding/json" "errors" "fmt" "io" "net/http" + "net/url" + "os" "sort" "strings" + "sync" "github.com/Masterminds/semver/v3" - "github.com/containerd/containerd/remotes" + "github.com/opencontainers/image-spec/specs-go" ocispec "github.com/opencontainers/image-spec/specs-go/v1" - "oras.land/oras-go/pkg/auth" - dockerauth "oras.land/oras-go/pkg/auth/docker" - "oras.land/oras-go/pkg/content" - "oras.land/oras-go/pkg/oras" - "oras.land/oras-go/pkg/registry" - registryremote "oras.land/oras-go/pkg/registry/remote" - registryauth "oras.land/oras-go/pkg/registry/remote/auth" - - "helm.sh/helm/v3/internal/version" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/helmpath" + "oras.land/oras-go/v2" + "oras.land/oras-go/v2/content" + "oras.land/oras-go/v2/content/memory" + "oras.land/oras-go/v2/registry" + "oras.land/oras-go/v2/registry/remote" + "oras.land/oras-go/v2/registry/remote/auth" + "oras.land/oras-go/v2/registry/remote/credentials" + "oras.land/oras-go/v2/registry/remote/retry" + + "helm.sh/helm/v4/internal/version" + chart "helm.sh/helm/v4/pkg/chart/v2" + "helm.sh/helm/v4/pkg/helmpath" ) // See https://github.com/helm/helm/issues/10166 @@ -50,18 +56,28 @@ an underscore (_) in chart version tags when pushing to a registry and back to a plus (+) when pulling from a registry.` type ( + // RemoteClient shadows the ORAS remote.Client interface + // (hiding the ORAS type from Helm client visibility) + // https://pkg.go.dev/oras.land/oras-go/pkg/registry/remote#Client + RemoteClient interface { + Do(req *http.Request) (*http.Response, error) + } + // Client works with OCI-compliant registries Client struct { debug bool enableCache bool // path to repository config file e.g. ~/.docker/config.json credentialsFile string + username string + password string out io.Writer - authorizer auth.Client - registryAuthorizer *registryauth.Client - resolver func(ref registry.Reference) (remotes.Resolver, error) + authorizer *auth.Client + registryAuthorizer RemoteClient + credentialsStore credentials.Store httpClient *http.Client plainHTTP bool + err error // pass any errors from the ClientOption functions } // ClientOption allows specifying various settings configurable by the user for overriding the defaults @@ -76,81 +92,70 @@ func NewClient(options ...ClientOption) (*Client, error) { } for _, option := range options { option(client) + if client.err != nil { + return nil, client.err + } } if client.credentialsFile == "" { client.credentialsFile = helmpath.ConfigPath(CredentialsFileBasename) } - if client.authorizer == nil { - authClient, err := dockerauth.NewClientWithDockerFallback(client.credentialsFile) - if err != nil { - return nil, err + if client.httpClient == nil { + type cloner[T any] interface { + Clone() T } - client.authorizer = authClient - } - resolverFn := client.resolver // copy for avoiding recursive call - client.resolver = func(ref registry.Reference) (remotes.Resolver, error) { - if resolverFn != nil { - // validate if the resolverFn returns a valid resolver - if resolver, err := resolverFn(ref); resolver != nil && err == nil { - return resolver, nil - } - } - headers := http.Header{} - headers.Set("User-Agent", version.GetUserAgent()) - opts := []auth.ResolverOption{auth.WithResolverHeaders(headers)} - if client.httpClient != nil { - opts = append(opts, auth.WithResolverClient(client.httpClient)) + // try to copy (clone) the http.DefaultTransport so any mutations we + // perform on it (e.g. TLS config) are not reflected globally + // follow https://github.com/golang/go/issues/39299 for a more elegant + // solution in the future + transport := http.DefaultTransport + if t, ok := transport.(cloner[*http.Transport]); ok { + transport = t.Clone() + } else if t, ok := transport.(cloner[http.RoundTripper]); ok { + // this branch will not be used with go 1.20, it was added + // optimistically to try to clone if the http.DefaultTransport + // implementation changes, still the Clone method in that case + // might not return http.RoundTripper... + transport = t.Clone() } - if client.plainHTTP { - opts = append(opts, auth.WithResolverPlainHTTP()) - } - resolver, err := client.authorizer.ResolverWithOpts(opts...) - if err != nil { - return nil, err + + client.httpClient = &http.Client{ + Transport: retry.NewTransport(transport), } - return resolver, nil } - // allocate a cache if option is set - var cache registryauth.Cache - if client.enableCache { - cache = registryauth.DefaultCache + storeOptions := credentials.StoreOptions{ + AllowPlaintextPut: true, + DetectDefaultNativeStore: true, + } + store, err := credentials.NewStore(client.credentialsFile, storeOptions) + if err != nil { + return nil, err + } + dockerStore, err := credentials.NewStoreFromDocker(storeOptions) + if err != nil { + // should only fail if user home directory can't be determined + client.credentialsStore = store + } else { + // use Helm credentials with fallback to Docker + client.credentialsStore = credentials.NewStoreWithFallbacks(store, dockerStore) } - if client.registryAuthorizer == nil { - client.registryAuthorizer = ®istryauth.Client{ - Client: client.httpClient, - Header: http.Header{ - "User-Agent": {version.GetUserAgent()}, - }, - Cache: cache, - Credential: func(_ context.Context, reg string) (registryauth.Credential, error) { - dockerClient, ok := client.authorizer.(*dockerauth.Client) - if !ok { - return registryauth.EmptyCredential, errors.New("unable to obtain docker client") - } - - username, password, err := dockerClient.Credential(reg) - if err != nil { - return registryauth.EmptyCredential, errors.New("unable to retrieve credentials") - } - // A blank returned username and password value is a bearer token - if username == "" && password != "" { - return registryauth.Credential{ - RefreshToken: password, - }, nil - } + if client.authorizer == nil { + authorizer := auth.Client{ + Client: client.httpClient, + } + authorizer.SetUserAgent(version.GetUserAgent()) - return registryauth.Credential{ - Username: username, - Password: password, - }, nil + authorizer.Credential = credentials.Credential(client.credentialsStore) - }, + if client.enableCache { + authorizer.Cache = auth.NewCache() } + client.authorizer = &authorizer } + return client, nil } @@ -168,6 +173,14 @@ func ClientOptEnableCache(enableCache bool) ClientOption { } } +// ClientOptBasicAuth returns a function that sets the username and password setting on client options set +func ClientOptBasicAuth(username, password string) ClientOption { + return func(client *Client) { + client.username = username + client.password = password + } +} + // ClientOptWriter returns a function that sets the writer setting on client options set func ClientOptWriter(out io.Writer) ClientOption { return func(client *Client) { @@ -175,6 +188,26 @@ func ClientOptWriter(out io.Writer) ClientOption { } } +// ClientOptAuthorizer returns a function that sets the authorizer setting on a client options set. This +// can be used to override the default authorization mechanism. +// +// Depending on the use-case you may need to set both ClientOptAuthorizer and ClientOptRegistryAuthorizer. +func ClientOptAuthorizer(authorizer auth.Client) ClientOption { + return func(client *Client) { + client.authorizer = &authorizer + } +} + +// ClientOptRegistryAuthorizer returns a function that sets the registry authorizer setting on a client options set. This +// can be used to override the default authorization mechanism. +// +// Depending on the use-case you may need to set both ClientOptAuthorizer and ClientOptRegistryAuthorizer. +func ClientOptRegistryAuthorizer(registryAuthorizer RemoteClient) ClientOption { + return func(client *Client) { + client.registryAuthorizer = registryAuthorizer + } +} + // ClientOptCredentialsFile returns a function that sets the credentialsFile setting on a client options set func ClientOptCredentialsFile(credentialsFile string) ClientOption { return func(client *Client) { @@ -195,74 +228,133 @@ func ClientOptPlainHTTP() ClientOption { } } -// ClientOptResolver returns a function that sets the resolver setting on a client options set -func ClientOptResolver(resolver remotes.Resolver) ClientOption { - return func(client *Client) { - client.resolver = func(_ registry.Reference) (remotes.Resolver, error) { - return resolver, nil - } - } -} - type ( // LoginOption allows specifying various settings on login LoginOption func(*loginOperation) loginOperation struct { - username string - password string - insecure bool - certFile string - keyFile string - caFile string + host string + client *Client } ) // Login logs into a registry func (c *Client) Login(host string, options ...LoginOption) error { - operation := &loginOperation{} for _, option := range options { - option(operation) + option(&loginOperation{host, c}) } - authorizerLoginOpts := []auth.LoginOption{ - auth.WithLoginContext(ctx(c.out, c.debug)), - auth.WithLoginHostname(host), - auth.WithLoginUsername(operation.username), - auth.WithLoginSecret(operation.password), - auth.WithLoginUserAgent(version.GetUserAgent()), - auth.WithLoginTLS(operation.certFile, operation.keyFile, operation.caFile), + + reg, err := remote.NewRegistry(host) + if err != nil { + return err } - if operation.insecure { - authorizerLoginOpts = append(authorizerLoginOpts, auth.WithLoginInsecure()) + reg.PlainHTTP = c.plainHTTP + reg.Client = c.authorizer + + ctx := context.Background() + cred, err := c.authorizer.Credential(ctx, host) + if err != nil { + return fmt.Errorf("fetching credentials for %q: %w", host, err) } - if err := c.authorizer.LoginWithOpts(authorizerLoginOpts...); err != nil { + + if err := reg.Ping(ctx); err != nil { + return fmt.Errorf("authenticating to %q: %w", host, err) + } + + key := credentials.ServerAddressFromRegistry(host) + if err := c.credentialsStore.Put(ctx, key, cred); err != nil { return err } + fmt.Fprintln(c.out, "Login Succeeded") return nil } // LoginOptBasicAuth returns a function that sets the username/password settings on login func LoginOptBasicAuth(username string, password string) LoginOption { - return func(operation *loginOperation) { - operation.username = username - operation.password = password + return func(o *loginOperation) { + o.client.username = username + o.client.password = password + o.client.authorizer.Credential = auth.StaticCredential(o.host, auth.Credential{Username: username, Password: password}) + } +} + +// LoginOptPlainText returns a function that allows plaintext (HTTP) login +func LoginOptPlainText(isPlainText bool) LoginOption { + return func(o *loginOperation) { + o.client.plainHTTP = isPlainText } } +func ensureTLSConfig(client *auth.Client) (*tls.Config, error) { + var transport *http.Transport + + switch t := client.Client.Transport.(type) { + case *http.Transport: + transport = t + case *retry.Transport: + switch t := t.Base.(type) { + case *http.Transport: + transport = t + } + } + + if transport == nil { + // we don't know how to access the http.Transport, most likely the + // auth.Client.Client was provided by API user + return nil, fmt.Errorf("unable to access TLS client configuration, the provided HTTP Transport is not supported, given: %T", client.Client.Transport) + } + + if transport.TLSClientConfig == nil { + transport.TLSClientConfig = &tls.Config{} + } + + return transport.TLSClientConfig, nil +} + // LoginOptInsecure returns a function that sets the insecure setting on login func LoginOptInsecure(insecure bool) LoginOption { - return func(operation *loginOperation) { - operation.insecure = insecure + return func(o *loginOperation) { + tlsConfig, err := ensureTLSConfig(o.client.authorizer) + + if err != nil { + panic(err) + } + + tlsConfig.InsecureSkipVerify = insecure } } // LoginOptTLSClientConfig returns a function that sets the TLS settings on login. func LoginOptTLSClientConfig(certFile, keyFile, caFile string) LoginOption { - return func(operation *loginOperation) { - operation.certFile = certFile - operation.keyFile = keyFile - operation.caFile = caFile + return func(o *loginOperation) { + if (certFile == "" || keyFile == "") && caFile == "" { + return + } + tlsConfig, err := ensureTLSConfig(o.client.authorizer) + if err != nil { + panic(err) + } + + if certFile != "" && keyFile != "" { + authCert, err := tls.LoadX509KeyPair(certFile, keyFile) + if err != nil { + panic(err) + } + tlsConfig.Certificates = []tls.Certificate{authCert} + } + + if caFile != "" { + certPool := x509.NewCertPool() + ca, err := os.ReadFile(caFile) + if err != nil { + panic(err) + } + if !certPool.AppendCertsFromPEM(ca) { + panic(fmt.Errorf("unable to parse CA file: %q", caFile)) + } + tlsConfig.RootCAs = certPool + } } } @@ -279,7 +371,8 @@ func (c *Client) Logout(host string, opts ...LogoutOption) error { for _, opt := range opts { opt(operation) } - if err := c.authorizer.Logout(ctx(c.out, c.debug), host); err != nil { + + if err := credentials.Logout(context.Background(), c.credentialsStore, host); err != nil { return err } fmt.Fprintf(c.out, "Removing login credentials for %s\n", host) @@ -319,7 +412,7 @@ type ( // Pull downloads a chart from a registry func (c *Client) Pull(ref string, options ...PullOption) (*PullResult, error) { - parsedRef, err := parseReference(ref) + parsedRef, err := newReference(ref) if err != nil { return nil, err } @@ -334,8 +427,9 @@ func (c *Client) Pull(ref string, options ...PullOption) (*PullResult, error) { return nil, errors.New( "must specify at least one layer to pull (chart/prov)") } - memoryStore := content.NewMemory() + memoryStore := memory.New() allowedMediaTypes := []string{ + ocispec.MediaTypeImageManifest, ConfigMediaType, } minNumDescriptors := 1 // 1 for the config @@ -351,18 +445,34 @@ func (c *Client) Pull(ref string, options ...PullOption) (*PullResult, error) { } var descriptors, layers []ocispec.Descriptor - remotesResolver, err := c.resolver(parsedRef) + + repository, err := remote.NewRepository(parsedRef.String()) if err != nil { return nil, err } - registryStore := content.Registry{Resolver: remotesResolver} + repository.PlainHTTP = c.plainHTTP + repository.Client = c.authorizer - manifest, err := oras.Copy(ctx(c.out, c.debug), registryStore, parsedRef.String(), memoryStore, "", - oras.WithPullEmptyNameAllowed(), - oras.WithAllowedMediaTypes(allowedMediaTypes), - oras.WithLayerDescriptors(func(l []ocispec.Descriptor) { - layers = l - })) + ctx := context.Background() + + sort.Strings(allowedMediaTypes) + + var mu sync.Mutex + manifest, err := oras.Copy(ctx, repository, parsedRef.String(), memoryStore, "", oras.CopyOptions{ + CopyGraphOptions: oras.CopyGraphOptions{ + PreCopy: func(_ context.Context, desc ocispec.Descriptor) error { + mediaType := desc.MediaType + if i := sort.SearchStrings(allowedMediaTypes, mediaType); i >= len(allowedMediaTypes) || allowedMediaTypes[i] != mediaType { + return fmt.Errorf("media type %q is not allowed, found in descriptor with digest: %q", mediaType, desc.Digest) + } + + mu.Lock() + layers = append(layers, desc) + mu.Unlock() + return nil + }, + }, + }) if err != nil { return nil, err } @@ -421,54 +531,37 @@ func (c *Client) Pull(ref string, options ...PullOption) (*PullResult, error) { Prov: &DescriptorPullSummary{}, Ref: parsedRef.String(), } - var getManifestErr error - if _, manifestData, ok := memoryStore.Get(manifest); !ok { - getManifestErr = fmt.Errorf("Unable to retrieve blob with digest %s", manifest.Digest) - } else { - result.Manifest.Data = manifestData - } - if getManifestErr != nil { - return nil, getManifestErr + + result.Manifest.Data, err = content.FetchAll(ctx, memoryStore, manifest) + if err != nil { + return nil, fmt.Errorf("unable to retrieve blob with digest %s: %w", manifest.Digest, err) } - var getConfigDescriptorErr error - if _, configData, ok := memoryStore.Get(*configDescriptor); !ok { - getConfigDescriptorErr = fmt.Errorf("Unable to retrieve blob with digest %s", configDescriptor.Digest) - } else { - result.Config.Data = configData - var meta *chart.Metadata - if err := json.Unmarshal(configData, &meta); err != nil { - return nil, err - } - result.Chart.Meta = meta + + result.Config.Data, err = content.FetchAll(ctx, memoryStore, *configDescriptor) + if err != nil { + return nil, fmt.Errorf("unable to retrieve blob with digest %s: %w", configDescriptor.Digest, err) } - if getConfigDescriptorErr != nil { - return nil, getConfigDescriptorErr + + if err := json.Unmarshal(result.Config.Data, &result.Chart.Meta); err != nil { + return nil, err } + if operation.withChart { - var getChartDescriptorErr error - if _, chartData, ok := memoryStore.Get(*chartDescriptor); !ok { - getChartDescriptorErr = fmt.Errorf("Unable to retrieve blob with digest %s", chartDescriptor.Digest) - } else { - result.Chart.Data = chartData - result.Chart.Digest = chartDescriptor.Digest.String() - result.Chart.Size = chartDescriptor.Size - } - if getChartDescriptorErr != nil { - return nil, getChartDescriptorErr + result.Chart.Data, err = content.FetchAll(ctx, memoryStore, *chartDescriptor) + if err != nil { + return nil, fmt.Errorf("unable to retrieve blob with digest %s: %w", chartDescriptor.Digest, err) } + result.Chart.Digest = chartDescriptor.Digest.String() + result.Chart.Size = chartDescriptor.Size } + if operation.withProv && !provMissing { - var getProvDescriptorErr error - if _, provData, ok := memoryStore.Get(*provDescriptor); !ok { - getProvDescriptorErr = fmt.Errorf("Unable to retrieve blob with digest %s", provDescriptor.Digest) - } else { - result.Prov.Data = provData - result.Prov.Digest = provDescriptor.Digest.String() - result.Prov.Size = provDescriptor.Size - } - if getProvDescriptorErr != nil { - return nil, getProvDescriptorErr + result.Prov.Data, err = content.FetchAll(ctx, memoryStore, *provDescriptor) + if err != nil { + return nil, fmt.Errorf("unable to retrieve blob with digest %s: %w", provDescriptor.Digest, err) } + result.Prov.Digest = provDescriptor.Digest.String() + result.Prov.Size = provDescriptor.Size } fmt.Fprintf(c.out, "Pulled: %s\n", result.Ref) @@ -535,7 +628,7 @@ type ( // Push uploads a chart to a registry. func (c *Client) Push(data []byte, ref string, options ...PushOption) (*PushResult, error) { - parsedRef, err := parseReference(ref) + parsedRef, err := newReference(ref) if err != nil { return nil, err } @@ -556,8 +649,11 @@ func (c *Client) Push(data []byte, ref string, options ...PushOption) (*PushResu "strict mode enabled, ref basename and tag must match the chart name and version") } } - memoryStore := content.NewMemory() - chartDescriptor, err := memoryStore.Add("", ChartLayerMediaType, data) + + ctx := context.Background() + + memoryStore := memory.New() + chartDescriptor, err := oras.PushBytes(ctx, memoryStore, ChartLayerMediaType, data) if err != nil { return nil, err } @@ -567,43 +663,57 @@ func (c *Client) Push(data []byte, ref string, options ...PushOption) (*PushResu return nil, err } - configDescriptor, err := memoryStore.Add("", ConfigMediaType, configData) + configDescriptor, err := oras.PushBytes(ctx, memoryStore, ConfigMediaType, configData) if err != nil { return nil, err } - descriptors := []ocispec.Descriptor{chartDescriptor} + layers := []ocispec.Descriptor{chartDescriptor} var provDescriptor ocispec.Descriptor if operation.provData != nil { - provDescriptor, err = memoryStore.Add("", ProvLayerMediaType, operation.provData) + provDescriptor, err = oras.PushBytes(ctx, memoryStore, ProvLayerMediaType, operation.provData) if err != nil { return nil, err } - descriptors = append(descriptors, provDescriptor) + layers = append(layers, provDescriptor) } + // sort layers for determinism, similar to how ORAS v1 does it + sort.Slice(layers, func(i, j int) bool { + return layers[i].Digest < layers[j].Digest + }) + ociAnnotations := generateOCIAnnotations(meta, operation.creationTime) + manifest := ocispec.Manifest{ + Versioned: specs.Versioned{SchemaVersion: 2}, + Config: configDescriptor, + Layers: layers, + Annotations: ociAnnotations, + } - manifestData, manifest, err := content.GenerateManifest(&configDescriptor, ociAnnotations, descriptors...) + manifestData, err := json.Marshal(manifest) if err != nil { return nil, err } - if err := memoryStore.StoreManifest(parsedRef.String(), manifest, manifestData); err != nil { + manifestDescriptor, err := oras.TagBytes(ctx, memoryStore, ocispec.MediaTypeImageManifest, manifestData, ref) + if err != nil { return nil, err } - remotesResolver, err := c.resolver(parsedRef) + repository, err := remote.NewRepository(parsedRef.String()) if err != nil { return nil, err } - registryStore := content.Registry{Resolver: remotesResolver} - _, err = oras.Copy(ctx(c.out, c.debug), memoryStore, parsedRef.String(), registryStore, "", - oras.WithNameValidation(nil)) + repository.PlainHTTP = c.plainHTTP + repository.Client = c.authorizer + + manifestDescriptor, err = oras.ExtendedCopy(ctx, memoryStore, parsedRef.String(), repository, parsedRef.String(), oras.DefaultExtendedCopyOptions) if err != nil { return nil, err } + chartSummary := &descriptorPushSummaryWithMeta{ Meta: meta, } @@ -611,8 +721,8 @@ func (c *Client) Push(data []byte, ref string, options ...PushOption) (*PushResu chartSummary.Size = chartDescriptor.Size result := &PushResult{ Manifest: &descriptorPushSummary{ - Digest: manifest.Digest.String(), - Size: manifest.Size, + Digest: manifestDescriptor.Digest.String(), + Size: manifestDescriptor.Size, }, Config: &descriptorPushSummary{ Digest: configDescriptor.Digest.String(), @@ -630,7 +740,7 @@ func (c *Client) Push(data []byte, ref string, options ...PushOption) (*PushResu } fmt.Fprintf(c.out, "Pushed: %s\n", result.Ref) fmt.Fprintf(c.out, "Digest: %s\n", result.Manifest.Digest) - if strings.Contains(parsedRef.Reference, "_") { + if strings.Contains(parsedRef.orasReference.Reference, "_") { fmt.Fprintf(c.out, "%s contains an underscore.\n", result.Ref) fmt.Fprint(c.out, registryUnderscoreMessage+"\n") } @@ -652,7 +762,7 @@ func PushOptStrictMode(strictMode bool) PushOption { } } -// PushOptCreationDate returns a function that sets the creation time +// PushOptCreationTime returns a function that sets the creation time func PushOptCreationTime(creationTime string) PushOption { return func(operation *pushOperation) { operation.creationTime = creationTime @@ -666,27 +776,29 @@ func (c *Client) Tags(ref string) ([]string, error) { return nil, err } - repository := registryremote.Repository{ - Reference: parsedReference, - Client: c.registryAuthorizer, - PlainHTTP: c.plainHTTP, - } - - var registryTags []string - - registryTags, err = registry.Tags(ctx(c.out, c.debug), &repository) + ctx := context.Background() + repository, err := remote.NewRepository(parsedReference.String()) if err != nil { return nil, err } + repository.PlainHTTP = c.plainHTTP + repository.Client = c.authorizer var tagVersions []*semver.Version - for _, tag := range registryTags { - // Change underscore (_) back to plus (+) for Helm - // See https://github.com/helm/helm/issues/10166 - tagVersion, err := semver.StrictNewVersion(strings.ReplaceAll(tag, "_", "+")) - if err == nil { - tagVersions = append(tagVersions, tagVersion) + err = repository.Tags(ctx, "", func(tags []string) error { + for _, tag := range tags { + // Change underscore (_) back to plus (+) for Helm + // See https://github.com/helm/helm/issues/10166 + tagVersion, err := semver.StrictNewVersion(strings.ReplaceAll(tag, "_", "+")) + if err == nil { + tagVersions = append(tagVersions, tagVersion) + } } + + return nil + }) + if err != nil { + return nil, err } // Sort the collection @@ -701,3 +813,88 @@ func (c *Client) Tags(ref string) ([]string, error) { return tags, nil } + +// Resolve a reference to a descriptor. +func (c *Client) Resolve(ref string) (desc ocispec.Descriptor, err error) { + remoteRepository, err := remote.NewRepository(ref) + if err != nil { + return desc, err + } + remoteRepository.PlainHTTP = c.plainHTTP + + parsedReference, err := newReference(ref) + if err != nil { + return desc, err + } + + ctx := context.Background() + parsedString := parsedReference.String() + return remoteRepository.Resolve(ctx, parsedString) +} + +// ValidateReference for path and version +func (c *Client) ValidateReference(ref, version string, u *url.URL) (*url.URL, error) { + var tag string + + registryReference, err := newReference(u.Host + u.Path) + if err != nil { + return nil, err + } + + if version == "" { + // Use OCI URI tag as default + version = registryReference.Tag + } else { + if registryReference.Tag != "" && registryReference.Tag != version { + return nil, fmt.Errorf("chart reference and version mismatch: %s is not %s", version, registryReference.Tag) + } + } + + if registryReference.Digest != "" { + if version == "" { + // Install by digest only + return u, nil + } + u.Path = fmt.Sprintf("%s@%s", registryReference.Repository, registryReference.Digest) + + // Validate the tag if it was specified + path := registryReference.Registry + "/" + registryReference.Repository + ":" + version + desc, err := c.Resolve(path) + if err != nil { + // The resource does not have to be tagged when digest is specified + return u, nil + } + if desc.Digest.String() != registryReference.Digest { + return nil, fmt.Errorf("chart reference digest mismatch: %s is not %s", desc.Digest.String(), registryReference.Digest) + } + return u, nil + } + + // Evaluate whether an explicit version has been provided. Otherwise, determine version to use + _, errSemVer := semver.NewVersion(version) + if errSemVer == nil { + tag = version + } else { + // Retrieve list of repository tags + tags, err := c.Tags(strings.TrimPrefix(ref, fmt.Sprintf("%s://", OCIScheme))) + if err != nil { + return nil, err + } + if len(tags) == 0 { + return nil, fmt.Errorf("Unable to locate any tags in provided repository: %s", ref) + } + + // Determine if version provided + // If empty, try to get the highest available tag + // If exact version, try to find it + // If semver constraint string, try to find a match + tag, err = GetTagMatchingVersionOrConstraint(tags, version) + if err != nil { + return nil, err + } + } + + u.Path = fmt.Sprintf("%s:%s", registryReference.Repository, tag) + + return u, err +} diff --git a/pkg/registry/client_http_test.go b/pkg/registry/client_http_test.go index 872d19fc9..043fd4205 100644 --- a/pkg/registry/client_http_test.go +++ b/pkg/registry/client_http_test.go @@ -17,12 +17,13 @@ limitations under the License. package registry import ( + "errors" "fmt" "os" "testing" - "github.com/containerd/containerd/errdefs" "github.com/stretchr/testify/suite" + "oras.land/oras-go/v2/content" ) type HTTPRegistryClientTestSuite struct { @@ -42,6 +43,18 @@ func (suite *HTTPRegistryClientTestSuite) TearDownSuite() { os.RemoveAll(suite.WorkspaceDir) } +func (suite *HTTPRegistryClientTestSuite) Test_0_Login() { + err := suite.RegistryClient.Login(suite.DockerRegistryHost, + LoginOptBasicAuth("badverybad", "ohsobad"), + LoginOptPlainText(true)) + suite.NotNil(err, "error logging into registry with bad credentials") + + err = suite.RegistryClient.Login(suite.DockerRegistryHost, + LoginOptBasicAuth(testUsername, testPassword), + LoginOptPlainText(true)) + suite.Nil(err, "no error logging into registry with good credentials") +} + func (suite *HTTPRegistryClientTestSuite) Test_1_Push() { testPush(&suite.TestSuite) } @@ -60,7 +73,7 @@ func (suite *HTTPRegistryClientTestSuite) Test_4_ManInTheMiddle() { // returns content that does not match the expected digest _, err := suite.RegistryClient.Pull(ref) suite.NotNil(err) - suite.True(errdefs.IsFailedPrecondition(err)) + suite.True(errors.Is(err, content.ErrMismatchedDigest)) } func TestHTTPRegistryClientTestSuite(t *testing.T) { diff --git a/pkg/registry/client_insecure_tls_test.go b/pkg/registry/client_insecure_tls_test.go index 5ba79b2ea..accbf1670 100644 --- a/pkg/registry/client_insecure_tls_test.go +++ b/pkg/registry/client_insecure_tls_test.go @@ -66,7 +66,10 @@ func (suite *InsecureTLSRegistryClientTestSuite) Test_3_Tags() { func (suite *InsecureTLSRegistryClientTestSuite) Test_4_Logout() { err := suite.RegistryClient.Logout("this-host-aint-real:5000") - suite.NotNil(err, "error logging out of registry that has no entry") + if err != nil { + // credential backend for mac generates an error + suite.NotNil(err, "failed to delete the credential for this-host-aint-real:5000") + } err = suite.RegistryClient.Logout(suite.DockerRegistryHost) suite.Nil(err, "no error logging out of registry") diff --git a/pkg/registry/client_tls_test.go b/pkg/registry/client_tls_test.go index 518cfced4..156ae4816 100644 --- a/pkg/registry/client_tls_test.go +++ b/pkg/registry/client_tls_test.go @@ -66,7 +66,10 @@ func (suite *TLSRegistryClientTestSuite) Test_3_Tags() { func (suite *TLSRegistryClientTestSuite) Test_4_Logout() { err := suite.RegistryClient.Logout("this-host-aint-real:5000") - suite.NotNil(err, "error logging out of registry that has no entry") + if err != nil { + // credential backend for mac generates an error + suite.NotNil(err, "failed to delete the credential for this-host-aint-real:5000") + } err = suite.RegistryClient.Logout(suite.DockerRegistryHost) suite.Nil(err, "no error logging out of registry") diff --git a/pkg/registry/constants.go b/pkg/registry/constants.go index 570b6f0d3..c455cf314 100644 --- a/pkg/registry/constants.go +++ b/pkg/registry/constants.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package registry // import "helm.sh/helm/v3/pkg/registry" +package registry // import "helm.sh/helm/v4/pkg/registry" const ( // OCIScheme is the URL scheme for OCI-based requests diff --git a/pkg/registry/reference.go b/pkg/registry/reference.go new file mode 100644 index 000000000..b5677761d --- /dev/null +++ b/pkg/registry/reference.go @@ -0,0 +1,78 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package registry + +import ( + "strings" + + "oras.land/oras-go/v2/registry" +) + +type reference struct { + orasReference registry.Reference + Registry string + Repository string + Tag string + Digest string +} + +// newReference will parse and validate the reference, and clean tags when +// applicable tags are only cleaned when plus (+) signs are present, and are +// converted to underscores (_) before pushing +// See https://github.com/helm/helm/issues/10166 +func newReference(raw string) (result reference, err error) { + // Remove oci:// prefix if it is there + raw = strings.TrimPrefix(raw, OCIScheme+"://") + + // The sole possible reference modification is replacing plus (+) signs + // present in tags with underscores (_). To do this properly, we first + // need to identify a tag, and then pass it on to the reference parser + // NOTE: Passing immediately to the reference parser will fail since (+) + // signs are an invalid tag character, and simply replacing all plus (+) + // occurrences could invalidate other portions of the URI + lastIndex := strings.LastIndex(raw, "@") + if lastIndex >= 0 { + result.Digest = raw[(lastIndex + 1):] + raw = raw[:lastIndex] + } + parts := strings.Split(raw, ":") + if len(parts) > 1 && !strings.Contains(parts[len(parts)-1], "/") { + tag := parts[len(parts)-1] + + if tag != "" { + // Replace any plus (+) signs with known underscore (_) conversion + newTag := strings.ReplaceAll(tag, "+", "_") + raw = strings.ReplaceAll(raw, tag, newTag) + } + } + + result.orasReference, err = registry.ParseReference(raw) + if err != nil { + return result, err + } + result.Registry = result.orasReference.Registry + result.Repository = result.orasReference.Repository + result.Tag = result.orasReference.Reference + return result, nil +} + +func (r *reference) String() string { + if r.Tag == "" { + return r.orasReference.String() + "@" + r.Digest + } + return r.orasReference.String() +} diff --git a/pkg/registry/reference_test.go b/pkg/registry/reference_test.go new file mode 100644 index 000000000..31317d18f --- /dev/null +++ b/pkg/registry/reference_test.go @@ -0,0 +1,99 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package registry + +import "testing" + +func verify(t *testing.T, actual reference, registry, repository, tag, digest string) { + if registry != actual.orasReference.Registry { + t.Errorf("Oras reference registry expected %v actual %v", registry, actual.Registry) + } + if repository != actual.orasReference.Repository { + t.Errorf("Oras reference repository expected %v actual %v", repository, actual.Repository) + } + if tag != actual.orasReference.Reference { + t.Errorf("Oras reference reference expected %v actual %v", tag, actual.Tag) + } + if registry != actual.Registry { + t.Errorf("Registry expected %v actual %v", registry, actual.Registry) + } + if repository != actual.Repository { + t.Errorf("Repository expected %v actual %v", repository, actual.Repository) + } + if tag != actual.Tag { + t.Errorf("Tag expected %v actual %v", tag, actual.Tag) + } + if digest != actual.Digest { + t.Errorf("Digest expected %v actual %v", digest, actual.Digest) + } + expectedString := registry + if repository != "" { + expectedString = expectedString + "/" + repository + } + if tag != "" { + expectedString = expectedString + ":" + tag + } else { + expectedString = expectedString + "@" + digest + } + if actual.String() != expectedString { + t.Errorf("String expected %s actual %s", expectedString, actual.String()) + } +} + +func TestNewReference(t *testing.T) { + actual, err := newReference("registry.example.com/repository:1.0@sha256:c6841b3a895f1444a6738b5d04564a57e860ce42f8519c3be807fb6d9bee7888") + if err != nil { + t.Errorf("Unexpected error %v", err) + } + verify(t, actual, "registry.example.com", "repository", "1.0", "sha256:c6841b3a895f1444a6738b5d04564a57e860ce42f8519c3be807fb6d9bee7888") + + actual, err = newReference("oci://registry.example.com/repository:1.0@sha256:c6841b3a895f1444a6738b5d04564a57e860ce42f8519c3be807fb6d9bee7888") + if err != nil { + t.Errorf("Unexpected error %v", err) + } + verify(t, actual, "registry.example.com", "repository", "1.0", "sha256:c6841b3a895f1444a6738b5d04564a57e860ce42f8519c3be807fb6d9bee7888") + + actual, err = newReference("a/b:1@c") + if err != nil { + t.Errorf("Unexpected error %v", err) + } + verify(t, actual, "a", "b", "1", "c") + + actual, err = newReference("a/b:@") + if err != nil { + t.Errorf("Unexpected error %v", err) + } + verify(t, actual, "a", "b", "", "") + + actual, err = newReference("registry.example.com/repository:1.0+001") + if err != nil { + t.Errorf("Unexpected error %v", err) + } + verify(t, actual, "registry.example.com", "repository", "1.0_001", "") + + actual, err = newReference("thing:1.0") + if err == nil { + t.Errorf("Expect error error %v", err) + } + verify(t, actual, "", "", "", "") + + actual, err = newReference("registry.example.com/the/repository@sha256:c6841b3a895f1444a6738b5d04564a57e860ce42f8519c3be807fb6d9bee7888") + if err != nil { + t.Errorf("Unexpected error %v", err) + } + verify(t, actual, "registry.example.com", "the/repository", "", "sha256:c6841b3a895f1444a6738b5d04564a57e860ce42f8519c3be807fb6d9bee7888") +} diff --git a/pkg/registry/util.go b/pkg/registry/util.go index f9ee6b58d..235edab1d 100644 --- a/pkg/registry/util.go +++ b/pkg/registry/util.go @@ -14,28 +14,23 @@ See the License for the specific language governing permissions and limitations under the License. */ -package registry // import "helm.sh/helm/v3/pkg/registry" +package registry // import "helm.sh/helm/v4/pkg/registry" import ( "bytes" - "context" "fmt" "io" "net/http" "strings" "time" - helmtime "helm.sh/helm/v3/pkg/time" + "helm.sh/helm/v4/internal/tlsutil" + chart "helm.sh/helm/v4/pkg/chart/v2" + "helm.sh/helm/v4/pkg/chart/v2/loader" + helmtime "helm.sh/helm/v4/pkg/time" "github.com/Masterminds/semver/v3" ocispec "github.com/opencontainers/image-spec/specs-go/v1" - "github.com/sirupsen/logrus" - orascontext "oras.land/oras-go/pkg/context" - "oras.land/oras-go/pkg/registry" - - "helm.sh/helm/v3/internal/tlsutil" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chart/loader" ) var immutableOciAnnotations = []string{ @@ -43,7 +38,7 @@ var immutableOciAnnotations = []string{ ocispec.AnnotationTitle, } -// IsOCI determines whether or not a URL is to be treated as an OCI URL +// IsOCI determines whether a URL is to be treated as an OCI URL func IsOCI(url string) bool { return strings.HasPrefix(url, fmt.Sprintf("%s://", OCIScheme)) } @@ -103,45 +98,13 @@ func extractChartMeta(chartData []byte) (*chart.Metadata, error) { return ch.Metadata, nil } -// ctx retrieves a fresh context. -// disable verbose logging coming from ORAS (unless debug is enabled) -func ctx(out io.Writer, debug bool) context.Context { - if !debug { - return orascontext.Background() - } - ctx := orascontext.WithLoggerFromWriter(context.Background(), out) - orascontext.GetLogger(ctx).Logger.SetLevel(logrus.DebugLevel) - return ctx -} - -// parseReference will parse and validate the reference, and clean tags when -// applicable tags are only cleaned when plus (+) signs are present, and are -// converted to underscores (_) before pushing -// See https://github.com/helm/helm/issues/10166 -func parseReference(raw string) (registry.Reference, error) { - // The sole possible reference modification is replacing plus (+) signs - // present in tags with underscores (_). To do this properly, we first - // need to identify a tag, and then pass it on to the reference parser - // NOTE: Passing immediately to the reference parser will fail since (+) - // signs are an invalid tag character, and simply replacing all plus (+) - // occurrences could invalidate other portions of the URI - parts := strings.Split(raw, ":") - if len(parts) > 1 && !strings.Contains(parts[len(parts)-1], "/") { - tag := parts[len(parts)-1] - - if tag != "" { - // Replace any plus (+) signs with known underscore (_) conversion - newTag := strings.ReplaceAll(tag, "+", "_") - raw = strings.ReplaceAll(raw, tag, newTag) - } - } - - return registry.ParseReference(raw) -} - // NewRegistryClientWithTLS is a helper function to create a new registry client with TLS enabled. func NewRegistryClientWithTLS(out io.Writer, certFile, keyFile, caFile string, insecureSkipTLSverify bool, registryConfig string, debug bool) (*Client, error) { - tlsConf, err := tlsutil.NewClientTLS(certFile, keyFile, caFile, insecureSkipTLSverify) + tlsConf, err := tlsutil.NewTLSConfig( + tlsutil.WithInsecureSkipVerify(insecureSkipTLSverify), + tlsutil.WithCertKeyPairFiles(certFile, keyFile), + tlsutil.WithCAFile(caFile), + ) if err != nil { return nil, fmt.Errorf("can't create TLS config for client: %s", err) } @@ -188,7 +151,7 @@ annotations: return ociAnnotations } -// getChartOCIAnnotations will generate OCI annotations from the provided chart +// generateChartOCIAnnotations will generate OCI annotations from the provided chart func generateChartOCIAnnotations(meta *chart.Metadata, creationTime string) map[string]string { chartOCIAnnotations := map[string]string{} @@ -207,7 +170,7 @@ func generateChartOCIAnnotations(meta *chart.Metadata, creationTime string) map[ chartOCIAnnotations = addToMap(chartOCIAnnotations, ocispec.AnnotationSource, meta.Sources[0]) } - if meta.Maintainers != nil && len(meta.Maintainers) > 0 { + if len(meta.Maintainers) > 0 { var maintainerSb strings.Builder for maintainerIdx, maintainer := range meta.Maintainers { diff --git a/pkg/registry/util_test.go b/pkg/registry/util_test.go index 908ea4950..c8ce4e4a4 100644 --- a/pkg/registry/util_test.go +++ b/pkg/registry/util_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package registry // import "helm.sh/helm/v3/pkg/registry" +package registry // import "helm.sh/helm/v4/pkg/registry" import ( "reflect" @@ -23,8 +23,8 @@ import ( ocispec "github.com/opencontainers/image-spec/specs-go/v1" - "helm.sh/helm/v3/pkg/chart" - helmtime "helm.sh/helm/v3/pkg/time" + chart "helm.sh/helm/v4/pkg/chart/v2" + helmtime "helm.sh/helm/v4/pkg/time" ) func TestGenerateOCIChartAnnotations(t *testing.T) { diff --git a/pkg/registry/utils_test.go b/pkg/registry/utils_test.go index d7aba2bb7..fe07c769a 100644 --- a/pkg/registry/utils_test.go +++ b/pkg/registry/utils_test.go @@ -40,7 +40,7 @@ import ( "github.com/stretchr/testify/suite" "golang.org/x/crypto/bcrypt" - "helm.sh/helm/v3/internal/tlsutil" + "helm.sh/helm/v4/internal/tlsutil" ) const ( @@ -88,15 +88,20 @@ func setup(suite *TestSuite, tlsEnabled, insecure bool) *registry.Registry { ClientOptEnableCache(true), ClientOptWriter(suite.Out), ClientOptCredentialsFile(credentialsFile), - ClientOptResolver(nil), + ClientOptBasicAuth(testUsername, testPassword), } if tlsEnabled { var tlsConf *tls.Config if insecure { - tlsConf, err = tlsutil.NewClientTLS("", "", "", true) + tlsConf, err = tlsutil.NewTLSConfig( + tlsutil.WithInsecureSkipVerify(true), + ) } else { - tlsConf, err = tlsutil.NewClientTLS(tlsCert, tlsKey, tlsCA, false) + tlsConf, err = tlsutil.NewTLSConfig( + tlsutil.WithCertKeyPairFiles(tlsCert, tlsKey), + tlsutil.WithCAFile(tlsCA), + ) } httpClient := &http.Client{ Transport: &http.Transport{ @@ -128,25 +133,23 @@ func setup(suite *TestSuite, tlsEnabled, insecure bool) *registry.Registry { // This is required because Docker enforces HTTP if the registry // host is localhost/127.0.0.1. suite.DockerRegistryHost = fmt.Sprintf("helm-test-registry:%d", port) - suite.srv, _ = mockdns.NewServer(map[string]mockdns.Zone{ + suite.srv, err = mockdns.NewServer(map[string]mockdns.Zone{ "helm-test-registry.": { A: []string{"127.0.0.1"}, }, }, false) + suite.Nil(err, "no error creating mock DNS server") suite.srv.PatchNet(net.DefaultResolver) config.HTTP.Addr = fmt.Sprintf(":%d", port) config.HTTP.DrainTimeout = time.Duration(10) * time.Second config.Storage = map[string]configuration.Parameters{"inmemory": map[string]interface{}{}} - // Basic auth is not possible if we are serving HTTP. - if tlsEnabled { - config.Auth = configuration.Auth{ - "htpasswd": configuration.Parameters{ - "realm": "localhost", - "path": htpasswdPath, - }, - } + config.Auth = configuration.Auth{ + "htpasswd": configuration.Parameters{ + "realm": "localhost", + "path": htpasswdPath, + }, } // config tls @@ -181,9 +184,7 @@ func initCompromisedRegistryTestServer() string { w.Header().Set("Content-Type", "application/vnd.oci.image.manifest.v1+json") w.WriteHeader(200) - // layers[0] is the blob []byte("a") - w.Write([]byte( - fmt.Sprintf(`{ "schemaVersion": 2, "config": { + fmt.Fprintf(w, `{ "schemaVersion": 2, "config": { "mediaType": "%s", "digest": "sha256:a705ee2789ab50a5ba20930f246dbd5cc01ff9712825bb98f57ee8414377f133", "size": 181 @@ -195,7 +196,7 @@ func initCompromisedRegistryTestServer() string { "size": 1 } ] -}`, ConfigMediaType, ChartLayerMediaType))) +}`, ConfigMediaType, ChartLayerMediaType) } else if r.URL.Path == "/v2/testrepo/supposedlysafechart/blobs/sha256:a705ee2789ab50a5ba20930f246dbd5cc01ff9712825bb98f57ee8414377f133" { w.Header().Set("Content-Type", "application/json") w.WriteHeader(200) @@ -275,7 +276,7 @@ func testPush(suite *TestSuite) { result, err := suite.RegistryClient.Push(chartData, ref, PushOptProvData(provData), PushOptCreationTime(testingChartCreationTime)) suite.Nil(err, "no error pushing good ref with prov") - _, err = suite.RegistryClient.Pull(ref) + _, err = suite.RegistryClient.Pull(ref, PullOptWithProv(true)) suite.Nil(err, "no error pulling a simple chart") // Validate the output @@ -349,7 +350,7 @@ func testPull(suite *TestSuite) { // full pull with chart and prov result, err := suite.RegistryClient.Pull(ref, PullOptWithProv(true)) - suite.Nil(err, "no error pulling a chart with prov") + suite.Require().Nil(err, "no error pulling a chart with prov") // Validate the output // Note: these digests/sizes etc may change if the test chart/prov files are modified, diff --git a/pkg/releaseutil/filter.go b/pkg/release/util/filter.go similarity index 94% rename from pkg/releaseutil/filter.go rename to pkg/release/util/filter.go index dbd0df8e2..f0a082cfd 100644 --- a/pkg/releaseutil/filter.go +++ b/pkg/release/util/filter.go @@ -14,9 +14,9 @@ See the License for the specific language governing permissions and limitations under the License. */ -package releaseutil // import "helm.sh/helm/v3/pkg/releaseutil" +package util // import "helm.sh/helm/v4/pkg/release/util" -import rspb "helm.sh/helm/v3/pkg/release" +import rspb "helm.sh/helm/v4/pkg/release/v1" // FilterFunc returns true if the release object satisfies // the predicate of the underlying filter func. diff --git a/pkg/releaseutil/filter_test.go b/pkg/release/util/filter_test.go similarity index 94% rename from pkg/releaseutil/filter_test.go rename to pkg/release/util/filter_test.go index 31ac306f6..5d2564619 100644 --- a/pkg/releaseutil/filter_test.go +++ b/pkg/release/util/filter_test.go @@ -14,12 +14,12 @@ See the License for the specific language governing permissions and limitations under the License. */ -package releaseutil // import "helm.sh/helm/v3/pkg/releaseutil" +package util // import "helm.sh/helm/v4/pkg/release/util" import ( "testing" - rspb "helm.sh/helm/v3/pkg/release" + rspb "helm.sh/helm/v4/pkg/release/v1" ) func TestFilterAny(t *testing.T) { diff --git a/pkg/releaseutil/kind_sorter.go b/pkg/release/util/kind_sorter.go similarity index 92% rename from pkg/releaseutil/kind_sorter.go rename to pkg/release/util/kind_sorter.go index bb8e84dda..bc074340f 100644 --- a/pkg/releaseutil/kind_sorter.go +++ b/pkg/release/util/kind_sorter.go @@ -14,12 +14,12 @@ See the License for the specific language governing permissions and limitations under the License. */ -package releaseutil +package util import ( "sort" - "helm.sh/helm/v3/pkg/release" + release "helm.sh/helm/v4/pkg/release/v1" ) // KindSortOrder is an ordering of Kinds. @@ -65,12 +65,17 @@ var InstallOrder KindSortOrder = []string{ "IngressClass", "Ingress", "APIService", + "MutatingWebhookConfiguration", + "ValidatingWebhookConfiguration", } // UninstallOrder is the order in which manifests should be uninstalled (by Kind). // // Those occurring earlier in the list get uninstalled before those occurring later in the list. var UninstallOrder KindSortOrder = []string{ + // For uninstall, we remove validation before mutation to ensure webhooks don't block removal + "ValidatingWebhookConfiguration", + "MutatingWebhookConfiguration", "APIService", "Ingress", "IngressClass", diff --git a/pkg/releaseutil/kind_sorter_test.go b/pkg/release/util/kind_sorter_test.go similarity index 95% rename from pkg/releaseutil/kind_sorter_test.go rename to pkg/release/util/kind_sorter_test.go index 9e24c4399..919de24e5 100644 --- a/pkg/releaseutil/kind_sorter_test.go +++ b/pkg/release/util/kind_sorter_test.go @@ -14,13 +14,13 @@ See the License for the specific language governing permissions and limitations under the License. */ -package releaseutil +package util import ( "bytes" "testing" - "helm.sh/helm/v3/pkg/release" + release "helm.sh/helm/v4/pkg/release/v1" ) func TestKindSorter(t *testing.T) { @@ -173,6 +173,14 @@ func TestKindSorter(t *testing.T) { Name: "F", Head: &SimpleHead{Kind: "PriorityClass"}, }, + { + Name: "M", + Head: &SimpleHead{Kind: "MutatingWebhookConfiguration"}, + }, + { + Name: "V", + Head: &SimpleHead{Kind: "ValidatingWebhookConfiguration"}, + }, } for _, test := range []struct { @@ -180,8 +188,8 @@ func TestKindSorter(t *testing.T) { order KindSortOrder expected string }{ - {"install", InstallOrder, "FaAbcC3deEf1gh2iIjJkKlLmnopqrxstuUvw!"}, - {"uninstall", UninstallOrder, "wvUmutsxrqponLlKkJjIi2hg1fEed3CcbAaF!"}, + {"install", InstallOrder, "FaAbcC3deEf1gh2iIjJkKlLmnopqrxstuUvwMV!"}, + {"uninstall", UninstallOrder, "VMwvUmutsxrqponLlKkJjIi2hg1fEed3CcbAaF!"}, } { var buf bytes.Buffer t.Run(test.description, func(t *testing.T) { diff --git a/pkg/releaseutil/manifest.go b/pkg/release/util/manifest.go similarity index 99% rename from pkg/releaseutil/manifest.go rename to pkg/release/util/manifest.go index 0b04a4599..9a87949f8 100644 --- a/pkg/releaseutil/manifest.go +++ b/pkg/release/util/manifest.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package releaseutil +package util import ( "fmt" diff --git a/pkg/releaseutil/manifest_sorter.go b/pkg/release/util/manifest_sorter.go similarity index 87% rename from pkg/releaseutil/manifest_sorter.go rename to pkg/release/util/manifest_sorter.go index 6aff5268a..be93ad1ed 100644 --- a/pkg/releaseutil/manifest_sorter.go +++ b/pkg/release/util/manifest_sorter.go @@ -14,11 +14,11 @@ See the License for the specific language governing permissions and limitations under the License. */ -package releaseutil +package util import ( "fmt" - "log" + "log/slog" "path" "sort" "strconv" @@ -26,8 +26,8 @@ import ( "sigs.k8s.io/yaml" - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/release" + chartutil "helm.sh/helm/v4/pkg/chart/v2/util" + release "helm.sh/helm/v4/pkg/release/v1" ) // Manifest represents a manifest file, which has a name and some content. @@ -128,6 +128,14 @@ func SortManifests(files map[string]string, _ chartutil.VersionSet, ordering Kin // metadata: // annotations: // helm.sh/hook-delete-policy: hook-succeeded +// +// To determine the policy to output logs of the hook (for Pod and Job only), it looks for a YAML structure like this: +// +// kind: Pod +// apiVersion: v1 +// metadata: +// annotations: +// helm.sh/hook-output-log-policy: hook-succeeded,hook-failed func (file *manifestFile) sort(result *result) error { // Go through manifests in order found in file (function `SplitManifests` creates integer-sortable keys) var sortedEntryKeys []string @@ -166,13 +174,14 @@ func (file *manifestFile) sort(result *result) error { hw := calculateHookWeight(entry) h := &release.Hook{ - Name: entry.Metadata.Name, - Kind: entry.Kind, - Path: file.path, - Manifest: m, - Events: []release.HookEvent{}, - Weight: hw, - DeletePolicies: []release.HookDeletePolicy{}, + Name: entry.Metadata.Name, + Kind: entry.Kind, + Path: file.path, + Manifest: m, + Events: []release.HookEvent{}, + Weight: hw, + DeletePolicies: []release.HookDeletePolicy{}, + OutputLogPolicies: []release.HookOutputLogPolicy{}, } isUnknownHook := false @@ -187,7 +196,7 @@ func (file *manifestFile) sort(result *result) error { } if isUnknownHook { - log.Printf("info: skipping unknown hook: %q", hookTypes) + slog.Info("skipping unknown hooks", "hookTypes", hookTypes) continue } @@ -196,6 +205,10 @@ func (file *manifestFile) sort(result *result) error { operateAnnotationValues(entry, release.HookDeleteAnnotation, func(value string) { h.DeletePolicies = append(h.DeletePolicies, release.HookDeletePolicy(value)) }) + + operateAnnotationValues(entry, release.HookOutputLogAnnotation, func(value string) { + h.OutputLogPolicies = append(h.OutputLogPolicies, release.HookOutputLogPolicy(value)) + }) } return nil diff --git a/pkg/releaseutil/manifest_sorter_test.go b/pkg/release/util/manifest_sorter_test.go similarity index 98% rename from pkg/releaseutil/manifest_sorter_test.go rename to pkg/release/util/manifest_sorter_test.go index 020ff4516..4360013e5 100644 --- a/pkg/releaseutil/manifest_sorter_test.go +++ b/pkg/release/util/manifest_sorter_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package releaseutil +package util import ( "reflect" @@ -22,7 +22,7 @@ import ( "sigs.k8s.io/yaml" - "helm.sh/helm/v3/pkg/release" + release "helm.sh/helm/v4/pkg/release/v1" ) func TestSortManifests(t *testing.T) { diff --git a/pkg/releaseutil/manifest_test.go b/pkg/release/util/manifest_test.go similarity index 95% rename from pkg/releaseutil/manifest_test.go rename to pkg/release/util/manifest_test.go index 8664d20ef..cfc19563d 100644 --- a/pkg/releaseutil/manifest_test.go +++ b/pkg/release/util/manifest_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package releaseutil // import "helm.sh/helm/v3/pkg/releaseutil" +package util // import "helm.sh/helm/v4/pkg/release/util" import ( "reflect" diff --git a/pkg/releaseutil/sorter.go b/pkg/release/util/sorter.go similarity index 95% rename from pkg/releaseutil/sorter.go rename to pkg/release/util/sorter.go index 1a8aa78a6..949adbda9 100644 --- a/pkg/releaseutil/sorter.go +++ b/pkg/release/util/sorter.go @@ -14,12 +14,12 @@ See the License for the specific language governing permissions and limitations under the License. */ -package releaseutil // import "helm.sh/helm/v3/pkg/releaseutil" +package util // import "helm.sh/helm/v4/pkg/release/util" import ( "sort" - rspb "helm.sh/helm/v3/pkg/release" + rspb "helm.sh/helm/v4/pkg/release/v1" ) type list []*rspb.Release diff --git a/pkg/releaseutil/sorter_test.go b/pkg/release/util/sorter_test.go similarity index 94% rename from pkg/releaseutil/sorter_test.go rename to pkg/release/util/sorter_test.go index 9544d2018..8a766efc9 100644 --- a/pkg/releaseutil/sorter_test.go +++ b/pkg/release/util/sorter_test.go @@ -14,14 +14,14 @@ See the License for the specific language governing permissions and limitations under the License. */ -package releaseutil // import "helm.sh/helm/v3/pkg/releaseutil" +package util // import "helm.sh/helm/v4/pkg/release/util" import ( "testing" "time" - rspb "helm.sh/helm/v3/pkg/release" - helmtime "helm.sh/helm/v3/pkg/time" + rspb "helm.sh/helm/v4/pkg/release/v1" + helmtime "helm.sh/helm/v4/pkg/time" ) // note: this test data is shared with filter_test.go. diff --git a/pkg/release/hook.go b/pkg/release/v1/hook.go similarity index 84% rename from pkg/release/hook.go rename to pkg/release/v1/hook.go index cb9955582..1ef5c1eb8 100644 --- a/pkg/release/hook.go +++ b/pkg/release/v1/hook.go @@ -14,10 +14,10 @@ See the License for the specific language governing permissions and limitations under the License. */ -package release +package v1 import ( - "helm.sh/helm/v3/pkg/time" + "helm.sh/helm/v4/pkg/time" ) // HookEvent specifies the hook event @@ -50,6 +50,17 @@ const ( func (x HookDeletePolicy) String() string { return string(x) } +// HookOutputLogPolicy specifies the hook output log policy +type HookOutputLogPolicy string + +// Hook output log policy types +const ( + HookOutputOnSucceeded HookOutputLogPolicy = "hook-succeeded" + HookOutputOnFailed HookOutputLogPolicy = "hook-failed" +) + +func (x HookOutputLogPolicy) String() string { return string(x) } + // HookAnnotation is the label name for a hook const HookAnnotation = "helm.sh/hook" @@ -59,6 +70,9 @@ const HookWeightAnnotation = "helm.sh/hook-weight" // HookDeleteAnnotation is the label name for the delete policy for a hook const HookDeleteAnnotation = "helm.sh/hook-delete-policy" +// HookOutputLogAnnotation is the label name for the output log policy for a hook +const HookOutputLogAnnotation = "helm.sh/hook-output-log-policy" + // Hook defines a hook object. type Hook struct { Name string `json:"name,omitempty"` @@ -76,6 +90,8 @@ type Hook struct { Weight int `json:"weight,omitempty"` // DeletePolicies are the policies that indicate when to delete the hook DeletePolicies []HookDeletePolicy `json:"delete_policies,omitempty"` + // OutputLogPolicies defines whether we should copy hook logs back to main process + OutputLogPolicies []HookOutputLogPolicy `json:"output_log_policies,omitempty"` } // A HookExecution records the result for the last execution of a hook for a given release. diff --git a/pkg/release/info.go b/pkg/release/v1/info.go similarity index 96% rename from pkg/release/info.go rename to pkg/release/v1/info.go index b030a8a54..ff98ab63e 100644 --- a/pkg/release/info.go +++ b/pkg/release/v1/info.go @@ -13,12 +13,12 @@ See the License for the specific language governing permissions and limitations under the License. */ -package release +package v1 import ( "k8s.io/apimachinery/pkg/runtime" - "helm.sh/helm/v3/pkg/time" + "helm.sh/helm/v4/pkg/time" ) // Info describes release information. diff --git a/pkg/release/mock.go b/pkg/release/v1/mock.go similarity index 83% rename from pkg/release/mock.go rename to pkg/release/v1/mock.go index a28e1dc16..9ca57284c 100644 --- a/pkg/release/mock.go +++ b/pkg/release/v1/mock.go @@ -14,14 +14,14 @@ See the License for the specific language governing permissions and limitations under the License. */ -package release +package v1 import ( "fmt" "math/rand" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/time" + chart "helm.sh/helm/v4/pkg/chart/v2" + "helm.sh/helm/v4/pkg/time" ) // MockHookTemplate is the hook template used for all mock release objects. @@ -74,6 +74,24 @@ func Mock(opts *MockReleaseOptions) *Release { Name: "foo", Version: "0.1.0-beta.1", AppVersion: "1.0", + Annotations: map[string]string{ + "category": "web-apps", + "supported": "true", + }, + Dependencies: []*chart.Dependency{ + { + Name: "cool-plugin", + Version: "1.0.0", + Repository: "https://coolplugin.io/charts", + Condition: "coolPlugin.enabled", + Enabled: true, + }, + { + Name: "crds", + Version: "2.7.1", + Condition: "crds.enabled", + }, + }, }, Templates: []*chart.File{ {Name: "templates/foo.tpl", Data: []byte(MockManifest)}, diff --git a/pkg/release/release.go b/pkg/release/v1/release.go similarity index 96% rename from pkg/release/release.go rename to pkg/release/v1/release.go index b90612873..74e834f7b 100644 --- a/pkg/release/release.go +++ b/pkg/release/v1/release.go @@ -13,9 +13,11 @@ See the License for the specific language governing permissions and limitations under the License. */ -package release +package v1 -import "helm.sh/helm/v3/pkg/chart" +import ( + chart "helm.sh/helm/v4/pkg/chart/v2" +) // Release describes a deployment of a chart, together with the chart // and the variables used to deploy that chart. diff --git a/pkg/release/responses.go b/pkg/release/v1/responses.go similarity index 98% rename from pkg/release/responses.go rename to pkg/release/v1/responses.go index 7ee1fc2ee..2a5608c67 100644 --- a/pkg/release/responses.go +++ b/pkg/release/v1/responses.go @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package release +package v1 // UninstallReleaseResponse represents a successful response to an uninstall request. type UninstallReleaseResponse struct { diff --git a/pkg/release/status.go b/pkg/release/v1/status.go similarity index 99% rename from pkg/release/status.go rename to pkg/release/v1/status.go index edd27a5f1..8d6459013 100644 --- a/pkg/release/status.go +++ b/pkg/release/v1/status.go @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package release +package v1 // Status is the status of a release type Status string diff --git a/pkg/repo/chartrepo.go b/pkg/repo/chartrepo.go index 01748384a..7631e2570 100644 --- a/pkg/repo/chartrepo.go +++ b/pkg/repo/chartrepo.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package repo // import "helm.sh/helm/v3/pkg/repo" +package repo // import "helm.sh/helm/v4/pkg/repo" import ( "crypto/rand" @@ -22,18 +22,14 @@ import ( "encoding/json" "fmt" "io" - "log" + "log/slog" "net/url" "os" "path/filepath" "strings" - "sigs.k8s.io/yaml" - - "helm.sh/helm/v3/pkg/chart/loader" - "helm.sh/helm/v3/pkg/getter" - "helm.sh/helm/v3/pkg/helmpath" - "helm.sh/helm/v3/pkg/provenance" + "helm.sh/helm/v4/pkg/getter" + "helm.sh/helm/v4/pkg/helmpath" ) // Entry represents a collection of parameters for chart repository @@ -51,11 +47,10 @@ type Entry struct { // ChartRepository represents a chart repository type ChartRepository struct { - Config *Entry - ChartPaths []string - IndexFile *IndexFile - Client getter.Getter - CachePath string + Config *Entry + IndexFile *IndexFile + Client getter.Getter + CachePath string } // NewChartRepository constructs ChartRepository @@ -78,40 +73,6 @@ func NewChartRepository(cfg *Entry, getters getter.Providers) (*ChartRepository, }, nil } -// Load loads a directory of charts as if it were a repository. -// -// It requires the presence of an index.yaml file in the directory. -// -// Deprecated: remove in Helm 4. -func (r *ChartRepository) Load() error { - dirInfo, err := os.Stat(r.Config.Name) - if err != nil { - return err - } - if !dirInfo.IsDir() { - return fmt.Errorf("%q is not a directory", r.Config.Name) - } - - // FIXME: Why are we recursively walking directories? - // FIXME: Why are we not reading the repositories.yaml to figure out - // what repos to use? - filepath.Walk(r.Config.Name, func(path string, f os.FileInfo, _ error) error { - if !f.IsDir() { - if strings.Contains(f.Name(), "-index.yaml") { - i, err := LoadIndexFile(path) - if err != nil { - return err - } - r.IndexFile = i - } else if strings.HasSuffix(f.Name(), ".tgz") { - r.ChartPaths = append(r.ChartPaths, path) - } - } - return nil - }) - return nil -} - // DownloadIndexFile fetches the index from a repository. func (r *ChartRepository) DownloadIndexFile() (string, error) { indexURL, err := ResolveReferenceURL(r.Config.URL, "index.yaml") @@ -155,73 +116,65 @@ func (r *ChartRepository) DownloadIndexFile() (string, error) { return fname, os.WriteFile(fname, index, 0644) } -// Index generates an index for the chart repository and writes an index.yaml file. -func (r *ChartRepository) Index() error { - err := r.generateIndex() - if err != nil { - return err - } - return r.saveIndexFile() +type findChartInRepoURLOptions struct { + Username string + Password string + PassCredentialsAll bool + InsecureSkipTLSverify bool + CertFile string + KeyFile string + CAFile string + ChartVersion string } -func (r *ChartRepository) saveIndexFile() error { - index, err := yaml.Marshal(r.IndexFile) - if err != nil { - return err +type FindChartInRepoURLOption func(*findChartInRepoURLOptions) + +// WithChartVersion specifies the chart version to find +func WithChartVersion(chartVersion string) FindChartInRepoURLOption { + return func(options *findChartInRepoURLOptions) { + options.ChartVersion = chartVersion } - return os.WriteFile(filepath.Join(r.Config.Name, indexPath), index, 0644) } -func (r *ChartRepository) generateIndex() error { - for _, path := range r.ChartPaths { - ch, err := loader.Load(path) - if err != nil { - return err - } - - digest, err := provenance.DigestFile(path) - if err != nil { - return err - } - - if !r.IndexFile.Has(ch.Name(), ch.Metadata.Version) { - if err := r.IndexFile.MustAdd(ch.Metadata, path, r.Config.URL, digest); err != nil { - return fmt.Errorf("failed adding to %s to index: %w", path, err) - } - } - // TODO: If a chart exists, but has a different Digest, should we error? +// WithUsernamePassword specifies the username/password credntials for the repository +func WithUsernamePassword(username, password string) FindChartInRepoURLOption { + return func(options *findChartInRepoURLOptions) { + options.Username = username + options.Password = password } - r.IndexFile.SortEntries() - return nil } -// FindChartInRepoURL finds chart in chart repository pointed by repoURL -// without adding repo to repositories -func FindChartInRepoURL(repoURL, chartName, chartVersion, certFile, keyFile, caFile string, getters getter.Providers) (string, error) { - return FindChartInAuthRepoURL(repoURL, "", "", chartName, chartVersion, certFile, keyFile, caFile, getters) +// WithPassCredentialsAll flags whether credentials should be passed on to other domains +func WithPassCredentialsAll(passCredentialsAll bool) FindChartInRepoURLOption { + return func(options *findChartInRepoURLOptions) { + options.PassCredentialsAll = passCredentialsAll + } } -// FindChartInAuthRepoURL finds chart in chart repository pointed by repoURL -// without adding repo to repositories, like FindChartInRepoURL, -// but it also receives credentials for the chart repository. -func FindChartInAuthRepoURL(repoURL, username, password, chartName, chartVersion, certFile, keyFile, caFile string, getters getter.Providers) (string, error) { - return FindChartInAuthAndTLSRepoURL(repoURL, username, password, chartName, chartVersion, certFile, keyFile, caFile, false, getters) +// WithClientTLS species the cert, key, and CA files for client mTLS +func WithClientTLS(certFile, keyFile, caFile string) FindChartInRepoURLOption { + return func(options *findChartInRepoURLOptions) { + options.CertFile = certFile + options.KeyFile = keyFile + options.CAFile = caFile + } } -// FindChartInAuthAndTLSRepoURL finds chart in chart repository pointed by repoURL -// without adding repo to repositories, like FindChartInRepoURL, -// but it also receives credentials and TLS verify flag for the chart repository. -// TODO Helm 4, FindChartInAuthAndTLSRepoURL should be integrated into FindChartInAuthRepoURL. -func FindChartInAuthAndTLSRepoURL(repoURL, username, password, chartName, chartVersion, certFile, keyFile, caFile string, insecureSkipTLSverify bool, getters getter.Providers) (string, error) { - return FindChartInAuthAndTLSAndPassRepoURL(repoURL, username, password, chartName, chartVersion, certFile, keyFile, caFile, insecureSkipTLSverify, false, getters) +// WithInsecureSkipTLSverify skips TLS verification for repostory communication +func WithInsecureSkipTLSverify(insecureSkipTLSverify bool) FindChartInRepoURLOption { + return func(options *findChartInRepoURLOptions) { + options.InsecureSkipTLSverify = insecureSkipTLSverify + } } -// FindChartInAuthAndTLSAndPassRepoURL finds chart in chart repository pointed by repoURL -// without adding repo to repositories, like FindChartInRepoURL, -// but it also receives credentials, TLS verify flag, and if credentials should -// be passed on to other domains. -// TODO Helm 4, FindChartInAuthAndTLSAndPassRepoURL should be integrated into FindChartInAuthRepoURL. -func FindChartInAuthAndTLSAndPassRepoURL(repoURL, username, password, chartName, chartVersion, certFile, keyFile, caFile string, insecureSkipTLSverify, passCredentialsAll bool, getters getter.Providers) (string, error) { +// FindChartInRepoURL finds chart in chart repository pointed by repoURL +// without adding repo to repositories +func FindChartInRepoURL(repoURL string, chartName string, getters getter.Providers, options ...FindChartInRepoURLOption) (string, error) { + + opts := findChartInRepoURLOptions{} + for _, option := range options { + option(&opts) + } // Download and write the index file to a temporary location buf := make([]byte, 20) @@ -230,14 +183,14 @@ func FindChartInAuthAndTLSAndPassRepoURL(repoURL, username, password, chartName, c := Entry{ URL: repoURL, - Username: username, - Password: password, - PassCredentialsAll: passCredentialsAll, - CertFile: certFile, - KeyFile: keyFile, - CAFile: caFile, + Username: opts.Username, + Password: opts.Password, + PassCredentialsAll: opts.PassCredentialsAll, + CertFile: opts.CertFile, + KeyFile: opts.KeyFile, + CAFile: opts.CAFile, Name: name, - InsecureSkipTLSverify: insecureSkipTLSverify, + InsecureSkipTLSverify: opts.InsecureSkipTLSverify, } r, err := NewChartRepository(&c, getters) if err != nil { @@ -259,10 +212,10 @@ func FindChartInAuthAndTLSAndPassRepoURL(repoURL, username, password, chartName, } errMsg := fmt.Sprintf("chart %q", chartName) - if chartVersion != "" { - errMsg = fmt.Sprintf("%s version %q", errMsg, chartVersion) + if opts.ChartVersion != "" { + errMsg = fmt.Sprintf("%s version %q", errMsg, opts.ChartVersion) } - cv, err := repoIndex.Get(chartName, chartVersion) + cv, err := repoIndex.Get(chartName, opts.ChartVersion) if err != nil { return "", fmt.Errorf("%s not found in %s repository", errMsg, repoURL) } @@ -310,7 +263,8 @@ func ResolveReferenceURL(baseURL, refURL string) (string, error) { func (e *Entry) String() string { buf, err := json.Marshal(e) if err != nil { - log.Panic(err) + slog.Error("failed to marshal entry", slog.Any("error", err)) + panic(err) } return string(buf) } diff --git a/pkg/repo/chartrepo_test.go b/pkg/repo/chartrepo_test.go index 0a658c0c2..41bac9827 100644 --- a/pkg/repo/chartrepo_test.go +++ b/pkg/repo/chartrepo_test.go @@ -21,8 +21,6 @@ import ( "net/http" "net/http/httptest" "os" - "path/filepath" - "reflect" "runtime" "strings" "testing" @@ -30,87 +28,10 @@ import ( "sigs.k8s.io/yaml" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/cli" - "helm.sh/helm/v3/pkg/getter" + "helm.sh/helm/v4/pkg/cli" + "helm.sh/helm/v4/pkg/getter" ) -const ( - testRepository = "testdata/repository" - testURL = "http://example-charts.com" -) - -func TestLoadChartRepository(t *testing.T) { - r, err := NewChartRepository(&Entry{ - Name: testRepository, - URL: testURL, - }, getter.All(&cli.EnvSettings{})) - if err != nil { - t.Errorf("Problem creating chart repository from %s: %v", testRepository, err) - } - - if err := r.Load(); err != nil { - t.Errorf("Problem loading chart repository from %s: %v", testRepository, err) - } - - paths := []string{ - filepath.Join(testRepository, "frobnitz-1.2.3.tgz"), - filepath.Join(testRepository, "sprocket-1.1.0.tgz"), - filepath.Join(testRepository, "sprocket-1.2.0.tgz"), - filepath.Join(testRepository, "universe/zarthal-1.0.0.tgz"), - } - - if r.Config.Name != testRepository { - t.Errorf("Expected %s as Name but got %s", testRepository, r.Config.Name) - } - - if !reflect.DeepEqual(r.ChartPaths, paths) { - t.Errorf("Expected %#v but got %#v\n", paths, r.ChartPaths) - } - - if r.Config.URL != testURL { - t.Errorf("Expected url for chart repository to be %s but got %s", testURL, r.Config.URL) - } -} - -func TestIndex(t *testing.T) { - r, err := NewChartRepository(&Entry{ - Name: testRepository, - URL: testURL, - }, getter.All(&cli.EnvSettings{})) - if err != nil { - t.Errorf("Problem creating chart repository from %s: %v", testRepository, err) - } - - if err := r.Load(); err != nil { - t.Errorf("Problem loading chart repository from %s: %v", testRepository, err) - } - - err = r.Index() - if err != nil { - t.Errorf("Error performing index: %v\n", err) - } - - tempIndexPath := filepath.Join(testRepository, indexPath) - actual, err := LoadIndexFile(tempIndexPath) - defer os.Remove(tempIndexPath) // clean up - if err != nil { - t.Errorf("Error loading index file %v", err) - } - verifyIndex(t, actual) - - // Re-index and test again. - err = r.Index() - if err != nil { - t.Errorf("Error performing re-index: %s\n", err) - } - second, err := LoadIndexFile(tempIndexPath) - if err != nil { - t.Errorf("Error re-loading index file %v", err) - } - verifyIndex(t, second) -} - type CustomGetter struct { repoUrls []string } @@ -169,97 +90,6 @@ func TestIndexCustomSchemeDownload(t *testing.T) { } } -func verifyIndex(t *testing.T, actual *IndexFile) { - var empty time.Time - if actual.Generated.Equal(empty) { - t.Errorf("Generated should be greater than 0: %s", actual.Generated) - } - - if actual.APIVersion != APIVersionV1 { - t.Error("Expected v1 API") - } - - entries := actual.Entries - if numEntries := len(entries); numEntries != 3 { - t.Errorf("Expected 3 charts to be listed in index file but got %v", numEntries) - } - - expects := map[string]ChartVersions{ - "frobnitz": { - { - Metadata: &chart.Metadata{ - Name: "frobnitz", - Version: "1.2.3", - }, - }, - }, - "sprocket": { - { - Metadata: &chart.Metadata{ - Name: "sprocket", - Version: "1.2.0", - }, - }, - { - Metadata: &chart.Metadata{ - Name: "sprocket", - Version: "1.1.0", - }, - }, - }, - "zarthal": { - { - Metadata: &chart.Metadata{ - Name: "zarthal", - Version: "1.0.0", - }, - }, - }, - } - - for name, versions := range expects { - got, ok := entries[name] - if !ok { - t.Errorf("Could not find %q entry", name) - continue - } - if len(versions) != len(got) { - t.Errorf("Expected %d versions, got %d", len(versions), len(got)) - continue - } - for i, e := range versions { - g := got[i] - if e.Name != g.Name { - t.Errorf("Expected %q, got %q", e.Name, g.Name) - } - if e.Version != g.Version { - t.Errorf("Expected %q, got %q", e.Version, g.Version) - } - if len(g.Keywords) != 3 { - t.Error("Expected 3 keywords.") - } - if len(g.Maintainers) != 2 { - t.Error("Expected 2 maintainers.") - } - if g.Created.Equal(empty) { - t.Error("Expected created to be non-empty") - } - if g.Description == "" { - t.Error("Expected description to be non-empty") - } - if g.Home == "" { - t.Error("Expected home to be non-empty") - } - if g.Digest == "" { - t.Error("Expected digest to be non-empty") - } - if len(g.URLs) != 1 { - t.Error("Expected exactly 1 URL") - } - } - } -} - // startLocalServerForTests Start the local helm server func startLocalServerForTests(handler http.Handler) (*httptest.Server, error) { if handler == nil { @@ -297,7 +127,12 @@ func TestFindChartInAuthAndTLSAndPassRepoURL(t *testing.T) { } defer srv.Close() - chartURL, err := FindChartInAuthAndTLSAndPassRepoURL(srv.URL, "", "", "nginx", "", "", "", "", true, false, getter.All(&cli.EnvSettings{})) + chartURL, err := FindChartInRepoURL( + srv.URL, + "nginx", + getter.All(&cli.EnvSettings{}), + WithInsecureSkipTLSverify(true), + ) if err != nil { t.Fatalf("%v", err) } @@ -306,7 +141,7 @@ func TestFindChartInAuthAndTLSAndPassRepoURL(t *testing.T) { } // If the insecureSkipTLSVerify is false, it will return an error that contains "x509: certificate signed by unknown authority". - _, err = FindChartInAuthAndTLSAndPassRepoURL(srv.URL, "", "", "nginx", "0.1.0", "", "", "", false, false, getter.All(&cli.EnvSettings{})) + _, err = FindChartInRepoURL(srv.URL, "nginx", getter.All(&cli.EnvSettings{}), WithChartVersion("0.1.0")) // Go communicates with the platform and different platforms return different messages. Go itself tests darwin // differently for its message. On newer versions of Darwin the message includes the "Acme Co" portion while older // versions of Darwin do not. As there are people developing Helm using both old and new versions of Darwin we test @@ -327,7 +162,7 @@ func TestFindChartInRepoURL(t *testing.T) { } defer srv.Close() - chartURL, err := FindChartInRepoURL(srv.URL, "nginx", "", "", "", "", getter.All(&cli.EnvSettings{})) + chartURL, err := FindChartInRepoURL(srv.URL, "nginx", getter.All(&cli.EnvSettings{})) if err != nil { t.Fatalf("%v", err) } @@ -335,7 +170,7 @@ func TestFindChartInRepoURL(t *testing.T) { t.Errorf("%s is not the valid URL", chartURL) } - chartURL, err = FindChartInRepoURL(srv.URL, "nginx", "0.1.0", "", "", "", getter.All(&cli.EnvSettings{})) + chartURL, err = FindChartInRepoURL(srv.URL, "nginx", getter.All(&cli.EnvSettings{}), WithChartVersion("0.1.0")) if err != nil { t.Errorf("%s", err) } @@ -350,7 +185,7 @@ func TestErrorFindChartInRepoURL(t *testing.T) { RepositoryCache: t.TempDir(), }) - if _, err := FindChartInRepoURL("http://someserver/something", "nginx", "", "", "", "", g); err == nil { + if _, err := FindChartInRepoURL("http://someserver/something", "nginx", g); err == nil { t.Errorf("Expected error for bad chart URL, but did not get any errors") } else if !strings.Contains(err.Error(), `looks like "http://someserver/something" is not a valid chart repository or cannot be reached`) { t.Errorf("Expected error for bad chart URL, but got a different error (%v)", err) @@ -362,19 +197,19 @@ func TestErrorFindChartInRepoURL(t *testing.T) { } defer srv.Close() - if _, err = FindChartInRepoURL(srv.URL, "nginx1", "", "", "", "", g); err == nil { + if _, err = FindChartInRepoURL(srv.URL, "nginx1", g); err == nil { t.Errorf("Expected error for chart not found, but did not get any errors") } else if err.Error() != `chart "nginx1" not found in `+srv.URL+` repository` { t.Errorf("Expected error for chart not found, but got a different error (%v)", err) } - if _, err = FindChartInRepoURL(srv.URL, "nginx1", "0.1.0", "", "", "", g); err == nil { + if _, err = FindChartInRepoURL(srv.URL, "nginx1", g, WithChartVersion("0.1.0")); err == nil { t.Errorf("Expected error for chart not found, but did not get any errors") } else if err.Error() != `chart "nginx1" version "0.1.0" not found in `+srv.URL+` repository` { t.Errorf("Expected error for chart not found, but got a different error (%v)", err) } - if _, err = FindChartInRepoURL(srv.URL, "chartWithNoURL", "", "", "", "", g); err == nil { + if _, err = FindChartInRepoURL(srv.URL, "chartWithNoURL", g); err == nil { t.Errorf("Expected error for no chart URLs available, but did not get any errors") } else if err.Error() != `chart "chartWithNoURL" has no downloadable URLs` { t.Errorf("Expected error for chart not found, but got a different error (%v)", err) diff --git a/pkg/repo/index.go b/pkg/repo/index.go index 37ce0cc53..c26d7581c 100644 --- a/pkg/repo/index.go +++ b/pkg/repo/index.go @@ -21,7 +21,7 @@ import ( "encoding/json" "errors" "fmt" - "log" + "log/slog" "os" "path" "path/filepath" @@ -32,15 +32,13 @@ import ( "github.com/Masterminds/semver/v3" "sigs.k8s.io/yaml" - "helm.sh/helm/v3/internal/fileutil" - "helm.sh/helm/v3/internal/urlutil" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chart/loader" - "helm.sh/helm/v3/pkg/provenance" + "helm.sh/helm/v4/internal/fileutil" + "helm.sh/helm/v4/internal/urlutil" + chart "helm.sh/helm/v4/pkg/chart/v2" + "helm.sh/helm/v4/pkg/chart/v2/loader" + "helm.sh/helm/v4/pkg/provenance" ) -var indexPath = "index.yaml" - // APIVersionV1 is the v1 API version for index and repository files. const APIVersionV1 = "v1" @@ -155,7 +153,7 @@ func (i IndexFile) MustAdd(md *chart.Metadata, filename, baseURL, digest string) // Deprecated: Use index.MustAdd instead. func (i IndexFile) Add(md *chart.Metadata, filename, baseURL, digest string) { if err := i.MustAdd(md, filename, baseURL, digest); err != nil { - log.Printf("skipping loading invalid entry for chart %q %q from %s: %s", md.Name, md.Version, filename, err) + slog.Error("skipping loading invalid entry for chart %q %q from %s: %s", md.Name, md.Version, filename, err) } } @@ -357,7 +355,7 @@ func loadIndex(data []byte, source string) (*IndexFile, error) { for name, cvs := range i.Entries { for idx := len(cvs) - 1; idx >= 0; idx-- { if cvs[idx] == nil { - log.Printf("skipping loading invalid entry for chart %q from %s: empty entry", name, source) + slog.Warn("skipping loading invalid entry for chart %q from %s: empty entry", name, source) continue } // When metadata section missing, initialize with no data @@ -368,7 +366,7 @@ func loadIndex(data []byte, source string) (*IndexFile, error) { cvs[idx].APIVersion = chart.APIVersionV1 } if err := cvs[idx].Validate(); ignoreSkippableChartValidationError(err) != nil { - log.Printf("skipping loading invalid entry for chart %q %q from %s: %s", name, cvs[idx].Version, source, err) + slog.Warn("skipping loading invalid entry for chart %q %q from %s: %s", name, cvs[idx].Version, source, err) cvs = append(cvs[:idx], cvs[idx+1:]...) } } diff --git a/pkg/repo/index_test.go b/pkg/repo/index_test.go index a1e3e9438..2a33cd1a9 100644 --- a/pkg/repo/index_test.go +++ b/pkg/repo/index_test.go @@ -28,10 +28,10 @@ import ( "strings" "testing" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/cli" - "helm.sh/helm/v3/pkg/getter" - "helm.sh/helm/v3/pkg/helmpath" + chart "helm.sh/helm/v4/pkg/chart/v2" + "helm.sh/helm/v4/pkg/cli" + "helm.sh/helm/v4/pkg/getter" + "helm.sh/helm/v4/pkg/helmpath" ) const ( @@ -123,17 +123,17 @@ func TestIndexFile(t *testing.T) { } cv, err := i.Get("setter", "0.1.9") - if err == nil && !strings.Contains(cv.Metadata.Version, "0.1.9") { - t.Errorf("Unexpected version: %s", cv.Metadata.Version) + if err == nil && !strings.Contains(cv.Version, "0.1.9") { + t.Errorf("Unexpected version: %s", cv.Version) } cv, err = i.Get("setter", "0.1.9+alpha") - if err != nil || cv.Metadata.Version != "0.1.9+alpha" { + if err != nil || cv.Version != "0.1.9+alpha" { t.Errorf("Expected version: 0.1.9+alpha") } cv, err = i.Get("setter", "0.1.8") - if err != nil || cv.Metadata.Version != "0.1.8" { + if err != nil || cv.Version != "0.1.8" { t.Errorf("Expected version: 0.1.8") } } diff --git a/pkg/repo/repo.go b/pkg/repo/repo.go index 1addb9277..48c0e0193 100644 --- a/pkg/repo/repo.go +++ b/pkg/repo/repo.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package repo // import "helm.sh/helm/v3/pkg/repo" +package repo // import "helm.sh/helm/v4/pkg/repo" import ( "fmt" diff --git a/pkg/repo/repotest/server.go b/pkg/repo/repotest/server.go index 4a86707cf..709a6f5fd 100644 --- a/pkg/repo/repotest/server.go +++ b/pkg/repo/repotest/server.go @@ -17,6 +17,7 @@ package repotest import ( "context" + "crypto/tls" "fmt" "net/http" "net/http/httptest" @@ -33,42 +34,110 @@ import ( "golang.org/x/crypto/bcrypt" "sigs.k8s.io/yaml" - "helm.sh/helm/v3/internal/tlsutil" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chart/loader" - "helm.sh/helm/v3/pkg/chartutil" - ociRegistry "helm.sh/helm/v3/pkg/registry" - "helm.sh/helm/v3/pkg/repo" + chart "helm.sh/helm/v4/pkg/chart/v2" + "helm.sh/helm/v4/pkg/chart/v2/loader" + chartutil "helm.sh/helm/v4/pkg/chart/v2/util" + ociRegistry "helm.sh/helm/v4/pkg/registry" + "helm.sh/helm/v4/pkg/repo" ) -// NewTempServerWithCleanup creates a server inside of a temp dir. +func BasicAuthMiddleware(t *testing.T) http.HandlerFunc { + return http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) { + username, password, ok := r.BasicAuth() + if !ok || username != "username" || password != "password" { + t.Errorf("Expected request to use basic auth and for username == 'username' and password == 'password', got '%v', '%s', '%s'", ok, username, password) + } + }) +} + +type ServerOption func(*testing.T, *Server) + +func WithTLSConfig(tlsConfig *tls.Config) ServerOption { + return func(_ *testing.T, server *Server) { + server.tlsConfig = tlsConfig + } +} + +func WithMiddleware(middleware http.HandlerFunc) ServerOption { + return func(_ *testing.T, server *Server) { + server.middleware = middleware + } +} + +func WithChartSourceGlob(glob string) ServerOption { + return func(_ *testing.T, server *Server) { + server.chartSourceGlob = glob + } +} + +// Server is an implementation of a repository server for testing. +type Server struct { + docroot string + srv *httptest.Server + middleware http.HandlerFunc + tlsConfig *tls.Config + chartSourceGlob string +} + +// NewTempServer creates a server inside of a temp dir. // // If the passed in string is not "", it will be treated as a shell glob, and files // will be copied from that path to the server's docroot. // -// The caller is responsible for stopping the server. +// The server is started automatically. The caller is responsible for stopping +// the server. +// // The temp dir will be removed by testing package automatically when test finished. -func NewTempServerWithCleanup(t *testing.T, glob string) (*Server, error) { - srv, err := NewTempServer(glob) +func NewTempServer(t *testing.T, options ...ServerOption) *Server { + + docrootTempDir, err := os.MkdirTemp("", "helm-repotest-") + if err != nil { + t.Fatal(err) + } + + srv := newServer(t, docrootTempDir, options...) + t.Cleanup(func() { os.RemoveAll(srv.docroot) }) - return srv, err + + if srv.chartSourceGlob != "" { + if _, err := srv.CopyCharts(srv.chartSourceGlob); err != nil { + t.Fatal(err) + } + } + + return srv } -// Set up a fake repo with basic auth enabled -func NewTempServerWithCleanupAndBasicAuth(t *testing.T, glob string) *Server { - srv, err := NewTempServerWithCleanup(t, glob) - srv.Stop() +// Create the server, but don't yet start it +func newServer(t *testing.T, docroot string, options ...ServerOption) *Server { + absdocroot, err := filepath.Abs(docroot) if err != nil { t.Fatal(err) } - srv.WithMiddleware(http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) { - username, password, ok := r.BasicAuth() - if !ok || username != "username" || password != "password" { - t.Errorf("Expected request to use basic auth and for username == 'username' and password == 'password', got '%v', '%s', '%s'", ok, username, password) + + s := &Server{ + docroot: absdocroot, + } + + for _, option := range options { + option(t, s) + } + + s.srv = httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if s.middleware != nil { + s.middleware.ServeHTTP(w, r) } + http.FileServer(http.Dir(s.Root())).ServeHTTP(w, r) })) - srv.Start() - return srv + + s.start() + + // Add the testing repository as the only repo. Server must be started for the server's URL to be valid + if err := setTestingRepository(s.URL(), filepath.Join(s.docroot, "repositories.yaml")); err != nil { + t.Fatal(err) + } + + return s } type OCIServer struct { @@ -113,7 +182,7 @@ func NewOCIServer(t *testing.T, dir string) (*OCIServer, error) { t.Fatalf("error finding free port for test registry") } - config.HTTP.Addr = fmt.Sprintf(":%d", port) + config.HTTP.Addr = fmt.Sprintf("127.0.0.1:%d", port) config.HTTP.DrainTimeout = time.Duration(10) * time.Second config.Storage = map[string]configuration.Parameters{"inmemory": map[string]interface{}{}} config.Auth = configuration.Auth{ @@ -163,9 +232,10 @@ func (srv *OCIServer) Run(t *testing.T, opts ...OCIServerOpt) { err = registryClient.Login( srv.RegistryURL, ociRegistry.LoginOptBasicAuth(srv.TestUsername, srv.TestPassword), - ociRegistry.LoginOptInsecure(false)) + ociRegistry.LoginOptInsecure(true), + ociRegistry.LoginOptPlainText(true)) if err != nil { - t.Fatalf("error logging into registry with good credentials") + t.Fatalf("error logging into registry with good credentials: %v", err) } ref := fmt.Sprintf("%s/u/ocitestuser/oci-dependent-chart:0.1.0", srv.RegistryURL) @@ -238,69 +308,6 @@ func (srv *OCIServer) Run(t *testing.T, opts ...OCIServerOpt) { result.Chart.Digest, result.Chart.Size) } -// NewTempServer creates a server inside of a temp dir. -// -// If the passed in string is not "", it will be treated as a shell glob, and files -// will be copied from that path to the server's docroot. -// -// The caller is responsible for destroying the temp directory as well as stopping -// the server. -// -// Deprecated: use NewTempServerWithCleanup -func NewTempServer(glob string) (*Server, error) { - tdir, err := os.MkdirTemp("", "helm-repotest-") - if err != nil { - return nil, err - } - srv := NewServer(tdir) - - if glob != "" { - if _, err := srv.CopyCharts(glob); err != nil { - srv.Stop() - return srv, err - } - } - - return srv, nil -} - -// NewServer creates a repository server for testing. -// -// docroot should be a temp dir managed by the caller. -// -// This will start the server, serving files off of the docroot. -// -// Use CopyCharts to move charts into the repository and then index them -// for service. -func NewServer(docroot string) *Server { - root, err := filepath.Abs(docroot) - if err != nil { - panic(err) - } - srv := &Server{ - docroot: root, - } - srv.Start() - // Add the testing repository as the only repo. - if err := setTestingRepository(srv.URL(), filepath.Join(root, "repositories.yaml")); err != nil { - panic(err) - } - return srv -} - -// Server is an implementation of a repository server for testing. -type Server struct { - docroot string - srv *httptest.Server - middleware http.HandlerFunc -} - -// WithMiddleware injects middleware in front of the server. This can be used to inject -// additional functionality like layering in an authentication frontend. -func (s *Server) WithMiddleware(middleware http.HandlerFunc) { - s.middleware = middleware -} - // Root gets the docroot for the server. func (s *Server) Root() string { return s.docroot @@ -347,46 +354,12 @@ func (s *Server) CreateIndex() error { return os.WriteFile(ifile, d, 0644) } -func (s *Server) Start() { - s.srv = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if s.middleware != nil { - s.middleware.ServeHTTP(w, r) - } - http.FileServer(http.Dir(s.docroot)).ServeHTTP(w, r) - })) -} - -func (s *Server) StartTLS() { - cd := "../../testdata" - ca, pub, priv := filepath.Join(cd, "rootca.crt"), filepath.Join(cd, "crt.pem"), filepath.Join(cd, "key.pem") - insecure := false - - s.srv = httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if s.middleware != nil { - s.middleware.ServeHTTP(w, r) - } - http.FileServer(http.Dir(s.Root())).ServeHTTP(w, r) - })) - tlsConf, err := tlsutil.NewClientTLS(pub, priv, ca, insecure) - if err != nil { - panic(err) - } - tlsConf.ServerName = "helm.sh" - s.srv.TLS = tlsConf - s.srv.StartTLS() - - // Set up repositories config with ca file - repoConfig := filepath.Join(s.Root(), "repositories.yaml") - - r := repo.NewFile() - r.Add(&repo.Entry{ - Name: "test", - URL: s.URL(), - CAFile: filepath.Join("../../testdata", "rootca.crt"), - }) - - if err := r.WriteFile(repoConfig, 0600); err != nil { - panic(err) +func (s *Server) start() { + if s.tlsConfig != nil { + s.srv.TLS = s.tlsConfig + s.srv.StartTLS() + } else { + s.srv.Start() } } @@ -406,6 +379,10 @@ func (s *Server) URL() string { return s.srv.URL } +func (s *Server) Client() *http.Client { + return s.srv.Client() +} + // LinkIndices links the index created with CreateIndex and makes a symbolic link to the cache index. // // This makes it possible to simulate a local cache of a repository. @@ -417,6 +394,10 @@ func (s *Server) LinkIndices() error { // setTestingRepository sets up a testing repository.yaml with only the given URL. func setTestingRepository(url, fname string) error { + if url == "" { + panic("no url") + } + r := repo.NewFile() r.Add(&repo.Entry{ Name: "test", diff --git a/pkg/repo/repotest/server_test.go b/pkg/repo/repotest/server_test.go index a7d7f5b95..cf68e5110 100644 --- a/pkg/repo/repotest/server_test.go +++ b/pkg/repo/repotest/server_test.go @@ -19,12 +19,13 @@ import ( "io" "net/http" "path/filepath" + "strings" "testing" "sigs.k8s.io/yaml" - "helm.sh/helm/v3/internal/test/ensure" - "helm.sh/helm/v3/pkg/repo" + "helm.sh/helm/v4/internal/test/ensure" + "helm.sh/helm/v4/pkg/repo" ) // Young'n, in these here parts, we test our tests. @@ -34,7 +35,7 @@ func TestServer(t *testing.T) { rootDir := t.TempDir() - srv := NewServer(rootDir) + srv := newServer(t, rootDir) defer srv.Stop() c, err := srv.CopyCharts("testdata/*.tgz") @@ -99,18 +100,123 @@ func TestServer(t *testing.T) { func TestNewTempServer(t *testing.T) { ensure.HelmHome(t) - srv, err := NewTempServerWithCleanup(t, "testdata/examplechart-0.1.0.tgz") - if err != nil { - t.Fatal(err) + type testCase struct { + options []ServerOption } - defer srv.Stop() - res, err := http.Head(srv.URL() + "/examplechart-0.1.0.tgz") - res.Body.Close() - if err != nil { - t.Error(err) + testCases := map[string]testCase{ + "plainhttp": { + options: []ServerOption{ + WithChartSourceGlob("testdata/examplechart-0.1.0.tgz"), + }, + }, + "tls": { + options: []ServerOption{ + WithChartSourceGlob("testdata/examplechart-0.1.0.tgz"), + WithTLSConfig(MakeTestTLSConfig(t, "../../../testdata")), + }, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + srv := NewTempServer( + t, + tc.options..., + ) + defer srv.Stop() + + if srv.srv.URL == "" { + t.Fatal("unstarted server") + } + + client := srv.Client() + + { + res, err := client.Head(srv.URL() + "/repositories.yaml") + if err != nil { + t.Error(err) + } + + res.Body.Close() + + if res.StatusCode != 200 { + t.Errorf("Expected 200, got %d", res.StatusCode) + } + + } + + { + res, err := client.Head(srv.URL() + "/examplechart-0.1.0.tgz") + if err != nil { + t.Error(err) + } + res.Body.Close() + + if res.StatusCode != 200 { + t.Errorf("Expected 200, got %d", res.StatusCode) + } + } + + res, err := client.Get(srv.URL() + "/examplechart-0.1.0.tgz") + res.Body.Close() + if err != nil { + t.Fatal(err) + } + + if res.ContentLength < 500 { + t.Errorf("Expected at least 500 bytes of data, got %d", res.ContentLength) + } + + res, err = client.Get(srv.URL() + "/index.yaml") + if err != nil { + t.Fatal(err) + } + + data, err := io.ReadAll(res.Body) + res.Body.Close() + if err != nil { + t.Fatal(err) + } + + m := repo.NewIndexFile() + if err := yaml.Unmarshal(data, m); err != nil { + t.Fatal(err) + } + + if l := len(m.Entries); l != 1 { + t.Fatalf("Expected 1 entry, got %d", l) + } + + expect := "examplechart" + if !m.Has(expect, "0.1.0") { + t.Errorf("missing %q", expect) + } + + res, err = client.Get(srv.URL() + "/index.yaml-nosuchthing") + res.Body.Close() + if err != nil { + t.Fatal(err) + } + if res.StatusCode != 404 { + t.Fatalf("Expected 404, got %d", res.StatusCode) + } + }) } - if res.StatusCode != 200 { - t.Errorf("Expected 200, got %d", res.StatusCode) + +} + +func TestNewTempServer_TLS(t *testing.T) { + ensure.HelmHome(t) + + srv := NewTempServer( + t, + WithChartSourceGlob("testdata/examplechart-0.1.0.tgz"), + WithTLSConfig(MakeTestTLSConfig(t, "../../../testdata")), + ) + defer srv.Stop() + + if !strings.HasPrefix(srv.URL(), "https://") { + t.Fatal("non-TLS server") } } diff --git a/pkg/repo/repotest/tlsconfig.go b/pkg/repo/repotest/tlsconfig.go new file mode 100644 index 000000000..3914a4d3f --- /dev/null +++ b/pkg/repo/repotest/tlsconfig.go @@ -0,0 +1,43 @@ +/* +Copyright The Helm Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package repotest + +import ( + "crypto/tls" + "path/filepath" + "testing" + + "helm.sh/helm/v4/internal/tlsutil" + + "github.com/stretchr/testify/require" +) + +func MakeTestTLSConfig(t *testing.T, path string) *tls.Config { + ca, pub, priv := filepath.Join(path, "rootca.crt"), filepath.Join(path, "crt.pem"), filepath.Join(path, "key.pem") + + insecure := false + tlsConf, err := tlsutil.NewTLSConfig( + tlsutil.WithInsecureSkipVerify(insecure), + tlsutil.WithCertKeyPairFiles(pub, priv), + tlsutil.WithCAFile(ca), + ) + //require.Nil(t, err, err.Error()) + require.Nil(t, err) + + tlsConf.ServerName = "helm.sh" + + return tlsConf +} diff --git a/pkg/storage/driver/cfgmaps.go b/pkg/storage/driver/cfgmaps.go index de43d027b..de097f294 100644 --- a/pkg/storage/driver/cfgmaps.go +++ b/pkg/storage/driver/cfgmaps.go @@ -14,11 +14,12 @@ See the License for the specific language governing permissions and limitations under the License. */ -package driver // import "helm.sh/helm/v3/pkg/storage/driver" +package driver // import "helm.sh/helm/v4/pkg/storage/driver" import ( "context" "fmt" + "log/slog" "strconv" "strings" "time" @@ -30,7 +31,7 @@ import ( "k8s.io/apimachinery/pkg/util/validation" corev1 "k8s.io/client-go/kubernetes/typed/core/v1" - rspb "helm.sh/helm/v3/pkg/release" + rspb "helm.sh/helm/v4/pkg/release/v1" ) var _ Driver = (*ConfigMaps)(nil) @@ -42,7 +43,6 @@ const ConfigMapsDriverName = "ConfigMap" // ConfigMapsInterface. type ConfigMaps struct { impl corev1.ConfigMapInterface - Log func(string, ...interface{}) } // NewConfigMaps initializes a new ConfigMaps wrapping an implementation of @@ -50,7 +50,6 @@ type ConfigMaps struct { func NewConfigMaps(impl corev1.ConfigMapInterface) *ConfigMaps { return &ConfigMaps{ impl: impl, - Log: func(_ string, _ ...interface{}) {}, } } @@ -69,16 +68,16 @@ func (cfgmaps *ConfigMaps) Get(key string) (*rspb.Release, error) { return nil, ErrReleaseNotFound } - cfgmaps.Log("get: failed to get %q: %s", key, err) + slog.Debug("failed to get release", "key", key, slog.Any("error", err)) return nil, err } // found the configmap, decode the base64 data string r, err := decodeRelease(obj.Data["release"]) if err != nil { - cfgmaps.Log("get: failed to decode data %q: %s", key, err) + slog.Debug("failed to decode data", "key", key, slog.Any("error", err)) return nil, err } - r.Labels = filterSystemLabels(obj.ObjectMeta.Labels) + r.Labels = filterSystemLabels(obj.Labels) // return the release object return r, nil } @@ -92,7 +91,7 @@ func (cfgmaps *ConfigMaps) List(filter func(*rspb.Release) bool) ([]*rspb.Releas list, err := cfgmaps.impl.List(context.Background(), opts) if err != nil { - cfgmaps.Log("list: failed to list: %s", err) + slog.Debug("failed to list releases", slog.Any("error", err)) return nil, err } @@ -103,11 +102,11 @@ func (cfgmaps *ConfigMaps) List(filter func(*rspb.Release) bool) ([]*rspb.Releas for _, item := range list.Items { rls, err := decodeRelease(item.Data["release"]) if err != nil { - cfgmaps.Log("list: failed to decode release: %v: %s", item, err) + slog.Debug("failed to decode release", "item", item, slog.Any("error", err)) continue } - rls.Labels = item.ObjectMeta.Labels + rls.Labels = item.Labels if filter(rls) { results = append(results, rls) @@ -131,7 +130,7 @@ func (cfgmaps *ConfigMaps) Query(labels map[string]string) ([]*rspb.Release, err list, err := cfgmaps.impl.List(context.Background(), opts) if err != nil { - cfgmaps.Log("query: failed to query with labels: %s", err) + slog.Debug("failed to query with labels", slog.Any("error", err)) return nil, err } @@ -143,10 +142,10 @@ func (cfgmaps *ConfigMaps) Query(labels map[string]string) ([]*rspb.Release, err for _, item := range list.Items { rls, err := decodeRelease(item.Data["release"]) if err != nil { - cfgmaps.Log("query: failed to decode release: %s", err) + slog.Debug("failed to decode release", slog.Any("error", err)) continue } - rls.Labels = item.ObjectMeta.Labels + rls.Labels = item.Labels results = append(results, rls) } return results, nil @@ -165,7 +164,7 @@ func (cfgmaps *ConfigMaps) Create(key string, rls *rspb.Release) error { // create a new configmap to hold the release obj, err := newConfigMapsObject(key, rls, lbs) if err != nil { - cfgmaps.Log("create: failed to encode release %q: %s", rls.Name, err) + slog.Debug("failed to encode release", "name", rls.Name, slog.Any("error", err)) return err } // push the configmap object out into the kubiverse @@ -174,7 +173,7 @@ func (cfgmaps *ConfigMaps) Create(key string, rls *rspb.Release) error { return ErrReleaseExists } - cfgmaps.Log("create: failed to create: %s", err) + slog.Debug("failed to create release", slog.Any("error", err)) return err } return nil @@ -193,13 +192,13 @@ func (cfgmaps *ConfigMaps) Update(key string, rls *rspb.Release) error { // create a new configmap object to hold the release obj, err := newConfigMapsObject(key, rls, lbs) if err != nil { - cfgmaps.Log("update: failed to encode release %q: %s", rls.Name, err) + slog.Debug("failed to encode release", "name", rls.Name, slog.Any("error", err)) return err } // push the configmap object out into the kubiverse _, err = cfgmaps.impl.Update(context.Background(), obj, metav1.UpdateOptions{}) if err != nil { - cfgmaps.Log("update: failed to update: %s", err) + slog.Debug("failed to update release", slog.Any("error", err)) return err } return nil diff --git a/pkg/storage/driver/cfgmaps_test.go b/pkg/storage/driver/cfgmaps_test.go index 9a539d6ec..a563eb7d9 100644 --- a/pkg/storage/driver/cfgmaps_test.go +++ b/pkg/storage/driver/cfgmaps_test.go @@ -16,12 +16,13 @@ package driver import ( "encoding/base64" "encoding/json" + "errors" "reflect" "testing" v1 "k8s.io/api/core/v1" - rspb "helm.sh/helm/v3/pkg/release" + rspb "helm.sh/helm/v4/pkg/release/v1" ) func TestConfigMapName(t *testing.T) { @@ -242,10 +243,8 @@ func TestConfigMapDelete(t *testing.T) { if !reflect.DeepEqual(rel, rls) { t.Errorf("Expected {%v}, got {%v}", rel, rls) } - - // fetch the deleted release _, err = cfgmaps.Get(key) - if !reflect.DeepEqual(ErrReleaseNotFound, err) { + if !errors.Is(err, ErrReleaseNotFound) { t.Errorf("Expected {%v}, got {%v}", ErrReleaseNotFound, err) } } diff --git a/pkg/storage/driver/driver.go b/pkg/storage/driver/driver.go index 3e0f9126f..4f9d63928 100644 --- a/pkg/storage/driver/driver.go +++ b/pkg/storage/driver/driver.go @@ -14,13 +14,13 @@ See the License for the specific language governing permissions and limitations under the License. */ -package driver // import "helm.sh/helm/v3/pkg/storage/driver" +package driver // import "helm.sh/helm/v4/pkg/storage/driver" import ( "errors" "fmt" - rspb "helm.sh/helm/v3/pkg/release" + rspb "helm.sh/helm/v4/pkg/release/v1" ) var ( diff --git a/pkg/storage/driver/labels_test.go b/pkg/storage/driver/labels_test.go index bfd80911b..81e561c15 100644 --- a/pkg/storage/driver/labels_test.go +++ b/pkg/storage/driver/labels_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package driver // import "helm.sh/helm/v3/pkg/storage/driver" +package driver // import "helm.sh/helm/v4/pkg/storage/driver" import ( "testing" diff --git a/pkg/storage/driver/memory.go b/pkg/storage/driver/memory.go index 91378f588..79e7f090e 100644 --- a/pkg/storage/driver/memory.go +++ b/pkg/storage/driver/memory.go @@ -21,7 +21,7 @@ import ( "strings" "sync" - rspb "helm.sh/helm/v3/pkg/release" + rspb "helm.sh/helm/v4/pkg/release/v1" ) var _ Driver = (*Memory)(nil) diff --git a/pkg/storage/driver/memory_test.go b/pkg/storage/driver/memory_test.go index 7a2e8578e..ee547b58b 100644 --- a/pkg/storage/driver/memory_test.go +++ b/pkg/storage/driver/memory_test.go @@ -21,7 +21,7 @@ import ( "reflect" "testing" - rspb "helm.sh/helm/v3/pkg/release" + rspb "helm.sh/helm/v4/pkg/release/v1" ) func TestMemoryName(t *testing.T) { diff --git a/pkg/storage/driver/mock_test.go b/pkg/storage/driver/mock_test.go index 81bda9324..1dda258bb 100644 --- a/pkg/storage/driver/mock_test.go +++ b/pkg/storage/driver/mock_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package driver // import "helm.sh/helm/v3/pkg/storage/driver" +package driver // import "helm.sh/helm/v4/pkg/storage/driver" import ( "context" @@ -31,7 +31,7 @@ import ( kblabels "k8s.io/apimachinery/pkg/labels" corev1 "k8s.io/client-go/kubernetes/typed/core/v1" - rspb "helm.sh/helm/v3/pkg/release" + rspb "helm.sh/helm/v4/pkg/release/v1" ) func releaseStub(name string, vers int, namespace string, status rspb.Status) *rspb.Release { @@ -80,7 +80,7 @@ func tsFixtureMemory(t *testing.T) *Memory { return mem } -// newTestFixture initializes a MockConfigMapsInterface. +// newTestFixtureCfgMaps initializes a MockConfigMapsInterface. // ConfigMaps are created for each release provided. func newTestFixtureCfgMaps(t *testing.T, releases ...*rspb.Release) *ConfigMaps { var mock MockConfigMapsInterface @@ -130,7 +130,7 @@ func (mock *MockConfigMapsInterface) List(_ context.Context, opts metav1.ListOpt } for _, cfgmap := range mock.objects { - if labelSelector.Matches(kblabels.Set(cfgmap.ObjectMeta.Labels)) { + if labelSelector.Matches(kblabels.Set(cfgmap.Labels)) { list.Items = append(list.Items, *cfgmap) } } @@ -139,7 +139,7 @@ func (mock *MockConfigMapsInterface) List(_ context.Context, opts metav1.ListOpt // Create creates a new ConfigMap. func (mock *MockConfigMapsInterface) Create(_ context.Context, cfgmap *v1.ConfigMap, _ metav1.CreateOptions) (*v1.ConfigMap, error) { - name := cfgmap.ObjectMeta.Name + name := cfgmap.Name if object, ok := mock.objects[name]; ok { return object, apierrors.NewAlreadyExists(v1.Resource("tests"), name) } @@ -149,7 +149,7 @@ func (mock *MockConfigMapsInterface) Create(_ context.Context, cfgmap *v1.Config // Update updates a ConfigMap. func (mock *MockConfigMapsInterface) Update(_ context.Context, cfgmap *v1.ConfigMap, _ metav1.UpdateOptions) (*v1.ConfigMap, error) { - name := cfgmap.ObjectMeta.Name + name := cfgmap.Name if _, ok := mock.objects[name]; !ok { return nil, apierrors.NewNotFound(v1.Resource("tests"), name) } @@ -166,7 +166,7 @@ func (mock *MockConfigMapsInterface) Delete(_ context.Context, name string, _ me return nil } -// newTestFixture initializes a MockSecretsInterface. +// newTestFixtureSecrets initializes a MockSecretsInterface. // Secrets are created for each release provided. func newTestFixtureSecrets(t *testing.T, releases ...*rspb.Release) *Secrets { var mock MockSecretsInterface @@ -216,7 +216,7 @@ func (mock *MockSecretsInterface) List(_ context.Context, opts metav1.ListOption } for _, secret := range mock.objects { - if labelSelector.Matches(kblabels.Set(secret.ObjectMeta.Labels)) { + if labelSelector.Matches(kblabels.Set(secret.Labels)) { list.Items = append(list.Items, *secret) } } @@ -225,7 +225,7 @@ func (mock *MockSecretsInterface) List(_ context.Context, opts metav1.ListOption // Create creates a new Secret. func (mock *MockSecretsInterface) Create(_ context.Context, secret *v1.Secret, _ metav1.CreateOptions) (*v1.Secret, error) { - name := secret.ObjectMeta.Name + name := secret.Name if object, ok := mock.objects[name]; ok { return object, apierrors.NewAlreadyExists(v1.Resource("tests"), name) } @@ -235,7 +235,7 @@ func (mock *MockSecretsInterface) Create(_ context.Context, secret *v1.Secret, _ // Update updates a Secret. func (mock *MockSecretsInterface) Update(_ context.Context, secret *v1.Secret, _ metav1.UpdateOptions) (*v1.Secret, error) { - name := secret.ObjectMeta.Name + name := secret.Name if _, ok := mock.objects[name]; !ok { return nil, apierrors.NewNotFound(v1.Resource("tests"), name) } @@ -262,7 +262,6 @@ func newTestFixtureSQL(t *testing.T, _ ...*rspb.Release) (*SQL, sqlmock.Sqlmock) sqlxDB := sqlx.NewDb(sqlDB, "sqlmock") return &SQL{ db: sqlxDB, - Log: func(_ string, _ ...interface{}) {}, namespace: "default", statementBuilder: sq.StatementBuilder.PlaceholderFormat(sq.Dollar), }, mock diff --git a/pkg/storage/driver/records.go b/pkg/storage/driver/records.go index 9df173384..6b4efef3a 100644 --- a/pkg/storage/driver/records.go +++ b/pkg/storage/driver/records.go @@ -14,13 +14,13 @@ See the License for the specific language governing permissions and limitations under the License. */ -package driver // import "helm.sh/helm/v3/pkg/storage/driver" +package driver // import "helm.sh/helm/v4/pkg/storage/driver" import ( "sort" "strconv" - rspb "helm.sh/helm/v3/pkg/release" + rspb "helm.sh/helm/v4/pkg/release/v1" ) // records holds a list of in-memory release records diff --git a/pkg/storage/driver/records_test.go b/pkg/storage/driver/records_test.go index 0a27839cc..34b2fb80c 100644 --- a/pkg/storage/driver/records_test.go +++ b/pkg/storage/driver/records_test.go @@ -14,13 +14,13 @@ See the License for the specific language governing permissions and limitations under the License. */ -package driver // import "helm.sh/helm/v3/pkg/storage/driver" +package driver // import "helm.sh/helm/v4/pkg/storage/driver" import ( "reflect" "testing" - rspb "helm.sh/helm/v3/pkg/release" + rspb "helm.sh/helm/v4/pkg/release/v1" ) func TestRecordsAdd(t *testing.T) { diff --git a/pkg/storage/driver/secrets.go b/pkg/storage/driver/secrets.go index 21e40e295..23a8f5cab 100644 --- a/pkg/storage/driver/secrets.go +++ b/pkg/storage/driver/secrets.go @@ -14,11 +14,12 @@ See the License for the specific language governing permissions and limitations under the License. */ -package driver // import "helm.sh/helm/v3/pkg/storage/driver" +package driver // import "helm.sh/helm/v4/pkg/storage/driver" import ( "context" "fmt" + "log/slog" "strconv" "strings" "time" @@ -30,7 +31,7 @@ import ( "k8s.io/apimachinery/pkg/util/validation" corev1 "k8s.io/client-go/kubernetes/typed/core/v1" - rspb "helm.sh/helm/v3/pkg/release" + rspb "helm.sh/helm/v4/pkg/release/v1" ) var _ Driver = (*Secrets)(nil) @@ -42,7 +43,6 @@ const SecretsDriverName = "Secret" // SecretsInterface. type Secrets struct { impl corev1.SecretInterface - Log func(string, ...interface{}) } // NewSecrets initializes a new Secrets wrapping an implementation of @@ -50,7 +50,6 @@ type Secrets struct { func NewSecrets(impl corev1.SecretInterface) *Secrets { return &Secrets{ impl: impl, - Log: func(_ string, _ ...interface{}) {}, } } @@ -75,7 +74,7 @@ func (secrets *Secrets) Get(key string) (*rspb.Release, error) { if err != nil { return r, fmt.Errorf("get: failed to decode data %q: %w", key, err) } - r.Labels = filterSystemLabels(obj.ObjectMeta.Labels) + r.Labels = filterSystemLabels(obj.Labels) return r, nil } @@ -98,11 +97,11 @@ func (secrets *Secrets) List(filter func(*rspb.Release) bool) ([]*rspb.Release, for _, item := range list.Items { rls, err := decodeRelease(string(item.Data["release"])) if err != nil { - secrets.Log("list: failed to decode release: %v: %s", item, err) + slog.Debug("list failed to decode release", "key", item.Name, slog.Any("error", err)) continue } - rls.Labels = item.ObjectMeta.Labels + rls.Labels = item.Labels if filter(rls) { results = append(results, rls) @@ -137,10 +136,10 @@ func (secrets *Secrets) Query(labels map[string]string) ([]*rspb.Release, error) for _, item := range list.Items { rls, err := decodeRelease(string(item.Data["release"])) if err != nil { - secrets.Log("query: failed to decode release: %s", err) + slog.Debug("failed to decode release", "key", item.Name, slog.Any("error", err)) continue } - rls.Labels = item.ObjectMeta.Labels + rls.Labels = item.Labels results = append(results, rls) } return results, nil diff --git a/pkg/storage/driver/secrets_test.go b/pkg/storage/driver/secrets_test.go index d15635486..9e45bae67 100644 --- a/pkg/storage/driver/secrets_test.go +++ b/pkg/storage/driver/secrets_test.go @@ -22,7 +22,7 @@ import ( v1 "k8s.io/api/core/v1" - rspb "helm.sh/helm/v3/pkg/release" + rspb "helm.sh/helm/v4/pkg/release/v1" ) func TestSecretName(t *testing.T) { @@ -243,8 +243,6 @@ func TestSecretDelete(t *testing.T) { if !reflect.DeepEqual(rel, rls) { t.Errorf("Expected {%v}, got {%v}", rel, rls) } - - // fetch the deleted release _, err = secrets.Get(key) if !errors.Is(err, ErrReleaseNotFound) { t.Errorf("Expected {%v}, got {%v}", ErrReleaseNotFound, err) diff --git a/pkg/storage/driver/sql.go b/pkg/storage/driver/sql.go index 33bde9b6a..c3740b9a3 100644 --- a/pkg/storage/driver/sql.go +++ b/pkg/storage/driver/sql.go @@ -14,10 +14,11 @@ See the License for the specific language governing permissions and limitations under the License. */ -package driver // import "helm.sh/helm/v3/pkg/storage/driver" +package driver // import "helm.sh/helm/v4/pkg/storage/driver" import ( "fmt" + "log/slog" "sort" "strconv" "time" @@ -30,7 +31,7 @@ import ( // Import pq for postgres dialect _ "github.com/lib/pq" - rspb "helm.sh/helm/v3/pkg/release" + rspb "helm.sh/helm/v4/pkg/release/v1" ) var _ Driver = (*SQL)(nil) @@ -86,8 +87,6 @@ type SQL struct { db *sqlx.DB namespace string statementBuilder sq.StatementBuilderType - - Log func(string, ...interface{}) } // Name returns the name of the driver. @@ -108,13 +107,13 @@ func (s *SQL) checkAlreadyApplied(migrations []*migrate.Migration) bool { records, err := migrate.GetMigrationRecords(s.db.DB, postgreSQLDialect) migrate.SetDisableCreateTable(false) if err != nil { - s.Log("checkAlreadyApplied: failed to get migration records: %v", err) + slog.Debug("failed to get migration records", slog.Any("error", err)) return false } for _, record := range records { if _, ok := migrationsIDs[record.Id]; ok { - s.Log("checkAlreadyApplied: found previous migration (Id: %v) applied at %v", record.Id, record.AppliedAt) + slog.Debug("found previous migration", "id", record.Id, "appliedAt", record.AppliedAt) delete(migrationsIDs, record.Id) } } @@ -122,7 +121,7 @@ func (s *SQL) checkAlreadyApplied(migrations []*migrate.Migration) bool { // check if all migrations applied if len(migrationsIDs) != 0 { for id := range migrationsIDs { - s.Log("checkAlreadyApplied: find unapplied migration (id: %v)", id) + slog.Debug("find unapplied migration", "id", id) } return false } @@ -276,7 +275,7 @@ type SQLReleaseCustomLabelWrapper struct { } // NewSQL initializes a new sql driver. -func NewSQL(connectionString string, logger func(string, ...interface{}), namespace string) (*SQL, error) { +func NewSQL(connectionString string, namespace string) (*SQL, error) { db, err := sqlx.Connect(postgreSQLDialect, connectionString) if err != nil { return nil, err @@ -284,7 +283,6 @@ func NewSQL(connectionString string, logger func(string, ...interface{}), namesp driver := &SQL{ db: db, - Log: logger, statementBuilder: sq.StatementBuilder.PlaceholderFormat(sq.Dollar), } @@ -309,24 +307,24 @@ func (s *SQL) Get(key string) (*rspb.Release, error) { query, args, err := qb.ToSql() if err != nil { - s.Log("failed to build query: %v", err) + slog.Debug("failed to build query", slog.Any("error", err)) return nil, err } // Get will return an error if the result is empty if err := s.db.Get(&record, query, args...); err != nil { - s.Log("got SQL error when getting release %s: %v", key, err) + slog.Debug("got SQL error when getting release", "key", key, slog.Any("error", err)) return nil, ErrReleaseNotFound } release, err := decodeRelease(record.Body) if err != nil { - s.Log("get: failed to decode data %q: %v", key, err) + slog.Debug("failed to decode data", "key", key, slog.Any("error", err)) return nil, err } if release.Labels, err = s.getReleaseCustomLabels(key, s.namespace); err != nil { - s.Log("failed to get release %s/%s custom labels: %v", s.namespace, key, err) + slog.Debug("failed to get release custom labels", "namespace", s.namespace, "key", key, slog.Any("error", err)) return nil, err } @@ -347,13 +345,13 @@ func (s *SQL) List(filter func(*rspb.Release) bool) ([]*rspb.Release, error) { query, args, err := sb.ToSql() if err != nil { - s.Log("failed to build query: %v", err) + slog.Debug("failed to build query", slog.Any("error", err)) return nil, err } var records = []SQLReleaseWrapper{} if err := s.db.Select(&records, query, args...); err != nil { - s.Log("list: failed to list: %v", err) + slog.Debug("failed to list", slog.Any("error", err)) return nil, err } @@ -361,12 +359,12 @@ func (s *SQL) List(filter func(*rspb.Release) bool) ([]*rspb.Release, error) { for _, record := range records { release, err := decodeRelease(record.Body) if err != nil { - s.Log("list: failed to decode release: %v: %v", record, err) + slog.Debug("failed to decode release", "record", record, slog.Any("error", err)) continue } if release.Labels, err = s.getReleaseCustomLabels(record.Key, record.Namespace); err != nil { - s.Log("failed to get release %s/%s custom labels: %v", record.Namespace, record.Key, err) + slog.Debug("failed to get release custom labels", "namespace", record.Namespace, "key", record.Key, slog.Any("error", err)) return nil, err } for k, v := range getReleaseSystemLabels(release) { @@ -396,7 +394,7 @@ func (s *SQL) Query(labels map[string]string) ([]*rspb.Release, error) { if _, ok := labelMap[key]; ok { sb = sb.Where(sq.Eq{key: labels[key]}) } else { - s.Log("unknown label %s", key) + slog.Debug("unknown label", "key", key) return nil, fmt.Errorf("unknown label %s", key) } } @@ -409,13 +407,13 @@ func (s *SQL) Query(labels map[string]string) ([]*rspb.Release, error) { // Build our query query, args, err := sb.ToSql() if err != nil { - s.Log("failed to build query: %v", err) + slog.Debug("failed to build query", slog.Any("error", err)) return nil, err } var records = []SQLReleaseWrapper{} if err := s.db.Select(&records, query, args...); err != nil { - s.Log("list: failed to query with labels: %v", err) + slog.Debug("failed to query with labels", slog.Any("error", err)) return nil, err } @@ -427,12 +425,12 @@ func (s *SQL) Query(labels map[string]string) ([]*rspb.Release, error) { for _, record := range records { release, err := decodeRelease(record.Body) if err != nil { - s.Log("list: failed to decode release: %v: %v", record, err) + slog.Debug("failed to decode release", "record", record, slog.Any("error", err)) continue } if release.Labels, err = s.getReleaseCustomLabels(record.Key, record.Namespace); err != nil { - s.Log("failed to get release %s/%s custom labels: %v", record.Namespace, record.Key, err) + slog.Debug("failed to get release custom labels", "namespace", record.Namespace, "key", record.Key, slog.Any("error", err)) return nil, err } @@ -456,13 +454,13 @@ func (s *SQL) Create(key string, rls *rspb.Release) error { body, err := encodeRelease(rls) if err != nil { - s.Log("failed to encode release: %v", err) + slog.Debug("failed to encode release", slog.Any("error", err)) return err } transaction, err := s.db.Beginx() if err != nil { - s.Log("failed to start SQL transaction: %v", err) + slog.Debug("failed to start SQL transaction", slog.Any("error", err)) return fmt.Errorf("error beginning transaction: %v", err) } @@ -491,7 +489,7 @@ func (s *SQL) Create(key string, rls *rspb.Release) error { int(time.Now().Unix()), ).ToSql() if err != nil { - s.Log("failed to build insert query: %v", err) + slog.Debug("failed to build insert query", slog.Any("error", err)) return err } @@ -505,17 +503,17 @@ func (s *SQL) Create(key string, rls *rspb.Release) error { Where(sq.Eq{sqlReleaseTableNamespaceColumn: s.namespace}). ToSql() if buildErr != nil { - s.Log("failed to build select query: %v", buildErr) + slog.Debug("failed to build select query", "error", buildErr) return err } var record SQLReleaseWrapper if err := transaction.Get(&record, selectQuery, args...); err == nil { - s.Log("release %s already exists", key) + slog.Debug("release already exists", "key", key) return ErrReleaseExists } - s.Log("failed to store release %s in SQL database: %v", key, err) + slog.Debug("failed to store release in SQL database", "key", key, slog.Any("error", err)) return err } @@ -538,13 +536,13 @@ func (s *SQL) Create(key string, rls *rspb.Release) error { if err != nil { defer transaction.Rollback() - s.Log("failed to build insert query: %v", err) + slog.Debug("failed to build insert query", slog.Any("error", err)) return err } if _, err := transaction.Exec(insertLabelsQuery, args...); err != nil { defer transaction.Rollback() - s.Log("failed to write Labels: %v", err) + slog.Debug("failed to write Labels", slog.Any("error", err)) return err } } @@ -563,7 +561,7 @@ func (s *SQL) Update(key string, rls *rspb.Release) error { body, err := encodeRelease(rls) if err != nil { - s.Log("failed to encode release: %v", err) + slog.Debug("failed to encode release", slog.Any("error", err)) return err } @@ -580,12 +578,12 @@ func (s *SQL) Update(key string, rls *rspb.Release) error { ToSql() if err != nil { - s.Log("failed to build update query: %v", err) + slog.Debug("failed to build update query", slog.Any("error", err)) return err } if _, err := s.db.Exec(query, args...); err != nil { - s.Log("failed to update release %s in SQL database: %v", key, err) + slog.Debug("failed to update release in SQL database", "key", key, slog.Any("error", err)) return err } @@ -596,7 +594,7 @@ func (s *SQL) Update(key string, rls *rspb.Release) error { func (s *SQL) Delete(key string) (*rspb.Release, error) { transaction, err := s.db.Beginx() if err != nil { - s.Log("failed to start SQL transaction: %v", err) + slog.Debug("failed to start SQL transaction", slog.Any("error", err)) return nil, fmt.Errorf("error beginning transaction: %v", err) } @@ -607,20 +605,20 @@ func (s *SQL) Delete(key string) (*rspb.Release, error) { Where(sq.Eq{sqlReleaseTableNamespaceColumn: s.namespace}). ToSql() if err != nil { - s.Log("failed to build select query: %v", err) + slog.Debug("failed to build select query", slog.Any("error", err)) return nil, err } var record SQLReleaseWrapper err = transaction.Get(&record, selectQuery, args...) if err != nil { - s.Log("release %s not found: %v", key, err) + slog.Debug("release not found", "key", key, slog.Any("error", err)) return nil, ErrReleaseNotFound } release, err := decodeRelease(record.Body) if err != nil { - s.Log("failed to decode release %s: %v", key, err) + slog.Debug("failed to decode release", "key", key, slog.Any("error", err)) transaction.Rollback() return nil, err } @@ -632,18 +630,18 @@ func (s *SQL) Delete(key string) (*rspb.Release, error) { Where(sq.Eq{sqlReleaseTableNamespaceColumn: s.namespace}). ToSql() if err != nil { - s.Log("failed to build delete query: %v", err) + slog.Debug("failed to build delete query", slog.Any("error", err)) return nil, err } _, err = transaction.Exec(deleteQuery, args...) if err != nil { - s.Log("failed perform delete query: %v", err) + slog.Debug("failed perform delete query", slog.Any("error", err)) return release, err } if release.Labels, err = s.getReleaseCustomLabels(key, s.namespace); err != nil { - s.Log("failed to get release %s/%s custom labels: %v", s.namespace, key, err) + slog.Debug("failed to get release custom labels", "namespace", s.namespace, "key", key, slog.Any("error", err)) return nil, err } @@ -654,7 +652,7 @@ func (s *SQL) Delete(key string) (*rspb.Release, error) { ToSql() if err != nil { - s.Log("failed to build delete Labels query: %v", err) + slog.Debug("failed to build delete Labels query", slog.Any("error", err)) return nil, err } _, err = transaction.Exec(deleteCustomLabelsQuery, args...) diff --git a/pkg/storage/driver/sql_test.go b/pkg/storage/driver/sql_test.go index c9989f3ef..bd2918aad 100644 --- a/pkg/storage/driver/sql_test.go +++ b/pkg/storage/driver/sql_test.go @@ -23,7 +23,7 @@ import ( sqlmock "github.com/DATA-DOG/go-sqlmock" migrate "github.com/rubenv/sql-migrate" - rspb "helm.sh/helm/v3/pkg/release" + rspb "helm.sh/helm/v4/pkg/release/v1" ) func TestSQLName(t *testing.T) { diff --git a/pkg/storage/driver/util.go b/pkg/storage/driver/util.go index 7bda5ec96..0abbe41b2 100644 --- a/pkg/storage/driver/util.go +++ b/pkg/storage/driver/util.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package driver // import "helm.sh/helm/v3/pkg/storage/driver" +package driver // import "helm.sh/helm/v4/pkg/storage/driver" import ( "bytes" @@ -23,7 +23,7 @@ import ( "encoding/json" "io" - rspb "helm.sh/helm/v3/pkg/release" + rspb "helm.sh/helm/v4/pkg/release/v1" ) var b64 = base64.StdEncoding diff --git a/pkg/storage/storage.go b/pkg/storage/storage.go index 32bba7c41..b43f7c0f2 100644 --- a/pkg/storage/storage.go +++ b/pkg/storage/storage.go @@ -14,16 +14,17 @@ See the License for the specific language governing permissions and limitations under the License. */ -package storage // import "helm.sh/helm/v3/pkg/storage" +package storage // import "helm.sh/helm/v4/pkg/storage" import ( "errors" "fmt" + "log/slog" "strings" - rspb "helm.sh/helm/v3/pkg/release" - relutil "helm.sh/helm/v3/pkg/releaseutil" - "helm.sh/helm/v3/pkg/storage/driver" + relutil "helm.sh/helm/v4/pkg/release/util" + rspb "helm.sh/helm/v4/pkg/release/v1" + "helm.sh/helm/v4/pkg/storage/driver" ) // HelmStorageType is the type field of the Kubernetes storage object which stores the Helm release @@ -41,15 +42,13 @@ type Storage struct { // be retained, including the most recent release. Values of 0 or less are // ignored (meaning no limits are imposed). MaxHistory int - - Log func(string, ...interface{}) } // Get retrieves the release from storage. An error is returned // if the storage driver failed to fetch the release, or the // release identified by the key, version pair does not exist. func (s *Storage) Get(name string, version int) (*rspb.Release, error) { - s.Log("getting release %q", makeKey(name, version)) + slog.Debug("getting release", "key", makeKey(name, version)) return s.Driver.Get(makeKey(name, version)) } @@ -57,7 +56,7 @@ func (s *Storage) Get(name string, version int) (*rspb.Release, error) { // error is returned if the storage driver fails to store the // release, or a release with an identical key already exists. func (s *Storage) Create(rls *rspb.Release) error { - s.Log("creating release %q", makeKey(rls.Name, rls.Version)) + slog.Debug("creating release", "key", makeKey(rls.Name, rls.Version)) if s.MaxHistory > 0 { // Want to make space for one more release. if err := s.removeLeastRecent(rls.Name, s.MaxHistory-1); err != nil && @@ -72,7 +71,7 @@ func (s *Storage) Create(rls *rspb.Release) error { // storage backend fails to update the release or if the release // does not exist. func (s *Storage) Update(rls *rspb.Release) error { - s.Log("updating release %q", makeKey(rls.Name, rls.Version)) + slog.Debug("updating release", "key", makeKey(rls.Name, rls.Version)) return s.Driver.Update(makeKey(rls.Name, rls.Version), rls) } @@ -80,22 +79,22 @@ func (s *Storage) Update(rls *rspb.Release) error { // the storage backend fails to delete the release or if the release // does not exist. func (s *Storage) Delete(name string, version int) (*rspb.Release, error) { - s.Log("deleting release %q", makeKey(name, version)) + slog.Debug("deleting release", "key", makeKey(name, version)) return s.Driver.Delete(makeKey(name, version)) } // ListReleases returns all releases from storage. An error is returned if the // storage backend fails to retrieve the releases. func (s *Storage) ListReleases() ([]*rspb.Release, error) { - s.Log("listing all releases in storage") - return s.Driver.List(func(_ *rspb.Release) bool { return true }) + slog.Debug("listing all releases in storage") + return s.List(func(_ *rspb.Release) bool { return true }) } // ListUninstalled returns all releases with Status == UNINSTALLED. An error is returned // if the storage backend fails to retrieve the releases. func (s *Storage) ListUninstalled() ([]*rspb.Release, error) { - s.Log("listing uninstalled releases in storage") - return s.Driver.List(func(rls *rspb.Release) bool { + slog.Debug("listing uninstalled releases in storage") + return s.List(func(rls *rspb.Release) bool { return relutil.StatusFilter(rspb.StatusUninstalled).Check(rls) }) } @@ -103,8 +102,8 @@ func (s *Storage) ListUninstalled() ([]*rspb.Release, error) { // ListDeployed returns all releases with Status == DEPLOYED. An error is returned // if the storage backend fails to retrieve the releases. func (s *Storage) ListDeployed() ([]*rspb.Release, error) { - s.Log("listing all deployed releases in storage") - return s.Driver.List(func(rls *rspb.Release) bool { + slog.Debug("listing all deployed releases in storage") + return s.List(func(rls *rspb.Release) bool { return relutil.StatusFilter(rspb.StatusDeployed).Check(rls) }) } @@ -131,9 +130,9 @@ func (s *Storage) Deployed(name string) (*rspb.Release, error) { // DeployedAll returns all deployed releases with the provided name, or // returns driver.NewErrNoDeployedReleases if not found. func (s *Storage) DeployedAll(name string) ([]*rspb.Release, error) { - s.Log("getting deployed releases from %q history", name) + slog.Debug("getting deployed releases", "name", name) - ls, err := s.Driver.Query(map[string]string{ + ls, err := s.Query(map[string]string{ "name": name, "owner": "helm", "status": "deployed", @@ -150,9 +149,9 @@ func (s *Storage) DeployedAll(name string) ([]*rspb.Release, error) { // History returns the revision history for the release with the provided name, or // returns driver.ErrReleaseNotFound if no such release name exists. func (s *Storage) History(name string) ([]*rspb.Release, error) { - s.Log("getting release history for %q", name) + slog.Debug("getting release history", "name", name) - return s.Driver.Query(map[string]string{"name": name, "owner": "helm"}) + return s.Query(map[string]string{"name": name, "owner": "helm"}) } // removeLeastRecent removes items from history until the length number of releases @@ -160,15 +159,15 @@ func (s *Storage) History(name string) ([]*rspb.Release, error) { // // We allow max to be set explicitly so that calling functions can "make space" // for the new records they are going to write. -func (s *Storage) removeLeastRecent(name string, max int) error { - if max < 0 { +func (s *Storage) removeLeastRecent(name string, maximum int) error { + if maximum < 0 { return nil } h, err := s.History(name) if err != nil { return err } - if len(h) <= max { + if len(h) <= maximum { return nil } @@ -182,8 +181,8 @@ func (s *Storage) removeLeastRecent(name string, max int) error { var toDelete []*rspb.Release for _, rel := range h { - // once we have enough releases to delete to reach the max, stop - if len(h)-len(toDelete) == max { + // once we have enough releases to delete to reach the maximum, stop + if len(h)-len(toDelete) == maximum { break } if lastDeployed != nil { @@ -205,7 +204,7 @@ func (s *Storage) removeLeastRecent(name string, max int) error { } } - s.Log("Pruned %d record(s) from %s with %d error(s)", len(toDelete), name, len(errs)) + slog.Debug("pruned records", "count", len(toDelete), "release", name, "errors", len(errs)) switch c := len(errs); c { case 0: return nil @@ -220,7 +219,7 @@ func (s *Storage) deleteReleaseVersion(name string, version int) error { key := makeKey(name, version) _, err := s.Delete(name, version) if err != nil { - s.Log("error pruning %s from release history: %s", key, err) + slog.Debug("error pruning release", "key", key, slog.Any("error", err)) return err } return nil @@ -228,7 +227,7 @@ func (s *Storage) deleteReleaseVersion(name string, version int) error { // Last fetches the last revision of the named release. func (s *Storage) Last(name string) (*rspb.Release, error) { - s.Log("getting last revision of %q", name) + slog.Debug("getting last revision", "name", name) h, err := s.History(name) if err != nil { return nil, err @@ -260,6 +259,5 @@ func Init(d driver.Driver) *Storage { } return &Storage{ Driver: d, - Log: func(_ string, _ ...interface{}) {}, } } diff --git a/pkg/storage/storage_test.go b/pkg/storage/storage_test.go index dc83a0afa..d3025eca3 100644 --- a/pkg/storage/storage_test.go +++ b/pkg/storage/storage_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package storage // import "helm.sh/helm/v3/pkg/storage" +package storage // import "helm.sh/helm/v4/pkg/storage" import ( "errors" @@ -22,8 +22,8 @@ import ( "reflect" "testing" - rspb "helm.sh/helm/v3/pkg/release" - "helm.sh/helm/v3/pkg/storage/driver" + rspb "helm.sh/helm/v4/pkg/release/v1" + "helm.sh/helm/v4/pkg/storage/driver" ) func TestStorageCreate(t *testing.T) { @@ -311,7 +311,6 @@ func (d *MaxHistoryMockDriver) Name() string { func TestMaxHistoryErrorHandling(t *testing.T) { //func TestStorageRemoveLeastRecentWithError(t *testing.T) { storage := Init(NewMaxHistoryMockDriver(driver.NewMemory())) - storage.Log = t.Logf storage.MaxHistory = 1 @@ -337,7 +336,6 @@ func TestMaxHistoryErrorHandling(t *testing.T) { func TestStorageRemoveLeastRecent(t *testing.T) { storage := Init(driver.NewMemory()) - storage.Log = t.Logf // Make sure that specifying this at the outset doesn't cause any bugs. storage.MaxHistory = 10 @@ -394,7 +392,6 @@ func TestStorageRemoveLeastRecent(t *testing.T) { func TestStorageDoNotDeleteDeployed(t *testing.T) { storage := Init(driver.NewMemory()) - storage.Log = t.Logf storage.MaxHistory = 3 const name = "angry-bird" @@ -475,7 +472,7 @@ func TestStorageLast(t *testing.T) { } } -// TestUpgradeInitiallyFailedRelease tests a case when there are no deployed release yet, but history limit has been +// TestUpgradeInitiallyFailedReleaseWithHistoryLimit tests a case when there are no deployed release yet, but history limit has been // reached: the has-no-deployed-releases error should not occur in such case. func TestUpgradeInitiallyFailedReleaseWithHistoryLimit(t *testing.T) { storage := Init(driver.NewMemory()) diff --git a/pkg/time/time.go b/pkg/time/time.go index 1abe8ae3d..5b3a0ccdc 100644 --- a/pkg/time/time.go +++ b/pkg/time/time.go @@ -30,7 +30,7 @@ import ( var emptyString = `""` // Time is a convenience wrapper around stdlib time, but with different -// marshalling and unmarshaling for zero values +// marshalling and unmarshalling for zero values type Time struct { time.Time } @@ -41,7 +41,7 @@ func Now() Time { } func (t Time) MarshalJSON() ([]byte, error) { - if t.Time.IsZero() { + if t.IsZero() { return []byte(emptyString), nil } @@ -70,8 +70,8 @@ func ParseInLocation(layout, value string, loc *time.Location) (Time, error) { return Time{Time: t}, err } -func Date(year int, month time.Month, day, hour, min, sec, nsec int, loc *time.Location) Time { - return Time{Time: time.Date(year, month, day, hour, min, sec, nsec, loc)} +func Date(year int, month time.Month, day, hour, minute, second, nanoSecond int, loc *time.Location) Time { + return Time{Time: time.Date(year, month, day, hour, minute, second, nanoSecond, loc)} } func Unix(sec int64, nsec int64) Time { return Time{Time: time.Unix(sec, nsec)} } diff --git a/pkg/uploader/chart_uploader.go b/pkg/uploader/chart_uploader.go index b1cd6e666..b3d612e38 100644 --- a/pkg/uploader/chart_uploader.go +++ b/pkg/uploader/chart_uploader.go @@ -20,8 +20,8 @@ import ( "io" "net/url" - "helm.sh/helm/v3/pkg/pusher" - "helm.sh/helm/v3/pkg/registry" + "helm.sh/helm/v4/pkg/pusher" + "helm.sh/helm/v4/pkg/registry" ) // ChartUploader handles uploading a chart. diff --git a/scripts/coverage.sh b/scripts/coverage.sh index 2d8258866..2164d94da 100755 --- a/scripts/coverage.sh +++ b/scripts/coverage.sh @@ -20,10 +20,6 @@ covermode=${COVERMODE:-atomic} coverdir=$(mktemp -d /tmp/coverage.XXXXXXXXXX) profile="${coverdir}/cover.out" -pushd / -hash goveralls 2>/dev/null || go install github.com/mattn/goveralls@v0.0.11 -popd - generate_cover_data() { for d in $(go list ./...) ; do ( @@ -36,10 +32,6 @@ generate_cover_data() { grep -h -v "^mode:" "$coverdir"/*.cover >>"$profile" } -push_to_coveralls() { - goveralls -coverprofile="${profile}" -service=github -} - generate_cover_data go tool cover -func "${profile}" @@ -47,8 +39,5 @@ case "${1-}" in --html) go tool cover -html "${profile}" ;; - --coveralls) - push_to_coveralls - ;; esac diff --git a/scripts/get-helm-3 b/scripts/get-helm-3 index 5f19bd3fa..3aa44daee 100755 --- a/scripts/get-helm-3 +++ b/scripts/get-helm-3 @@ -279,7 +279,7 @@ testVersion() { help () { echo "Accepted cli arguments are:" echo -e "\t[--help|-h ] ->> prints this help" - echo -e "\t[--version|-v ] . When not defined it fetches the latest release from GitHub" + echo -e "\t[--version|-v ] . When not defined it fetches the latest release tag from the Helm CDN" echo -e "\te.g. --version v3.0.0 or -v canary" echo -e "\t[--no-sudo] ->> install without sudo" } diff --git a/scripts/validate-license.sh b/scripts/validate-license.sh index dc247436f..f67812ca5 100755 --- a/scripts/validate-license.sh +++ b/scripts/validate-license.sh @@ -19,7 +19,7 @@ IFS=$'\n\t' find_files() { find . -not \( \ \( \ - -wholename './vendor' \ + -wholename './.git' \ -o -wholename '*testdata*' \ -o -wholename '*third_party*' \ \) -prune \ diff --git a/testdata/localhost-crt.pem b/testdata/localhost-crt.pem new file mode 100644 index 000000000..70fa0a429 --- /dev/null +++ b/testdata/localhost-crt.pem @@ -0,0 +1,73 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + 7f:5e:fa:21:fa:ee:e4:6a:be:9b:c2:80:bf:ed:42:f3:2d:47:f5:d2 + Signature Algorithm: sha256WithRSAEncryption + Issuer: C=US, ST=CO, L=Boulder, O=Helm, CN=helm.sh + Validity + Not Before: Nov 6 21:59:18 2023 GMT + Not After : Nov 3 21:59:18 2033 GMT + Subject: C=CA, ST=ON, L=Kitchener, O=Helm, CN=localhost + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public-Key: (2048 bit) + Modulus: + 00:c8:89:55:0d:0b:f1:da:e6:c0:70:7d:d3:27:cd: + b8:a8:81:8b:7c:a4:89:e5:d1:b1:78:01:1d:df:44: + 88:0b:fc:d6:81:35:3d:d1:3b:5e:8f:bb:93:b3:7e: + 28:db:ed:ff:a0:13:3a:70:a3:fe:94:6b:0b:fe:fb: + 63:00:b0:cb:dc:81:cd:80:dc:d0:2f:bf:b2:4f:9a: + 81:d4:22:dc:97:c8:8f:27:86:59:91:fa:92:05:75: + c4:cc:6b:f5:a9:6b:74:1e:f5:db:a9:f8:bf:8c:a2: + 25:fd:a0:cc:79:f4:25:57:74:a9:23:9b:e2:b7:22: + 7a:14:7a:3d:ea:f1:7e:32:6b:57:6c:2e:c6:4f:75: + 54:f9:6b:54:d2:ca:eb:54:1c:af:39:15:9b:d0:7c: + 0f:f8:55:51:04:ea:da:fa:7b:8b:63:0f:ac:39:b1: + f6:4b:8e:4e:f6:ea:e9:7b:e6:ba:5e:5a:8e:91:ef: + dc:b1:7d:52:3f:73:83:52:46:83:48:49:ff:f2:2d: + ca:54:f2:36:bb:49:cc:59:99:c0:9e:cf:8e:78:55: + 6c:ed:7d:7e:83:b8:59:2c:7d:f8:1a:81:f0:7d:f5: + 27:f2:db:ae:d4:31:54:38:fe:47:b2:ee:16:20:0f: + f1:db:2d:28:bf:6f:38:eb:11:bb:9a:d4:b2:5a:3a: + 4a:7f + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Alternative Name: + DNS:localhost + Signature Algorithm: sha256WithRSAEncryption + 47:47:fe:29:ca:94:28:75:59:ba:ab:67:ab:c6:a6:0b:0a:f2: + 0f:26:d9:1d:35:db:68:a5:d8:f5:1f:d1:87:e7:a7:74:fd:c0: + 22:aa:c8:ec:6c:d3:ac:8a:0b:ed:59:3a:a0:12:77:7c:53:74: + fd:30:59:34:8f:a4:ef:5b:98:3f:ff:cf:89:87:ed:d3:7f:41: + 2f:b1:9a:12:71:bb:fe:3a:cf:77:16:32:bc:83:90:cc:52:2f: + 3b:f4:ae:db:b1:bb:f0:dd:30:d4:03:17:5e:47:b7:06:86:7a: + 16:b1:72:2f:80:5d:d4:c0:f9:6c:91:df:5a:c5:15:86:66:68: + c8:90:8e:f1:a2:bb:40:0f:ef:26:1b:02:c4:42:de:8c:69:ec: + ad:27:d0:bc:da:7c:76:33:86:de:b7:c4:04:64:e6:f6:dc:44: + 89:7b:b8:2f:c7:28:7a:4c:a6:01:ad:a5:17:64:3a:23:da:aa: + db:ce:3f:86:e9:92:dc:0d:c4:5a:b4:52:a8:8a:ee:3d:62:7d: + b1:c8:fa:ef:96:2b:ab:f1:e1:6d:6f:7d:1e:ce:bc:7a:d0:92: + 02:1b:c8:55:36:77:bf:d4:42:d3:fc:57:ca:b7:cc:95:be:ce: + f8:6e:b2:28:ca:4d:9a:00:7d:78:c8:56:04:2e:b3:ac:03:fa: + 05:d8:42:bd +-----BEGIN CERTIFICATE----- +MIIDRDCCAiygAwIBAgIUf176Ifru5Gq+m8KAv+1C8y1H9dIwDQYJKoZIhvcNAQEL +BQAwTTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNPMRAwDgYDVQQHDAdCb3VsZGVy +MQ0wCwYDVQQKDARIZWxtMRAwDgYDVQQDDAdoZWxtLnNoMB4XDTIzMTEwNjIxNTkx +OFoXDTMzMTEwMzIxNTkxOFowUTELMAkGA1UEBhMCQ0ExCzAJBgNVBAgMAk9OMRIw +EAYDVQQHDAlLaXRjaGVuZXIxDTALBgNVBAoMBEhlbG0xEjAQBgNVBAMMCWxvY2Fs +aG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMiJVQ0L8drmwHB9 +0yfNuKiBi3ykieXRsXgBHd9EiAv81oE1PdE7Xo+7k7N+KNvt/6ATOnCj/pRrC/77 +YwCwy9yBzYDc0C+/sk+agdQi3JfIjyeGWZH6kgV1xMxr9alrdB7126n4v4yiJf2g +zHn0JVd0qSOb4rciehR6PerxfjJrV2wuxk91VPlrVNLK61QcrzkVm9B8D/hVUQTq +2vp7i2MPrDmx9kuOTvbq6Xvmul5ajpHv3LF9Uj9zg1JGg0hJ//ItylTyNrtJzFmZ +wJ7PjnhVbO19foO4WSx9+BqB8H31J/LbrtQxVDj+R7LuFiAP8dstKL9vOOsRu5rU +slo6Sn8CAwEAAaMYMBYwFAYDVR0RBA0wC4IJbG9jYWxob3N0MA0GCSqGSIb3DQEB +CwUAA4IBAQBHR/4pypQodVm6q2erxqYLCvIPJtkdNdtopdj1H9GH56d0/cAiqsjs +bNOsigvtWTqgEnd8U3T9MFk0j6TvW5g//8+Jh+3Tf0EvsZoScbv+Os93FjK8g5DM +Ui879K7bsbvw3TDUAxdeR7cGhnoWsXIvgF3UwPlskd9axRWGZmjIkI7xortAD+8m +GwLEQt6MaeytJ9C82nx2M4bet8QEZOb23ESJe7gvxyh6TKYBraUXZDoj2qrbzj+G +6ZLcDcRatFKoiu49Yn2xyPrvliur8eFtb30ezrx60JICG8hVNne/1ELT/FfKt8yV +vs74brIoyk2aAH14yFYELrOsA/oF2EK9 +-----END CERTIFICATE----- diff --git a/testdata/openssl.conf b/testdata/openssl.conf index 9b27e445b..be5ff04b7 100644 --- a/testdata/openssl.conf +++ b/testdata/openssl.conf @@ -40,3 +40,7 @@ subjectAltName = @alternate_names [alternate_names] DNS.1 = helm.sh IP.1 = 127.0.0.1 + +# # Used to generate localhost-crt.pem +# [alternate_names] +# DNS.1 = localhost