diff --git a/.github/ISSUE_TEMPLATE/bug-report.yaml b/.github/ISSUE_TEMPLATE/bug-report.yaml
new file mode 100644
index 000000000..4309d800b
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug-report.yaml
@@ -0,0 +1,69 @@
+name: Bug Report
+description: Report a bug encountered in Helm
+labels: kind/bug
+body:
+ - type: textarea
+ id: problem
+ attributes:
+ label: What happened?
+ description: |
+ Please provide as much info as possible. Not doing so may result in your bug not being addressed in a timely manner.
+ validations:
+ required: true
+
+ - type: textarea
+ id: expected
+ attributes:
+ label: What did you expect to happen?
+ validations:
+ required: true
+
+ - type: textarea
+ id: repro
+ attributes:
+ label: How can we reproduce it (as minimally and precisely as possible)?
+ description: |
+ Please list steps someone can follow to trigger the issue.
+
+ For example:
+ 1. Run `helm install mychart ./path-to-chart -f values.yaml --debug`
+ 2. Observe the following error: ...
+
+ You can include:
+ - a sample `values.yaml` block
+ - a link to a chart
+ - specific `helm` commands used
+
+ This helps others reproduce and debug your issue more effectively.
+ validations:
+ required: true
+
+ - type: textarea
+ id: helmVersion
+ attributes:
+ label: Helm version
+ value: |
+
+ ```console
+ $ helm version
+ # paste output here
+ ```
+
+ validations:
+ required: true
+
+ - type: textarea
+ id: kubeVersion
+ attributes:
+ label: Kubernetes version
+ value: |
+
+
+ ```console
+ $ kubectl version
+ # paste output here
+ ```
+
+
+ validations:
+ required: true
diff --git a/.github/ISSUE_TEMPLATE/documentation.yaml b/.github/ISSUE_TEMPLATE/documentation.yaml
new file mode 100644
index 000000000..bb1b7537c
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/documentation.yaml
@@ -0,0 +1,27 @@
+name: Documentation
+description: Report any mistakes or missing information from the documentation or the examples
+labels: kind/documentation
+body:
+ - type: markdown
+ attributes:
+ value: |
+ ⚠️ **Note**: Most documentation lives in [helm/helm-www](https://github.com/helm/helm-www).
+ If your issue is about Helm website documentation or examples, please [open an issue there](https://github.com/helm/helm-www/issues/new/choose).
+
+ - type: textarea
+ id: feature
+ attributes:
+ label: What would you like to be added?
+ description: |
+ Link to the issue (please include a link to the specific documentation or example).
+ Link to the issue raised in [Helm Documentation Improvement Proposal](https://github.com/helm/helm-www)
+ validations:
+ required: true
+
+ - type: textarea
+ id: rationale
+ attributes:
+ label: Why is this needed?
+ validations:
+ required: true
+
diff --git a/.github/ISSUE_TEMPLATE/feature.yaml b/.github/ISSUE_TEMPLATE/feature.yaml
new file mode 100644
index 000000000..45b9c3f94
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature.yaml
@@ -0,0 +1,21 @@
+name: Enhancement/feature
+description: Provide supporting details for a feature in development
+labels: kind/feature
+body:
+ - type: textarea
+ id: feature
+ attributes:
+ label: What would you like to be added?
+ description: |
+ Feature requests are unlikely to make progress as issues.
+ Initial discussion and ideas can happen on an issue.
+ But significant changes or features must be proposed as a [Helm Improvement Proposal](https://github.com/helm/community/blob/main/hips/hip-0001.md) (HIP)
+ validations:
+ required: true
+
+ - type: textarea
+ id: rationale
+ attributes:
+ label: Why is this needed?
+ validations:
+ required: true
diff --git a/.github/env b/.github/env
new file mode 100644
index 000000000..4384ba074
--- /dev/null
+++ b/.github/env
@@ -0,0 +1,2 @@
+GOLANG_VERSION=1.24
+GOLANGCI_LINT_VERSION=v2.1.0
diff --git a/.github/issue_template.md b/.github/issue_template.md
deleted file mode 100644
index 48f48e5b6..000000000
--- a/.github/issue_template.md
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-Output of `helm version`:
-
-Output of `kubectl version`:
-
-Cloud Provider/Platform (AKS, GKE, Minikube etc.):
-
-
diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml
index 2ccea3d0e..11a5c49ec 100644
--- a/.github/workflows/build-test.yml
+++ b/.github/workflows/build-test.yml
@@ -19,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@f111f3307d8850f501ac008e886eec1fd1932a34 # pin@5.3.0
+ uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # pin@5.5.0
with:
- go-version: '1.23'
+ 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 5971ada24..3059b05a2 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@f111f3307d8850f501ac008e886eec1fd1932a34 # pin@5.3.0
+ uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # pin@5.5.0
with:
- go-version: '1.23'
+ go-version: '${{ env.GOLANG_VERSION }}'
check-latest: true
- name: golangci-lint
- uses: golangci/golangci-lint-action@2226d7cb06a077cd73e56eedd38eecad18e5d837 #pin@6.5.0
+ uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 #pin@8.0.0
with:
- version: v1.62
+ version: ${{ env.GOLANGCI_LINT_VERSION }}
diff --git a/.github/workflows/govulncheck.yml b/.github/workflows/govulncheck.yml
index f8572f2d6..67cfa4c36 100644
--- a/.github/workflows/govulncheck.yml
+++ b/.github/workflows/govulncheck.yml
@@ -13,10 +13,14 @@ jobs:
name: govulncheck
runs-on: ubuntu-latest
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@f111f3307d8850f501ac008e886eec1fd1932a34 # pin@5.3.0
+ uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # pin@5.5.0
with:
- go-version: '1.23'
+ 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 c5e7c6840..96138caf1 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@f111f3307d8850f501ac008e886eec1fd1932a34 # pin@5.3.0
+ uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # pin@5.5.0
with:
- go-version: '1.23'
-
+ 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@f111f3307d8850f501ac008e886eec1fd1932a34 # pin@5.3.0
+ uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # pin@5.5.0
with:
- go-version: '1.23'
+ 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 7f568cf9d..4b135bb2a 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@05b42c624433fc40578a4040d5cf5e36ddca8cde # v2.4.2
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@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
+ uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: SARIF file
path: results.sarif
diff --git a/.gitignore b/.gitignore
index 75698e993..7ea0717ed 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,4 +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 ff0dad5f6..a9b13c35f 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -1,45 +1,71 @@
-run:
- timeout: 10m
+formatters:
+ enable:
+ - gofmt
+ - goimports
+
+ exclusions:
+ generated: lax
+
+ settings:
+ gofmt:
+ simplify: true
+
+ goimports:
+ local-prefixes:
+ - helm.sh/helm/v4
linters:
- disable-all: true
+ default: none
+
enable:
+ - depguard
- dupl
- - gofmt
- - goimports
- - gosimple
+ - gomodguard
- govet
- ineffassign
- misspell
- nakedret
- revive
- - unused
- staticcheck
+ - thelper
+ - unused
+ - usestdlibvars
+ - usetesting
+
+ exclusions:
+ generated: lax
+
+ presets:
+ - comments
+ - common-false-positives
+ - legacy
+ - std-error-handling
+
+ rules: []
+
+ warn-unused: true
+
+ settings:
+ depguard:
+ rules:
+ Main:
+ deny:
+ - pkg: github.com/hashicorp/go-multierror
+ desc: "use errors instead"
+ - pkg: github.com/pkg/errors
+ desc: "use errors instead"
+
+ dupl:
+ threshold: 400
+
+ gomodguard:
+ blocked:
+ modules:
+ - github.com/evanphx/json-patch:
+ recommendations:
+ - github.com/evanphx/json-patch/v5
+
+run:
+ timeout: 10m
-linters-settings:
- gofmt:
- simplify: true
- goimports:
- local-prefixes: helm.sh/helm/v4
- dupl:
- threshold: 400
-issues:
- exclude-rules:
- # 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.
- - 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"
+version: "2"
diff --git a/Makefile b/Makefile
index d2c82b033..0785fdb2e 100644
--- a/Makefile
+++ b/Makefile
@@ -65,8 +65,8 @@ K8S_MODULES_MINOR_VER=$(word 2,$(K8S_MODULES_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/util.k8sVersionMajor=$(K8S_MODULES_MAJOR_VER)
-LDFLAGS += -X helm.sh/helm/v4/pkg/chart/util.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/OWNERS b/OWNERS
index de3e4e6a6..761cf76a3 100644
--- a/OWNERS
+++ b/OWNERS
@@ -9,6 +9,7 @@ maintainers:
- technosophos
triage:
- banjoh
+ - TerryHowe
- yxxhero
- zonggen
- z4ce
diff --git a/README.md b/README.md
index cf177aa4b..ef994e742 100644
--- a/README.md
+++ b/README.md
@@ -1,10 +1,11 @@
# Helm
[](https://github.com/helm/helm/actions?workflow=release)
-[](https://goreportcard.com/report/github.com/helm/helm)
+[](https://goreportcard.com/report/helm.sh/helm/v4)
[](https://pkg.go.dev/helm.sh/helm/v4)
[](https://bestpractices.coreinfrastructure.org/projects/3131)
[](https://scorecard.dev/viewer/?uri=github.com/helm/helm)
+[](https://insights.linuxfoundation.org/project/helm)
Helm is a tool for managing Charts. Charts are packages of pre-configured Kubernetes resources.
@@ -56,7 +57,7 @@ including installing pre-releases.
## Docs
-Get started with the [Quick Start guide](https://helm.sh/docs/intro/quickstart/) or plunge into the [complete documentation](https://helm.sh/docs)
+Get started with the [Quick Start guide](https://helm.sh/docs/intro/quickstart/) or plunge into the [complete documentation](https://helm.sh/docs).
## Roadmap
diff --git a/cmd/helm/helm.go b/cmd/helm/helm.go
index c8de18796..05e7e7ba2 100644
--- a/cmd/helm/helm.go
+++ b/cmd/helm/helm.go
@@ -17,51 +17,16 @@ limitations under the License.
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/v4/pkg/action"
- "helm.sh/helm/v4/pkg/cli"
+ helmcmd "helm.sh/helm/v4/pkg/cmd"
"helm.sh/helm/v4/pkg/kube"
- kubefake "helm.sh/helm/v4/pkg/kube/fake"
- "helm.sh/helm/v4/pkg/release"
- "helm.sh/helm/v4/pkg/storage/driver"
)
-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...)
-}
-
-// hookOutputWriter provides the writer for writing hook logs.
-func hookOutputWriter(_, _, _ string) io.Writer {
- return log.Writer()
-}
-
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
@@ -69,70 +34,18 @@ 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:], helmcmd.SetupLogging)
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)
- }
- actionConfig.SetHookOutputFunc(hookOutputWriter)
- })
-
if err := cmd.Execute(); err != nil {
- debug("%+v", 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 e7a05aecf..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/v4/internal/test"
- "helm.sh/helm/v4/pkg/action"
- chartutil "helm.sh/helm/v4/pkg/chart/util"
- "helm.sh/helm/v4/pkg/cli"
- kubefake "helm.sh/helm/v4/pkg/kube/fake"
- "helm.sh/helm/v4/pkg/release"
- "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,
- 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/install-hide-secret.txt b/cmd/helm/testdata/output/install-hide-secret.txt
deleted file mode 100644
index aaf73b478..000000000
--- a/cmd/helm/testdata/output/install-hide-secret.txt
+++ /dev/null
@@ -1 +0,0 @@
-Error: INSTALLATION FAILED: Hiding Kubernetes secrets requires a dry-run mode
diff --git a/cmd/helm/testdata/output/issue-totoml.txt b/cmd/helm/testdata/output/issue-totoml.txt
deleted file mode 100644
index 06cf4bb8d..000000000
--- a/cmd/helm/testdata/output/issue-totoml.txt
+++ /dev/null
@@ -1,8 +0,0 @@
----
-# Source: issue-totoml/templates/configmap.yaml
-apiVersion: v1
-kind: ConfigMap
-metadata:
- name: issue-totoml
-data: |
- key = 13
diff --git a/cmd/helm/testdata/testcharts/issue-totoml/Chart.yaml b/cmd/helm/testdata/testcharts/issue-totoml/Chart.yaml
deleted file mode 100644
index f4be7a213..000000000
--- a/cmd/helm/testdata/testcharts/issue-totoml/Chart.yaml
+++ /dev/null
@@ -1,3 +0,0 @@
-apiVersion: v2
-name: issue-totoml
-version: 0.1.0
diff --git a/cmd/helm/testdata/testcharts/issue-totoml/templates/configmap.yaml b/cmd/helm/testdata/testcharts/issue-totoml/templates/configmap.yaml
deleted file mode 100644
index 621e70d48..000000000
--- a/cmd/helm/testdata/testcharts/issue-totoml/templates/configmap.yaml
+++ /dev/null
@@ -1,6 +0,0 @@
-apiVersion: v1
-kind: ConfigMap
-metadata:
- name: issue-totoml
-data: |
- {{ .Values.global | toToml }}
diff --git a/cmd/helm/testdata/testcharts/issue-totoml/values.yaml b/cmd/helm/testdata/testcharts/issue-totoml/values.yaml
deleted file mode 100644
index dd0140449..000000000
--- a/cmd/helm/testdata/testcharts/issue-totoml/values.yaml
+++ /dev/null
@@ -1,2 +0,0 @@
-global:
- key: 13
\ No newline at end of file
diff --git a/go.mod b/go.mod
index c84140350..106c499b2 100644
--- a/go.mod
+++ b/go.mod
@@ -1,51 +1,51 @@
module helm.sh/helm/v4
-go 1.23.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/semver/v3 v3.4.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.25
github.com/cyphar/filepath-securejoin v0.4.1
- github.com/distribution/distribution/v3 v3.0.0-rc.3
- github.com/evanphx/json-patch v5.9.11+incompatible
+ github.com/distribution/distribution/v3 v3.0.0
+ github.com/evanphx/json-patch/v5 v5.9.11
+ github.com/fluxcd/cli-utils v0.36.0-flux.13
github.com/foxcpp/go-mockdns v1.1.0
github.com/gobwas/glob v0.2.3
github.com/gofrs/flock v0.12.1
github.com/gosuri/uitable v0.0.4
- github.com/hashicorp/go-multierror v1.1.1
github.com/jmoiron/sqlx v1.4.0
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.2
- github.com/opencontainers/image-spec v1.1.0
+ 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.1
+ github.com/rubenv/sql-migrate v1.8.0
+ github.com/santhosh-tekuri/jsonschema/v6 v6.0.2
github.com/spf13/cobra v1.9.1
github.com/spf13/pflag v1.0.6
github.com/stretchr/testify v1.10.0
- github.com/xeipuuv/gojsonschema v1.2.0
- golang.org/x/crypto v0.33.0
- golang.org/x/term v0.29.0
- golang.org/x/text v0.22.0
- k8s.io/api v0.32.2
- k8s.io/apiextensions-apiserver v0.32.2
- k8s.io/apimachinery v0.32.2
- k8s.io/apiserver v0.32.2
- k8s.io/cli-runtime v0.32.2
- k8s.io/client-go v0.32.2
+ golang.org/x/crypto v0.39.0
+ golang.org/x/term v0.32.0
+ golang.org/x/text v0.26.0
+ gopkg.in/yaml.v3 v3.0.1
+ k8s.io/api v0.33.2
+ k8s.io/apiextensions-apiserver v0.33.2
+ k8s.io/apimachinery v0.33.2
+ k8s.io/apiserver v0.33.2
+ k8s.io/cli-runtime v0.33.2
+ k8s.io/client-go v0.33.2
k8s.io/klog/v2 v2.130.1
- k8s.io/kubectl v0.32.2
- oras.land/oras-go/v2 v2.5.0
- sigs.k8s.io/yaml v1.4.0
+ k8s.io/kubectl v0.33.2
+ oras.land/oras-go/v2 v2.6.0
+ sigs.k8s.io/controller-runtime v0.21.0
+ sigs.k8s.io/yaml v1.5.0
)
require (
@@ -59,9 +59,6 @@ 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.6 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
@@ -70,43 +67,40 @@ require (
github.com/docker/docker-credential-helpers v0.8.2 // 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/emicklei/go-restful/v3 v3.12.1 // 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.21.0 // indirect
- github.com/go-openapi/jsonreference v0.20.2 // 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/go-cmp v0.6.0 // indirect
- github.com/google/gofuzz v1.2.0 // indirect
+ github.com/google/btree v1.1.3 // indirect
+ github.com/google/gnostic-models v0.6.9 // indirect
+ github.com/google/go-cmp v0.7.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/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // 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/grpc-ecosystem/grpc-gateway/v2 v2.24.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/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.11 // indirect
+ github.com/klauspost/compress v1.18.0 // 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
@@ -119,66 +113,69 @@ require (
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.37.0 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
+ github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
- github.com/prometheus/client_golang v1.20.5 // indirect
+ github.com/prometheus/client_golang v1.22.0 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
- github.com/prometheus/common v0.60.1 // 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/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.32.0 // indirect
+ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.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 v1.33.0 // indirect
+ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.33.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.32.0 // indirect
- go.opentelemetry.io/otel/sdk v1.32.0 // indirect
+ go.opentelemetry.io/otel/metric v1.34.0 // indirect
+ go.opentelemetry.io/otel/sdk v1.33.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.32.0 // indirect
- go.opentelemetry.io/proto/otlp v1.3.1 // indirect
- golang.org/x/mod v0.21.0 // indirect
- golang.org/x/net v0.33.0 // indirect
- golang.org/x/oauth2 v0.23.0 // indirect
- golang.org/x/sync v0.11.0 // indirect
- golang.org/x/sys v0.30.0 // indirect
- golang.org/x/time v0.7.0 // indirect
- golang.org/x/tools v0.26.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.35.2 // indirect
+ go.opentelemetry.io/otel/trace v1.34.0 // indirect
+ go.opentelemetry.io/proto/otlp v1.4.0 // indirect
+ go.yaml.in/yaml/v2 v2.4.2 // indirect
+ go.yaml.in/yaml/v3 v3.0.3 // indirect
+ golang.org/x/mod v0.25.0 // indirect
+ golang.org/x/net v0.40.0 // indirect
+ golang.org/x/oauth2 v0.29.0 // indirect
+ golang.org/x/sync v0.15.0 // indirect
+ golang.org/x/sys v0.33.0 // indirect
+ golang.org/x/time v0.11.0 // indirect
+ golang.org/x/tools v0.33.0 // indirect
+ google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 // indirect
+ google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 // indirect
+ google.golang.org/grpc v1.68.1 // indirect
+ google.golang.org/protobuf v1.36.5 // 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.32.2 // indirect
- k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect
- k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect
- sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect
- sigs.k8s.io/kustomize/api v0.18.0 // indirect
- sigs.k8s.io/kustomize/kyaml v0.18.1 // indirect
- sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect
+ k8s.io/component-base v0.33.2 // indirect
+ k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect
+ k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e // indirect
+ sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
+ sigs.k8s.io/kustomize/api v0.19.0 // indirect
+ sigs.k8s.io/kustomize/kyaml v0.19.0 // indirect
+ sigs.k8s.io/randfill v1.0.0 // indirect
+ sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect
)
diff --git a/go.sum b/go.sum
index 995e14598..8d8fe710e 100644
--- a/go.sum
+++ b/go.sum
@@ -6,16 +6,16 @@ github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
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.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
-github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
+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=
github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE=
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
-github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0=
-github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
+github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
+github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs=
github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0=
github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM=
@@ -37,10 +37,11 @@ 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/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
@@ -48,19 +49,10 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF
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/containerd/containerd v1.7.25 h1:khEQOAXOEJalRO228yzVsuASLH42vT7DIo9Ss+9SMFQ=
-github.com/containerd/containerd v1.7.25/go.mod h1:tWfHzVI0azhw4CT2vaIjsb2CoV4LJ9PrMPaULAr21Ok=
-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.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
-github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
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.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s=
@@ -71,34 +63,38 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
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.3 h1:JRJso9IVLoooKX76oWR+DWCCdZlK5m4nRtDWvzB1ITg=
-github.com/distribution/distribution/v3 v3.0.0-rc.3/go.mod h1:offoOgrnYs+CFwis8nE0hyzYZqRCZj5EFc5kgfszwiE=
+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/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-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/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/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/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 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.13 h1:2X5yjz/rk9mg7+bMFBDZKGKzeZpAmY2s6iwbNZz7OzM=
+github.com/fluxcd/cli-utils v0.36.0-flux.13/go.mod h1:b2iSoIeDTtjfCB0IKtGgqlhhvWa1oux3e90CjOf81oA=
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=
@@ -109,12 +105,12 @@ 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/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
+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.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/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=
@@ -135,19 +131,17 @@ github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
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/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.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/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
+github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
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-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo=
-github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
+github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8=
+github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
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=
@@ -156,19 +150,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.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo=
+github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA=
github.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY=
github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo=
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA=
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
-github.com/grpc-ecosystem/grpc-gateway/v2 v2.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=
-github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
-github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0 h1:TmHmbvxPmaegwhDubVz0lICL0J5Ka2vwTzhoePEXsGE=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0/go.mod h1:qztMSjm835F2bXf+5HKAPIS5qsmQDqZna/PgVt4rWtI=
github.com/hashicorp/golang-lru/arc/v2 v2.0.5 h1:l2zaLDubNhW4XO3LnliVj0GXO3+/CGNJAg1dcN2Fpfw=
github.com/hashicorp/golang-lru/arc/v2 v2.0.5/go.mod h1:ny6zBSQZi2JxIeYcv7kt2sH2PXJtirBN7RDhRpxPkxU=
github.com/hashicorp/golang-lru/v2 v2.0.5 h1:wW7h1TG88eUIJ2i69gaE3uNVtEPIagzhGvHgwfx2Vm4=
@@ -189,15 +178,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.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
-github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
+github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
+github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
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=
@@ -210,8 +196,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=
@@ -253,14 +239,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.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM=
-github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo=
-github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4=
-github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog=
+github.com/onsi/ginkgo/v2 v2.23.4 h1:ktYTpKJAVZnDT4VjxSbiBenUjmlL/5QkBEocaWXiQus=
+github.com/onsi/ginkgo/v2 v2.23.4/go.mod h1:Bt66ApGPBFzHyR+JO10Zbt0Gsp4uWxu5mIOTusL46e8=
+github.com/onsi/gomega v1.37.0 h1:CdEG8g0S133B4OswTDC/5XPSzE1OeP29QOioj2PID2Y=
+github.com/onsi/gomega v1.37.0/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0=
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=
@@ -276,16 +262,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.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y=
-github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
+github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
+github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
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.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.60.1 h1:FUas6GcOw66yB/73KC+BOZoFJmbo/1pojoILArPAaSc=
-github.com/prometheus/common v0.60.1/go.mod h1:h0LYf1R1deLSKtD4Vdg8gy4RuOvENW2J/h19V5NADQw=
+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=
@@ -296,14 +282,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/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.7.1 h1:f/o0WgfO/GqNuVg+6801K/KW3WdDSupzSjDYODmiUq4=
-github.com/rubenv/sql-migrate v1.7.1/go.mod h1:Ob2Psprc0/3ggbM6wCzyYVFFuc6FyZrb2AS+ezLDFb4=
+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.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ=
+github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU=
github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
@@ -319,41 +307,31 @@ 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.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.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.32.0 h1:WnBN+Xjcteh0zdk01SVqV55d/m62NJLJdIyb4y/WO5U=
-go.opentelemetry.io/otel v1.32.0/go.mod h1:00DCVSB0RQcnzlwyTfqtxSm+DRr9hpYrHjNGiBHVQIg=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 h1:yd02MEjBdJkG3uabWP9apV+OuWRIXGDuJEUJbOHmCFU=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0/go.mod h1:umTcuxiv1n/s/S6/c2AT/g2CQ7u5C59sHDNmfSwgz7Q=
+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=
@@ -362,10 +340,10 @@ go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0 h1:j7Z
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 v1.33.0 h1:Vh5HayB/0HHfOQA7Ctx69E/Y/DcQSMPpKANYVMQ7fBA=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0/go.mod h1:cpgtDBaqD/6ok/UG0jT15/uKjAY8mRA53diogHBg3UI=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.33.0 h1:5pojmb1U1AogINhN3SurB+zm/nIcusopeBNp42f45QM=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.33.0/go.mod h1:57gTHJSE5S1tqg+EKsLPlTWhpHMsWlVmer+LA926XiA=
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=
@@ -378,20 +356,30 @@ go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.32.0 h1:cC2yDI3IQd0Udsu
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.32.0 h1:xV2umtmNcThh2/a/aCP+h64Xx5wsj8qqnkYZktzNa0M=
-go.opentelemetry.io/otel/metric v1.32.0/go.mod h1:jH7CIbbK6SH2V2wE16W05BHCtIDzauciCRLoc/SyMv8=
-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/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.33.0 h1:iax7M131HuAm9QkZotNHEfstof92xM+N8sr3uHXc2IM=
+go.opentelemetry.io/otel/sdk v1.33.0/go.mod h1:A1Q5oi7/9XaMlIWzPSxLRWOI8nG3FnzHJNbiENQuihM=
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.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM=
-go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8=
-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.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.4.0 h1:TA9WRvW6zMwP+Ssb6fLoUIuirti1gGbP28GcKG1jgeg=
+go.opentelemetry.io/proto/otlp v1.4.0/go.mod h1:PPBWZIP98o2ElSqI35IHfu7hIhSwvc5N38Jw8pXuGFY=
+go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
+go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
+go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
+go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
+go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
+go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
+go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
+go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
+go.yaml.in/yaml/v3 v3.0.3 h1:bXOww4E/J3f66rav3pX3m8w6jDE4knZjGOw8b5Y6iNE=
+go.yaml.in/yaml/v3 v3.0.3/go.mod h1:tBHosrYAkRZjRAOREWbDnBXUf08JOwYq++0QNwQiWzI=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@@ -400,16 +388,16 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
-golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
-golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
+golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
+golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.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.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
-golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
+golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
+golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -423,10 +411,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.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
-golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
-golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs=
-golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
+golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
+golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
+golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98=
+golang.org/x/oauth2 v0.29.0/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=
@@ -437,8 +425,8 @@ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.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.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
-golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
+golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -460,8 +448,8 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.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.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
-golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
+golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
@@ -469,8 +457,8 @@ golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww=
-golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
-golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
+golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
+golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
@@ -478,10 +466,10 @@ 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.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
-golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
-golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
-golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
+golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
+golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
+golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
+golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
@@ -490,20 +478,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.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ=
-golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
+golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
+golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-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/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.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io=
-google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
+google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 h1:CkkIfIt50+lT6NHAVoRYEyAvQGFM7xEwXUUywFvEb3Q=
+google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576/go.mod h1:1R3kvZ1dtP3+4p4d3G8uJ8rFk/fWlScl38vanWACI08=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 h1:8ZmaLZE4XWrtU3MyClkYqqtl6Oegr3235h7jxsDyqCY=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU=
+google.golang.org/grpc v1.68.1 h1:oI5oTa11+ng8r8XMMN7jAOmWfPZWbYpCFaMUTACxkM0=
+google.golang.org/grpc v1.68.1/go.mod h1:+q1XYFJjShcqn0QZHvCyeR4CXPA+llXIeUIfIe00waw=
+google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
+google.golang.org/protobuf v1.36.5/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=
@@ -518,37 +506,43 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-k8s.io/api v0.32.2 h1:bZrMLEkgizC24G9eViHGOPbW+aRo9duEISRIJKfdJuw=
-k8s.io/api v0.32.2/go.mod h1:hKlhk4x1sJyYnHENsrdCWw31FEmCijNGPJO5WzHiJ6Y=
-k8s.io/apiextensions-apiserver v0.32.2 h1:2YMk285jWMk2188V2AERy5yDwBYrjgWYggscghPCvV4=
-k8s.io/apiextensions-apiserver v0.32.2/go.mod h1:GPwf8sph7YlJT3H6aKUWtd0E+oyShk/YHWQHf/OOgCA=
-k8s.io/apimachinery v0.32.2 h1:yoQBR9ZGkA6Rgmhbp/yuT9/g+4lxtsGYwW6dR6BDPLQ=
-k8s.io/apimachinery v0.32.2/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE=
-k8s.io/apiserver v0.32.2 h1:WzyxAu4mvLkQxwD9hGa4ZfExo3yZZaYzoYvvVDlM6vw=
-k8s.io/apiserver v0.32.2/go.mod h1:PEwREHiHNU2oFdte7BjzA1ZyjWjuckORLIK/wLV5goM=
-k8s.io/cli-runtime v0.32.2 h1:aKQR4foh9qeyckKRkNXUccP9moxzffyndZAvr+IXMks=
-k8s.io/cli-runtime v0.32.2/go.mod h1:a/JpeMztz3xDa7GCyyShcwe55p8pbcCVQxvqZnIwXN8=
-k8s.io/client-go v0.32.2 h1:4dYCD4Nz+9RApM2b/3BtVvBHw54QjMFUl1OLcJG5yOA=
-k8s.io/client-go v0.32.2/go.mod h1:fpZ4oJXclZ3r2nDOv+Ux3XcJutfrwjKTCHz2H3sww94=
-k8s.io/component-base v0.32.2 h1:1aUL5Vdmu7qNo4ZsE+569PV5zFatM9hl+lb3dEea2zU=
-k8s.io/component-base v0.32.2/go.mod h1:PXJ61Vx9Lg+P5mS8TLd7bCIr+eMJRQTyXe8KvkrvJq0=
+k8s.io/api v0.33.2 h1:YgwIS5jKfA+BZg//OQhkJNIfie/kmRsO0BmNaVSimvY=
+k8s.io/api v0.33.2/go.mod h1:fhrbphQJSM2cXzCWgqU29xLDuks4mu7ti9vveEnpSXs=
+k8s.io/apiextensions-apiserver v0.33.2 h1:6gnkIbngnaUflR3XwE1mCefN3YS8yTD631JXQhsU6M8=
+k8s.io/apiextensions-apiserver v0.33.2/go.mod h1:IvVanieYsEHJImTKXGP6XCOjTwv2LUMos0YWc9O+QP8=
+k8s.io/apimachinery v0.33.2 h1:IHFVhqg59mb8PJWTLi8m1mAoepkUNYmptHsV+Z1m5jY=
+k8s.io/apimachinery v0.33.2/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM=
+k8s.io/apiserver v0.33.2 h1:KGTRbxn2wJagJowo29kKBp4TchpO1DRO3g+dB/KOJN4=
+k8s.io/apiserver v0.33.2/go.mod h1:9qday04wEAMLPWWo9AwqCZSiIn3OYSZacDyu/AcoM/M=
+k8s.io/cli-runtime v0.33.2 h1:koNYQKSDdq5AExa/RDudXMhhtFasEg48KLS2KSAU74Y=
+k8s.io/cli-runtime v0.33.2/go.mod h1:gnhsAWpovqf1Zj5YRRBBU7PFsRc6NkEkwYNQE+mXL88=
+k8s.io/client-go v0.33.2 h1:z8CIcc0P581x/J1ZYf4CNzRKxRvQAwoAolYPbtQes+E=
+k8s.io/client-go v0.33.2/go.mod h1:9mCgT4wROvL948w6f6ArJNb7yQd7QsvqavDeZHvNmHo=
+k8s.io/component-base v0.33.2 h1:sCCsn9s/dG3ZrQTX/Us0/Sx2R0G5kwa0wbZFYoVp/+0=
+k8s.io/component-base v0.33.2/go.mod h1:/41uw9wKzuelhN+u+/C59ixxf4tYQKW7p32ddkYNe2k=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
-k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f h1:GA7//TjRY9yWGy1poLzYYJJ4JRdzg3+O6e8I+e+8T5Y=
-k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f/go.mod h1:R/HEjbvWI0qdfb8viZUeVZm0X6IZnxAydC7YU42CMw4=
-k8s.io/kubectl v0.32.2 h1:TAkag6+XfSBgkqK9I7ZvwtF0WVtUAvK8ZqTt+5zi1Us=
-k8s.io/kubectl v0.32.2/go.mod h1:+h/NQFSPxiDZYX/WZaWw9fwYezGLISP0ud8nQKg+3g8=
-k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro=
-k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/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/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8=
-sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo=
-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.18.1 h1:WvBo56Wzw3fjS+7vBjN6TeivvpbW9GmRaWZ9CIVmt4E=
-sigs.k8s.io/kustomize/kyaml v0.18.1/go.mod h1:C3L2BFVU1jgcddNBE1TxuVLgS46TjObMwW5FT9FcjYo=
-sigs.k8s.io/structured-merge-diff/v4 v4.4.2 h1:MdmvkGuXi/8io6ixD5wud3vOLwc1rj0aNqRlpuvjmwA=
-sigs.k8s.io/structured-merge-diff/v4 v4.4.2/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4=
-sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
+k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4=
+k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8=
+k8s.io/kubectl v0.33.2 h1:7XKZ6DYCklu5MZQzJe+CkCjoGZwD1wWl7t/FxzhMz7Y=
+k8s.io/kubectl v0.33.2/go.mod h1:8rC67FB8tVTYraovAGNi/idWIK90z2CHFNMmGJZJ3KI=
+k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e h1:KqK5c/ghOm8xkHYhlodbp6i6+r+ChV2vuAuVRdFbLro=
+k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
+oras.land/oras-go/v2 v2.6.0 h1:X4ELRsiGkrbeox69+9tzTu492FMUu7zJQW6eJU+I2oc=
+oras.land/oras-go/v2 v2.6.0/go.mod h1:magiQDfG6H1O9APp+rOsvCPcW1GD2MM7vgnKY0Y+u1o=
+sigs.k8s.io/controller-runtime v0.21.0 h1:CYfjpEuicjUecRk+KAeyYh+ouUBn4llGyDYytIGcJS8=
+sigs.k8s.io/controller-runtime v0.21.0/go.mod h1:OSg14+F65eWqIu4DceX7k/+QRAbTTvxeQSNSOQpukWM=
+sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE=
+sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
+sigs.k8s.io/kustomize/api v0.19.0 h1:F+2HB2mU1MSiR9Hp1NEgoU2q9ItNOaBJl0I4Dlus5SQ=
+sigs.k8s.io/kustomize/api v0.19.0/go.mod h1:/BbwnivGVcBh1r+8m3tH1VNxJmHSk1PzP5fkP6lbL1o=
+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/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
+sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
+sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
+sigs.k8s.io/structured-merge-diff/v4 v4.6.0 h1:IUA9nvMmnKWcj5jl84xn+T5MnlZKThmUW1TdblaLVAc=
+sigs.k8s.io/structured-merge-diff/v4 v4.6.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps=
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
+sigs.k8s.io/yaml v1.5.0 h1:M10b2U7aEUY6hRtU870n2VTPgR5RZiL/I6Lcc2F4NUQ=
+sigs.k8s.io/yaml v1.5.0/go.mod h1:wZs27Rbxoai4C0f8/9urLZtZtF3avA3gKvGyPdDqTO4=
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 d6d454653..fcf04b7a4 100644
--- a/internal/monocular/search.go
+++ b/internal/monocular/search.go
@@ -25,7 +25,7 @@ import (
"time"
"helm.sh/helm/v4/internal/version"
- "helm.sh/helm/v4/pkg/chart"
+ chart "helm.sh/helm/v4/pkg/chart/v2"
)
// SearchPath is the url path to the search API in monocular.
@@ -129,7 +129,7 @@ func (c *Client) Search(term string) ([]SearchResult, error) {
}
defer res.Body.Close()
- if res.StatusCode != 200 {
+ if res.StatusCode != http.StatusOK {
return nil, fmt.Errorf("failed to fetch %s : %s", p.String(), res.Status)
}
diff --git a/internal/resolver/resolver.go b/internal/resolver/resolver.go
index e695a179c..fb04aa366 100644
--- a/internal/resolver/resolver.go
+++ b/internal/resolver/resolver.go
@@ -18,17 +18,18 @@ package resolver
import (
"bytes"
"encoding/json"
+ "errors"
"fmt"
+ "io/fs"
"os"
"path/filepath"
"strings"
"time"
"github.com/Masterminds/semver/v3"
- "github.com/pkg/errors"
- "helm.sh/helm/v4/pkg/chart"
- "helm.sh/helm/v4/pkg/chart/loader"
+ 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"
@@ -66,7 +67,7 @@ func (r *Resolver) Resolve(reqs []*chart.Dependency, repoNames map[string]string
if d.Version != "" {
constraint, err = semver.NewConstraint(d.Version)
if err != nil {
- return nil, errors.Wrapf(err, "dependency %q has an invalid version/constraint format", d.Name)
+ return nil, fmt.Errorf("dependency %q has an invalid version/constraint format: %w", d.Name, err)
}
}
@@ -132,12 +133,12 @@ func (r *Resolver) Resolve(reqs []*chart.Dependency, repoNames map[string]string
if !registry.IsOCI(d.Repository) {
repoIndex, err := repo.LoadIndexFile(filepath.Join(r.cachepath, helmpath.CacheIndexFile(repoName)))
if err != nil {
- return nil, errors.Wrapf(err, "no cached repository for %s found. (try 'helm repo update')", repoName)
+ return nil, fmt.Errorf("no cached repository for %s found. (try 'helm repo update'): %w", repoName, err)
}
vs, ok = repoIndex.Entries[d.Name]
if !ok {
- return nil, errors.Errorf("%s chart not found in repo %s", d.Name, d.Repository)
+ return nil, fmt.Errorf("%s chart not found in repo %s", d.Name, d.Repository)
}
found = false
} else {
@@ -159,7 +160,7 @@ func (r *Resolver) Resolve(reqs []*chart.Dependency, repoNames map[string]string
ref := fmt.Sprintf("%s/%s", strings.TrimPrefix(d.Repository, fmt.Sprintf("%s://", registry.OCIScheme)), d.Name)
tags, err := r.registryClient.Tags(ref)
if err != nil {
- return nil, errors.Wrapf(err, "could not retrieve list of tags for repository %s", d.Repository)
+ return nil, fmt.Errorf("could not retrieve list of tags for repository %s: %w", d.Repository, err)
}
vs = make(repo.ChartVersions, len(tags))
@@ -200,7 +201,7 @@ func (r *Resolver) Resolve(reqs []*chart.Dependency, repoNames map[string]string
}
}
if len(missing) > 0 {
- return nil, errors.Errorf("can't get a valid version for %d subchart(s): %s. Make sure a matching chart version exists in the repo, or change the version constraint in Chart.yaml", len(missing), strings.Join(missing, ", "))
+ return nil, fmt.Errorf("can't get a valid version for %d subchart(s): %s. Make sure a matching chart version exists in the repo, or change the version constraint in Chart.yaml", len(missing), strings.Join(missing, ", "))
}
digest, err := HashReq(reqs, locked)
@@ -260,8 +261,8 @@ func GetLocalPath(repo, chartpath string) (string, error) {
depPath = filepath.Join(chartpath, p)
}
- if _, err = os.Stat(depPath); os.IsNotExist(err) {
- return "", errors.Errorf("directory %s not found", depPath)
+ if _, err = os.Stat(depPath); errors.Is(err, fs.ErrNotExist) {
+ return "", fmt.Errorf("directory %s not found", depPath)
} else if err != nil {
return "", err
}
diff --git a/internal/resolver/resolver_test.go b/internal/resolver/resolver_test.go
index 807fc1af6..cbc391c3b 100644
--- a/internal/resolver/resolver_test.go
+++ b/internal/resolver/resolver_test.go
@@ -19,7 +19,7 @@ import (
"runtime"
"testing"
- "helm.sh/helm/v4/pkg/chart"
+ chart "helm.sh/helm/v4/pkg/chart/v2"
"helm.sh/helm/v4/pkg/registry"
)
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 6b221fb6c..f67b9f1b9 100644
--- a/internal/sympath/walk.go
+++ b/internal/sympath/walk.go
@@ -21,12 +21,11 @@ limitations under the License.
package sympath
import (
- "log"
+ "fmt"
+ "log/slog"
"os"
"path/filepath"
"sort"
-
- "github.com/pkg/errors"
)
// Walk walks the file tree rooted at root, calling walkFn for each file or directory
@@ -69,10 +68,10 @@ func symwalk(path string, info os.FileInfo, walkFn filepath.WalkFunc) error {
if IsSymlink(info) {
resolved, err := filepath.EvalSymlinks(path)
if err != nil {
- return errors.Wrapf(err, "error evaluating symlink %s", path)
+ 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)
+ 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 d4e2ceeaa..1eba8b996 100644
--- a/internal/sympath/walk_test.go
+++ b/internal/sympath/walk_test.go
@@ -76,6 +76,7 @@ func walkTree(n *Node, path string, f func(path string, n *Node)) {
}
func makeTree(t *testing.T) {
+ t.Helper()
walkTree(tree, tree.name, func(path string, n *Node) {
if n.entries == nil {
if n.symLinkedTo != "" {
@@ -99,6 +100,7 @@ func makeTree(t *testing.T) {
}
func checkMarks(t *testing.T, report bool) {
+ t.Helper()
walkTree(tree, tree.name, func(path string, n *Node) {
if n.marks != n.expectedMarks && report {
t.Errorf("node %s mark = %d; expected %d", path, n.marks, n.expectedMarks)
diff --git a/internal/test/ensure/ensure.go b/internal/test/ensure/ensure.go
index 0d8dd9abc..a72f48c2d 100644
--- a/internal/test/ensure/ensure.go
+++ b/internal/test/ensure/ensure.go
@@ -29,12 +29,12 @@ import (
func HelmHome(t *testing.T) {
t.Helper()
base := t.TempDir()
- os.Setenv(xdg.CacheHomeEnvVar, base)
- os.Setenv(xdg.ConfigHomeEnvVar, base)
- os.Setenv(xdg.DataHomeEnvVar, base)
- os.Setenv(helmpath.CacheHomeEnvVar, "")
- os.Setenv(helmpath.ConfigHomeEnvVar, "")
- os.Setenv(helmpath.DataHomeEnvVar, "")
+ t.Setenv(xdg.CacheHomeEnvVar, base)
+ t.Setenv(xdg.ConfigHomeEnvVar, base)
+ t.Setenv(xdg.DataHomeEnvVar, base)
+ t.Setenv(helmpath.CacheHomeEnvVar, "")
+ t.Setenv(helmpath.ConfigHomeEnvVar, "")
+ t.Setenv(helmpath.DataHomeEnvVar, "")
}
// TempFile ensures a temp file for unit testing purposes.
@@ -46,9 +46,10 @@ func HelmHome(t *testing.T) {
// tempdir := TempFile(t, "foo", []byte("bar"))
// filename := filepath.Join(tempdir, "foo")
func TempFile(t *testing.T, name string, data []byte) string {
+ t.Helper()
path := t.TempDir()
filename := filepath.Join(path, name)
- if err := os.WriteFile(filename, data, 0755); err != nil {
+ if err := os.WriteFile(filename, data, 0o755); err != nil {
t.Fatal(err)
}
return path
diff --git a/internal/test/test.go b/internal/test/test.go
index e6821282c..632bc72fd 100644
--- a/internal/test/test.go
+++ b/internal/test/test.go
@@ -19,10 +19,9 @@ package test
import (
"bytes"
"flag"
+ "fmt"
"os"
"path/filepath"
-
- "github.com/pkg/errors"
)
// UpdateGolden writes out the golden files with the latest values, rather than failing the test.
@@ -75,11 +74,11 @@ func compare(actual []byte, filename string) error {
expected, err := os.ReadFile(filename)
if err != nil {
- return errors.Wrapf(err, "unable to read testdata %s", filename)
+ return fmt.Errorf("unable to read testdata %s: %w", filename, err)
}
expected = normalize(expected)
if !bytes.Equal(expected, actual) {
- return errors.Errorf("does not match golden file %s\n\nWANT:\n'%s'\n\nGOT:\n'%s'", filename, expected, actual)
+ return fmt.Errorf("does not match golden file %s\n\nWANT:\n'%s'\n\nGOT:\n'%s'", filename, expected, actual)
}
return nil
}
@@ -92,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 d29bb5f87..717eff04d 100644
--- a/internal/third_party/dep/fs/fs.go
+++ b/internal/third_party/dep/fs/fs.go
@@ -32,13 +32,14 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package fs
import (
+ "errors"
+ "fmt"
"io"
+ "io/fs"
"os"
"path/filepath"
"runtime"
"syscall"
-
- "github.com/pkg/errors"
)
// fs contains a copy of a few functions from dep tool code to avoid a dependency on golang/dep.
@@ -51,7 +52,7 @@ import (
func RenameWithFallback(src, dst string) error {
_, err := os.Stat(src)
if err != nil {
- return errors.Wrapf(err, "cannot stat %s", src)
+ return fmt.Errorf("cannot stat %s: %w", src, err)
}
err = os.Rename(src, dst)
@@ -69,20 +70,24 @@ func renameByCopy(src, dst string) error {
if dir, _ := IsDir(src); dir {
cerr = CopyDir(src, dst)
if cerr != nil {
- cerr = errors.Wrap(cerr, "copying directory failed")
+ cerr = fmt.Errorf("copying directory failed: %w", cerr)
}
} else {
cerr = copyFile(src, dst)
if cerr != nil {
- cerr = errors.Wrap(cerr, "copying file failed")
+ cerr = fmt.Errorf("copying file failed: %w", cerr)
}
}
if cerr != nil {
- return errors.Wrapf(cerr, "rename fallback failed: cannot rename %s to %s", src, dst)
+ return fmt.Errorf("rename fallback failed: cannot rename %s to %s: %w", src, dst, cerr)
+ }
+
+ if err := os.RemoveAll(src); err != nil {
+ return fmt.Errorf("cannot delete %s: %w", src, err)
}
- return errors.Wrapf(os.RemoveAll(src), "cannot delete %s", src)
+ return nil
}
var (
@@ -107,7 +112,7 @@ func CopyDir(src, dst string) error {
}
_, err = os.Stat(dst)
- if err != nil && !os.IsNotExist(err) {
+ if err != nil && !errors.Is(err, fs.ErrNotExist) {
return err
}
if err == nil {
@@ -115,12 +120,12 @@ func CopyDir(src, dst string) error {
}
if err = os.MkdirAll(dst, fi.Mode()); err != nil {
- return errors.Wrapf(err, "cannot mkdir %s", dst)
+ return fmt.Errorf("cannot mkdir %s: %w", dst, err)
}
entries, err := os.ReadDir(src)
if err != nil {
- return errors.Wrapf(err, "cannot read directory %s", dst)
+ return fmt.Errorf("cannot read directory %s: %w", dst, err)
}
for _, entry := range entries {
@@ -129,13 +134,13 @@ func CopyDir(src, dst string) error {
if entry.IsDir() {
if err = CopyDir(srcPath, dstPath); err != nil {
- return errors.Wrap(err, "copying directory failed")
+ return fmt.Errorf("copying directory failed: %w", err)
}
} else {
// This will include symlinks, which is what we want when
// copying things.
if err = copyFile(srcPath, dstPath); err != nil {
- return errors.Wrap(err, "copying file failed")
+ return fmt.Errorf("copying file failed: %w", err)
}
}
}
@@ -149,7 +154,7 @@ func CopyDir(src, dst string) error {
// of the source file. The file mode will be copied from the source.
func copyFile(src, dst string) (err error) {
if sym, err := IsSymlink(src); err != nil {
- return errors.Wrap(err, "symlink check failed")
+ return fmt.Errorf("symlink check failed: %w", err)
} else if sym {
if err := cloneSymlink(src, dst); err != nil {
if runtime.GOOS == "windows" {
@@ -172,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
@@ -205,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.
@@ -226,7 +231,7 @@ func IsDir(name string) (bool, error) {
return false, err
}
if !fi.IsDir() {
- return false, errors.Errorf("%q is not a directory", name)
+ return false, fmt.Errorf("%q is not a directory", name)
}
return true, nil
}
diff --git a/internal/third_party/dep/fs/fs_test.go b/internal/third_party/dep/fs/fs_test.go
index d42c3f110..4c59d17fe 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()
@@ -476,6 +457,7 @@ func TestCopyFileFail(t *testing.T) {
// files this function creates. It is the caller's responsibility to call
// this function before the test is done running, whether there's an error or not.
func setupInaccessibleDir(t *testing.T, op func(dir string) error) func() {
+ t.Helper()
dir := t.TempDir()
subdir := filepath.Join(dir, "dir")
diff --git a/internal/third_party/dep/fs/rename.go b/internal/third_party/dep/fs/rename.go
index a3e5e56a6..5f13b1ca3 100644
--- a/internal/third_party/dep/fs/rename.go
+++ b/internal/third_party/dep/fs/rename.go
@@ -34,10 +34,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package fs
import (
+ "fmt"
"os"
"syscall"
-
- "github.com/pkg/errors"
)
// renameFallback attempts to determine the appropriate fallback to failed rename
@@ -51,7 +50,7 @@ func renameFallback(err error, src, dst string) error {
if !ok {
return err
} else if terr.Err != syscall.EXDEV {
- return errors.Wrapf(terr, "link error: cannot rename %s to %s", src, dst)
+ return fmt.Errorf("link error: cannot rename %s to %s: %w", src, dst, terr)
}
return renameByCopy(src, dst)
diff --git a/internal/third_party/dep/fs/rename_windows.go b/internal/third_party/dep/fs/rename_windows.go
index a377720a6..566f695d3 100644
--- a/internal/third_party/dep/fs/rename_windows.go
+++ b/internal/third_party/dep/fs/rename_windows.go
@@ -34,10 +34,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package fs
import (
+ "fmt"
"os"
"syscall"
-
- "github.com/pkg/errors"
)
// renameFallback attempts to determine the appropriate fallback to failed rename
@@ -61,7 +60,7 @@ func renameFallback(err error, src, dst string) error {
// 0x11 (ERROR_NOT_SAME_DEVICE) is the windows error.
// See https://msdn.microsoft.com/en-us/library/cc231199.aspx
if ok && noerr != 0x11 {
- return errors.Wrapf(terr, "link error: cannot rename %s to %s", src, dst)
+ return fmt.Errorf("link error: cannot rename %s to %s: %w", src, dst, terr)
}
}
diff --git a/internal/tlsutil/tls_test.go b/internal/tlsutil/tls_test.go
index eb1cc183e..3d7e75c86 100644
--- a/internal/tlsutil/tls_test.go
+++ b/internal/tlsutil/tls_test.go
@@ -30,8 +30,9 @@ const (
)
func testfile(t *testing.T, file string) (path string) {
- var err error
- if path, err = filepath.Abs(filepath.Join(tlsTestDir, file)); err != nil {
+ t.Helper()
+ path, err := filepath.Abs(filepath.Join(tlsTestDir, file))
+ if err != nil {
t.Fatalf("error getting absolute path to test file %q: %v", file, err)
}
return path
diff --git a/pkg/action/action.go b/pkg/action/action.go
index 6efc6c2ee..40194dfd7 100644
--- a/pkg/action/action.go
+++ b/pkg/action/action.go
@@ -18,29 +18,31 @@ package action
import (
"bytes"
+ "errors"
"fmt"
"io"
+ "log/slog"
"os"
"path"
"path/filepath"
- "regexp"
"strings"
+ "sync"
+ "text/template"
- "github.com/pkg/errors"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/client-go/discovery"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
- "helm.sh/helm/v4/pkg/chart"
- chartutil "helm.sh/helm/v4/pkg/chart/util"
+ 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"
- "helm.sh/helm/v4/pkg/release"
- "helm.sh/helm/v4/pkg/releaseutil"
+ 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"
@@ -63,21 +65,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.
@@ -95,10 +82,13 @@ 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
+
+ mutex sync.Mutex
}
// renderResources renders the templates in a chart
@@ -118,7 +108,7 @@ func (cfg *Configuration) renderResources(ch *chart.Chart, values chartutil.Valu
if ch.Metadata.KubeVersion != "" {
if !chartutil.IsCompatibleRange(ch.Metadata.KubeVersion, caps.KubeVersion.String()) {
- return hs, b, "", errors.Errorf("chart requires kubeVersion: %s which is incompatible with Kubernetes %s", ch.Metadata.KubeVersion, caps.KubeVersion.String())
+ return hs, b, "", fmt.Errorf("chart requires kubeVersion: %s which is incompatible with Kubernetes %s", ch.Metadata.KubeVersion, caps.KubeVersion.String())
}
}
@@ -135,10 +125,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)
}
@@ -229,7 +223,7 @@ func (cfg *Configuration) renderResources(ch *chart.Chart, values chartutil.Valu
if pr != nil {
b, err = pr.Run(b)
if err != nil {
- return hs, b, notes, errors.Wrap(err, "error while running post render on files")
+ return hs, b, notes, fmt.Errorf("error while running post render on files: %w", err)
}
}
@@ -243,9 +237,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 {
@@ -253,13 +244,13 @@ func (cfg *Configuration) getCapabilities() (*chartutil.Capabilities, error) {
}
dc, err := cfg.RESTClientGetter.ToDiscoveryClient()
if err != nil {
- return nil, errors.Wrap(err, "could not get Kubernetes discovery client")
+ return nil, fmt.Errorf("could not get Kubernetes discovery client: %w", err)
}
// force a discovery cache invalidation to always fetch the latest server version/capabilities.
dc.Invalidate()
kubeVersion, err := dc.ServerVersion()
if err != nil {
- return nil, errors.Wrap(err, "could not get server version from Kubernetes")
+ return nil, fmt.Errorf("could not get server version from Kubernetes: %w", err)
}
// Issue #6361:
// Client-Go emits an error when an API service is registered but unimplemented.
@@ -269,10 +260,10 @@ 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, errors.Wrap(err, "could not get apiVersions from Kubernetes")
+ return nil, fmt.Errorf("could not get apiVersions from Kubernetes: %w", err)
}
}
@@ -292,7 +283,7 @@ func (cfg *Configuration) getCapabilities() (*chartutil.Capabilities, error) {
func (cfg *Configuration) KubernetesClientSet() (kubernetes.Interface, error) {
conf, err := cfg.RESTClientGetter.ToRESTConfig()
if err != nil {
- return nil, errors.Wrap(err, "unable to generate config for kubernetes client")
+ return nil, fmt.Errorf("unable to generate config for kubernetes client: %w", err)
}
return kubernetes.NewForConfig(conf)
@@ -308,7 +299,7 @@ func (cfg *Configuration) Now() time.Time {
func (cfg *Configuration) releaseContent(name string, version int) (*release.Release, error) {
if err := chartutil.ValidateReleaseName(name); err != nil {
- return nil, errors.Errorf("releaseContent: Release name is invalid: %s", name)
+ return nil, fmt.Errorf("releaseContent: Release name is invalid: %s", name)
}
if version <= 0 {
@@ -322,7 +313,7 @@ func (cfg *Configuration) releaseContent(name string, version int) (*release.Rel
func GetVersionSet(client discovery.ServerResourcesInterface) (chartutil.VersionSet, error) {
groups, resources, err := client.ServerGroupsAndResources()
if err != nil && !discovery.IsGroupDiscoveryFailedError(err) {
- return chartutil.DefaultVersionSet, errors.Wrap(err, "could not get apiVersions from Kubernetes")
+ return chartutil.DefaultVersionSet, fmt.Errorf("could not get apiVersions from Kubernetes: %w", err)
}
// FIXME: The Kubernetes test fixture for cli appears to always return nil
@@ -369,14 +360,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,
@@ -387,11 +377,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
@@ -411,21 +399,19 @@ 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 {
- return errors.Wrap(err, "unable to instantiate SQL driver")
+ return fmt.Errorf("unable to instantiate SQL driver: %w", err)
}
store = storage.Init(d)
default:
- return errors.Errorf("unknown driver %q", helmDriver)
+ return fmt.Errorf("unknown driver %q", helmDriver)
}
cfg.RESTClientGetter = getter
cfg.KubeClient = kc
cfg.Releases = store
- cfg.Log = log
cfg.HookOutputFunc = func(_, _, _ string) io.Writer { return io.Discard }
return nil
diff --git a/pkg/action/action_test.go b/pkg/action/action_test.go
index aa5d589f8..9436abef5 100644
--- a/pkg/action/action_test.go
+++ b/pkg/action/action_test.go
@@ -19,25 +19,38 @@ import (
"flag"
"fmt"
"io"
+ "log/slog"
"testing"
"github.com/stretchr/testify/assert"
fakeclientset "k8s.io/client-go/kubernetes/fake"
- "helm.sh/helm/v4/pkg/chart"
- chartutil "helm.sh/helm/v4/pkg/chart/util"
+ "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"
- "helm.sh/helm/v4/pkg/release"
+ 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 {
t.Helper()
+ 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 {
@@ -46,15 +59,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...)
- }
- },
}
}
@@ -334,7 +341,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)
@@ -347,7 +354,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 e0ff56cce..03c370c8e 100644
--- a/pkg/action/dependency.go
+++ b/pkg/action/dependency.go
@@ -26,8 +26,8 @@ import (
"github.com/Masterminds/semver/v3"
"github.com/gosuri/uitable"
- "helm.sh/helm/v4/pkg/chart"
- "helm.sh/helm/v4/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.
diff --git a/pkg/action/dependency_test.go b/pkg/action/dependency_test.go
index 38f2668ae..5be7bf5a9 100644
--- a/pkg/action/dependency_test.go
+++ b/pkg/action/dependency_test.go
@@ -25,8 +25,8 @@ import (
"github.com/stretchr/testify/assert"
"helm.sh/helm/v4/internal/test"
- "helm.sh/helm/v4/pkg/chart"
- chartutil "helm.sh/helm/v4/pkg/chart/util"
+ 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 4c0683f3e..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/v4/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 190e9ccb9..e760ae4d1 100644
--- a/pkg/action/get_metadata.go
+++ b/pkg/action/get_metadata.go
@@ -21,7 +21,7 @@ import (
"strings"
"time"
- "helm.sh/helm/v4/pkg/chart"
+ chart "helm.sh/helm/v4/pkg/chart/v2"
)
// GetMetadata is the action for checking a given release's metadata.
diff --git a/pkg/action/get_values.go b/pkg/action/get_values.go
index 21253b7aa..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 (
- chartutil "helm.sh/helm/v4/pkg/chart/util"
+ 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 1c5cfa86f..d7af1d6a4 100644
--- a/pkg/action/history.go
+++ b/pkg/action/history.go
@@ -17,10 +17,12 @@ limitations under the License.
package action
import (
- "github.com/pkg/errors"
+ "log/slog"
- chartutil "helm.sh/helm/v4/pkg/chart/util"
- "helm.sh/helm/v4/pkg/release"
+ "fmt"
+
+ 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.
@@ -50,9 +52,9 @@ func (h *History) Run(name string) ([]*release.Release, error) {
}
if err := chartutil.ValidateReleaseName(name); err != nil {
- return nil, errors.Errorf("release name is invalid: %s", name)
+ 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 b6c505807..1213e87e2 100644
--- a/pkg/action/hooks.go
+++ b/pkg/action/hooks.go
@@ -25,17 +25,15 @@ import (
"helm.sh/helm/v4/pkg/kube"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-
- "github.com/pkg/errors"
"gopkg.in/yaml.v3"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- "helm.sh/helm/v4/pkg/release"
+ 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 {
@@ -49,23 +47,17 @@ 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 len(h.DeletePolicies) == 0 {
- // TODO(jlegrone): Only apply before-hook-creation delete policy to run to completion
- // resources. For all other resource types update in place if a
- // resource with the same name already exists and is owned by the
- // current release.
- h.DeletePolicies = []release.HookDeletePolicy{release.HookBeforeHookCreation}
- }
+ cfg.hookSetDeletePolicy(h)
- if err := cfg.deleteHookByPolicy(h, release.HookBeforeHookCreation, timeout); err != nil {
+ if err := cfg.deleteHookByPolicy(h, release.HookBeforeHookCreation, waitStrategy, timeout); err != nil {
return err
}
resources, err := cfg.KubeClient.Build(bytes.NewBufferString(h.Manifest), true)
if err != nil {
- return errors.Wrapf(err, "unable to build kubernetes object for %s hook %s", hook, h.Path)
+ return fmt.Errorf("unable to build kubernetes object for %s hook %s: %w", hook, h.Path, err)
}
// Record the time at which the hook was applied to the cluster
@@ -84,11 +76,15 @@ func (cfg *Configuration) execHook(rl *release.Release, hook release.HookEvent,
if _, err := cfg.KubeClient.Create(resources); err != nil {
h.LastRun.CompletedAt = helmtime.Now()
h.LastRun.Phase = release.HookPhaseFailed
- return errors.Wrapf(err, "warning: Hook %s %s failed", hook, h.Path)
+ return fmt.Errorf("warning: Hook %s %s failed: %w", hook, h.Path, err)
}
+ waiter, err := cfg.KubeClient.GetWaiter(waitStrategy)
+ if err != nil {
+ return fmt.Errorf("unable to get waiter: %w", err)
+ }
// 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
@@ -101,10 +97,17 @@ func (cfg *Configuration) execHook(rl *release.Release, hook release.HookEvent,
}
// 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 errDeleting := cfg.deleteHookByPolicy(h, release.HookFailed, timeout); errDeleting != 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
@@ -118,7 +121,7 @@ func (cfg *Configuration) execHook(rl *release.Release, hook release.HookEvent,
// 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, timeout); err != nil {
+ if err := cfg.deleteHookByPolicy(h, release.HookSucceeded, waitStrategy, timeout); err != nil {
return err
}
}
@@ -139,41 +142,64 @@ 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" {
return nil
}
- if hookHasDeletePolicy(h, policy) {
+ if cfg.hookHasDeletePolicy(h, policy) {
resources, err := cfg.KubeClient.Build(bytes.NewBufferString(h.Manifest), false)
if err != nil {
- return errors.Wrapf(err, "unable to build kubernetes object for deleting hook %s", h.Path)
+ return fmt.Errorf("unable to build kubernetes object for deleting hook %s: %w", h.Path, err)
}
_, errs := cfg.KubeClient.Delete(resources)
if len(errs) > 0 {
- return errors.New(joinErrors(errs))
+ 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 {
- for _, v := range h.DeletePolicies {
- if policy == v {
- return true
- }
+func (cfg *Configuration) hookHasDeletePolicy(h *release.Hook, policy release.HookDeletePolicy) bool {
+ cfg.mutex.Lock()
+ defer cfg.mutex.Unlock()
+ return slices.Contains(h.DeletePolicies, policy)
+}
+
+// hookSetDeletePolicy determines whether the defined hook deletion policy matches the hook deletion polices
+// supported by helm. If so, mark the hook as one should be deleted.
+func (cfg *Configuration) hookSetDeletePolicy(h *release.Hook) {
+ cfg.mutex.Lock()
+ defer cfg.mutex.Unlock()
+ if len(h.DeletePolicies) == 0 {
+ // TODO(jlegrone): Only apply before-hook-creation delete policy to run to completion
+ // resources. For all other resource types update in place if a
+ // resource with the same name already exists and is owned by the
+ // current release.
+ h.DeletePolicies = []release.HookDeletePolicy{release.HookBeforeHookCreation}
}
- return false
}
// outputLogsByPolicy outputs a pods logs if the hook policy instructs it to
@@ -216,7 +242,7 @@ func (cfg *Configuration) deriveNamespace(h *release.Hook, namespace string) (st
}{}
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)
+ return "", fmt.Errorf("unable to parse metadata.namespace from kubernetes manifest for output logs hook %s: %w", h.Path, err)
}
if tmp.Metadata.Namespace == "" {
return namespace, nil
diff --git a/pkg/action/hooks_test.go b/pkg/action/hooks_test.go
index 0f4a9be34..ad1de2c59 100644
--- a/pkg/action/hooks_test.go
+++ b/pkg/action/hooks_test.go
@@ -20,13 +20,22 @@ 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"
- "helm.sh/helm/v4/pkg/chart"
+ 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/release"
+ 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 {
@@ -158,6 +167,7 @@ func TestInstallRelease_HooksOutputLogsOnSuccessAndFailure(t *testing.T) {
}
func runInstallForHooksWithSuccess(t *testing.T, manifest, expectedNamespace string, shouldOutput bool) {
+ t.Helper()
var expectedOutput string
if shouldOutput {
expectedOutput = fmt.Sprintf("attempted to output logs for namespace: %s", expectedNamespace)
@@ -181,6 +191,7 @@ func runInstallForHooksWithSuccess(t *testing.T, manifest, expectedNamespace str
}
func runInstallForHooksWithFailure(t *testing.T, manifest, expectedNamespace string, shouldOutput bool) {
+ t.Helper()
var expectedOutput string
if shouldOutput {
expectedOutput = fmt.Sprintf("attempted to output logs for namespace: %s", expectedNamespace)
@@ -206,3 +217,187 @@ func runInstallForHooksWithFailure(t *testing.T, manifest, expectedNamespace str
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 6ad77a509..440f41baa 100644
--- a/pkg/action/install.go
+++ b/pkg/action/install.go
@@ -19,8 +19,11 @@ package action
import (
"bytes"
"context"
+ "errors"
"fmt"
"io"
+ "io/fs"
+ "log/slog"
"net/url"
"os"
"path"
@@ -31,7 +34,6 @@ import (
"time"
"github.com/Masterminds/sprig/v3"
- "github.com/pkg/errors"
v1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
@@ -39,8 +41,8 @@ import (
"k8s.io/cli-runtime/pkg/resource"
"sigs.k8s.io/yaml"
- "helm.sh/helm/v4/pkg/chart"
- chartutil "helm.sh/helm/v4/pkg/chart/util"
+ 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"
@@ -48,8 +50,8 @@ import (
kubefake "helm.sh/helm/v4/pkg/kube/fake"
"helm.sh/helm/v4/pkg/postrender"
"helm.sh/helm/v4/pkg/registry"
- "helm.sh/helm/v4/pkg/release"
- "helm.sh/helm/v4/pkg/releaseutil"
+ 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"
@@ -79,7 +81,7 @@ type Install struct {
HideSecret bool
DisableHooks bool
Replace bool
- Wait bool
+ WaitStrategy kube.WaitStrategy
WaitForJobs bool
Devel bool
DependencyUpdate bool
@@ -142,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 {
@@ -164,7 +166,7 @@ func (i *Install) installCRDs(crds []chart.CRD) error {
// Read in the resources
res, err := i.cfg.KubeClient.Build(bytes.NewBuffer(obj.File.Data), false)
if err != nil {
- return errors.Wrapf(err, "failed to install CRD %s", obj.Name)
+ return fmt.Errorf("failed to install CRD %s: %w", obj.Name, err)
}
// Send them to Kube
@@ -172,16 +174,20 @@ 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 errors.Wrapf(err, "failed to install CRD %s", obj.Name)
+ return fmt.Errorf("failed to install CRD %s: %w", obj.Name, err)
}
totalItems = append(totalItems, res...)
}
if len(totalItems) > 0 {
+ waiter, err := i.cfg.KubeClient.GetWaiter(i.WaitStrategy)
+ if err != nil {
+ return fmt.Errorf("unable to get waiter: %w", err)
+ }
// 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
}
@@ -196,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()
@@ -209,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()
}
}
@@ -233,25 +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 {
- i.cfg.Log(fmt.Sprintf("ERROR: Cluster reachability check failed: %v", err))
- return nil, errors.Wrap(err, "cluster reachability check failed")
+ slog.Error(fmt.Sprintf("cluster reachability check failed: %v", err))
+ return nil, fmt.Errorf("cluster reachability check failed: %w", err)
}
}
// HideSecret must be used with dry run. Otherwise, return an error.
if !i.isDryRun() && i.HideSecret {
- i.cfg.Log("ERROR: Hiding Kubernetes secrets requires a dry-run mode")
- return nil, errors.New("Hiding Kubernetes secrets requires a dry-run mode")
+ 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 {
- i.cfg.Log(fmt.Sprintf("ERROR: Release name check failed: %v", err))
- return nil, errors.Wrap(err, "release name check failed")
+ slog.Error("release name check failed", slog.Any("error", err))
+ return nil, fmt.Errorf("release name check failed: %w", err)
}
if err := chartutil.ProcessDependencies(chrt, vals); err != nil {
- i.cfg.Log(fmt.Sprintf("ERROR: Processing chart dependencies failed: %v", err))
- return nil, errors.Wrap(err, "chart dependencies processing failed")
+ slog.Error("chart dependencies processing failed", slog.Any("error", err))
+ return nil, fmt.Errorf("chart dependencies processing failed: %w", err)
}
var interactWithRemote bool
@@ -264,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
}
@@ -284,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 {
@@ -335,7 +343,7 @@ func (i *Install) RunWithContext(ctx context.Context, chrt *chart.Chart, vals ma
var toBeAdopted kube.ResourceList
resources, err := i.cfg.KubeClient.Build(bytes.NewBufferString(rel.Manifest), !i.DisableOpenAPIValidation)
if err != nil {
- return nil, errors.Wrap(err, "unable to build kubernetes objects from release manifest")
+ return nil, fmt.Errorf("unable to build kubernetes objects from release manifest: %w", err)
}
// It is safe to use "force" here because these are resources currently rendered by the chart.
@@ -357,7 +365,7 @@ func (i *Install) RunWithContext(ctx context.Context, chrt *chart.Chart, vals ma
toBeAdopted, err = existingResourceConflict(resources, rel.Name, rel.Namespace)
}
if err != nil {
- return nil, errors.Wrap(err, "Unable to continue with install")
+ return nil, fmt.Errorf("unable to continue with install: %w", err)
}
}
@@ -448,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)
}
}
@@ -459,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)
}
}
@@ -496,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
@@ -505,15 +520,15 @@ 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
uninstall.Timeout = i.Timeout
if _, uninstallErr := uninstall.Run(i.ReleaseName); uninstallErr != nil {
- return rel, errors.Wrapf(uninstallErr, "an error occurred while uninstalling the release. original install error: %s", err)
+ return rel, fmt.Errorf("an error occurred while uninstalling the release. original install error: %w: %w", err, uninstallErr)
}
- return rel, errors.Wrapf(err, "release %s failed, and has been uninstalled due to atomic being set", i.ReleaseName)
+ return rel, fmt.Errorf("release %s failed, and has been uninstalled due to atomic being set: %w", i.ReleaseName, err)
}
i.recordRelease(rel) // Ignore the error, since we have another error to deal with.
return rel, err
@@ -531,7 +546,7 @@ func (i *Install) availableName() error {
start := i.ReleaseName
if err := chartutil.ValidateReleaseName(start); err != nil {
- return errors.Wrapf(err, "release name %q", start)
+ return fmt.Errorf("release name %q: %w", start, err)
}
// On dry run, bail here
if i.isDryRun() {
@@ -618,7 +633,7 @@ func writeToFile(outputDir string, name string, data string, appendData bool) er
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
@@ -639,7 +654,7 @@ func createOrOpenFile(filename string, appendData bool) (*os.File, error) {
func ensureDirectoryForFile(file string) error {
baseDir := path.Dir(file)
_, err := os.Stat(baseDir)
- if err != nil && !os.IsNotExist(err) {
+ if err != nil && !errors.Is(err, fs.ErrNotExist) {
return err
}
@@ -661,7 +676,7 @@ func (i *Install) NameAndChart(args []string) (string, string, error) {
}
if len(args) > 2 {
- return args[0], args[1], errors.Errorf("expected at most two arguments, unexpected arguments: %v", strings.Join(args[2:], ", "))
+ return args[0], args[1], fmt.Errorf("expected at most two arguments, unexpected arguments: %v", strings.Join(args[2:], ", "))
}
if len(args) == 2 {
@@ -726,7 +741,7 @@ OUTER:
}
if len(missing) > 0 {
- return errors.Errorf("found in Chart.yaml, but missing in charts/ directory: %s", strings.Join(missing, ", "))
+ return fmt.Errorf("found in Chart.yaml, but missing in charts/ directory: %s", strings.Join(missing, ", "))
}
return nil
}
@@ -762,7 +777,7 @@ func (c *ChartPathOptions) LocateChart(name string, settings *cli.EnvSettings) (
return abs, nil
}
if filepath.IsAbs(name) || strings.HasPrefix(name, ".") {
- return name, errors.Errorf("path %q not found", name)
+ return name, fmt.Errorf("path %q not found", name)
}
dl := downloader.ChartDownloader{
diff --git a/pkg/action/install_test.go b/pkg/action/install_test.go
index 9d90accc5..6c2c91d0a 100644
--- a/pkg/action/install_test.go
+++ b/pkg/action/install_test.go
@@ -19,8 +19,11 @@ package action
import (
"bytes"
"context"
+ "errors"
"fmt"
"io"
+ "io/fs"
+ "net/http"
"os"
"path/filepath"
"regexp"
@@ -31,12 +34,21 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
+ 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"
- "helm.sh/helm/v4/pkg/chart"
- chartutil "helm.sh/helm/v4/pkg/chart/util"
+ 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/release"
+ release "helm.sh/helm/v4/pkg/release/v1"
"helm.sh/helm/v4/pkg/storage/driver"
helmtime "helm.sh/helm/v4/pkg/time"
)
@@ -47,7 +59,64 @@ 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 {
+ t.Helper()
config := actionConfigFixture(t)
instAction := NewInstall(config)
instAction.Namespace = "spaced"
@@ -62,7 +131,7 @@ func TestInstallRelease(t *testing.T) {
instAction := installAction(t)
vals := map[string]interface{}{}
- ctx, done := context.WithCancel(context.Background())
+ ctx, done := context.WithCancel(t.Context())
res, err := instAction.RunWithContext(ctx, buildChart(), vals)
if err != nil {
t.Fatalf("Failed install: %s", err)
@@ -92,6 +161,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)
@@ -323,7 +447,9 @@ func TestInstallReleaseIncorrectTemplate_DryRun(t *testing.T) {
instAction.DryRun = true
vals := map[string]interface{}{}
_, err := instAction.Run(buildChart(withSampleIncludingIncorrectTemplates()), vals)
- expectedErr := "\"hello/templates/incorrect\" at <.Values.bad.doh>: nil pointer evaluating interface {}.doh"
+ expectedErr := `hello/templates/incorrect:1:10
+ executing "hello/templates/incorrect" at <.Values.bad.doh>:
+ nil pointer evaluating interface {}.doh`
if err == nil {
t.Fatalf("Install should fail containing error: %s", expectedErr)
}
@@ -411,7 +537,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()
@@ -430,10 +556,10 @@ 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())
+ ctx, cancel := context.WithCancel(t.Context())
time.AfterFunc(time.Second, cancel)
goroutines := runtime.NumGoroutine()
@@ -453,7 +579,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{}{}
@@ -517,9 +643,11 @@ func TestInstallRelease_Atomic_Interrupted(t *testing.T) {
instAction.Atomic = true
vals := map[string]interface{}{}
- ctx, cancel := context.WithCancel(context.Background())
+ ctx, cancel := context.WithCancel(t.Context())
time.AfterFunc(time.Second, cancel)
+ goroutines := runtime.NumGoroutine()
+
res, err := instAction.RunWithContext(ctx, buildChart(), vals)
is.Error(err)
is.Contains(err.Error(), "context canceled")
@@ -530,6 +658,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) {
@@ -630,7 +761,7 @@ func TestInstallReleaseOutputDir(t *testing.T) {
test.AssertGoldenFile(t, filepath.Join(dir, "hello/templates/rbac"), "rbac.txt")
_, err = os.Stat(filepath.Join(dir, "hello/templates/empty"))
- is.True(os.IsNotExist(err))
+ is.True(errors.Is(err, fs.ErrNotExist))
}
func TestInstallOutputDirWithReleaseName(t *testing.T) {
@@ -666,7 +797,7 @@ func TestInstallOutputDirWithReleaseName(t *testing.T) {
test.AssertGoldenFile(t, filepath.Join(newDir, "hello/templates/rbac"), "rbac.txt")
_, err = os.Stat(filepath.Join(newDir, "hello/templates/empty"))
- is.True(os.IsNotExist(err))
+ is.True(errors.Is(err, fs.ErrNotExist))
}
func TestNameAndChart(t *testing.T) {
diff --git a/pkg/action/lint.go b/pkg/action/lint.go
index a6fd7c71c..7b3c00ad2 100644
--- a/pkg/action/lint.go
+++ b/pkg/action/lint.go
@@ -17,13 +17,12 @@ limitations under the License.
package action
import (
+ "fmt"
"os"
"path/filepath"
"strings"
- "github.com/pkg/errors"
-
- chartutil "helm.sh/helm/v4/pkg/chart/util"
+ chartutil "helm.sh/helm/v4/pkg/chart/v2/util"
"helm.sh/helm/v4/pkg/lint"
"helm.sh/helm/v4/pkg/lint/support"
)
@@ -94,26 +93,26 @@ func lintChart(path string, vals map[string]interface{}, namespace string, kubeV
if strings.HasSuffix(path, ".tgz") || strings.HasSuffix(path, ".tar.gz") {
tempDir, err := os.MkdirTemp("", "helm-lint")
if err != nil {
- return linter, errors.Wrap(err, "unable to create temp dir to extract tarball")
+ return linter, fmt.Errorf("unable to create temp dir to extract tarball: %w", err)
}
defer os.RemoveAll(tempDir)
file, err := os.Open(path)
if err != nil {
- return linter, errors.Wrap(err, "unable to open tarball")
+ return linter, fmt.Errorf("unable to open tarball: %w", err)
}
defer file.Close()
if err = chartutil.Expand(tempDir, file); err != nil {
- return linter, errors.Wrap(err, "unable to extract tarball")
+ return linter, fmt.Errorf("unable to extract tarball: %w", err)
}
files, err := os.ReadDir(tempDir)
if err != nil {
- return linter, errors.Wrapf(err, "unable to read temporary output directory %s", tempDir)
+ return linter, fmt.Errorf("unable to read temporary output directory %s: %w", tempDir, err)
}
if !files[0].IsDir() {
- return linter, errors.Errorf("unexpected file %s in temporary output directory %s", files[0].Name(), tempDir)
+ return linter, fmt.Errorf("unexpected file %s in temporary output directory %s", files[0].Name(), tempDir)
}
chartPath = filepath.Join(tempDir, files[0].Name())
@@ -123,7 +122,7 @@ func lintChart(path string, vals map[string]interface{}, namespace string, kubeV
// Guard: Error out if this is not a chart.
if _, err := os.Stat(filepath.Join(chartPath, "Chart.yaml")); err != nil {
- return linter, errors.Wrap(err, "unable to check Chart.yaml file in chart")
+ return linter, fmt.Errorf("unable to check Chart.yaml file in chart: %w", err)
}
return lint.RunAll(
diff --git a/pkg/action/list.go b/pkg/action/list.go
index f90c31acd..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/v4/pkg/release"
- "helm.sh/helm/v4/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 a7eb8a920..b6f89fa1e 100644
--- a/pkg/action/list_test.go
+++ b/pkg/action/list_test.go
@@ -21,7 +21,7 @@ import (
"github.com/stretchr/testify/assert"
- "helm.sh/helm/v4/pkg/release"
+ release "helm.sh/helm/v4/pkg/release/v1"
"helm.sh/helm/v4/pkg/storage"
)
@@ -64,13 +64,14 @@ func TestList_Empty(t *testing.T) {
}
func newListFixture(t *testing.T) *List {
+ t.Helper()
return NewList(actionConfigFixture(t))
}
func TestList_OneNamespace(t *testing.T) {
is := assert.New(t)
lister := newListFixture(t)
- makeMeSomeReleases(lister.cfg.Releases, t)
+ makeMeSomeReleases(t, lister.cfg.Releases)
list, err := lister.Run()
is.NoError(err)
is.Len(list, 3)
@@ -79,7 +80,7 @@ func TestList_OneNamespace(t *testing.T) {
func TestList_AllNamespaces(t *testing.T) {
is := assert.New(t)
lister := newListFixture(t)
- makeMeSomeReleases(lister.cfg.Releases, t)
+ makeMeSomeReleases(t, lister.cfg.Releases)
lister.AllNamespaces = true
lister.SetStateMask()
list, err := lister.Run()
@@ -91,7 +92,7 @@ func TestList_Sort(t *testing.T) {
is := assert.New(t)
lister := newListFixture(t)
lister.Sort = ByNameDesc // Other sorts are tested elsewhere
- makeMeSomeReleases(lister.cfg.Releases, t)
+ makeMeSomeReleases(t, lister.cfg.Releases)
list, err := lister.Run()
is.NoError(err)
is.Len(list, 3)
@@ -104,7 +105,7 @@ func TestList_Limit(t *testing.T) {
is := assert.New(t)
lister := newListFixture(t)
lister.Limit = 2
- makeMeSomeReleases(lister.cfg.Releases, t)
+ makeMeSomeReleases(t, lister.cfg.Releases)
list, err := lister.Run()
is.NoError(err)
is.Len(list, 2)
@@ -117,7 +118,7 @@ func TestList_BigLimit(t *testing.T) {
is := assert.New(t)
lister := newListFixture(t)
lister.Limit = 20
- makeMeSomeReleases(lister.cfg.Releases, t)
+ makeMeSomeReleases(t, lister.cfg.Releases)
list, err := lister.Run()
is.NoError(err)
is.Len(list, 3)
@@ -133,7 +134,7 @@ func TestList_LimitOffset(t *testing.T) {
lister := newListFixture(t)
lister.Limit = 2
lister.Offset = 1
- makeMeSomeReleases(lister.cfg.Releases, t)
+ makeMeSomeReleases(t, lister.cfg.Releases)
list, err := lister.Run()
is.NoError(err)
is.Len(list, 2)
@@ -148,7 +149,7 @@ func TestList_LimitOffsetOutOfBounds(t *testing.T) {
lister := newListFixture(t)
lister.Limit = 2
lister.Offset = 3 // Last item is index 2
- makeMeSomeReleases(lister.cfg.Releases, t)
+ makeMeSomeReleases(t, lister.cfg.Releases)
list, err := lister.Run()
is.NoError(err)
is.Len(list, 0)
@@ -163,7 +164,7 @@ func TestList_LimitOffsetOutOfBounds(t *testing.T) {
func TestList_StateMask(t *testing.T) {
is := assert.New(t)
lister := newListFixture(t)
- makeMeSomeReleases(lister.cfg.Releases, t)
+ makeMeSomeReleases(t, lister.cfg.Releases)
one, err := lister.cfg.Releases.Get("one", 1)
is.NoError(err)
one.SetStatus(release.StatusUninstalled, "uninstalled")
@@ -193,7 +194,7 @@ func TestList_StateMaskWithStaleRevisions(t *testing.T) {
lister := newListFixture(t)
lister.StateMask = ListFailed
- makeMeSomeReleasesWithStaleFailure(lister.cfg.Releases, t)
+ makeMeSomeReleasesWithStaleFailure(t, lister.cfg.Releases)
res, err := lister.Run()
@@ -205,7 +206,7 @@ func TestList_StateMaskWithStaleRevisions(t *testing.T) {
is.Equal("failed", res[0].Name)
}
-func makeMeSomeReleasesWithStaleFailure(store *storage.Storage, t *testing.T) {
+func makeMeSomeReleasesWithStaleFailure(t *testing.T, store *storage.Storage) {
t.Helper()
one := namedReleaseStub("clean", release.StatusDeployed)
one.Namespace = "default"
@@ -242,7 +243,7 @@ func TestList_Filter(t *testing.T) {
is := assert.New(t)
lister := newListFixture(t)
lister.Filter = "th."
- makeMeSomeReleases(lister.cfg.Releases, t)
+ makeMeSomeReleases(t, lister.cfg.Releases)
res, err := lister.Run()
is.NoError(err)
@@ -254,13 +255,13 @@ func TestList_FilterFailsCompile(t *testing.T) {
is := assert.New(t)
lister := newListFixture(t)
lister.Filter = "t[h.{{{"
- makeMeSomeReleases(lister.cfg.Releases, t)
+ makeMeSomeReleases(t, lister.cfg.Releases)
_, err := lister.Run()
is.Error(err)
}
-func makeMeSomeReleases(store *storage.Storage, t *testing.T) {
+func makeMeSomeReleases(t *testing.T, store *storage.Storage) {
t.Helper()
one := releaseStub()
one.Name = "one"
diff --git a/pkg/action/package.go b/pkg/action/package.go
index 8343ba109..e57ce4921 100644
--- a/pkg/action/package.go
+++ b/pkg/action/package.go
@@ -18,16 +18,16 @@ package action
import (
"bufio"
+ "errors"
"fmt"
"os"
"syscall"
"github.com/Masterminds/semver/v3"
- "github.com/pkg/errors"
"golang.org/x/term"
- "helm.sh/helm/v4/pkg/chart/loader"
- chartutil "helm.sh/helm/v4/pkg/chart/util"
+ "helm.sh/helm/v4/pkg/chart/v2/loader"
+ chartutil "helm.sh/helm/v4/pkg/chart/v2/util"
"helm.sh/helm/v4/pkg/provenance"
)
@@ -39,6 +39,7 @@ type Package struct {
Key string
Keyring string
PassphraseFile string
+ cachedPassphrase []byte
Version string
AppVersion string
Destination string
@@ -55,6 +56,10 @@ type Package struct {
InsecureSkipTLSverify bool
}
+const (
+ passPhraseFileStdin = "-"
+)
+
// NewPackage creates a new Package object with the given configuration.
func NewPackage() *Package {
return &Package{}
@@ -100,7 +105,7 @@ func (p *Package) Run(path string, _ map[string]interface{}) (string, error) {
name, err := chartutil.Save(ch, dest)
if err != nil {
- return "", errors.Wrap(err, "failed to save")
+ return "", fmt.Errorf("failed to save: %w", err)
}
if p.Sign {
@@ -128,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
}
@@ -156,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 26eeb1a2b..12bea10dd 100644
--- a/pkg/action/package_test.go
+++ b/pkg/action/package_test.go
@@ -29,8 +29,9 @@ import (
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 fa85fe242..a2f53af0d 100644
--- a/pkg/action/pull.go
+++ b/pkg/action/pull.go
@@ -22,9 +22,7 @@ import (
"path/filepath"
"strings"
- "github.com/pkg/errors"
-
- chartutil "helm.sh/helm/v4/pkg/chart/util"
+ 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"
@@ -111,7 +109,7 @@ func (p *Pull) Run(chartRef string) (string, error) {
var err error
dest, err = os.MkdirTemp("", "helm-")
if err != nil {
- return out.String(), errors.Wrap(err, "failed to untar")
+ return out.String(), fmt.Errorf("failed to untar: %w", err)
}
defer os.RemoveAll(dest)
}
@@ -163,11 +161,10 @@ func (p *Pull) Run(chartRef string) (string, error) {
if _, err := os.Stat(udCheck); err != nil {
if err := os.MkdirAll(udCheck, 0755); err != nil {
- return out.String(), errors.Wrap(err, "failed to untar (mkdir)")
+ return out.String(), fmt.Errorf("failed to untar (mkdir): %w", err)
}
-
} else {
- return out.String(), errors.Errorf("failed to untar: a file or directory with the name %s already exists", udCheck)
+ return out.String(), fmt.Errorf("failed to untar: a file or directory with the name %s already exists", udCheck)
}
return out.String(), chartutil.ExpandFile(ud, saved)
diff --git a/pkg/action/release_testing.go b/pkg/action/release_testing.go
index a2c68ad64..b5f6fe712 100644
--- a/pkg/action/release_testing.go
+++ b/pkg/action/release_testing.go
@@ -24,11 +24,11 @@ import (
"sort"
"time"
- "github.com/pkg/errors"
v1 "k8s.io/api/core/v1"
- chartutil "helm.sh/helm/v4/pkg/chart/util"
- "helm.sh/helm/v4/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 (
@@ -63,7 +63,7 @@ func (r *ReleaseTesting) Run(name string) (*release.Release, error) {
}
if err := chartutil.ValidateReleaseName(name); err != nil {
- return nil, errors.Errorf("releaseTest: Release name is invalid: %s", name)
+ return nil, fmt.Errorf("releaseTest: Release name is invalid: %s", name)
}
// finds the non-deleted release with the given name
@@ -96,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
@@ -112,7 +112,7 @@ func (r *ReleaseTesting) Run(name string) (*release.Release, error) {
func (r *ReleaseTesting) GetPodLogs(out io.Writer, rel *release.Release) error {
client, err := r.cfg.KubernetesClientSet()
if err != nil {
- return errors.Wrap(err, "unable to get kubernetes client to fetch pod logs")
+ return fmt.Errorf("unable to get kubernetes client to fetch pod logs: %w", err)
}
hooksByWight := append([]*release.Hook{}, rel.Hooks...)
@@ -129,14 +129,14 @@ func (r *ReleaseTesting) GetPodLogs(out io.Writer, rel *release.Release) error {
req := client.CoreV1().Pods(r.Namespace).GetLogs(h.Name, &v1.PodLogOptions{})
logReader, err := req.Stream(context.Background())
if err != nil {
- return errors.Wrapf(err, "unable to get pod logs for %s", h.Name)
+ return fmt.Errorf("unable to get pod logs for %s: %w", h.Name, err)
}
fmt.Fprintf(out, "POD LOGS: %s\n", h.Name)
_, err = io.Copy(out, logReader)
fmt.Fprintln(out)
if err != nil {
- return errors.Wrapf(err, "unable to write pod logs for %s", h.Name)
+ return fmt.Errorf("unable to write pod logs for %s: %w", h.Name, err)
}
}
}
diff --git a/pkg/action/resource_policy.go b/pkg/action/resource_policy.go
index f18acb880..b72e94124 100644
--- a/pkg/action/resource_policy.go
+++ b/pkg/action/resource_policy.go
@@ -20,7 +20,7 @@ import (
"strings"
"helm.sh/helm/v4/pkg/kube"
- "helm.sh/helm/v4/pkg/releaseutil"
+ 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 961ef8377..1dc0c7f84 100644
--- a/pkg/action/rollback.go
+++ b/pkg/action/rollback.go
@@ -19,13 +19,13 @@ package action
import (
"bytes"
"fmt"
+ "log/slog"
"strings"
"time"
- "github.com/pkg/errors"
-
- chartutil "helm.sh/helm/v4/pkg/chart/util"
- "helm.sh/helm/v4/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"
helmtime "helm.sh/helm/v4/pkg/time"
)
@@ -37,11 +37,10 @@ type Rollback struct {
Version int
Timeout time.Duration
- Wait bool
+ WaitStrategy kube.WaitStrategy
WaitForJobs bool
DisableHooks bool
DryRun bool
- Recreate bool // will (if true) recreate pods after a rollback.
Force bool // will (if true) force resource upgrade through uninstall/recreate if needed
CleanupOnFail bool
MaxHistory int // MaxHistory limits the maximum number of revisions saved per release
@@ -62,26 +61,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
}
@@ -93,7 +92,7 @@ func (r *Rollback) Run(name string) error {
// the previous release's configuration
func (r *Rollback) prepareRollback(name string) (*release.Release, *release.Release, error) {
if err := chartutil.ValidateReleaseName(name); err != nil {
- return nil, nil, errors.Errorf("prepareRollback: Release name is invalid: %s", name)
+ return nil, nil, fmt.Errorf("prepareRollback: Release name is invalid: %s", name)
}
if r.Version < 0 {
@@ -125,10 +124,10 @@ func (r *Rollback) prepareRollback(name string) (*release.Release, *release.Rele
}
}
if !previousVersionExist {
- return nil, nil, errors.Errorf("release has no %d version", previousVersion)
+ 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 {
@@ -161,89 +160,79 @@ 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
}
current, err := r.cfg.KubeClient.Build(bytes.NewBufferString(currentRelease.Manifest), false)
if err != nil {
- return targetRelease, errors.Wrap(err, "unable to build kubernetes objects from current release manifest")
+ return targetRelease, fmt.Errorf("unable to build kubernetes objects from current release manifest: %w", err)
}
target, err := r.cfg.KubeClient.Build(bytes.NewBufferString(targetRelease.Manifest), false)
if err != nil {
- return targetRelease, errors.Wrap(err, "unable to build kubernetes objects from new release manifest")
+ return targetRelease, fmt.Errorf("unable to build kubernetes objects from new release manifest: %w", err)
}
// 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.
err = target.Visit(setMetadataVisitor(targetRelease.Name, targetRelease.Namespace, true))
if err != nil {
- return targetRelease, errors.Wrap(err, "unable to set metadata visitor from target release")
+ return targetRelease, fmt.Errorf("unable to set metadata visitor from target release: %w", err)
}
results, err := r.cfg.KubeClient.Update(current, target, r.Force)
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 {
- var errorList []string
- for _, e := range errs {
- errorList = append(errorList, e.Error())
- }
- return targetRelease, errors.Wrapf(fmt.Errorf("unable to cleanup resources: %s", strings.Join(errorList, ", ")), "an error occurred while cleaning up resources. original rollback error: %s", err)
+ 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
}
- if r.Recreate {
- // NOTE: Because this is not critical for a release to succeed, we just
- // log if an error occurs and continue onward. If we ever introduce log
- // 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())
+ 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)
}
- }
-
- 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, errors.Wrapf(err, "release %s failed", targetRelease.Name)
- }
- } 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, errors.Wrapf(err, "release %s failed", targetRelease.Name)
- }
+ } 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
}
}
@@ -254,7 +243,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 3b2722eef..a3fbcfa9e 100644
--- a/pkg/action/show.go
+++ b/pkg/action/show.go
@@ -21,13 +21,12 @@ import (
"fmt"
"strings"
- "github.com/pkg/errors"
"k8s.io/cli-runtime/pkg/printers"
"sigs.k8s.io/yaml"
- "helm.sh/helm/v4/pkg/chart"
- "helm.sh/helm/v4/pkg/chart/loader"
- chartutil "helm.sh/helm/v4/pkg/chart/util"
+ 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"
)
@@ -69,14 +68,14 @@ 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.
@@ -105,7 +104,7 @@ func (s *Show) Run(chartpath string) (string, error) {
if s.JSONPathTemplate != "" {
printer, err := printers.NewJSONPathPrinter(s.JSONPathTemplate)
if err != nil {
- return "", errors.Wrapf(err, "error parsing jsonpath %s", s.JSONPathTemplate)
+ return "", fmt.Errorf("error parsing jsonpath %s: %w", s.JSONPathTemplate, err)
}
printer.Execute(&out, s.chart.Values)
} else {
diff --git a/pkg/action/show_test.go b/pkg/action/show_test.go
index e8c998198..b1c5d6164 100644
--- a/pkg/action/show_test.go
+++ b/pkg/action/show_test.go
@@ -19,7 +19,7 @@ package action
import (
"testing"
- "helm.sh/helm/v4/pkg/chart"
+ chart "helm.sh/helm/v4/pkg/chart/v2"
)
func TestShow(t *testing.T) {
diff --git a/pkg/action/status.go b/pkg/action/status.go
index db9a3b3f0..509c52cd9 100644
--- a/pkg/action/status.go
+++ b/pkg/action/status.go
@@ -21,7 +21,7 @@ import (
"errors"
"helm.sh/helm/v4/pkg/kube"
- "helm.sh/helm/v4/pkg/release"
+ release "helm.sh/helm/v4/pkg/release/v1"
)
// Status is the action for checking the deployment status of releases.
diff --git a/pkg/action/uninstall.go b/pkg/action/uninstall.go
index b786c37f7..61e10b2c8 100644
--- a/pkg/action/uninstall.go
+++ b/pkg/action/uninstall.go
@@ -17,17 +17,17 @@ limitations under the License.
package action
import (
+ "fmt"
+ "log/slog"
"strings"
"time"
- "github.com/pkg/errors"
-
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- chartutil "helm.sh/helm/v4/pkg/chart/util"
+ chartutil "helm.sh/helm/v4/pkg/chart/v2/util"
"helm.sh/helm/v4/pkg/kube"
- "helm.sh/helm/v4/pkg/release"
- "helm.sh/helm/v4/pkg/releaseutil"
+ releaseutil "helm.sh/helm/v4/pkg/release/util"
+ release "helm.sh/helm/v4/pkg/release/v1"
helmtime "helm.sh/helm/v4/pkg/time"
)
@@ -41,7 +41,7 @@ type Uninstall struct {
DryRun bool
IgnoreNotFound bool
KeepHistory bool
- Wait bool
+ WaitStrategy kube.WaitStrategy
DeletionPropagation string
Timeout time.Duration
Description string
@@ -60,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)
@@ -70,7 +75,7 @@ func (u *Uninstall) Run(name string) (*release.UninstallReleaseResponse, error)
}
if err := chartutil.ValidateReleaseName(name); err != nil {
- return nil, errors.Errorf("uninstall: Release name is invalid: %s", name)
+ return nil, fmt.Errorf("uninstall: Release name is invalid: %s", name)
}
rels, err := u.cfg.Releases.History(name)
@@ -78,7 +83,7 @@ func (u *Uninstall) Run(name string) (*release.UninstallReleaseResponse, error)
if u.IgnoreNotFound {
return nil, nil
}
- return nil, errors.Wrapf(err, "uninstall: Release not loaded: %s", name)
+ return nil, fmt.Errorf("uninstall: Release not loaded: %s: %w", name, err)
}
if len(rels) < 1 {
return nil, errMissingRelease
@@ -92,37 +97,37 @@ func (u *Uninstall) Run(name string) (*release.UninstallReleaseResponse, error)
if rel.Info.Status == release.StatusUninstalled {
if !u.KeepHistory {
if err := u.purgeReleases(rels...); err != nil {
- return nil, errors.Wrap(err, "uninstall: Failed to purge the release")
+ return nil, fmt.Errorf("uninstall: Failed to purge the release: %w", err)
}
return &release.UninstallReleaseResponse{Release: rel}, nil
}
- return nil, errors.Errorf("the release named %q is already deleted", name)
+ 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)
- return nil, errors.Errorf("failed to delete release: %s", name)
+ slog.Debug("uninstall: Failed to delete release", slog.Any("error", errs))
+ return nil, fmt.Errorf("failed to delete release: %s", name)
}
if kept != "" {
@@ -130,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)
}
}
@@ -152,26 +153,26 @@ 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, errors.Wrap(err, "uninstall: Failed to purge the release"))
+ errs = append(errs, fmt.Errorf("uninstall: Failed to purge the release: %w", err))
}
// Return the errors that occurred while deleting the release, if any
if len(errs) > 0 {
- return res, errors.Errorf("uninstallation completed with %d error(s): %s", len(errs), joinErrors(errs))
+ return res, fmt.Errorf("uninstallation completed with %d error(s): %w", len(errs), joinErrors(errs, "; "))
}
return res, nil
}
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 {
- return res, errors.Errorf("uninstallation completed with %d error(s): %s", len(errs), joinErrors(errs))
+ return res, fmt.Errorf("uninstallation completed with %d error(s): %w", len(errs), joinErrors(errs, "; "))
}
return res, nil
}
@@ -185,12 +186,28 @@ func (u *Uninstall) purgeReleases(rels ...*release.Release) error {
return nil
}
-func joinErrors(errs []error) string {
- es := make([]string, 0, len(errs))
- for _, e := range errs {
- es = append(es, e.Error())
+type joinedErrors struct {
+ errs []error
+ sep string
+}
+
+func joinErrors(errs []error, sep string) error {
+ return &joinedErrors{
+ errs: errs,
+ sep: sep,
}
- return strings.Join(es, "; ")
+}
+
+func (e *joinedErrors) Error() string {
+ errs := make([]string, 0, len(e.errs))
+ for _, err := range e.errs {
+ errs = append(errs, err.Error())
+ }
+ return strings.Join(errs, e.sep)
+}
+
+func (e *joinedErrors) Unwrap() []error {
+ return e.errs
}
// deleteRelease deletes the release and returns list of delete resources and manifests that were kept in the deletion process
@@ -204,7 +221,7 @@ func (u *Uninstall) deleteRelease(rel *release.Release) (kube.ResourceList, stri
// FIXME: One way to delete at this point would be to try a label-based
// deletion. The problem with this is that we could get a false positive
// and delete something that was not legitimately part of this release.
- return nil, rel.Manifest, []error{errors.Wrap(err, "corrupted release record. You must manually delete the resources")}
+ return nil, rel.Manifest, []error{fmt.Errorf("corrupted release record. You must manually delete the resources: %w", err)}
}
filesToKeep, filesToDelete := filterManifestsToKeep(files)
@@ -220,11 +237,11 @@ func (u *Uninstall) deleteRelease(rel *release.Release) (kube.ResourceList, stri
resources, err := u.cfg.KubeClient.Build(strings.NewReader(builder.String()), false)
if err != nil {
- return nil, "", []error{errors.Wrap(err, "unable to build kubernetes objects for delete")}
+ return nil, "", []error{fmt.Errorf("unable to build kubernetes objects for delete: %w", err)}
}
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)
@@ -232,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
@@ -241,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 eca9e6ad8..8b148522c 100644
--- a/pkg/action/uninstall_test.go
+++ b/pkg/action/uninstall_test.go
@@ -22,11 +22,13 @@ import (
"github.com/stretchr/testify/assert"
+ "helm.sh/helm/v4/pkg/kube"
kubefake "helm.sh/helm/v4/pkg/kube/fake"
- "helm.sh/helm/v4/pkg/release"
+ release "helm.sh/helm/v4/pkg/release/v1"
)
func uninstallAction(t *testing.T) *Uninstall {
+ t.Helper()
config := actionConfigFixture(t)
unAction := NewUninstall(config)
return unAction
@@ -82,7 +84,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 +101,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 +115,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 c5397c984..271bc8aa9 100644
--- a/pkg/action/upgrade.go
+++ b/pkg/action/upgrade.go
@@ -19,22 +19,22 @@ package action
import (
"bytes"
"context"
+ "errors"
"fmt"
+ "log/slog"
"strings"
"sync"
"time"
- "github.com/pkg/errors"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/cli-runtime/pkg/resource"
- "helm.sh/helm/v4/pkg/chart"
- chartutil "helm.sh/helm/v4/pkg/chart/util"
+ 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"
- "helm.sh/helm/v4/pkg/release"
- "helm.sh/helm/v4/pkg/releaseutil"
+ releaseutil "helm.sh/helm/v4/pkg/release/util"
+ release "helm.sh/helm/v4/pkg/release/v1"
"helm.sh/helm/v4/pkg/storage/driver"
)
@@ -64,8 +64,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.
@@ -87,8 +87,6 @@ type Upgrade struct {
ReuseValues bool
// ResetThenReuseValues will reset the values to the chart's built-ins then merge with user's last supplied values.
ResetThenReuseValues bool
- // Recreate will (if true) recreate pods after a rollback.
- Recreate bool
// MaxHistory limits the maximum number of revisions saved per release
MaxHistory int
// Atomic, if true, will roll back on failure.
@@ -131,14 +129,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 +153,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, errors.Errorf("release name is invalid: %s", name)
+ 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 +169,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 +177,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
}
@@ -202,7 +202,7 @@ func (u *Upgrade) prepareUpgrade(name string, chart *chart.Chart, vals map[strin
// HideSecret must be used with dry run. Otherwise, return an error.
if !u.isDryRun() && u.HideSecret {
- return nil, nil, errors.New("Hiding Kubernetes secrets requires a dry-run mode")
+ return nil, nil, errors.New("hiding Kubernetes secrets requires a dry-run mode")
}
// finds the last non-deleted release with the given name
@@ -313,15 +313,15 @@ func (u *Upgrade) performUpgrade(ctx context.Context, originalRelease, upgradedR
// Checking for removed Kubernetes API error so can provide a more informative error message to the user
// Ref: https://github.com/helm/helm/issues/7219
if strings.Contains(err.Error(), "unable to recognize \"\": no matches for kind") {
- return upgradedRelease, errors.Wrap(err, "current release manifest contains removed kubernetes api(s) for this "+
+ return upgradedRelease, fmt.Errorf("current release manifest contains removed kubernetes api(s) for this "+
"kubernetes version and it is therefore unable to build the kubernetes "+
- "objects for performing the diff. error from kubernetes")
+ "objects for performing the diff. error from kubernetes: %w", err)
}
- return upgradedRelease, errors.Wrap(err, "unable to build kubernetes objects from current release manifest")
+ return upgradedRelease, fmt.Errorf("unable to build kubernetes objects from current release manifest: %w", err)
}
target, err := u.cfg.KubeClient.Build(bytes.NewBufferString(upgradedRelease.Manifest), !u.DisableOpenAPIValidation)
if err != nil {
- return upgradedRelease, errors.Wrap(err, "unable to build kubernetes objects from new release manifest")
+ return upgradedRelease, fmt.Errorf("unable to build kubernetes objects from new release manifest: %w", err)
}
// It is safe to use force only on target because these are resources currently rendered by the chart.
@@ -350,7 +350,7 @@ func (u *Upgrade) performUpgrade(ctx context.Context, originalRelease, upgradedR
toBeUpdated, err = existingResourceConflict(toBeCreated, upgradedRelease.Name, upgradedRelease.Namespace)
}
if err != nil {
- return nil, errors.Wrap(err, "Unable to continue with update")
+ return nil, fmt.Errorf("unable to continue with update: %w", err)
}
toBeUpdated.Visit(func(r *resource.Info, err error) error {
@@ -363,7 +363,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 +372,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 +418,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)
@@ -433,38 +433,29 @@ func (u *Upgrade) releasingUpgrade(c chan<- resultMessage, upgradedRelease *rele
return
}
- if u.Recreate {
- // NOTE: Because this is not critical for a release to succeed, we just
- // log if an error occurs and continue onward. If we ever introduce log
- // 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())
- }
+ 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.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
- }
+ 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,32 +475,35 @@ 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 {
- var errorList []string
- for _, e := range errs {
- errorList = append(errorList, e.Error())
- }
- return rel, errors.Wrapf(fmt.Errorf("unable to cleanup resources: %s", strings.Join(errorList, ", ")), "an error occurred while cleaning up resources. original upgrade error: %s", err)
+ return rel, fmt.Errorf(
+ "an error occurred while cleaning up resources. original upgrade error: %w: %w",
+ err,
+ fmt.Errorf(
+ "unable to cleanup resources: %w",
+ joinErrors(errs, ", "),
+ ),
+ )
}
- 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
hist := NewHistory(u.cfg)
fullHistory, herr := hist.Run(rel.Name)
if herr != nil {
- return rel, errors.Wrapf(herr, "an error occurred while finding last successful release. original upgrade error: %s", err)
+ return rel, fmt.Errorf("an error occurred while finding last successful release. original upgrade error: %w: %w", err, herr)
}
// There isn't a way to tell if a previous release was successful, but
@@ -519,23 +513,24 @@ func (u *Upgrade) failRelease(rel *release.Release, created kube.ResourceList, e
return r.Info.Status == release.StatusSuperseded || r.Info.Status == release.StatusDeployed
}).Filter(fullHistory)
if len(filteredHistory) == 0 {
- return rel, errors.Wrap(err, "unable to find a previously successful release when attempting to rollback. original upgrade error")
+ return rel, fmt.Errorf("unable to find a previously successful release when attempting to rollback. original upgrade error: %w", err)
}
releaseutil.Reverse(filteredHistory, releaseutil.SortByRevision)
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
rollin.Force = u.Force
rollin.Timeout = u.Timeout
if rollErr := rollin.Run(rel.Name); rollErr != nil {
- return rel, errors.Wrapf(rollErr, "an error occurred while rolling back the release. original upgrade error: %s", err)
+ return rel, fmt.Errorf("an error occurred while rolling back the release. original upgrade error: %w: %w", err, rollErr)
}
- return rel, errors.Wrapf(err, "release %s failed, and has been rolled back due to atomic being set", rel.Name)
+ return rel, fmt.Errorf("release %s failed, and has been rolled back due to atomic being set: %w", rel.Name, err)
}
return rel, err
@@ -552,18 +547,18 @@ 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)
if err != nil {
- return nil, errors.Wrap(err, "failed to rebuild old values")
+ return nil, fmt.Errorf("failed to rebuild old values: %w", err)
}
newVals = chartutil.CoalesceTables(newVals, current.Config)
@@ -575,7 +570,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)
@@ -583,7 +578,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
@@ -594,42 +589,6 @@ func validateManifest(c kube.Interface, manifest []byte, openAPIValidation bool)
return err
}
-// recreate captures all the logic for recreating pods for both upgrade and
-// rollback. If we end up refactoring rollback to use upgrade, this can just be
-// made an unexported method on the upgrade action.
-func recreate(cfg *Configuration, resources kube.ResourceList) error {
- for _, res := range resources {
- versioned := kube.AsVersioned(res)
- selector, err := kube.SelectorsForObject(versioned)
- if err != nil {
- // If no selector is returned, it means this object is
- // definitely not a pod, so continue onward
- continue
- }
-
- client, err := cfg.KubernetesClientSet()
- if err != nil {
- return errors.Wrapf(err, "unable to recreate pods for object %s/%s because an error occurred", res.Namespace, res.Name)
- }
-
- pods, err := client.CoreV1().Pods(res.Namespace).List(context.Background(), metav1.ListOptions{
- LabelSelector: selector.String(),
- })
- if err != nil {
- return errors.Wrapf(err, "unable to recreate pods for object %s/%s because an error occurred", res.Namespace, res.Name)
- }
-
- // Restart pods
- for _, pod := range pods.Items {
- // Delete each pod for get them restarted with changed spec.
- if err := client.CoreV1().Pods(pod.Namespace).Delete(context.Background(), pod.Name, *metav1.NewPreconditionDeleteOptions(string(pod.UID))); err != nil {
- return errors.Wrapf(err, "unable to recreate pods for object %s/%s because an error occurred", res.Namespace, res.Name)
- }
- }
- }
- return nil
-}
-
func objectKey(r *resource.Info) string {
gvk := r.Object.GetObjectKind().GroupVersionKind()
return fmt.Sprintf("%s/%s/%s/%s", gvk.GroupVersion().String(), gvk.Kind, r.Namespace, r.Name)
diff --git a/pkg/action/upgrade_test.go b/pkg/action/upgrade_test.go
index 5437490cb..e20955560 100644
--- a/pkg/action/upgrade_test.go
+++ b/pkg/action/upgrade_test.go
@@ -23,18 +23,20 @@ import (
"testing"
"time"
- "helm.sh/helm/v4/pkg/chart"
+ 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/v4/pkg/kube/fake"
- "helm.sh/helm/v4/pkg/release"
+ release "helm.sh/helm/v4/pkg/release/v1"
helmtime "helm.sh/helm/v4/pkg/time"
)
func upgradeAction(t *testing.T) *Upgrade {
+ t.Helper()
config := actionConfigFixture(t)
upAction := NewUpgrade(config)
upAction.Namespace = "spaced"
@@ -52,10 +54,10 @@ 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())
+ ctx, done := context.WithCancel(t.Context())
res, err := upAction.RunWithContext(ctx, rel.Name, buildChart(), vals)
done()
req.NoError(err)
@@ -82,7 +84,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 +106,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 +130,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{}{}
@@ -382,7 +384,6 @@ func TestUpgradeRelease_Pending(t *testing.T) {
}
func TestUpgradeRelease_Interrupted_Wait(t *testing.T) {
-
is := assert.New(t)
req := require.New(t)
@@ -395,11 +396,10 @@ 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()
- ctx, cancel := context.WithCancel(ctx)
+ ctx, cancel := context.WithCancel(t.Context())
time.AfterFunc(time.Second, cancel)
res, err := upAction.RunWithContext(ctx, rel.Name, buildChart(), vals)
@@ -407,11 +407,9 @@ func TestUpgradeRelease_Interrupted_Wait(t *testing.T) {
req.Error(err)
is.Contains(res.Info.Description, "Upgrade \"interrupted-release\" failed: context canceled")
is.Equal(res.Info.Status, release.StatusFailed)
-
}
func TestUpgradeRelease_Interrupted_Atomic(t *testing.T) {
-
is := assert.New(t)
req := require.New(t)
@@ -427,8 +425,7 @@ func TestUpgradeRelease_Interrupted_Atomic(t *testing.T) {
upAction.Atomic = true
vals := map[string]interface{}{}
- ctx := context.Background()
- ctx, cancel := context.WithCancel(ctx)
+ ctx, cancel := context.WithCancel(t.Context())
time.AfterFunc(time.Second, cancel)
res, err := upAction.RunWithContext(ctx, rel.Name, buildChart(), vals)
@@ -444,7 +441,7 @@ func TestUpgradeRelease_Interrupted_Atomic(t *testing.T) {
}
func TestMergeCustomLabels(t *testing.T) {
- var tests = [][3]map[string]string{
+ tests := [][3]map[string]string{
{nil, nil, map[string]string{}},
{map[string]string{}, map[string]string{}, map[string]string{}},
{map[string]string{"k1": "v1", "k2": "v2"}, nil, map[string]string{"k1": "v1", "k2": "v2"}},
@@ -549,7 +546,7 @@ func TestUpgradeRelease_DryRun(t *testing.T) {
upAction.DryRun = true
vals := map[string]interface{}{}
- ctx, done := context.WithCancel(context.Background())
+ ctx, done := context.WithCancel(t.Context())
res, err := upAction.RunWithContext(ctx, rel.Name, buildChart(withSampleSecret()), vals)
done()
req.NoError(err)
@@ -565,7 +562,7 @@ func TestUpgradeRelease_DryRun(t *testing.T) {
upAction.HideSecret = true
vals = map[string]interface{}{}
- ctx, done = context.WithCancel(context.Background())
+ ctx, done = context.WithCancel(t.Context())
res, err = upAction.RunWithContext(ctx, rel.Name, buildChart(withSampleSecret()), vals)
done()
req.NoError(err)
@@ -581,7 +578,7 @@ func TestUpgradeRelease_DryRun(t *testing.T) {
upAction.DryRun = false
vals = map[string]interface{}{}
- ctx, done = context.WithCancel(context.Background())
+ ctx, done = context.WithCancel(t.Context())
_, err = upAction.RunWithContext(ctx, rel.Name, buildChart(withSampleSecret()), vals)
done()
req.Error(err)
diff --git a/pkg/action/validate.go b/pkg/action/validate.go
index 4bc70e90c..e1021860f 100644
--- a/pkg/action/validate.go
+++ b/pkg/action/validate.go
@@ -18,8 +18,8 @@ package action
import (
"fmt"
+ "maps"
- "github.com/pkg/errors"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime"
@@ -52,7 +52,7 @@ func requireAdoption(resources kube.ResourceList) (kube.ResourceList, error) {
if apierrors.IsNotFound(err) {
return nil
}
- return errors.Wrapf(err, "could not get information about the resource %s", resourceString(info))
+ return fmt.Errorf("could not get information about the resource %s: %w", resourceString(info), err)
}
requireUpdate.Append(info)
@@ -76,7 +76,7 @@ func existingResourceConflict(resources kube.ResourceList, releaseName, releaseN
if apierrors.IsNotFound(err) {
return nil
}
- return errors.Wrapf(err, "could not get information about the resource %s", resourceString(info))
+ return fmt.Errorf("could not get information about the resource %s: %w", resourceString(info), err)
}
// Allow adoption of the resource if it is managed by Helm and is annotated with correct release name and namespace.
@@ -113,11 +113,7 @@ func checkOwnership(obj runtime.Object, releaseName, releaseNamespace string) er
}
if len(errs) > 0 {
- err := errors.New("invalid ownership metadata")
- for _, e := range errs {
- err = fmt.Errorf("%w; %s", err, e)
- }
- return err
+ return fmt.Errorf("invalid ownership metadata; %w", joinErrors(errs, "; "))
}
return nil
@@ -199,11 +195,7 @@ func mergeAnnotations(obj runtime.Object, annotations map[string]string) error {
// merge two maps, always taking the value on the right
func mergeStrStrMaps(current, desired map[string]string) map[string]string {
result := make(map[string]string)
- for k, v := range current {
- result[k] = v
- }
- for k, desiredVal := range desired {
- result[k] = desiredVal
- }
+ maps.Copy(result, current)
+ maps.Copy(result, desired)
return result
}
diff --git a/pkg/chart/chart.go b/pkg/chart/v2/chart.go
similarity index 96%
rename from pkg/chart/chart.go
rename to pkg/chart/v2/chart.go
index a3bed63a3..66ddf98a5 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"
@@ -113,6 +113,8 @@ func (ch *Chart) ChartPath() string {
}
// ChartFullPath returns the full path to this chart.
+// Note that the path may not correspond to the path where the file can be found on the file system if the path
+// points to an aliased subchart.
func (ch *Chart) ChartFullPath() string {
if !ch.IsRoot() {
return ch.Parent().ChartFullPath() + "/charts/" + ch.Name()
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 99%
rename from pkg/chart/dependency.go
rename to pkg/chart/v2/dependency.go
index eda0f5a89..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"
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/chart/v2/doc.go b/pkg/chart/v2/doc.go
new file mode 100644
index 000000000..d36ca3ec4
--- /dev/null
+++ b/pkg/chart/v2/doc.go
@@ -0,0 +1,23 @@
+/*
+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 v2 provides chart handling for apiVersion v1 and v2 charts
+
+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 77%
rename from pkg/chart/loader/archive.go
rename to pkg/chart/v2/loader/archive.go
index 51dd264f7..b9f370f56 100644
--- a/pkg/chart/loader/archive.go
+++ b/pkg/chart/v2/loader/archive.go
@@ -20,6 +20,7 @@ import (
"archive/tar"
"bytes"
"compress/gzip"
+ "errors"
"fmt"
"io"
"net/http"
@@ -28,11 +29,18 @@ import (
"regexp"
"strings"
- "github.com/pkg/errors"
-
- "helm.sh/helm/v4/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
@@ -119,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()
@@ -160,7 +169,7 @@ func LoadArchiveFiles(in io.Reader) ([]*BufferedFile, error) {
n = path.Clean(n)
if n == "." {
// In this case, the original path was relative when it should have been absolute.
- return nil, errors.Errorf("chart illegally contains content outside the base directory: %q", hd.Name)
+ return nil, fmt.Errorf("chart illegally contains content outside the base directory: %q", hd.Name)
}
if strings.HasPrefix(n, "..") {
return nil, errors.New("chart illegally references parent directory")
@@ -178,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 98%
rename from pkg/chart/loader/archive_test.go
rename to pkg/chart/v2/loader/archive_test.go
index 4d6db9ed4..d16c47563 100644
--- a/pkg/chart/loader/archive_test.go
+++ b/pkg/chart/v2/loader/archive_test.go
@@ -33,6 +33,7 @@ func TestLoadArchiveFiles(t *testing.T) {
name: "empty input should return no files",
generate: func(_ *tar.Writer) {},
check: func(t *testing.T, _ []*BufferedFile, err error) {
+ t.Helper()
if err.Error() != "no files in chart archive" {
t.Fatalf(`expected "no files in chart archive", got [%#v]`, err)
}
@@ -61,6 +62,7 @@ func TestLoadArchiveFiles(t *testing.T) {
}
},
check: func(t *testing.T, files []*BufferedFile, err error) {
+ t.Helper()
if err != nil {
t.Fatalf(`got unwanted error [%#v] for tar file with pax_global_header content`, err)
}
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 1dc30e4dc..4f72925dc 100644
--- a/pkg/chart/loader/directory.go
+++ b/pkg/chart/v2/loader/directory.go
@@ -23,10 +23,8 @@ import (
"path/filepath"
"strings"
- "github.com/pkg/errors"
-
"helm.sh/helm/v4/internal/sympath"
- "helm.sh/helm/v4/pkg/chart"
+ chart "helm.sh/helm/v4/pkg/chart/v2"
"helm.sh/helm/v4/pkg/ignore"
)
@@ -101,9 +99,13 @@ 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 errors.Wrapf(err, "error reading %s", n)
+ return fmt.Errorf("error reading %s: %w", n, err)
}
data = bytes.TrimPrefix(data, utf8bom)
diff --git a/pkg/chart/loader/load.go b/pkg/chart/v2/loader/load.go
similarity index 88%
rename from pkg/chart/loader/load.go
rename to pkg/chart/v2/loader/load.go
index e32094ef5..75c73e959 100644
--- a/pkg/chart/loader/load.go
+++ b/pkg/chart/v2/loader/load.go
@@ -19,18 +19,19 @@ package loader
import (
"bufio"
"bytes"
- "encoding/json"
+ "errors"
+ "fmt"
"io"
"log"
+ "maps"
"os"
"path/filepath"
"strings"
- "github.com/pkg/errors"
utilyaml "k8s.io/apimachinery/pkg/util/yaml"
"sigs.k8s.io/yaml"
- "helm.sh/helm/v4/pkg/chart"
+ chart "helm.sh/helm/v4/pkg/chart/v2"
)
// ChartLoader loads a chart.
@@ -48,7 +49,6 @@ func Loader(name string) (ChartLoader, error) {
return DirLoader(name), nil
}
return FileLoader(name), nil
-
}
// Load takes a string name, tries to resolve it to a file or directory, and then loads it.
@@ -86,7 +86,7 @@ func LoadFiles(files []*BufferedFile) (*chart.Chart, error) {
c.Metadata = new(chart.Metadata)
}
if err := yaml.Unmarshal(f.Data, c.Metadata); err != nil {
- return c, errors.Wrap(err, "cannot load Chart.yaml")
+ return c, fmt.Errorf("cannot load Chart.yaml: %w", err)
}
// NOTE(bacongobbler): while the chart specification says that APIVersion must be set,
// Helm 2 accepted charts that did not provide an APIVersion in their chart metadata.
@@ -104,12 +104,12 @@ func LoadFiles(files []*BufferedFile) (*chart.Chart, error) {
case f.Name == "Chart.lock":
c.Lock = new(chart.Lock)
if err := yaml.Unmarshal(f.Data, &c.Lock); err != nil {
- return c, errors.Wrap(err, "cannot load Chart.lock")
+ return c, fmt.Errorf("cannot load Chart.lock: %w", err)
}
case f.Name == "values.yaml":
values, err := LoadValues(bytes.NewReader(f.Data))
if err != nil {
- return c, errors.Wrap(err, "cannot load values.yaml")
+ return c, fmt.Errorf("cannot load values.yaml: %w", err)
}
c.Values = values
case f.Name == "values.schema.json":
@@ -125,7 +125,7 @@ func LoadFiles(files []*BufferedFile) (*chart.Chart, error) {
log.Printf("Warning: Dependencies are handled in Chart.yaml since apiVersion \"v2\". We recommend migrating dependencies to Chart.yaml.")
}
if err := yaml.Unmarshal(f.Data, c.Metadata); err != nil {
- return c, errors.Wrap(err, "cannot load requirements.yaml")
+ return c, fmt.Errorf("cannot load requirements.yaml: %w", err)
}
if c.Metadata.APIVersion == chart.APIVersionV1 {
c.Files = append(c.Files, &chart.File{Name: f.Name, Data: f.Data})
@@ -134,7 +134,7 @@ func LoadFiles(files []*BufferedFile) (*chart.Chart, error) {
case f.Name == "requirements.lock":
c.Lock = new(chart.Lock)
if err := yaml.Unmarshal(f.Data, &c.Lock); err != nil {
- return c, errors.Wrap(err, "cannot load requirements.lock")
+ return c, fmt.Errorf("cannot load requirements.lock: %w", err)
}
if c.Metadata == nil {
c.Metadata = new(chart.Metadata)
@@ -163,7 +163,7 @@ func LoadFiles(files []*BufferedFile) (*chart.Chart, error) {
}
if c.Metadata == nil {
- return c, errors.New("Chart.yaml file is missing")
+ return c, errors.New("Chart.yaml file is missing") //nolint:staticcheck
}
if err := c.Validate(); err != nil {
@@ -179,7 +179,7 @@ func LoadFiles(files []*BufferedFile) (*chart.Chart, error) {
case filepath.Ext(n) == ".tgz":
file := files[0]
if file.Name != n {
- return c, errors.Errorf("error unpacking subchart tar in %s: expected %s, got %s", c.Name(), n, file.Name)
+ return c, fmt.Errorf("error unpacking subchart tar in %s: expected %s, got %s", c.Name(), n, file.Name)
}
// Untar the chart and add to c.Dependencies
sc, err = LoadArchive(bytes.NewBuffer(file.Data))
@@ -199,7 +199,7 @@ func LoadFiles(files []*BufferedFile) (*chart.Chart, error) {
}
if err != nil {
- return c, errors.Wrapf(err, "error unpacking subchart %s in %s", n, c.Name())
+ return c, fmt.Errorf("error unpacking subchart %s in %s: %w", n, c.Name(), err)
}
c.AddDependency(sc)
}
@@ -221,13 +221,10 @@ func LoadValues(data io.Reader) (map[string]interface{}, error) {
if err == io.EOF {
break
}
- return nil, errors.Wrap(err, "error reading yaml document")
+ 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, errors.Wrap(err, "cannot unmarshal yaml document")
+ if err := yaml.Unmarshal(raw, ¤tMap); err != nil {
+ return nil, fmt.Errorf("cannot unmarshal yaml document: %w", err)
}
values = MergeMaps(values, currentMap)
}
@@ -238,9 +235,7 @@ func LoadValues(data io.Reader) (map[string]interface{}, error) {
// If the value is a map, the maps will be merged recursively.
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
- }
+ maps.Copy(out, a)
for k, v := range b {
if v, ok := v.(map[string]interface{}); ok {
if bv, ok := out[k]; ok {
diff --git a/pkg/chart/loader/load_test.go b/pkg/chart/v2/loader/load_test.go
similarity index 99%
rename from pkg/chart/loader/load_test.go
rename to pkg/chart/v2/loader/load_test.go
index e34124829..41154421c 100644
--- a/pkg/chart/loader/load_test.go
+++ b/pkg/chart/v2/loader/load_test.go
@@ -30,7 +30,7 @@ import (
"testing"
"time"
- "helm.sh/helm/v4/pkg/chart"
+ chart "helm.sh/helm/v4/pkg/chart/v2"
)
func TestLoadDir(t *testing.T) {
@@ -648,6 +648,7 @@ func verifyChart(t *testing.T, c *chart.Chart) {
}
func verifyDependencies(t *testing.T, c *chart.Chart) {
+ t.Helper()
if len(c.Metadata.Dependencies) != 2 {
t.Errorf("Expected 2 dependencies, got %d", len(c.Metadata.Dependencies))
}
@@ -670,6 +671,7 @@ func verifyDependencies(t *testing.T, c *chart.Chart) {
}
func verifyDependenciesLock(t *testing.T, c *chart.Chart) {
+ t.Helper()
if len(c.Metadata.Dependencies) != 2 {
t.Errorf("Expected 2 dependencies, got %d", len(c.Metadata.Dependencies))
}
@@ -692,10 +694,12 @@ func verifyDependenciesLock(t *testing.T, c *chart.Chart) {
}
func verifyFrobnitz(t *testing.T, c *chart.Chart) {
+ t.Helper()
verifyChartFileAndTemplate(t, c, "frobnitz")
}
func verifyChartFileAndTemplate(t *testing.T, c *chart.Chart, name string) {
+ t.Helper()
if c.Metadata == nil {
t.Fatal("Metadata is nil")
}
@@ -750,6 +754,7 @@ func verifyChartFileAndTemplate(t *testing.T, c *chart.Chart, name string) {
}
func verifyBomStripped(t *testing.T, files []*chart.File) {
+ t.Helper()
for _, file := range files {
if bytes.HasPrefix(file.Data, utf8bom) {
t.Errorf("Byte Order Mark still present in processed file %s", file.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/chart/util/capabilities.go b/pkg/chart/v2/util/capabilities.go
similarity index 97%
rename from pkg/chart/util/capabilities.go
rename to pkg/chart/v2/util/capabilities.go
index d4b420b2f..23b6d46fa 100644
--- a/pkg/chart/util/capabilities.go
+++ b/pkg/chart/v2/util/capabilities.go
@@ -17,6 +17,7 @@ package util
import (
"fmt"
+ "slices"
"strconv"
"github.com/Masterminds/semver/v3"
@@ -102,12 +103,7 @@ type VersionSet []string
//
// vs.Has("apps/v1")
func (v VersionSet) Has(apiVersion string) bool {
- for _, x := range v {
- if x == apiVersion {
- return true
- }
- }
- return false
+ return slices.Contains(v, apiVersion)
}
func allKnownVersions() VersionSet {
diff --git a/pkg/chart/util/capabilities_test.go b/pkg/chart/v2/util/capabilities_test.go
similarity index 100%
rename from pkg/chart/util/capabilities_test.go
rename to pkg/chart/v2/util/capabilities_test.go
diff --git a/pkg/chart/util/chartfile.go b/pkg/chart/v2/util/chartfile.go
similarity index 71%
rename from pkg/chart/util/chartfile.go
rename to pkg/chart/v2/util/chartfile.go
index acab80d0a..1f9c712b2 100644
--- a/pkg/chart/util/chartfile.go
+++ b/pkg/chart/v2/util/chartfile.go
@@ -17,13 +17,15 @@ limitations under the License.
package util
import (
+ "errors"
+ "fmt"
+ "io/fs"
"os"
"path/filepath"
- "github.com/pkg/errors"
"sigs.k8s.io/yaml"
- "helm.sh/helm/v4/pkg/chart"
+ chart "helm.sh/helm/v4/pkg/chart/v2"
)
// LoadChartfile loads a Chart.yaml file into a *chart.Metadata.
@@ -37,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')
@@ -64,17 +77,17 @@ func IsChartDir(dirName string) (bool, error) {
if fi, err := os.Stat(dirName); err != nil {
return false, err
} else if !fi.IsDir() {
- return false, errors.Errorf("%q is not a directory", dirName)
+ return false, fmt.Errorf("%q is not a directory", dirName)
}
chartYaml := filepath.Join(dirName, ChartfileName)
- if _, err := os.Stat(chartYaml); os.IsNotExist(err) {
- return false, errors.Errorf("no %s exists in directory %q", ChartfileName, dirName)
+ if _, err := os.Stat(chartYaml); errors.Is(err, fs.ErrNotExist) {
+ return false, fmt.Errorf("no %s exists in directory %q", ChartfileName, dirName)
}
chartYamlContent, err := os.ReadFile(chartYaml)
if err != nil {
- return false, errors.Errorf("cannot read %s in directory %q", ChartfileName, dirName)
+ return false, fmt.Errorf("cannot read %s in directory %q", ChartfileName, dirName)
}
chartContent := new(chart.Metadata)
@@ -82,10 +95,10 @@ func IsChartDir(dirName string) (bool, error) {
return false, err
}
if chartContent == nil {
- return false, errors.Errorf("chart metadata (%s) missing", ChartfileName)
+ return false, fmt.Errorf("chart metadata (%s) missing", ChartfileName)
}
if chartContent.Name == "" {
- return false, errors.Errorf("invalid chart (%s): name must not be empty", ChartfileName)
+ return false, fmt.Errorf("invalid chart (%s): name must not be empty", ChartfileName)
}
return true, nil
diff --git a/pkg/chart/util/chartfile_test.go b/pkg/chart/v2/util/chartfile_test.go
similarity index 98%
rename from pkg/chart/util/chartfile_test.go
rename to pkg/chart/v2/util/chartfile_test.go
index 6eb599680..00c530b8a 100644
--- a/pkg/chart/util/chartfile_test.go
+++ b/pkg/chart/v2/util/chartfile_test.go
@@ -19,7 +19,7 @@ package util
import (
"testing"
- "helm.sh/helm/v4/pkg/chart"
+ chart "helm.sh/helm/v4/pkg/chart/v2"
)
const testfile = "testdata/chartfiletest.yaml"
@@ -34,7 +34,7 @@ func TestLoadChartfile(t *testing.T) {
}
func verifyChartfile(t *testing.T, f *chart.Metadata, name string) {
-
+ t.Helper()
if f == nil { //nolint:staticcheck
t.Fatal("Failed verifyChartfile because f is nil")
}
diff --git a/pkg/chart/util/coalesce.go b/pkg/chart/v2/util/coalesce.go
similarity index 98%
rename from pkg/chart/util/coalesce.go
rename to pkg/chart/v2/util/coalesce.go
index 9ab5c1015..a3e0f5ae8 100644
--- a/pkg/chart/util/coalesce.go
+++ b/pkg/chart/v2/util/coalesce.go
@@ -19,11 +19,11 @@ package util
import (
"fmt"
"log"
+ "maps"
"github.com/mitchellh/copystructure"
- "github.com/pkg/errors"
- "helm.sh/helm/v4/pkg/chart"
+ chart "helm.sh/helm/v4/pkg/chart/v2"
)
func concatPrefix(a, b string) string {
@@ -108,7 +108,7 @@ func coalesceDeps(printf printFn, chrt *chart.Chart, dest map[string]interface{}
// If dest doesn't already have the key, create it.
dest[subchart.Name()] = make(map[string]interface{})
} else if !istable(c) {
- return dest, errors.Errorf("type mismatch on %s: %t", subchart.Name(), c)
+ return dest, fmt.Errorf("type mismatch on %s: %t", subchart.Name(), c)
}
if dv, ok := dest[subchart.Name()]; ok {
dvmap := dv.(map[string]interface{})
@@ -183,9 +183,7 @@ func coalesceGlobals(printf printFn, dest, src map[string]interface{}, prefix st
func copyMap(src map[string]interface{}) map[string]interface{} {
m := make(map[string]interface{}, len(src))
- for k, v := range src {
- m[k] = v
- }
+ maps.Copy(m, src)
return m
}
@@ -283,6 +281,11 @@ func coalesceTablesFullKey(printf printFn, dst, src map[string]interface{}, pref
if dst == nil {
return src
}
+ for key, val := range dst {
+ if val == nil {
+ src[key] = nil
+ }
+ }
// Because dest has higher precedence than src, dest values override src
// values.
for key, val := range src {
diff --git a/pkg/chart/util/coalesce_test.go b/pkg/chart/v2/util/coalesce_test.go
similarity index 99%
rename from pkg/chart/util/coalesce_test.go
rename to pkg/chart/v2/util/coalesce_test.go
index 5a8dfe94a..e2c45a435 100644
--- a/pkg/chart/util/coalesce_test.go
+++ b/pkg/chart/v2/util/coalesce_test.go
@@ -19,11 +19,12 @@ package util
import (
"encoding/json"
"fmt"
+ "maps"
"testing"
"github.com/stretchr/testify/assert"
- "helm.sh/helm/v4/pkg/chart"
+ chart "helm.sh/helm/v4/pkg/chart/v2"
)
// ref: http://www.yaml.org/spec/1.2/spec.html#id2803362
@@ -144,9 +145,7 @@ func TestCoalesceValues(t *testing.T) {
// to CoalesceValues as argument, so that we can
// use it for asserting later
valsCopy := make(Values, len(vals))
- for key, value := range vals {
- valsCopy[key] = value
- }
+ maps.Copy(valsCopy, vals)
v, err := CoalesceValues(c, vals)
if err != nil {
@@ -304,9 +303,7 @@ func TestMergeValues(t *testing.T) {
// to MergeValues as argument, so that we can
// use it for asserting later
valsCopy := make(Values, len(vals))
- for key, value := range vals {
- valsCopy[key] = value
- }
+ maps.Copy(valsCopy, vals)
v, err := MergeValues(c, vals)
if err != nil {
diff --git a/pkg/chart/util/compatible.go b/pkg/chart/v2/util/compatible.go
similarity index 100%
rename from pkg/chart/util/compatible.go
rename to pkg/chart/v2/util/compatible.go
diff --git a/pkg/chart/util/compatible_test.go b/pkg/chart/v2/util/compatible_test.go
similarity index 100%
rename from pkg/chart/util/compatible_test.go
rename to pkg/chart/v2/util/compatible_test.go
diff --git a/pkg/chart/util/create.go b/pkg/chart/v2/util/create.go
similarity index 86%
rename from pkg/chart/util/create.go
rename to pkg/chart/v2/util/create.go
index dfb5099f2..93699e0fd 100644
--- a/pkg/chart/util/create.go
+++ b/pkg/chart/v2/util/create.go
@@ -24,11 +24,10 @@ import (
"regexp"
"strings"
- "github.com/pkg/errors"
"sigs.k8s.io/yaml"
- "helm.sh/helm/v4/pkg/chart"
- "helm.sh/helm/v4/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.
@@ -54,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.
@@ -177,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
@@ -297,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:
@@ -444,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 }}
@@ -558,7 +650,7 @@ var Stderr io.Writer = os.Stderr
func CreateFrom(chartfile *chart.Metadata, dest, src string) error {
schart, err := loader.Load(src)
if err != nil {
- return errors.Wrapf(err, "could not load %s", src)
+ return fmt.Errorf("could not load %s: %w", src, err)
}
schart.Metadata = chartfile
@@ -573,12 +665,12 @@ func CreateFrom(chartfile *chart.Metadata, dest, src string) error {
schart.Templates = updatedTemplates
b, err := yaml.Marshal(schart.Values)
if err != nil {
- return errors.Wrap(err, "reading values file")
+ return fmt.Errorf("reading values file: %w", err)
}
var m map[string]interface{}
if err := yaml.Unmarshal(transform(string(b), schart.Name()), &m); err != nil {
- return errors.Wrap(err, "transforming values file")
+ return fmt.Errorf("transforming values file: %w", err)
}
schart.Values = m
@@ -622,12 +714,12 @@ func Create(name, dir string) (string, error) {
if fi, err := os.Stat(path); err != nil {
return path, err
} else if !fi.IsDir() {
- return path, errors.Errorf("no such directory %s", path)
+ return path, fmt.Errorf("no such directory %s", path)
}
cdir := filepath.Join(path, name)
if fi, err := os.Stat(cdir); err == nil && !fi.IsDir() {
- return cdir, errors.Errorf("file %s already exists and is not a directory", cdir)
+ return cdir, fmt.Errorf("file %s already exists and is not a directory", cdir)
}
// Note: If adding a new template below (i.e., to `helm create`) which is disabled by default (similar to hpa and
@@ -658,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/chart/util/create_test.go b/pkg/chart/v2/util/create_test.go
similarity index 98%
rename from pkg/chart/util/create_test.go
rename to pkg/chart/v2/util/create_test.go
index e67ce3e1f..086c4e5c8 100644
--- a/pkg/chart/util/create_test.go
+++ b/pkg/chart/v2/util/create_test.go
@@ -22,8 +22,8 @@ import (
"path/filepath"
"testing"
- "helm.sh/helm/v4/pkg/chart"
- "helm.sh/helm/v4/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/chart/util/dependencies.go b/pkg/chart/v2/util/dependencies.go
similarity index 87%
rename from pkg/chart/util/dependencies.go
rename to pkg/chart/v2/util/dependencies.go
index fb9052d71..e2cce6f2f 100644
--- a/pkg/chart/util/dependencies.go
+++ b/pkg/chart/v2/util/dependencies.go
@@ -16,12 +16,12 @@ limitations under the License.
package util
import (
- "log"
+ "log/slog"
"strings"
"github.com/mitchellh/copystructure"
- "helm.sh/helm/v4/pkg/chart"
+ chart "helm.sh/helm/v4/pkg/chart/v2"
)
// ProcessDependencies checks through this chart's dependencies, processing accordingly.
@@ -48,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))
}
}
}
@@ -79,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)
}
}
}
@@ -91,6 +91,7 @@ func processDependencyTags(reqs []*chart.Dependency, cvals Values) {
}
}
+// getAliasDependency finds the chart for an alias dependency and copies parts that will be modified
func getAliasDependency(charts []*chart.Chart, dep *chart.Dependency) *chart.Chart {
for _, c := range charts {
if c == nil {
@@ -104,17 +105,38 @@ func getAliasDependency(charts []*chart.Chart, dep *chart.Dependency) *chart.Cha
}
out := *c
- md := *c.Metadata
- out.Metadata = &md
+ out.Metadata = copyMetadata(c.Metadata)
+
+ // empty dependencies and shallow copy all dependencies, otherwise parent info may be corrupted if
+ // there is more than one dependency aliasing this chart
+ out.SetDependencies()
+ for _, dependency := range c.Dependencies() {
+ cpy := *dependency
+ out.AddDependency(&cpy)
+ }
if dep.Alias != "" {
- md.Name = dep.Alias
+ out.Metadata.Name = dep.Alias
}
return &out
}
return nil
}
+func copyMetadata(metadata *chart.Metadata) *chart.Metadata {
+ md := *metadata
+
+ if md.Dependencies != nil {
+ dependencies := make([]*chart.Dependency, len(md.Dependencies))
+ for i := range md.Dependencies {
+ dependency := *md.Dependencies[i]
+ dependencies[i] = &dependency
+ }
+ md.Dependencies = dependencies
+ }
+ return &md
+}
+
// processDependencyEnabled removes disabled charts from dependencies
func processDependencyEnabled(c *chart.Chart, v map[string]interface{}, path string) error {
if c.Metadata.Dependencies == nil {
@@ -254,7 +276,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
@@ -271,7 +293,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/chart/util/dependencies_test.go b/pkg/chart/v2/util/dependencies_test.go
similarity index 88%
rename from pkg/chart/util/dependencies_test.go
rename to pkg/chart/v2/util/dependencies_test.go
index 10fca265e..d645d7bf5 100644
--- a/pkg/chart/util/dependencies_test.go
+++ b/pkg/chart/v2/util/dependencies_test.go
@@ -15,15 +15,14 @@ limitations under the License.
package util
import (
- "encoding/json"
"os"
"path/filepath"
"sort"
"strconv"
"testing"
- "helm.sh/helm/v4/pkg/chart"
- "helm.sh/helm/v4/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 {
@@ -134,7 +133,7 @@ func TestDependencyEnabled(t *testing.T) {
}
}
-// extractCharts recursively searches chart dependencies returning all charts found
+// extractChartNames recursively searches chart dependencies returning all charts found
func extractChartNames(c *chart.Chart) []string {
var out []string
var fn func(c *chart.Chart)
@@ -238,20 +237,6 @@ 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)
@@ -286,6 +271,38 @@ func TestProcessDependencyImportValues(t *testing.T) {
}
}
+func TestProcessDependencyImportValuesFromSharedDependencyToAliases(t *testing.T) {
+ c := loadChart(t, "testdata/chart-with-import-from-aliased-dependencies")
+
+ if err := processDependencyEnabled(c, c.Values, ""); err != nil {
+ t.Fatalf("expected no errors but got %q", err)
+ }
+ if err := processDependencyImportValues(c, true); err != nil {
+ t.Fatalf("processing import values dependencies %v", err)
+ }
+ e := make(map[string]string)
+
+ e["foo-defaults.defaultValue"] = "42"
+ e["bar-defaults.defaultValue"] = "42"
+
+ e["foo.defaults.defaultValue"] = "42"
+ e["bar.defaults.defaultValue"] = "42"
+
+ e["foo.grandchild.defaults.defaultValue"] = "42"
+ e["bar.grandchild.defaults.defaultValue"] = "42"
+
+ cValues := Values(c.Values)
+ for kk, vv := range e {
+ pv, err := cValues.PathValue(kk)
+ if err != nil {
+ t.Fatalf("retrieving import values table %v %v", kk, err)
+ }
+ if pv != vv {
+ t.Errorf("failed to match imported value %v with expected %v", pv, vv)
+ }
+ }
+}
+
func TestProcessDependencyImportValuesMultiLevelPrecedence(t *testing.T) {
c := loadChart(t, "testdata/three-level-dependent-chart/umbrella")
@@ -324,10 +341,6 @@ 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)
@@ -430,6 +443,9 @@ func TestDependentChartAliases(t *testing.T) {
if aliasChart == nil {
t.Fatalf("failed to get dependency chart for alias %s", req[2].Name)
}
+ if aliasChart.Parent() != c {
+ t.Fatalf("dependency chart has wrong parent, expected %s but got %s", c.Name(), aliasChart.Parent().Name())
+ }
if req[2].Alias != "" {
if aliasChart.Name() != req[2].Alias {
t.Fatalf("dependency chart name should be %s but got %s", req[2].Alias, aliasChart.Name())
@@ -521,3 +537,33 @@ func TestDependentChartsWithSomeSubchartsSpecifiedInDependency(t *testing.T) {
t.Fatalf("expected 1 dependency specified in Chart.yaml, got %d", len(c.Metadata.Dependencies))
}
}
+
+func validateDependencyTree(t *testing.T, c *chart.Chart) {
+ t.Helper()
+ for _, dependency := range c.Dependencies() {
+ if dependency.Parent() != c {
+ if dependency.Parent() != c {
+ t.Fatalf("dependency chart %s has wrong parent, expected %s but got %s", dependency.Name(), c.Name(), dependency.Parent().Name())
+ }
+ }
+ // recurse entire tree
+ validateDependencyTree(t, dependency)
+ }
+}
+
+func TestChartWithDependencyAliasedTwiceAndDoublyReferencedSubDependency(t *testing.T) {
+ c := loadChart(t, "testdata/chart-with-dependency-aliased-twice")
+
+ if len(c.Dependencies()) != 1 {
+ t.Fatalf("expected one dependency for this chart, but got %d", len(c.Dependencies()))
+ }
+
+ if err := processDependencyEnabled(c, c.Values, ""); err != nil {
+ t.Fatalf("expected no errors but got %q", err)
+ }
+
+ if len(c.Dependencies()) != 2 {
+ t.Fatal("expected two dependencies after processing aliases")
+ }
+ validateDependencyTree(t, c)
+}
diff --git a/pkg/chart/util/doc.go b/pkg/chart/v2/util/doc.go
similarity index 95%
rename from pkg/chart/util/doc.go
rename to pkg/chart/v2/util/doc.go
index 587fcaeb1..141062074 100644
--- a/pkg/chart/util/doc.go
+++ b/pkg/chart/v2/util/doc.go
@@ -42,4 +42,4 @@ into a Chart.
When creating charts in memory, use the 'helm.sh/helm/pkg/chart'
package directly.
*/
-package util // import chartutil "helm.sh/helm/v4/pkg/chart/util"
+package util // import chartutil "helm.sh/helm/v4/pkg/chart/v2/util"
diff --git a/pkg/chart/util/errors.go b/pkg/chart/v2/util/errors.go
similarity index 100%
rename from pkg/chart/util/errors.go
rename to pkg/chart/v2/util/errors.go
diff --git a/pkg/chart/util/errors_test.go b/pkg/chart/v2/util/errors_test.go
similarity index 100%
rename from pkg/chart/util/errors_test.go
rename to pkg/chart/v2/util/errors_test.go
diff --git a/pkg/chart/util/expand.go b/pkg/chart/v2/util/expand.go
similarity index 93%
rename from pkg/chart/util/expand.go
rename to pkg/chart/v2/util/expand.go
index 4b83bf584..9d08571ed 100644
--- a/pkg/chart/util/expand.go
+++ b/pkg/chart/v2/util/expand.go
@@ -17,16 +17,17 @@ limitations under the License.
package util
import (
+ "errors"
+ "fmt"
"io"
"os"
"path/filepath"
securejoin "github.com/cyphar/filepath-securejoin"
- "github.com/pkg/errors"
"sigs.k8s.io/yaml"
- "helm.sh/helm/v4/pkg/chart"
- "helm.sh/helm/v4/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.
@@ -42,7 +43,7 @@ func Expand(dir string, r io.Reader) error {
if file.Name == "Chart.yaml" {
ch := &chart.Metadata{}
if err := yaml.Unmarshal(file.Data, ch); err != nil {
- return errors.Wrap(err, "cannot load Chart.yaml")
+ return fmt.Errorf("cannot load Chart.yaml: %w", err)
}
chartName = ch.Name
}
diff --git a/pkg/chart/util/expand_test.go b/pkg/chart/v2/util/expand_test.go
similarity index 100%
rename from pkg/chart/util/expand_test.go
rename to pkg/chart/v2/util/expand_test.go
diff --git a/pkg/chart/util/jsonschema.go b/pkg/chart/v2/util/jsonschema.go
similarity index 52%
rename from pkg/chart/util/jsonschema.go
rename to pkg/chart/v2/util/jsonschema.go
index 616c6d444..820e5953a 100644
--- a/pkg/chart/util/jsonschema.go
+++ b/pkg/chart/v2/util/jsonschema.go
@@ -18,27 +18,28 @@ package util
import (
"bytes"
+ "errors"
"fmt"
+ "log/slog"
"strings"
- "github.com/pkg/errors"
- "github.com/xeipuuv/gojsonschema"
- "sigs.k8s.io/yaml"
+ "github.com/santhosh-tekuri/jsonschema/v6"
- "helm.sh/helm/v4/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/chart/util/jsonschema_test.go b/pkg/chart/v2/util/jsonschema_test.go
similarity index 64%
rename from pkg/chart/util/jsonschema_test.go
rename to pkg/chart/v2/util/jsonschema_test.go
index c5600044a..3279eb0db 100644
--- a/pkg/chart/util/jsonschema_test.go
+++ b/pkg/chart/v2/util/jsonschema_test.go
@@ -20,7 +20,7 @@ import (
"os"
"testing"
- "helm.sh/helm/v4/pkg/chart"
+ chart "helm.sh/helm/v4/pkg/chart/v2"
)
func TestValidateAgainstSingleSchema(t *testing.T) {
@@ -55,8 +55,8 @@ func TestValidateAgainstInvalidSingleSchema(t *testing.T) {
errString = err.Error()
}
- expectedErrString := "unable to validate schema: runtime error: invalid " +
- "memory address or nil pointer dereference"
+ expectedErrString := `"file:///values.schema.json#" is not valid against metaschema: jsonschema validation failed with 'https://json-schema.org/draft/2020-12/schema#'
+- at '': got number, want boolean or object`
if errString != expectedErrString {
t.Errorf("Error string :\n`%s`\ndoes not match expected\n`%s`", errString, expectedErrString)
}
@@ -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/chart/util/save.go b/pkg/chart/v2/util/save.go
similarity index 92%
rename from pkg/chart/util/save.go
rename to pkg/chart/v2/util/save.go
index 635ff7944..624a5b562 100644
--- a/pkg/chart/util/save.go
+++ b/pkg/chart/v2/util/save.go
@@ -20,15 +20,16 @@ import (
"archive/tar"
"compress/gzip"
"encoding/json"
+ "errors"
"fmt"
+ "io/fs"
"os"
"path/filepath"
"time"
- "github.com/pkg/errors"
"sigs.k8s.io/yaml"
- "helm.sh/helm/v4/pkg/chart"
+ chart "helm.sh/helm/v4/pkg/chart/v2"
)
var headerBytes = []byte("+aHR0cHM6Ly95b3V0dS5iZS96OVV6MWljandyTQo=")
@@ -45,7 +46,7 @@ func SaveDir(c *chart.Chart, dest string) error {
}
outdir := filepath.Join(dest, c.Name())
if fi, err := os.Stat(outdir); err == nil && !fi.IsDir() {
- return errors.Errorf("file %s already exists and is not a directory", outdir)
+ return fmt.Errorf("file %s already exists and is not a directory", outdir)
}
if err := os.MkdirAll(outdir, 0755); err != nil {
return err
@@ -89,7 +90,7 @@ func SaveDir(c *chart.Chart, dest string) error {
for _, dep := range c.Dependencies() {
// Here, we write each dependency as a tar file.
if _, err := Save(dep, base); err != nil {
- return errors.Wrapf(err, "saving %s", dep.ChartFullPath())
+ return fmt.Errorf("saving %s: %w", dep.ChartFullPath(), err)
}
}
return nil
@@ -105,22 +106,22 @@ func SaveDir(c *chart.Chart, dest string) error {
// This returns the absolute path to the chart archive file.
func Save(c *chart.Chart, outDir string) (string, error) {
if err := c.Validate(); err != nil {
- return "", errors.Wrap(err, "chart validation")
+ return "", fmt.Errorf("chart validation: %w", err)
}
filename := fmt.Sprintf("%s-%s.tgz", c.Name(), c.Metadata.Version)
filename = filepath.Join(outDir, filename)
dir := filepath.Dir(filename)
if stat, err := os.Stat(dir); err != nil {
- if os.IsNotExist(err) {
+ if errors.Is(err, fs.ErrNotExist) {
if err2 := os.MkdirAll(dir, 0755); err2 != nil {
return "", err2
}
} else {
- return "", errors.Wrapf(err, "stat %s", dir)
+ return "", fmt.Errorf("stat %s: %w", dir, err)
}
} else if !stat.IsDir() {
- return "", errors.Errorf("is not a directory: %s", dir)
+ return "", fmt.Errorf("is not a directory: %s", dir)
}
f, err := os.Create(filename)
@@ -130,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)
@@ -203,7 +204,7 @@ func writeTarContents(out *tar.Writer, c *chart.Chart, prefix string) error {
// Save values.schema.json if it exists
if c.Schema != nil {
if !json.Valid(c.Schema) {
- return errors.New("Invalid JSON in " + SchemafileName)
+ return errors.New("invalid JSON in " + SchemafileName)
}
if err := writeToTar(out, filepath.Join(base, SchemafileName), c.Schema); err != nil {
return err
diff --git a/pkg/chart/util/save_test.go b/pkg/chart/v2/util/save_test.go
similarity index 98%
rename from pkg/chart/util/save_test.go
rename to pkg/chart/v2/util/save_test.go
index a7338c8d7..ff96331b5 100644
--- a/pkg/chart/util/save_test.go
+++ b/pkg/chart/v2/util/save_test.go
@@ -29,8 +29,8 @@ import (
"testing"
"time"
- "helm.sh/helm/v4/pkg/chart"
- "helm.sh/helm/v4/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/chart/v2/util/testdata/chart-with-dependency-aliased-twice/Chart.yaml b/pkg/chart/v2/util/testdata/chart-with-dependency-aliased-twice/Chart.yaml
new file mode 100644
index 000000000..d778f8fe9
--- /dev/null
+++ b/pkg/chart/v2/util/testdata/chart-with-dependency-aliased-twice/Chart.yaml
@@ -0,0 +1,14 @@
+apiVersion: v2
+appVersion: 1.0.0
+name: chart-with-dependency-aliased-twice
+type: application
+version: 1.0.0
+
+dependencies:
+ - name: child
+ alias: foo
+ version: 1.0.0
+ - name: child
+ alias: bar
+ version: 1.0.0
+
diff --git a/pkg/chart/v2/util/testdata/chart-with-dependency-aliased-twice/charts/child/Chart.yaml b/pkg/chart/v2/util/testdata/chart-with-dependency-aliased-twice/charts/child/Chart.yaml
new file mode 100644
index 000000000..220fda663
--- /dev/null
+++ b/pkg/chart/v2/util/testdata/chart-with-dependency-aliased-twice/charts/child/Chart.yaml
@@ -0,0 +1,6 @@
+apiVersion: v2
+appVersion: 1.0.0
+name: child
+type: application
+version: 1.0.0
+
diff --git a/pkg/chart/v2/util/testdata/chart-with-dependency-aliased-twice/charts/child/charts/grandchild/Chart.yaml b/pkg/chart/v2/util/testdata/chart-with-dependency-aliased-twice/charts/child/charts/grandchild/Chart.yaml
new file mode 100644
index 000000000..50e620a8d
--- /dev/null
+++ b/pkg/chart/v2/util/testdata/chart-with-dependency-aliased-twice/charts/child/charts/grandchild/Chart.yaml
@@ -0,0 +1,6 @@
+apiVersion: v2
+appVersion: 1.0.0
+name: grandchild
+type: application
+version: 1.0.0
+
diff --git a/pkg/chart/v2/util/testdata/chart-with-dependency-aliased-twice/charts/child/charts/grandchild/templates/dummy.yaml b/pkg/chart/v2/util/testdata/chart-with-dependency-aliased-twice/charts/child/charts/grandchild/templates/dummy.yaml
new file mode 100644
index 000000000..1830492ef
--- /dev/null
+++ b/pkg/chart/v2/util/testdata/chart-with-dependency-aliased-twice/charts/child/charts/grandchild/templates/dummy.yaml
@@ -0,0 +1,7 @@
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: {{ .Chart.Name }}-{{ .Values.from }}
+data:
+ {{- toYaml .Values | nindent 2 }}
+
diff --git a/pkg/chart/v2/util/testdata/chart-with-dependency-aliased-twice/charts/child/templates/dummy.yaml b/pkg/chart/v2/util/testdata/chart-with-dependency-aliased-twice/charts/child/templates/dummy.yaml
new file mode 100644
index 000000000..b5d55af7c
--- /dev/null
+++ b/pkg/chart/v2/util/testdata/chart-with-dependency-aliased-twice/charts/child/templates/dummy.yaml
@@ -0,0 +1,7 @@
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: {{ .Chart.Name }}
+data:
+ {{- toYaml .Values | nindent 2 }}
+
diff --git a/pkg/chart/v2/util/testdata/chart-with-dependency-aliased-twice/values.yaml b/pkg/chart/v2/util/testdata/chart-with-dependency-aliased-twice/values.yaml
new file mode 100644
index 000000000..695521a4a
--- /dev/null
+++ b/pkg/chart/v2/util/testdata/chart-with-dependency-aliased-twice/values.yaml
@@ -0,0 +1,7 @@
+foo:
+ grandchild:
+ from: foo
+bar:
+ grandchild:
+ from: bar
+
diff --git a/pkg/chart/v2/util/testdata/chart-with-import-from-aliased-dependencies/Chart.yaml b/pkg/chart/v2/util/testdata/chart-with-import-from-aliased-dependencies/Chart.yaml
new file mode 100644
index 000000000..c408f0ca8
--- /dev/null
+++ b/pkg/chart/v2/util/testdata/chart-with-import-from-aliased-dependencies/Chart.yaml
@@ -0,0 +1,20 @@
+apiVersion: v2
+appVersion: 1.0.0
+name: chart-with-dependency-aliased-twice
+type: application
+version: 1.0.0
+
+dependencies:
+ - name: child
+ alias: foo
+ version: 1.0.0
+ import-values:
+ - parent: foo-defaults
+ child: defaults
+ - name: child
+ alias: bar
+ version: 1.0.0
+ import-values:
+ - parent: bar-defaults
+ child: defaults
+
diff --git a/pkg/chart/v2/util/testdata/chart-with-import-from-aliased-dependencies/charts/child/Chart.yaml b/pkg/chart/v2/util/testdata/chart-with-import-from-aliased-dependencies/charts/child/Chart.yaml
new file mode 100644
index 000000000..ecdaf04dc
--- /dev/null
+++ b/pkg/chart/v2/util/testdata/chart-with-import-from-aliased-dependencies/charts/child/Chart.yaml
@@ -0,0 +1,12 @@
+apiVersion: v2
+appVersion: 1.0.0
+name: child
+type: application
+version: 1.0.0
+
+dependencies:
+ - name: grandchild
+ version: 1.0.0
+ import-values:
+ - parent: defaults
+ child: defaults
diff --git a/pkg/chart/v2/util/testdata/chart-with-import-from-aliased-dependencies/charts/child/charts/grandchild/Chart.yaml b/pkg/chart/v2/util/testdata/chart-with-import-from-aliased-dependencies/charts/child/charts/grandchild/Chart.yaml
new file mode 100644
index 000000000..50e620a8d
--- /dev/null
+++ b/pkg/chart/v2/util/testdata/chart-with-import-from-aliased-dependencies/charts/child/charts/grandchild/Chart.yaml
@@ -0,0 +1,6 @@
+apiVersion: v2
+appVersion: 1.0.0
+name: grandchild
+type: application
+version: 1.0.0
+
diff --git a/pkg/chart/v2/util/testdata/chart-with-import-from-aliased-dependencies/charts/child/charts/grandchild/values.yaml b/pkg/chart/v2/util/testdata/chart-with-import-from-aliased-dependencies/charts/child/charts/grandchild/values.yaml
new file mode 100644
index 000000000..f51c594f4
--- /dev/null
+++ b/pkg/chart/v2/util/testdata/chart-with-import-from-aliased-dependencies/charts/child/charts/grandchild/values.yaml
@@ -0,0 +1,2 @@
+defaults:
+ defaultValue: "42"
\ No newline at end of file
diff --git a/pkg/chart/v2/util/testdata/chart-with-import-from-aliased-dependencies/charts/child/templates/dummy.yaml b/pkg/chart/v2/util/testdata/chart-with-import-from-aliased-dependencies/charts/child/templates/dummy.yaml
new file mode 100644
index 000000000..3140f53dd
--- /dev/null
+++ b/pkg/chart/v2/util/testdata/chart-with-import-from-aliased-dependencies/charts/child/templates/dummy.yaml
@@ -0,0 +1,7 @@
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: {{ .Chart.Name }}
+data:
+ {{ .Values.defaults | toYaml }}
+
diff --git a/pkg/chart/v2/util/testdata/chart-with-import-from-aliased-dependencies/templates/dummy.yaml b/pkg/chart/v2/util/testdata/chart-with-import-from-aliased-dependencies/templates/dummy.yaml
new file mode 100644
index 000000000..a2b62c95a
--- /dev/null
+++ b/pkg/chart/v2/util/testdata/chart-with-import-from-aliased-dependencies/templates/dummy.yaml
@@ -0,0 +1,7 @@
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: {{ .Chart.Name }}
+data:
+ {{ toYaml .Values.defaults | indent 2 }}
+
diff --git a/pkg/chart/util/testdata/chartfiletest.yaml b/pkg/chart/v2/util/testdata/chartfiletest.yaml
similarity index 100%
rename from pkg/chart/util/testdata/chartfiletest.yaml
rename to pkg/chart/v2/util/testdata/chartfiletest.yaml
diff --git a/pkg/chart/util/testdata/coleridge.yaml b/pkg/chart/v2/util/testdata/coleridge.yaml
similarity index 100%
rename from pkg/chart/util/testdata/coleridge.yaml
rename to pkg/chart/v2/util/testdata/coleridge.yaml
diff --git a/pkg/chart/util/testdata/dependent-chart-alias/.helmignore b/pkg/chart/v2/util/testdata/dependent-chart-alias/.helmignore
similarity index 100%
rename from pkg/chart/util/testdata/dependent-chart-alias/.helmignore
rename to pkg/chart/v2/util/testdata/dependent-chart-alias/.helmignore
diff --git a/pkg/chart/util/testdata/dependent-chart-alias/Chart.lock b/pkg/chart/v2/util/testdata/dependent-chart-alias/Chart.lock
similarity index 100%
rename from pkg/chart/util/testdata/dependent-chart-alias/Chart.lock
rename to pkg/chart/v2/util/testdata/dependent-chart-alias/Chart.lock
diff --git a/pkg/chart/util/testdata/dependent-chart-alias/Chart.yaml b/pkg/chart/v2/util/testdata/dependent-chart-alias/Chart.yaml
similarity index 100%
rename from pkg/chart/util/testdata/dependent-chart-alias/Chart.yaml
rename to pkg/chart/v2/util/testdata/dependent-chart-alias/Chart.yaml
diff --git a/pkg/chart/util/testdata/dependent-chart-alias/INSTALL.txt b/pkg/chart/v2/util/testdata/dependent-chart-alias/INSTALL.txt
similarity index 100%
rename from pkg/chart/util/testdata/dependent-chart-alias/INSTALL.txt
rename to pkg/chart/v2/util/testdata/dependent-chart-alias/INSTALL.txt
diff --git a/pkg/chart/util/testdata/dependent-chart-alias/LICENSE b/pkg/chart/v2/util/testdata/dependent-chart-alias/LICENSE
similarity index 100%
rename from pkg/chart/util/testdata/dependent-chart-alias/LICENSE
rename to pkg/chart/v2/util/testdata/dependent-chart-alias/LICENSE
diff --git a/pkg/chart/util/testdata/dependent-chart-alias/README.md b/pkg/chart/v2/util/testdata/dependent-chart-alias/README.md
similarity index 100%
rename from pkg/chart/util/testdata/dependent-chart-alias/README.md
rename to pkg/chart/v2/util/testdata/dependent-chart-alias/README.md
diff --git a/pkg/chart/util/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/chart/util/testdata/dependent-chart-alias/charts/_ignore_me
rename to pkg/chart/v2/util/testdata/dependent-chart-alias/charts/_ignore_me
diff --git a/pkg/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/testdata/dependent-chart-alias/docs/README.md
rename to pkg/chart/v2/util/testdata/dependent-chart-alias/docs/README.md
diff --git a/pkg/chart/util/testdata/dependent-chart-alias/icon.svg b/pkg/chart/v2/util/testdata/dependent-chart-alias/icon.svg
similarity index 100%
rename from pkg/chart/util/testdata/dependent-chart-alias/icon.svg
rename to pkg/chart/v2/util/testdata/dependent-chart-alias/icon.svg
diff --git a/pkg/chart/util/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/chart/util/testdata/dependent-chart-alias/ignore/me.txt
rename to pkg/chart/v2/util/testdata/dependent-chart-alias/ignore/me.txt
diff --git a/pkg/chart/util/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/chart/util/testdata/dependent-chart-alias/templates/template.tpl
rename to pkg/chart/v2/util/testdata/dependent-chart-alias/templates/template.tpl
diff --git a/pkg/chart/util/testdata/dependent-chart-alias/values.yaml b/pkg/chart/v2/util/testdata/dependent-chart-alias/values.yaml
similarity index 100%
rename from pkg/chart/util/testdata/dependent-chart-alias/values.yaml
rename to pkg/chart/v2/util/testdata/dependent-chart-alias/values.yaml
diff --git a/pkg/chart/util/testdata/dependent-chart-helmignore/.helmignore b/pkg/chart/v2/util/testdata/dependent-chart-helmignore/.helmignore
similarity index 100%
rename from pkg/chart/util/testdata/dependent-chart-helmignore/.helmignore
rename to pkg/chart/v2/util/testdata/dependent-chart-helmignore/.helmignore
diff --git a/pkg/chart/util/testdata/dependent-chart-helmignore/Chart.yaml b/pkg/chart/v2/util/testdata/dependent-chart-helmignore/Chart.yaml
similarity index 100%
rename from pkg/chart/util/testdata/dependent-chart-helmignore/Chart.yaml
rename to pkg/chart/v2/util/testdata/dependent-chart-helmignore/Chart.yaml
diff --git a/pkg/chart/util/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/chart/util/testdata/dependent-chart-helmignore/charts/.ignore_me
rename to pkg/chart/v2/util/testdata/dependent-chart-helmignore/charts/.ignore_me
diff --git a/pkg/chart/util/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/chart/util/testdata/dependent-chart-helmignore/charts/_ignore_me
rename to pkg/chart/v2/util/testdata/dependent-chart-helmignore/charts/_ignore_me
diff --git a/pkg/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/testdata/dependent-chart-helmignore/templates/template.tpl
rename to pkg/chart/v2/util/testdata/dependent-chart-helmignore/templates/template.tpl
diff --git a/pkg/chart/util/testdata/dependent-chart-helmignore/values.yaml b/pkg/chart/v2/util/testdata/dependent-chart-helmignore/values.yaml
similarity index 100%
rename from pkg/chart/util/testdata/dependent-chart-helmignore/values.yaml
rename to pkg/chart/v2/util/testdata/dependent-chart-helmignore/values.yaml
diff --git a/pkg/chart/util/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/chart/util/testdata/dependent-chart-no-requirements-yaml/.helmignore
rename to pkg/chart/v2/util/testdata/dependent-chart-no-requirements-yaml/.helmignore
diff --git a/pkg/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/testdata/dependent-chart-no-requirements-yaml/LICENSE
rename to pkg/chart/v2/util/testdata/dependent-chart-no-requirements-yaml/LICENSE
diff --git a/pkg/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/testdata/frobnitz-1.2.3.tgz b/pkg/chart/v2/util/testdata/frobnitz-1.2.3.tgz
similarity index 100%
rename from pkg/chart/util/testdata/frobnitz-1.2.3.tgz
rename to pkg/chart/v2/util/testdata/frobnitz-1.2.3.tgz
diff --git a/pkg/chart/util/testdata/frobnitz/.helmignore b/pkg/chart/v2/util/testdata/frobnitz/.helmignore
similarity index 100%
rename from pkg/chart/util/testdata/frobnitz/.helmignore
rename to pkg/chart/v2/util/testdata/frobnitz/.helmignore
diff --git a/pkg/chart/util/testdata/frobnitz/Chart.lock b/pkg/chart/v2/util/testdata/frobnitz/Chart.lock
similarity index 100%
rename from pkg/chart/util/testdata/frobnitz/Chart.lock
rename to pkg/chart/v2/util/testdata/frobnitz/Chart.lock
diff --git a/pkg/chart/util/testdata/frobnitz/Chart.yaml b/pkg/chart/v2/util/testdata/frobnitz/Chart.yaml
similarity index 100%
rename from pkg/chart/util/testdata/frobnitz/Chart.yaml
rename to pkg/chart/v2/util/testdata/frobnitz/Chart.yaml
diff --git a/pkg/chart/util/testdata/frobnitz/INSTALL.txt b/pkg/chart/v2/util/testdata/frobnitz/INSTALL.txt
similarity index 100%
rename from pkg/chart/util/testdata/frobnitz/INSTALL.txt
rename to pkg/chart/v2/util/testdata/frobnitz/INSTALL.txt
diff --git a/pkg/chart/util/testdata/frobnitz/LICENSE b/pkg/chart/v2/util/testdata/frobnitz/LICENSE
similarity index 100%
rename from pkg/chart/util/testdata/frobnitz/LICENSE
rename to pkg/chart/v2/util/testdata/frobnitz/LICENSE
diff --git a/pkg/chart/util/testdata/frobnitz/README.md b/pkg/chart/v2/util/testdata/frobnitz/README.md
similarity index 100%
rename from pkg/chart/util/testdata/frobnitz/README.md
rename to pkg/chart/v2/util/testdata/frobnitz/README.md
diff --git a/pkg/chart/util/testdata/frobnitz/charts/_ignore_me b/pkg/chart/v2/util/testdata/frobnitz/charts/_ignore_me
similarity index 100%
rename from pkg/chart/util/testdata/frobnitz/charts/_ignore_me
rename to pkg/chart/v2/util/testdata/frobnitz/charts/_ignore_me
diff --git a/pkg/chart/util/testdata/frobnitz/charts/alpine/Chart.yaml b/pkg/chart/v2/util/testdata/frobnitz/charts/alpine/Chart.yaml
similarity index 100%
rename from pkg/chart/util/testdata/frobnitz/charts/alpine/Chart.yaml
rename to pkg/chart/v2/util/testdata/frobnitz/charts/alpine/Chart.yaml
diff --git a/pkg/chart/util/testdata/frobnitz/charts/alpine/README.md b/pkg/chart/v2/util/testdata/frobnitz/charts/alpine/README.md
similarity index 100%
rename from pkg/chart/util/testdata/frobnitz/charts/alpine/README.md
rename to pkg/chart/v2/util/testdata/frobnitz/charts/alpine/README.md
diff --git a/pkg/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/testdata/frobnitz/charts/alpine/values.yaml b/pkg/chart/v2/util/testdata/frobnitz/charts/alpine/values.yaml
similarity index 100%
rename from pkg/chart/util/testdata/frobnitz/charts/alpine/values.yaml
rename to pkg/chart/v2/util/testdata/frobnitz/charts/alpine/values.yaml
diff --git a/pkg/chart/util/testdata/frobnitz/charts/mariner/Chart.yaml b/pkg/chart/v2/util/testdata/frobnitz/charts/mariner/Chart.yaml
similarity index 100%
rename from pkg/chart/util/testdata/frobnitz/charts/mariner/Chart.yaml
rename to pkg/chart/v2/util/testdata/frobnitz/charts/mariner/Chart.yaml
diff --git a/pkg/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/testdata/frobnitz/charts/mariner/templates/placeholder.tpl
rename to pkg/chart/v2/util/testdata/frobnitz/charts/mariner/templates/placeholder.tpl
diff --git a/pkg/chart/util/testdata/frobnitz/charts/mariner/values.yaml b/pkg/chart/v2/util/testdata/frobnitz/charts/mariner/values.yaml
similarity index 100%
rename from pkg/chart/util/testdata/frobnitz/charts/mariner/values.yaml
rename to pkg/chart/v2/util/testdata/frobnitz/charts/mariner/values.yaml
diff --git a/pkg/chart/util/testdata/frobnitz/docs/README.md b/pkg/chart/v2/util/testdata/frobnitz/docs/README.md
similarity index 100%
rename from pkg/chart/util/testdata/frobnitz/docs/README.md
rename to pkg/chart/v2/util/testdata/frobnitz/docs/README.md
diff --git a/pkg/chart/util/testdata/frobnitz/icon.svg b/pkg/chart/v2/util/testdata/frobnitz/icon.svg
similarity index 100%
rename from pkg/chart/util/testdata/frobnitz/icon.svg
rename to pkg/chart/v2/util/testdata/frobnitz/icon.svg
diff --git a/pkg/chart/util/testdata/frobnitz/ignore/me.txt b/pkg/chart/v2/util/testdata/frobnitz/ignore/me.txt
similarity index 100%
rename from pkg/chart/util/testdata/frobnitz/ignore/me.txt
rename to pkg/chart/v2/util/testdata/frobnitz/ignore/me.txt
diff --git a/pkg/chart/util/testdata/frobnitz/templates/template.tpl b/pkg/chart/v2/util/testdata/frobnitz/templates/template.tpl
similarity index 100%
rename from pkg/chart/util/testdata/frobnitz/templates/template.tpl
rename to pkg/chart/v2/util/testdata/frobnitz/templates/template.tpl
diff --git a/pkg/chart/util/testdata/frobnitz/values.yaml b/pkg/chart/v2/util/testdata/frobnitz/values.yaml
similarity index 100%
rename from pkg/chart/util/testdata/frobnitz/values.yaml
rename to pkg/chart/v2/util/testdata/frobnitz/values.yaml
diff --git a/pkg/chart/util/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/chart/util/testdata/frobnitz_backslash-1.2.3.tgz
rename to pkg/chart/v2/util/testdata/frobnitz_backslash-1.2.3.tgz
diff --git a/pkg/chart/util/testdata/genfrob.sh b/pkg/chart/v2/util/testdata/genfrob.sh
similarity index 100%
rename from pkg/chart/util/testdata/genfrob.sh
rename to pkg/chart/v2/util/testdata/genfrob.sh
diff --git a/pkg/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/testdata/joonix/Chart.yaml b/pkg/chart/v2/util/testdata/joonix/Chart.yaml
similarity index 100%
rename from pkg/chart/util/testdata/joonix/Chart.yaml
rename to pkg/chart/v2/util/testdata/joonix/Chart.yaml
diff --git a/pkg/chart/util/testdata/joonix/charts/.gitkeep b/pkg/chart/v2/util/testdata/joonix/charts/.gitkeep
similarity index 100%
rename from pkg/chart/util/testdata/joonix/charts/.gitkeep
rename to pkg/chart/v2/util/testdata/joonix/charts/.gitkeep
diff --git a/pkg/chart/util/testdata/subpop/Chart.yaml b/pkg/chart/v2/util/testdata/subpop/Chart.yaml
similarity index 100%
rename from pkg/chart/util/testdata/subpop/Chart.yaml
rename to pkg/chart/v2/util/testdata/subpop/Chart.yaml
diff --git a/pkg/chart/util/testdata/subpop/README.md b/pkg/chart/v2/util/testdata/subpop/README.md
similarity index 100%
rename from pkg/chart/util/testdata/subpop/README.md
rename to pkg/chart/v2/util/testdata/subpop/README.md
diff --git a/pkg/chart/util/testdata/subpop/charts/subchart1/Chart.yaml b/pkg/chart/v2/util/testdata/subpop/charts/subchart1/Chart.yaml
similarity index 100%
rename from pkg/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/testdata/subpop/charts/subchart1/values.yaml b/pkg/chart/v2/util/testdata/subpop/charts/subchart1/values.yaml
similarity index 100%
rename from pkg/chart/util/testdata/subpop/charts/subchart1/values.yaml
rename to pkg/chart/v2/util/testdata/subpop/charts/subchart1/values.yaml
diff --git a/pkg/chart/util/testdata/subpop/charts/subchart2/Chart.yaml b/pkg/chart/v2/util/testdata/subpop/charts/subchart2/Chart.yaml
similarity index 100%
rename from pkg/chart/util/testdata/subpop/charts/subchart2/Chart.yaml
rename to pkg/chart/v2/util/testdata/subpop/charts/subchart2/Chart.yaml
diff --git a/pkg/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/testdata/subpop/charts/subchart2/values.yaml b/pkg/chart/v2/util/testdata/subpop/charts/subchart2/values.yaml
similarity index 100%
rename from pkg/chart/util/testdata/subpop/charts/subchart2/values.yaml
rename to pkg/chart/v2/util/testdata/subpop/charts/subchart2/values.yaml
diff --git a/pkg/chart/util/testdata/subpop/noreqs/Chart.yaml b/pkg/chart/v2/util/testdata/subpop/noreqs/Chart.yaml
similarity index 100%
rename from pkg/chart/util/testdata/subpop/noreqs/Chart.yaml
rename to pkg/chart/v2/util/testdata/subpop/noreqs/Chart.yaml
diff --git a/pkg/chart/util/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/chart/util/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/chart/util/testdata/subpop/noreqs/values.yaml b/pkg/chart/v2/util/testdata/subpop/noreqs/values.yaml
similarity index 100%
rename from pkg/chart/util/testdata/subpop/noreqs/values.yaml
rename to pkg/chart/v2/util/testdata/subpop/noreqs/values.yaml
diff --git a/pkg/chart/util/testdata/subpop/values.yaml b/pkg/chart/v2/util/testdata/subpop/values.yaml
similarity index 100%
rename from pkg/chart/util/testdata/subpop/values.yaml
rename to pkg/chart/v2/util/testdata/subpop/values.yaml
diff --git a/pkg/chart/util/testdata/test-values-invalid.schema.json b/pkg/chart/v2/util/testdata/test-values-invalid.schema.json
similarity index 100%
rename from pkg/chart/util/testdata/test-values-invalid.schema.json
rename to pkg/chart/v2/util/testdata/test-values-invalid.schema.json
diff --git a/pkg/chart/util/testdata/test-values-negative.yaml b/pkg/chart/v2/util/testdata/test-values-negative.yaml
similarity index 100%
rename from pkg/chart/util/testdata/test-values-negative.yaml
rename to pkg/chart/v2/util/testdata/test-values-negative.yaml
diff --git a/pkg/chart/util/testdata/test-values.schema.json b/pkg/chart/v2/util/testdata/test-values.schema.json
similarity index 100%
rename from pkg/chart/util/testdata/test-values.schema.json
rename to pkg/chart/v2/util/testdata/test-values.schema.json
diff --git a/pkg/chart/util/testdata/test-values.yaml b/pkg/chart/v2/util/testdata/test-values.yaml
similarity index 100%
rename from pkg/chart/util/testdata/test-values.yaml
rename to pkg/chart/v2/util/testdata/test-values.yaml
diff --git a/pkg/chart/util/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/chart/util/testdata/three-level-dependent-chart/README.md
rename to pkg/chart/v2/util/testdata/three-level-dependent-chart/README.md
diff --git a/pkg/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/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/chart/util/validate_name.go b/pkg/chart/v2/util/validate_name.go
similarity index 99%
rename from pkg/chart/util/validate_name.go
rename to pkg/chart/v2/util/validate_name.go
index 73be43303..6595e085d 100644
--- a/pkg/chart/util/validate_name.go
+++ b/pkg/chart/v2/util/validate_name.go
@@ -17,10 +17,9 @@ limitations under the License.
package util
import (
+ "errors"
"fmt"
"regexp"
-
- "github.com/pkg/errors"
)
// validName is a regular expression for resource names.
diff --git a/pkg/chart/util/validate_name_test.go b/pkg/chart/v2/util/validate_name_test.go
similarity index 100%
rename from pkg/chart/util/validate_name_test.go
rename to pkg/chart/v2/util/validate_name_test.go
diff --git a/pkg/chart/util/values.go b/pkg/chart/v2/util/values.go
similarity index 94%
rename from pkg/chart/util/values.go
rename to pkg/chart/v2/util/values.go
index 46952c079..6850e8b9b 100644
--- a/pkg/chart/util/values.go
+++ b/pkg/chart/v2/util/values.go
@@ -17,16 +17,15 @@ limitations under the License.
package util
import (
- "encoding/json"
+ "errors"
"fmt"
"io"
"os"
"strings"
- "github.com/pkg/errors"
"sigs.k8s.io/yaml"
- "helm.sh/helm/v4/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.
@@ -106,10 +105,7 @@ 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, func(d *json.Decoder) *json.Decoder {
- d.UseNumber()
- return d
- })
+ err = yaml.Unmarshal(data, &vals)
if len(vals) == 0 {
vals = Values{}
}
@@ -169,8 +165,7 @@ func ToRenderValuesWithSchemaValidation(chrt *chart.Chart, chrtVals map[string]i
if !skipSchemaValidation {
if err := ValidateAgainstSchema(chrt, vals); err != nil {
- errFmt := "values don't meet the specifications of the schema(s) in the following chart(s):\n%s"
- return top, fmt.Errorf(errFmt, err.Error())
+ return top, fmt.Errorf("values don't meet the specifications of the schema(s) in the following chart(s):\n%w", err)
}
}
diff --git a/pkg/chart/util/values_test.go b/pkg/chart/v2/util/values_test.go
similarity index 99%
rename from pkg/chart/util/values_test.go
rename to pkg/chart/v2/util/values_test.go
index 660b7e4ed..1a25fafb8 100644
--- a/pkg/chart/util/values_test.go
+++ b/pkg/chart/v2/util/values_test.go
@@ -22,7 +22,7 @@ import (
"testing"
"text/template"
- "helm.sh/helm/v4/pkg/chart"
+ chart "helm.sh/helm/v4/pkg/chart/v2"
)
func TestReadValues(t *testing.T) {
@@ -224,6 +224,7 @@ chapter:
}
func matchValues(t *testing.T, data map[string]interface{}) {
+ t.Helper()
if data["poet"] != "Coleridge" {
t.Errorf("Unexpected poet: %s", data["poet"])
}
diff --git a/pkg/cli/environment_test.go b/pkg/cli/environment_test.go
index 8a3b87936..52326eeff 100644
--- a/pkg/cli/environment_test.go
+++ b/pkg/cli/environment_test.go
@@ -38,7 +38,6 @@ func TestSetNamespace(t *testing.T) {
if settings.namespace != "testns" {
t.Errorf("Expected namespace testns, got %s", settings.namespace)
}
-
}
func TestEnvSettings(t *testing.T) {
@@ -126,7 +125,7 @@ func TestEnvSettings(t *testing.T) {
defer resetEnv()()
for k, v := range tt.envvars {
- os.Setenv(k, v)
+ t.Setenv(k, v)
}
flags := pflag.NewFlagSet("testing", pflag.ContinueOnError)
@@ -233,10 +232,7 @@ func TestEnvOrBool(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.env != "" {
- t.Cleanup(func() {
- os.Unsetenv(tt.env)
- })
- os.Setenv(tt.env, tt.val)
+ t.Setenv(tt.env, tt.val)
}
actual := envBoolOr(tt.env, tt.def)
if actual != tt.expected {
diff --git a/pkg/cli/output/output.go b/pkg/cli/output/output.go
index 01649c812..28d503741 100644
--- a/pkg/cli/output/output.go
+++ b/pkg/cli/output/output.go
@@ -22,7 +22,6 @@ import (
"io"
"github.com/gosuri/uitable"
- "github.com/pkg/errors"
"sigs.k8s.io/yaml"
)
@@ -107,7 +106,7 @@ func EncodeJSON(out io.Writer, obj interface{}) error {
enc := json.NewEncoder(out)
err := enc.Encode(obj)
if err != nil {
- return errors.Wrap(err, "unable to write JSON output")
+ return fmt.Errorf("unable to write JSON output: %w", err)
}
return nil
}
@@ -117,12 +116,12 @@ func EncodeJSON(out io.Writer, obj interface{}) error {
func EncodeYAML(out io.Writer, obj interface{}) error {
raw, err := yaml.Marshal(obj)
if err != nil {
- return errors.Wrap(err, "unable to write YAML output")
+ return fmt.Errorf("unable to write YAML output: %w", err)
}
_, err = out.Write(raw)
if err != nil {
- return errors.Wrap(err, "unable to write YAML output")
+ return fmt.Errorf("unable to write YAML output: %w", err)
}
return nil
}
@@ -134,7 +133,7 @@ func EncodeTable(out io.Writer, table *uitable.Table) error {
raw = append(raw, []byte("\n")...)
_, err := out.Write(raw)
if err != nil {
- return errors.Wrap(err, "unable to write table output")
+ return fmt.Errorf("unable to write table output: %w", err)
}
return nil
}
diff --git a/pkg/cli/values/options.go b/pkg/cli/values/options.go
index 2c3706b84..cd65fa885 100644
--- a/pkg/cli/values/options.go
+++ b/pkg/cli/values/options.go
@@ -19,14 +19,13 @@ package values
import (
"bytes"
"encoding/json"
+ "fmt"
"io"
"net/url"
"os"
"strings"
- "github.com/pkg/errors"
-
- "helm.sh/helm/v4/pkg/chart/loader"
+ "helm.sh/helm/v4/pkg/chart/v2/loader"
"helm.sh/helm/v4/pkg/getter"
"helm.sh/helm/v4/pkg/strvals"
)
@@ -54,7 +53,7 @@ func (opts *Options) MergeValues(p getter.Providers) (map[string]interface{}, er
}
currentMap, err := loader.LoadValues(bytes.NewReader(raw))
if err != nil {
- return nil, errors.Wrapf(err, "failed to parse %s", filePath)
+ return nil, fmt.Errorf("failed to parse %s: %w", filePath, err)
}
// Merge with the previous map
base = loader.MergeMaps(base, currentMap)
@@ -67,13 +66,13 @@ func (opts *Options) MergeValues(p getter.Providers) (map[string]interface{}, er
// 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, errors.Errorf("failed parsing --set-json data JSON: %s", value)
+ 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, errors.Errorf("failed parsing --set-json data %s", value)
+ return nil, fmt.Errorf("failed parsing --set-json data %s", value)
}
}
}
@@ -81,14 +80,14 @@ func (opts *Options) MergeValues(p getter.Providers) (map[string]interface{}, er
// User specified a value via --set
for _, value := range opts.Values {
if err := strvals.ParseInto(value, base); err != nil {
- return nil, errors.Wrap(err, "failed parsing --set data")
+ return nil, fmt.Errorf("failed parsing --set data: %w", err)
}
}
// User specified a value via --set-string
for _, value := range opts.StringValues {
if err := strvals.ParseIntoString(value, base); err != nil {
- return nil, errors.Wrap(err, "failed parsing --set-string data")
+ return nil, fmt.Errorf("failed parsing --set-string data: %w", err)
}
}
@@ -102,14 +101,14 @@ func (opts *Options) MergeValues(p getter.Providers) (map[string]interface{}, er
return string(bytes), err
}
if err := strvals.ParseIntoFile(value, base, reader); err != nil {
- return nil, errors.Wrap(err, "failed parsing --set-file data")
+ return nil, fmt.Errorf("failed parsing --set-file data: %w", err)
}
}
// User specified a value via --set-literal
for _, value := range opts.LiteralValues {
if err := strvals.ParseLiteralInto(value, base); err != nil {
- return nil, errors.Wrap(err, "failed parsing --set-literal data")
+ return nil, fmt.Errorf("failed parsing --set-literal data: %w", err)
}
}
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 5d2186939..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/v4/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 4dd427232..375a9a97d 100644
--- a/cmd/helm/completion_test.go
+++ b/pkg/cmd/completion_test.go
@@ -14,19 +14,20 @@ 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/v4/pkg/chart"
- "helm.sh/helm/v4/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'
func checkFileCompletion(t *testing.T, cmdName string, shouldBePerformed bool) {
+ t.Helper()
storage := storageFixture()
storage.Create(&release.Release{
Name: "myrelease",
@@ -64,6 +65,7 @@ func TestCompletionFileCompletion(t *testing.T) {
}
func checkReleaseCompletion(t *testing.T, cmdName string, multiReleasesAllowed bool) {
+ t.Helper()
multiReleaseTestGolden := "output/empty_nofile_comp.txt"
if multiReleasesAllowed {
multiReleaseTestGolden = "output/release_list_repeat_comp.txt"
diff --git a/cmd/helm/create.go b/pkg/cmd/create.go
similarity index 96%
rename from cmd/helm/create.go
rename to pkg/cmd/create.go
index a18f2c915..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,9 +23,9 @@ import (
"github.com/spf13/cobra"
- "helm.sh/helm/v4/cmd/helm/require"
- "helm.sh/helm/v4/pkg/chart"
- chartutil "helm.sh/helm/v4/pkg/chart/util"
+ 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"
)
diff --git a/cmd/helm/create_test.go b/pkg/cmd/create_test.go
similarity index 88%
rename from cmd/helm/create_test.go
rename to pkg/cmd/create_test.go
index 76fce804c..90ed90eff 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"
@@ -23,17 +23,16 @@ import (
"testing"
"helm.sh/helm/v4/internal/test/ensure"
- "helm.sh/helm/v4/pkg/chart"
- "helm.sh/helm/v4/pkg/chart/loader"
- chartutil "helm.sh/helm/v4/pkg/chart/util"
+ 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) {
+ t.Chdir(t.TempDir())
ensure.HelmHome(t)
cname := "testchart"
- dir := t.TempDir()
- defer testChdir(t, dir)()
// Run a create
if _, _, err := executeActionCommand("create " + cname); err != nil {
@@ -61,22 +60,20 @@ func TestCreateCmd(t *testing.T) {
}
func TestCreateStarterCmd(t *testing.T) {
+ t.Chdir(t.TempDir())
ensure.HelmHome(t)
cname := "testchart"
defer resetEnv()()
- os.MkdirAll(helmpath.CachePath(), 0755)
- defer testChdir(t, helmpath.CachePath())()
-
// Create a starter.
starterchart := helmpath.DataPath("starters")
- os.MkdirAll(starterchart, 0755)
+ os.MkdirAll(starterchart, 0o755)
if dest, err := chartutil.Create("starterchart", starterchart); err != nil {
t.Fatalf("Could not create chart: %s", err)
} else {
t.Logf("Created %s", dest)
}
tplpath := filepath.Join(starterchart, "starterchart", "templates", "foo.tpl")
- if err := os.WriteFile(tplpath, []byte("test"), 0644); err != nil {
+ if err := os.WriteFile(tplpath, []byte("test"), 0o644); err != nil {
t.Fatalf("Could not write template: %s", err)
}
@@ -105,7 +102,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)
}
@@ -122,30 +119,27 @@ func TestCreateStarterCmd(t *testing.T) {
if !found {
t.Error("Did not find foo.tpl")
}
-
}
func TestCreateStarterAbsoluteCmd(t *testing.T) {
+ t.Chdir(t.TempDir())
defer resetEnv()()
ensure.HelmHome(t)
cname := "testchart"
// Create a starter.
starterchart := helmpath.DataPath("starters")
- os.MkdirAll(starterchart, 0755)
+ os.MkdirAll(starterchart, 0o755)
if dest, err := chartutil.Create("starterchart", starterchart); err != nil {
t.Fatalf("Could not create chart: %s", err)
} else {
t.Logf("Created %s", dest)
}
tplpath := filepath.Join(starterchart, "starterchart", "templates", "foo.tpl")
- if err := os.WriteFile(tplpath, []byte("test"), 0644); err != nil {
+ if err := os.WriteFile(tplpath, []byte("test"), 0o644); err != nil {
t.Fatalf("Could not write template: %s", err)
}
- os.MkdirAll(helmpath.CachePath(), 0755)
- defer testChdir(t, helmpath.CachePath())()
-
starterChartPath := filepath.Join(starterchart, "starterchart")
// Run a create
@@ -173,7 +167,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 98%
rename from cmd/helm/dependency.go
rename to pkg/cmd/dependency.go
index df2bd9427..d2034fdd9 100644
--- a/cmd/helm/dependency.go
+++ b/pkg/cmd/dependency.go
@@ -13,7 +13,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-package main
+package cmd
import (
"io"
@@ -22,8 +22,8 @@ import (
"github.com/spf13/cobra"
"github.com/spf13/pflag"
- "helm.sh/helm/v4/cmd/helm/require"
"helm.sh/helm/v4/pkg/action"
+ "helm.sh/helm/v4/pkg/cmd/require"
)
const dependencyDesc = `
diff --git a/cmd/helm/dependency_build.go b/pkg/cmd/dependency_build.go
similarity index 98%
rename from cmd/helm/dependency_build.go
rename to pkg/cmd/dependency_build.go
index 719c720a7..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,8 +24,8 @@ import (
"github.com/spf13/cobra"
"k8s.io/client-go/util/homedir"
- "helm.sh/helm/v4/cmd/helm/require"
"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"
)
diff --git a/cmd/helm/dependency_build_test.go b/pkg/cmd/dependency_build_test.go
similarity index 98%
rename from cmd/helm/dependency_build_test.go
rename to pkg/cmd/dependency_build_test.go
index 76c01d911..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,7 +22,7 @@ import (
"strings"
"testing"
- chartutil "helm.sh/helm/v4/pkg/chart/util"
+ 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"
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 98%
rename from cmd/helm/dependency_update.go
rename to pkg/cmd/dependency_update.go
index 563d7eba5..921e5ef49 100644
--- a/cmd/helm/dependency_update.go
+++ b/pkg/cmd/dependency_update.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,8 +22,8 @@ import (
"github.com/spf13/cobra"
- "helm.sh/helm/v4/cmd/helm/require"
"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"
)
diff --git a/cmd/helm/dependency_update_test.go b/pkg/cmd/dependency_update_test.go
similarity index 97%
rename from cmd/helm/dependency_update_test.go
rename to pkg/cmd/dependency_update_test.go
index 0732ba7b5..9646c6816 100644
--- a/cmd/helm/dependency_update_test.go
+++ b/pkg/cmd/dependency_update_test.go
@@ -13,18 +13,20 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-package main
+package cmd
import (
+ "errors"
"fmt"
+ "io/fs"
"os"
"path/filepath"
"strings"
"testing"
"helm.sh/helm/v4/internal/test/ensure"
- "helm.sh/helm/v4/pkg/chart"
- chartutil "helm.sh/helm/v4/pkg/chart/util"
+ 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"
@@ -202,7 +204,7 @@ func TestDependencyUpdateCmd_DoNotDeleteOldChartsOnError(t *testing.T) {
// Make sure tmpcharts-x is deleted
tmpPath := filepath.Join(dir(chartname), fmt.Sprintf("tmpcharts-%d", os.Getpid()))
- if _, err := os.Stat(tmpPath); !os.IsNotExist(err) {
+ if _, err := os.Stat(tmpPath); !errors.Is(err, fs.ErrNotExist) {
t.Fatalf("tmpcharts dir still exists")
}
}
@@ -248,6 +250,7 @@ func TestDependencyUpdateCmd_WithRepoThatWasNotAdded(t *testing.T) {
}
func setupMockRepoServer(t *testing.T) *repotest.Server {
+ t.Helper()
srv := repotest.NewTempServer(
t,
repotest.WithChartSourceGlob("testdata/testcharts/*.tgz"),
diff --git a/cmd/helm/docs.go b/pkg/cmd/docs.go
similarity index 93%
rename from cmd/helm/docs.go
rename to pkg/cmd/docs.go
index 571840b5f..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"
@@ -22,13 +22,12 @@ import (
"path/filepath"
"strings"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/cobra/doc"
"golang.org/x/text/cases"
"golang.org/x/text/language"
- "helm.sh/helm/v4/cmd/helm/require"
+ "helm.sh/helm/v4/pkg/cmd/require"
)
const docsDesc = `
@@ -86,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)
}
@@ -99,6 +98,6 @@ func (o *docsOptions) run(_ io.Writer) error {
case "bash":
return o.topCmd.GenBashCompletionFile(filepath.Join(o.dest, "completions.bash"))
default:
- return errors.Errorf("unknown doc type %q. Try 'markdown' or 'man'", o.docTypeString)
+ return fmt.Errorf("unknown doc type %q. Try 'markdown' or 'man'", o.docTypeString)
}
}
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 c7305434b..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/v4/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 83%
rename from cmd/helm/flags.go
rename to pkg/cmd/flags.go
index 8d0f644d6..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"
@@ -32,6 +33,7 @@ import (
"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"
)
@@ -51,6 +53,52 @@ func addValueOptionsFlags(f *pflag.FlagSet, v *values.Options) {
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 295f55022..cbc2e6419 100644
--- a/cmd/helm/flags_test.go
+++ b/pkg/cmd/flags_test.go
@@ -14,18 +14,22 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-package main
+package cmd
import (
"fmt"
"testing"
- "helm.sh/helm/v4/pkg/chart"
- "helm.sh/helm/v4/pkg/release"
+ "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) {
+ t.Helper()
releasesMockWithStatus := func(info *release.Info, hooks ...*release.Hook) []*release.Release {
info.LastDeployed = helmtime.Unix(1452902400, 0).UTC()
return []*release.Release{{
@@ -93,3 +97,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 96%
rename from cmd/helm/get.go
rename to pkg/cmd/get.go
index 27d536f8b..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/v4/cmd/helm/require"
"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 97%
rename from cmd/helm/get_all.go
rename to pkg/cmd/get_all.go
index 522327b67..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/v4/cmd/helm/require"
"helm.sh/helm/v4/pkg/action"
"helm.sh/helm/v4/pkg/cli/output"
+ "helm.sh/helm/v4/pkg/cmd/require"
)
var getAllHelp = `
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 60ea1161d..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/v4/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 97%
rename from cmd/helm/get_hooks.go
rename to pkg/cmd/get_hooks.go
index 25c6eb4e3..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/v4/cmd/helm/require"
"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 75cf448cc..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/v4/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 97%
rename from cmd/helm/get_manifest.go
rename to pkg/cmd/get_manifest.go
index d2a4bbe96..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/v4/cmd/helm/require"
"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 b266620e7..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/v4/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 98%
rename from cmd/helm/get_metadata.go
rename to pkg/cmd/get_metadata.go
index 4ab0c8cab..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"
@@ -24,9 +24,9 @@ import (
"github.com/spf13/cobra"
k8sLabels "k8s.io/apimachinery/pkg/labels"
- "helm.sh/helm/v4/cmd/helm/require"
"helm.sh/helm/v4/pkg/action"
"helm.sh/helm/v4/pkg/cli/output"
+ "helm.sh/helm/v4/pkg/cmd/require"
)
type metadataWriter struct {
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 28c3c3649..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/v4/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 97%
rename from cmd/helm/get_notes.go
rename to pkg/cmd/get_notes.go
index 8a8e67079..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/v4/cmd/helm/require"
"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 4ddd4c7a8..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/v4/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 98%
rename from cmd/helm/get_values.go
rename to pkg/cmd/get_values.go
index 8244fbaaa..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/v4/cmd/helm/require"
"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 44610c103..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/v4/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..8c06db4ae
--- /dev/null
+++ b/pkg/cmd/helpers_test.go
@@ -0,0 +1,151 @@
+/*
+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, SetupLogging)
+ 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()
+ }
+}
diff --git a/cmd/helm/history.go b/pkg/cmd/history.go
similarity index 96%
rename from cmd/helm/history.go
rename to pkg/cmd/history.go
index 91d005e7a..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,12 +25,12 @@ import (
"github.com/gosuri/uitable"
"github.com/spf13/cobra"
- "helm.sh/helm/v4/cmd/helm/require"
"helm.sh/helm/v4/pkg/action"
- "helm.sh/helm/v4/pkg/chart"
+ chart "helm.sh/helm/v4/pkg/chart/v2"
"helm.sh/helm/v4/pkg/cli/output"
- "helm.sh/helm/v4/pkg/release"
- "helm.sh/helm/v4/pkg/releaseutil"
+ "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"
)
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 07f5134c6..d26ed9ecf 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/v4/pkg/release"
+ release "helm.sh/helm/v4/pkg/release/v1"
)
func TestHistoryCmd(t *testing.T) {
@@ -75,6 +75,7 @@ func TestHistoryOutputCompletion(t *testing.T) {
}
func revisionFlagCompletionTest(t *testing.T, cmdName string) {
+ t.Helper()
mk := func(name string, vers int, status release.Status) *release.Release {
return release.Mock(&release.MockReleaseOptions{
Name: name,
diff --git a/cmd/helm/install.go b/pkg/cmd/install.go
similarity index 91%
rename from cmd/helm/install.go
rename to pkg/cmd/install.go
index fe09dfc53..3496a4bbd 100644
--- a/cmd/helm/install.go
+++ b/pkg/cmd/install.go
@@ -14,31 +14,33 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-package main
+package cmd
import (
"context"
+ "errors"
"fmt"
"io"
"log"
+ "log/slog"
"os"
"os/signal"
+ "slices"
"syscall"
"time"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
- "helm.sh/helm/v4/cmd/helm/require"
"helm.sh/helm/v4/pkg/action"
- "helm.sh/helm/v4/pkg/chart"
- "helm.sh/helm/v4/pkg/chart/loader"
+ 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"
- "helm.sh/helm/v4/pkg/release"
+ release "helm.sh/helm/v4/pkg/release/v1"
)
const installDesc = `
@@ -158,7 +160,7 @@ func newInstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
}
rel, err := runInstall(args, client, valueOpts, out)
if err != nil {
- return errors.Wrap(err, "INSTALLATION FAILED")
+ return fmt.Errorf("INSTALLATION FAILED: %w", err)
}
return outfmt.Write(out, &statusPrinter{
@@ -194,7 +196,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")
@@ -202,7 +203,7 @@ 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")
@@ -212,6 +213,7 @@ func addInstallFlags(cmd *cobra.Command, f *pflag.FlagSet, client *action.Instal
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
@@ -229,9 +231,9 @@ func addInstallFlags(cmd *cobra.Command, f *pflag.FlagSet, client *action.Instal
}
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"
}
@@ -241,12 +243,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)
@@ -265,7 +267,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 {
@@ -273,12 +275,11 @@ func runInstall(args []string, client *action.Install, valueOpts *values.Options
// As of Helm 2.4.0, this is treated as a stopping condition:
// https://github.com/helm/helm/issues/2209
if err := action.CheckDependencies(chartRequested, req); err != nil {
- err = errors.Wrap(err, "An error occurred while checking for chart dependencies. You may need to run `helm dependency build` to fetch missing dependencies")
if client.DependencyUpdate {
man := &downloader.Manager{
Out: out,
ChartPath: cp,
- Keyring: client.ChartPathOptions.Keyring,
+ Keyring: client.Keyring,
SkipUpdate: false,
Getters: p,
RepositoryConfig: settings.RepositoryConfig,
@@ -291,10 +292,10 @@ func runInstall(args []string, client *action.Install, valueOpts *values.Options
}
// Reload the chart with the updated Chart.lock file.
if chartRequested, err = loader.Load(cp); err != nil {
- return nil, errors.Wrap(err, "failed reloading chart after repo update")
+ return nil, fmt.Errorf("failed reloading chart after repo update: %w", err)
}
} else {
- return nil, err
+ return nil, fmt.Errorf("an error occurred while checking for chart dependencies. You may need to run `helm dependency build` to fetch missing dependencies: %w", err)
}
}
}
@@ -332,7 +333,7 @@ func checkIfInstallable(ch *chart.Chart) error {
case "", "application":
return nil
}
- return errors.Errorf("%s charts are not installable", ch.Metadata.Type)
+ return fmt.Errorf("%s charts are not installable", ch.Metadata.Type)
}
// Provide dynamic auto-completion for the install and template commands
@@ -350,15 +351,9 @@ func compInstall(args []string, toComplete string, client *action.Install) ([]st
func validateDryRunOptionFlag(dryRunOptionFlagValue string) error {
// Validate dry-run flag value with a set of allowed value
allowedDryRunValues := []string{"false", "true", "none", "client", "server"}
- isAllowed := false
- for _, v := range allowedDryRunValues {
- if dryRunOptionFlagValue == v {
- isAllowed = true
- break
- }
- }
+ isAllowed := slices.Contains(allowedDryRunValues, dryRunOptionFlagValue)
if !isAllowed {
- return errors.New("Invalid dry-run flag. Flag must one of the following: false, true, none, client, server")
+ return errors.New("invalid dry-run flag. Flag must one of the following: false, true, none, client, server")
}
return nil
}
diff --git a/cmd/helm/install_test.go b/pkg/cmd/install_test.go
similarity index 99%
rename from cmd/helm/install_test.go
rename to pkg/cmd/install_test.go
index be8480423..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"
diff --git a/cmd/helm/lint.go b/pkg/cmd/lint.go
similarity index 98%
rename from cmd/helm/lint.go
rename to pkg/cmd/lint.go
index 3e37922b2..78083a7ea 100644
--- a/cmd/helm/lint.go
+++ b/pkg/cmd/lint.go
@@ -14,20 +14,20 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-package main
+package cmd
import (
+ "errors"
"fmt"
"io"
"os"
"path/filepath"
"strings"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
"helm.sh/helm/v4/pkg/action"
- chartutil "helm.sh/helm/v4/pkg/chart/util"
+ 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"
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 67da22cdf..5af43adad 100644
--- a/cmd/helm/list.go
+++ b/pkg/cmd/list.go
@@ -14,21 +14,22 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-package main
+package cmd
import (
"fmt"
"io"
"os"
+ "slices"
"strconv"
"github.com/gosuri/uitable"
"github.com/spf13/cobra"
- "helm.sh/helm/v4/cmd/helm/require"
"helm.sh/helm/v4/pkg/action"
"helm.sh/helm/v4/pkg/cli/output"
- "helm.sh/helm/v4/pkg/release"
+ "helm.sh/helm/v4/pkg/cmd/require"
+ release "helm.sh/helm/v4/pkg/release/v1"
)
var listHelp = `
@@ -71,7 +72,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
}
}
@@ -203,13 +204,7 @@ func filterReleases(releases []*release.Release, ignoredReleaseNames []string) [
var filteredReleases []*release.Release
for _, rel := range releases {
- found := false
- for _, ignoredName := range ignoredReleaseNames {
- if rel.Name == ignoredName {
- found = true
- break
- }
- }
+ found := slices.Contains(ignoredReleaseNames, rel.Name)
if !found {
filteredReleases = append(filteredReleases, rel)
}
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 01b6d7490..82b25a768 100644
--- a/cmd/helm/list_test.go
+++ b/pkg/cmd/list_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 (
"testing"
- "helm.sh/helm/v4/pkg/chart"
- "helm.sh/helm/v4/pkg/release"
+ chart "helm.sh/helm/v4/pkg/chart/v2"
+ release "helm.sh/helm/v4/pkg/release/v1"
"helm.sh/helm/v4/pkg/time"
)
diff --git a/cmd/helm/load_plugins.go b/pkg/cmd/load_plugins.go
similarity index 97%
rename from cmd/helm/load_plugins.go
rename to pkg/cmd/load_plugins.go
index 23b1b7ff4..385990d82 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"
@@ -23,11 +23,11 @@ import (
"os"
"os/exec"
"path/filepath"
+ "slices"
"strconv"
"strings"
"syscall"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
"sigs.k8s.io/yaml"
@@ -39,9 +39,9 @@ const (
pluginDynamicCompletionExecutable = "plugin.complete"
)
-type pluginError struct {
+type PluginError struct {
error
- code int
+ Code int
}
// loadPlugins loads plugins into the command list.
@@ -50,7 +50,6 @@ type pluginError struct {
// to inspect its environment and then add commands to the base command
// as it finds them.
func loadPlugins(baseCmd *cobra.Command, out io.Writer) {
-
// If HELM_NO_PLUGINS is set to 1, do not load plugins.
if os.Getenv("HELM_NO_PLUGINS") == "1" {
return
@@ -87,7 +86,7 @@ func loadPlugins(baseCmd *cobra.Command, out io.Writer) {
main, argv, prepCmdErr := plug.PrepareCommand(u)
if prepCmdErr != nil {
os.Stderr.WriteString(prepCmdErr.Error())
- return errors.Errorf("plugin %q exited with error", md.Name)
+ return fmt.Errorf("plugin %q exited with error", md.Name)
}
return callPluginExecutable(md.Name, main, argv, out)
@@ -138,9 +137,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{
- error: errors.Errorf("plugin %q exited with error", pluginName),
- code: status.ExitStatus(),
+ return PluginError{
+ error: fmt.Errorf("plugin %q exited with error", pluginName),
+ Code: status.ExitStatus(),
}
}
return err
@@ -165,10 +164,8 @@ func manuallyProcessArgs(args []string) ([]string, []string) {
}
isKnown := func(v string) string {
- for _, i := range kvargs {
- if i == v {
- return v
- }
+ if slices.Contains(kvargs, v) {
+ return v
}
return ""
}
diff --git a/cmd/helm/package.go b/pkg/cmd/package.go
similarity index 97%
rename from cmd/helm/package.go
rename to pkg/cmd/package.go
index 185442b20..40c503222 100644
--- a/cmd/helm/package.go
+++ b/pkg/cmd/package.go
@@ -14,15 +14,15 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-package main
+package cmd
import (
+ "errors"
"fmt"
"io"
"os"
"path/filepath"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
"helm.sh/helm/v4/pkg/action"
@@ -57,7 +57,7 @@ func newPackageCmd(out io.Writer) *cobra.Command {
Long: packageDesc,
RunE: func(_ *cobra.Command, args []string) error {
if len(args) == 0 {
- return errors.Errorf("need at least one argument, the path to the chart")
+ return fmt.Errorf("need at least one argument, the path to the chart")
}
if client.Sign {
if client.Key == "" {
diff --git a/cmd/helm/package_test.go b/pkg/cmd/package_test.go
similarity index 95%
rename from cmd/helm/package_test.go
rename to pkg/cmd/package_test.go
index 107928765..db4a2523a 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,9 @@ import (
"strings"
"testing"
- "helm.sh/helm/v4/pkg/chart"
- "helm.sh/helm/v4/pkg/chart/loader"
+ "helm.sh/helm/v4/internal/test/ensure"
+ chart "helm.sh/helm/v4/pkg/chart/v2"
+ "helm.sh/helm/v4/pkg/chart/v2/loader"
)
func TestPackage(t *testing.T) {
@@ -110,10 +111,10 @@ func TestPackage(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- cachePath := t.TempDir()
- defer testChdir(t, cachePath)()
+ t.Chdir(t.TempDir())
+ ensure.HelmHome(t)
- if err := os.MkdirAll("toot", 0777); err != nil {
+ if err := os.MkdirAll("toot", 0o777); err != nil {
t.Fatal(err)
}
diff --git a/cmd/helm/plugin.go b/pkg/cmd/plugin.go
similarity index 90%
rename from cmd/helm/plugin.go
rename to pkg/cmd/plugin.go
index 82fd34b72..a2bb838df 100644
--- a/cmd/helm/plugin.go
+++ b/pkg/cmd/plugin.go
@@ -13,14 +13,15 @@ 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/pkg/errors"
"github.com/spf13/cobra"
"helm.sh/helm/v4/pkg/plugin"
@@ -66,13 +67,13 @@ func runHook(p *plugin.Plugin, event string) error {
prog := exec.Command(main, argv...)
- debug("running %s hook: %s", event, prog)
+ slog.Debug("running hook", "event", event, "program", prog)
prog.Stdout, prog.Stderr = os.Stdout, os.Stderr
if err := prog.Run(); err != nil {
if eerr, ok := err.(*exec.ExitError); ok {
os.Stderr.Write(eerr.Stderr)
- return errors.Errorf("plugin %s hook for %q exited with error", event, p.Metadata.Name)
+ return fmt.Errorf("plugin %s hook for %q exited with error", event, p.Metadata.Name)
}
return err
}
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 d8dff6316..945bf8ee0 100644
--- a/cmd/helm/plugin_install.go
+++ b/pkg/cmd/plugin_install.go
@@ -13,16 +13,16 @@ 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/pkg/errors"
"github.com/spf13/cobra"
- "helm.sh/helm/v4/cmd/helm/require"
+ "helm.sh/helm/v4/pkg/cmd/require"
"helm.sh/helm/v4/pkg/plugin"
"helm.sh/helm/v4/pkg/plugin/installer"
)
@@ -79,10 +79,10 @@ 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 errors.Wrap(err, "plugin is installed but unusable")
+ return fmt.Errorf("plugin is installed but unusable: %w", err)
}
if err := runHook(p, plugin.Install); err != nil {
diff --git a/cmd/helm/plugin_list.go b/pkg/cmd/plugin_list.go
similarity index 91%
rename from cmd/helm/plugin_list.go
rename to pkg/cmd/plugin_list.go
index 27ce3c973..5bb9ff68d 100644
--- a/cmd/helm/plugin_list.go
+++ b/pkg/cmd/plugin_list.go
@@ -13,11 +13,13 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-package main
+package cmd
import (
"fmt"
"io"
+ "log/slog"
+ "slices"
"github.com/gosuri/uitable"
"github.com/spf13/cobra"
@@ -32,7 +34,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
@@ -59,13 +61,7 @@ func filterPlugins(plugins []*plugin.Plugin, ignoredPluginNames []string) []*plu
var filteredPlugins []*plugin.Plugin
for _, plugin := range plugins {
- found := false
- for _, ignoredName := range ignoredPluginNames {
- if plugin.Metadata.Name == ignoredName {
- found = true
- break
- }
- }
+ found := slices.Contains(ignoredPluginNames, plugin.Metadata.Name)
if !found {
filteredPlugins = append(filteredPlugins, plugin)
}
diff --git a/cmd/helm/plugin_test.go b/pkg/cmd/plugin_test.go
similarity index 97%
rename from cmd/helm/plugin_test.go
rename to pkg/cmd/plugin_test.go
index 4d2aa1a59..74f7a276a 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/v4/pkg/release"
+ release "helm.sh/helm/v4/pkg/release/v1"
)
func TestManuallyProcessArgs(t *testing.T) {
@@ -79,7 +79,6 @@ func TestManuallyProcessArgs(t *testing.T) {
t.Errorf("expected unknown flag %d to be %q, got %q", i, expectUnknown[i], k)
}
}
-
}
func TestLoadPlugins(t *testing.T) {
@@ -143,12 +142,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 +217,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)
@@ -276,6 +275,7 @@ func TestLoadPluginsForCompletion(t *testing.T) {
}
func checkCommand(t *testing.T, plugins []*cobra.Command, tests []staticCompletionDetails) {
+ t.Helper()
if len(plugins) != len(tests) {
t.Fatalf("Expected commands %v, got %v", tests, plugins)
}
@@ -326,7 +326,6 @@ func checkCommand(t *testing.T, plugins []*cobra.Command, tests []staticCompleti
}
func TestPluginDynamicCompletion(t *testing.T) {
-
tests := []cmdTestCase{{
name: "completion for plugin",
cmd: "__complete args ''",
@@ -363,7 +362,7 @@ func TestLoadPlugins_HelmNoPlugins(t *testing.T) {
settings.PluginsDirectory = "testdata/helmhome/helm/plugins"
settings.RepositoryConfig = "testdata/helmhome/helm/repository"
- os.Setenv("HELM_NO_PLUGINS", "1")
+ t.Setenv("HELM_NO_PLUGINS", "1")
out := bytes.NewBuffer(nil)
cmd := &cobra.Command{}
@@ -376,7 +375,6 @@ func TestLoadPlugins_HelmNoPlugins(t *testing.T) {
}
func TestPluginCmdsCompletion(t *testing.T) {
-
tests := []cmdTestCase{{
name: "completion for plugin update",
cmd: "__complete plugin update ''",
diff --git a/cmd/helm/plugin_uninstall.go b/pkg/cmd/plugin_uninstall.go
similarity index 85%
rename from cmd/helm/plugin_uninstall.go
rename to pkg/cmd/plugin_uninstall.go
index 6ef4e4f59..ec73ad6df 100644
--- a/cmd/helm/plugin_uninstall.go
+++ b/pkg/cmd/plugin_uninstall.go
@@ -13,15 +13,15 @@ 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"
- "strings"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
"helm.sh/helm/v4/pkg/plugin"
@@ -60,25 +60,25 @@ 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
}
- var errorPlugins []string
+ var errorPlugins []error
for _, name := range o.names {
if found := findPlugin(plugins, name); found != nil {
if err := uninstallPlugin(found); err != nil {
- errorPlugins = append(errorPlugins, fmt.Sprintf("Failed to uninstall plugin %s, got error (%v)", name, err))
+ errorPlugins = append(errorPlugins, fmt.Errorf("failed to uninstall plugin %s, got error (%v)", name, err))
} else {
fmt.Fprintf(out, "Uninstalled plugin: %s\n", name)
}
} else {
- errorPlugins = append(errorPlugins, fmt.Sprintf("Plugin: %s not found", name))
+ errorPlugins = append(errorPlugins, fmt.Errorf("plugin: %s not found", name))
}
}
if len(errorPlugins) > 0 {
- return errors.New(strings.Join(errorPlugins, "\n"))
+ return errors.Join(errorPlugins...)
}
return nil
}
diff --git a/cmd/helm/plugin_update.go b/pkg/cmd/plugin_update.go
similarity index 85%
rename from cmd/helm/plugin_update.go
rename to pkg/cmd/plugin_update.go
index 5d0465274..59d884877 100644
--- a/cmd/helm/plugin_update.go
+++ b/pkg/cmd/plugin_update.go
@@ -13,15 +13,15 @@ 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"
- "strings"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
"helm.sh/helm/v4/pkg/plugin"
@@ -62,26 +62,26 @@ 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
}
- var errorPlugins []string
+ var errorPlugins []error
for _, name := range o.names {
if found := findPlugin(plugins, name); found != nil {
if err := updatePlugin(found); err != nil {
- errorPlugins = append(errorPlugins, fmt.Sprintf("Failed to update plugin %s, got error (%v)", name, err))
+ errorPlugins = append(errorPlugins, fmt.Errorf("failed to update plugin %s, got error (%v)", name, err))
} else {
fmt.Fprintf(out, "Updated plugin: %s\n", name)
}
} else {
- errorPlugins = append(errorPlugins, fmt.Sprintf("Plugin: %s not found", name))
+ errorPlugins = append(errorPlugins, fmt.Errorf("plugin: %s not found", name))
}
}
if len(errorPlugins) > 0 {
- return errors.New(strings.Join(errorPlugins, "\n"))
+ return errors.Join(errorPlugins...)
}
return nil
}
@@ -104,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/cmd/helm/profiling.go b/pkg/cmd/profiling.go
similarity index 99%
rename from cmd/helm/profiling.go
rename to pkg/cmd/profiling.go
index 950ad15da..45e7b9342 100644
--- a/cmd/helm/profiling.go
+++ b/pkg/cmd/profiling.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"
diff --git a/cmd/helm/pull.go b/pkg/cmd/pull.go
similarity index 97%
rename from cmd/helm/pull.go
rename to pkg/cmd/pull.go
index 231db30bc..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/v4/cmd/helm/require"
"helm.sh/helm/v4/pkg/action"
+ "helm.sh/helm/v4/pkg/cmd/require"
)
const pullDesc = `
@@ -60,7 +61,7 @@ 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"
}
diff --git a/cmd/helm/pull_test.go b/pkg/cmd/pull_test.go
similarity index 99%
rename from cmd/helm/pull_test.go
rename to pkg/cmd/pull_test.go
index 1110a6bdf..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"
diff --git a/cmd/helm/push.go b/pkg/cmd/push.go
similarity index 98%
rename from cmd/helm/push.go
rename to pkg/cmd/push.go
index f5b275b9d..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,8 +22,8 @@ import (
"github.com/spf13/cobra"
- "helm.sh/helm/v4/cmd/helm/require"
"helm.sh/helm/v4/pkg/action"
+ "helm.sh/helm/v4/pkg/cmd/require"
"helm.sh/helm/v4/pkg/pusher"
)
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 98%
rename from cmd/helm/registry.go
rename to pkg/cmd/registry.go
index f771dcb9c..fcd06f13b 100644
--- a/cmd/helm/registry.go
+++ b/pkg/cmd/registry.go
@@ -13,7 +13,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/registry_login.go b/pkg/cmd/registry_login.go
similarity index 94%
rename from cmd/helm/registry_login.go
rename to pkg/cmd/registry_login.go
index 74ad4cebe..1350fb244 100644
--- a/cmd/helm/registry_login.go
+++ b/pkg/cmd/registry_login.go
@@ -14,25 +14,30 @@ 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/v4/cmd/helm/require"
"helm.sh/helm/v4/pkg/action"
+ "helm.sh/helm/v4/pkg/cmd/require"
)
const registryLoginDesc = `
Authenticate to a remote registry.
+
+For example for Github Container Registry:
+
+ echo "$GITHUB_TOKEN" | helm registry login ghcr.io -u $GITHUB_USER --password-stdin
`
type registryLoginOptions struct {
@@ -122,7 +127,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 96%
rename from cmd/helm/registry_logout.go
rename to pkg/cmd/registry_logout.go
index 13190c8cf..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/v4/cmd/helm/require"
"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 97%
rename from cmd/helm/release_testing.go
rename to pkg/cmd/release_testing.go
index a8c57f5d9..1dac28534 100644
--- a/cmd/helm/release_testing.go
+++ b/pkg/cmd/release_testing.go
@@ -14,9 +14,10 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-package main
+package cmd
import (
+ "errors"
"fmt"
"io"
"regexp"
@@ -25,9 +26,9 @@ import (
"github.com/spf13/cobra"
- "helm.sh/helm/v4/cmd/helm/require"
"helm.sh/helm/v4/pkg/action"
"helm.sh/helm/v4/pkg/cli/output"
+ "helm.sh/helm/v4/pkg/cmd/require"
)
const releaseTestHelp = `
@@ -85,7 +86,7 @@ func newReleaseTestCmd(cfg *action.Configuration, out io.Writer) *cobra.Command
// Print a newline to stdout to separate the output
fmt.Fprintln(out)
if err := client.GetPodLogs(out, rel); err != nil {
- return err
+ return errors.Join(runErr, 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 91%
rename from cmd/helm/repo.go
rename to pkg/cmd/repo.go
index 291f0bb10..0dc2a7175 100644
--- a/cmd/helm/repo.go
+++ b/pkg/cmd/repo.go
@@ -14,16 +14,16 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-package main
+package cmd
import (
+ "errors"
"io"
- "os"
+ "io/fs"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
- "helm.sh/helm/v4/cmd/helm/require"
+ "helm.sh/helm/v4/pkg/cmd/require"
)
var repoHelm = `
@@ -50,5 +50,5 @@ func newRepoCmd(out io.Writer) *cobra.Command {
}
func isNotExist(err error) bool {
- return os.IsNotExist(errors.Cause(err))
+ return errors.Is(err, fs.ErrNotExist)
}
diff --git a/cmd/helm/repo_add.go b/pkg/cmd/repo_add.go
similarity index 89%
rename from cmd/helm/repo_add.go
rename to pkg/cmd/repo_add.go
index cd3dc8a62..187234486 100644
--- a/cmd/helm/repo_add.go
+++ b/pkg/cmd/repo_add.go
@@ -14,24 +14,25 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-package main
+package cmd
import (
"context"
+ "errors"
"fmt"
"io"
+ "io/fs"
"os"
"path/filepath"
"strings"
"time"
"github.com/gofrs/flock"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
"golang.org/x/term"
"sigs.k8s.io/yaml"
- "helm.sh/helm/v4/cmd/helm/require"
+ "helm.sh/helm/v4/pkg/cmd/require"
"helm.sh/helm/v4/pkg/getter"
"helm.sh/helm/v4/pkg/repo"
)
@@ -51,6 +52,7 @@ type repoAddOptions struct {
passCredentialsAll bool
forceUpdate bool
allowDeprecatedRepos bool
+ timeout time.Duration
certFile string
keyFile string
@@ -95,6 +97,7 @@ func newRepoAddCmd(out io.Writer) *cobra.Command {
f.BoolVar(&o.insecureSkipTLSverify, "insecure-skip-tls-verify", false, "skip tls certificate checks for the repository")
f.BoolVar(&o.allowDeprecatedRepos, "allow-deprecated-repos", false, "by default, this command will not allow adding official repos that have been permanently deleted. This disables that behavior")
f.BoolVar(&o.passCredentialsAll, "pass-credentials", false, "pass credentials to all domains")
+ f.DurationVar(&o.timeout, "timeout", getter.DefaultHTTPTimeout*time.Second, "time to wait for the index file download to complete")
return cmd
}
@@ -135,7 +138,7 @@ func (o *repoAddOptions) run(out io.Writer) error {
}
b, err := os.ReadFile(o.repoFile)
- if err != nil && !os.IsNotExist(err) {
+ if err != nil && !errors.Is(err, fs.ErrNotExist) {
return err
}
@@ -179,7 +182,7 @@ func (o *repoAddOptions) run(out io.Writer) error {
// Check if the repo name is legal
if strings.Contains(o.name, "/") {
- return errors.Errorf("repository name (%s) contains '/', please specify a different name without '/'", o.name)
+ return fmt.Errorf("repository name (%s) contains '/', please specify a different name without '/'", o.name)
}
// If the repo exists do one of two things:
@@ -188,10 +191,9 @@ func (o *repoAddOptions) run(out io.Writer) error {
if !o.forceUpdate && f.Has(o.name) {
existing := f.Get(o.name)
if c != *existing {
-
// The input coming in for the name is different from what is already
// configured. Return an error.
- return errors.Errorf("repository name (%s) already exists, please specify a different name", o.name)
+ return fmt.Errorf("repository name (%s) already exists, please specify a different name", o.name)
}
// The add is idempotent so do nothing
@@ -199,7 +201,7 @@ func (o *repoAddOptions) run(out io.Writer) error {
return nil
}
- r, err := repo.NewChartRepository(&c, getter.All(settings))
+ r, err := repo.NewChartRepository(&c, getter.All(settings, getter.WithTimeout(o.timeout)))
if err != nil {
return err
}
@@ -208,12 +210,12 @@ func (o *repoAddOptions) run(out io.Writer) error {
r.CachePath = o.repoCache
}
if _, err := r.DownloadIndexFile(); err != nil {
- return errors.Wrapf(err, "looks like %q is not a valid chart repository or cannot be reached", o.url)
+ return fmt.Errorf("looks like %q is not a valid chart repository or cannot be reached: %w", o.url, err)
}
f.Update(&c)
- if err := f.WriteFile(o.repoFile, 0600); err != nil {
+ if err := f.WriteFile(o.repoFile, 0o600); err != nil {
return err
}
fmt.Fprintf(out, "%q has been added to your repositories\n", o.name)
diff --git a/cmd/helm/repo_add_test.go b/pkg/cmd/repo_add_test.go
similarity index 95%
rename from cmd/helm/repo_add_test.go
rename to pkg/cmd/repo_add_test.go
index 35911d5ae..aa6c4eaad 100644
--- a/cmd/helm/repo_add_test.go
+++ b/pkg/cmd/repo_add_test.go
@@ -14,11 +14,13 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-package main
+package cmd
import (
+ "errors"
"fmt"
"io"
+ "io/fs"
"os"
"path/filepath"
"strings"
@@ -48,7 +50,7 @@ func TestRepoAddCmd(t *testing.T) {
defer srv2.Stop()
tmpdir := filepath.Join(t.TempDir(), "path-component.yaml/data")
- if err := os.MkdirAll(tmpdir, 0777); err != nil {
+ if err := os.MkdirAll(tmpdir, 0o777); err != nil {
t.Fatal(err)
}
repoFile := filepath.Join(tmpdir, "repositories.yaml")
@@ -97,7 +99,7 @@ func TestRepoAdd(t *testing.T) {
forceUpdate: false,
repoFile: repoFile,
}
- os.Setenv(xdg.CacheHomeEnvVar, rootDir)
+ t.Setenv(xdg.CacheHomeEnvVar, rootDir)
if err := o.run(io.Discard); err != nil {
t.Error(err)
@@ -113,11 +115,11 @@ func TestRepoAdd(t *testing.T) {
}
idx := filepath.Join(helmpath.CachePath("repository"), helmpath.CacheIndexFile(testRepoName))
- if _, err := os.Stat(idx); os.IsNotExist(err) {
+ if _, err := os.Stat(idx); errors.Is(err, fs.ErrNotExist) {
t.Errorf("Error cache index file was not created for repository %s", testRepoName)
}
idx = filepath.Join(helmpath.CachePath("repository"), helmpath.CacheChartsFile(testRepoName))
- if _, err := os.Stat(idx); os.IsNotExist(err) {
+ if _, err := os.Stat(idx); errors.Is(err, fs.ErrNotExist) {
t.Errorf("Error cache charts file was not created for repository %s", testRepoName)
}
@@ -151,7 +153,7 @@ func TestRepoAddCheckLegalName(t *testing.T) {
forceUpdate: false,
repoFile: repoFile,
}
- os.Setenv(xdg.CacheHomeEnvVar, rootDir)
+ t.Setenv(xdg.CacheHomeEnvVar, rootDir)
wantErrorMsg := fmt.Sprintf("repository name (%s) contains '/', please specify a different name without '/'", testRepoName)
@@ -189,6 +191,7 @@ func TestRepoAddConcurrentHiddenFile(t *testing.T) {
}
func repoAddConcurrent(t *testing.T, testName, repoFile string) {
+ t.Helper()
ts := repotest.NewTempServer(
t,
repotest.WithChartSourceGlob("testdata/testserver/*.*"),
diff --git a/cmd/helm/repo_index.go b/pkg/cmd/repo_index.go
similarity index 92%
rename from cmd/helm/repo_index.go
rename to pkg/cmd/repo_index.go
index c84a3f1ab..c17fd9391 100644
--- a/cmd/helm/repo_index.go
+++ b/pkg/cmd/repo_index.go
@@ -14,17 +14,19 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-package main
+package cmd
import (
+ "errors"
+ "fmt"
"io"
+ "io/fs"
"os"
"path/filepath"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
- "helm.sh/helm/v4/cmd/helm/require"
+ "helm.sh/helm/v4/pkg/cmd/require"
"helm.sh/helm/v4/pkg/repo"
)
@@ -97,13 +99,13 @@ func index(dir, url, mergeTo string, json bool) error {
if mergeTo != "" {
// if index.yaml is missing then create an empty one to merge into
var i2 *repo.IndexFile
- if _, err := os.Stat(mergeTo); os.IsNotExist(err) {
+ if _, err := os.Stat(mergeTo); errors.Is(err, fs.ErrNotExist) {
i2 = repo.NewIndexFile()
writeIndexFile(i2, mergeTo, json)
} else {
i2, err = repo.LoadIndexFile(mergeTo)
if err != nil {
- return errors.Wrap(err, "merge failed")
+ return fmt.Errorf("merge failed: %w", err)
}
}
i.Merge(i2)
@@ -114,7 +116,7 @@ func index(dir, url, mergeTo string, json bool) error {
func writeIndexFile(i *repo.IndexFile, out string, json bool) error {
if json {
- return i.WriteJSONFile(out, 0644)
+ return i.WriteJSONFile(out, 0o644)
}
- return i.WriteFile(out, 0644)
+ return i.WriteFile(out, 0o644)
}
diff --git a/cmd/helm/repo_index_test.go b/pkg/cmd/repo_index_test.go
similarity index 99%
rename from cmd/helm/repo_index_test.go
rename to pkg/cmd/repo_index_test.go
index e63a7bf63..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"
diff --git a/cmd/helm/repo_list.go b/pkg/cmd/repo_list.go
similarity index 88%
rename from cmd/helm/repo_list.go
rename to pkg/cmd/repo_list.go
index e0ad10147..70f57992e 100644
--- a/cmd/helm/repo_list.go
+++ b/pkg/cmd/repo_list.go
@@ -14,18 +14,17 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-package main
+package cmd
import (
"fmt"
"io"
"github.com/gosuri/uitable"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
- "helm.sh/helm/v4/cmd/helm/require"
"helm.sh/helm/v4/pkg/cli/output"
+ "helm.sh/helm/v4/pkg/cmd/require"
"helm.sh/helm/v4/pkg/repo"
)
@@ -37,10 +36,14 @@ func newRepoListCmd(out io.Writer) *cobra.Command {
Short: "list chart repositories",
Args: require.NoArgs,
ValidArgsFunction: noMoreArgsCompFunc,
- RunE: func(_ *cobra.Command, _ []string) error {
+ RunE: func(cmd *cobra.Command, _ []string) error {
+ // The error is silently ignored. If no repository file exists, it cannot be loaded,
+ // or the file isn't the right format to be parsed the error is ignored. The
+ // repositories will be 0.
f, _ := repo.LoadFile(settings.RepositoryConfig)
- if len(f.Repositories) == 0 && !(outfmt == output.JSON || outfmt == output.YAML) {
- return errors.New("no repositories to show")
+ if len(f.Repositories) == 0 && outfmt != output.JSON && outfmt != output.YAML {
+ fmt.Fprintln(cmd.ErrOrStderr(), "no repositories to show")
+ return nil
}
return outfmt.Write(out, &repoListWriter{f.Repositories})
diff --git a/pkg/cmd/repo_list_test.go b/pkg/cmd/repo_list_test.go
new file mode 100644
index 000000000..2f6a9e4ad
--- /dev/null
+++ b/pkg/cmd/repo_list_test.go
@@ -0,0 +1,54 @@
+/*
+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 (
+ "fmt"
+ "path/filepath"
+ "testing"
+)
+
+func TestRepoListOutputCompletion(t *testing.T) {
+ outputFlagCompletionTest(t, "repo list")
+}
+
+func TestRepoListFileCompletion(t *testing.T) {
+ checkFileCompletion(t, "repo list", false)
+}
+
+func TestRepoList(t *testing.T) {
+ rootDir := t.TempDir()
+ repoFile := filepath.Join(rootDir, "repositories.yaml")
+ repoFile2 := "testdata/repositories.yaml"
+
+ tests := []cmdTestCase{
+ {
+ name: "list with no repos",
+ cmd: fmt.Sprintf("repo list --repository-config %s --repository-cache %s", repoFile, rootDir),
+ golden: "output/repo-list-empty.txt",
+ wantError: false,
+ },
+ {
+ name: "list with repos",
+ cmd: fmt.Sprintf("repo list --repository-config %s --repository-cache %s", repoFile2, rootDir),
+ golden: "output/repo-list.txt",
+ wantError: false,
+ },
+ }
+
+ runTestCmd(t, tests)
+}
diff --git a/cmd/helm/repo_remove.go b/pkg/cmd/repo_remove.go
similarity index 90%
rename from cmd/helm/repo_remove.go
rename to pkg/cmd/repo_remove.go
index 6b72b0710..d0a3aa205 100644
--- a/cmd/helm/repo_remove.go
+++ b/pkg/cmd/repo_remove.go
@@ -14,18 +14,19 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-package main
+package cmd
import (
+ "errors"
"fmt"
"io"
+ "io/fs"
"os"
"path/filepath"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
- "helm.sh/helm/v4/cmd/helm/require"
+ "helm.sh/helm/v4/pkg/cmd/require"
"helm.sh/helm/v4/pkg/helmpath"
"helm.sh/helm/v4/pkg/repo"
)
@@ -65,7 +66,7 @@ func (o *repoRemoveOptions) run(out io.Writer) error {
for _, name := range o.names {
if !r.Remove(name) {
- return errors.Errorf("no repo named %q found", name)
+ return fmt.Errorf("no repo named %q found", name)
}
if err := r.WriteFile(o.repoFile, 0600); err != nil {
return err
@@ -87,10 +88,10 @@ func removeRepoCache(root, name string) error {
}
idx = filepath.Join(root, helmpath.CacheIndexFile(name))
- if _, err := os.Stat(idx); os.IsNotExist(err) {
+ if _, err := os.Stat(idx); errors.Is(err, fs.ErrNotExist) {
return nil
} else if err != nil {
- return errors.Wrapf(err, "can't remove index file %s", idx)
+ return fmt.Errorf("can't remove index file %s: %w", idx, err)
}
return os.Remove(idx)
}
diff --git a/cmd/helm/repo_remove_test.go b/pkg/cmd/repo_remove_test.go
similarity index 99%
rename from cmd/helm/repo_remove_test.go
rename to pkg/cmd/repo_remove_test.go
index 7e6609671..bd8757812 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"
@@ -153,6 +153,7 @@ func createCacheFiles(rootDir string, repoName string) (cacheIndexFile string, c
}
func testCacheFiles(t *testing.T, cacheIndexFile string, cacheChartsFile string, repoName string) {
+ t.Helper()
if _, err := os.Stat(cacheIndexFile); err == nil {
t.Errorf("Error cache index file was not removed for repository %s", repoName)
}
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 76%
rename from cmd/helm/repo_update.go
rename to pkg/cmd/repo_update.go
index 1379385c1..54318bf29 100644
--- a/cmd/helm/repo_update.go
+++ b/pkg/cmd/repo_update.go
@@ -14,18 +14,19 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-package main
+package cmd
import (
+ "errors"
"fmt"
"io"
"slices"
"sync"
+ "time"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
- "helm.sh/helm/v4/cmd/helm/require"
+ "helm.sh/helm/v4/pkg/cmd/require"
"helm.sh/helm/v4/pkg/getter"
"helm.sh/helm/v4/pkg/repo"
)
@@ -42,11 +43,11 @@ 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
+ timeout time.Duration
}
func newRepoUpdateCmd(out io.Writer) *cobra.Command {
@@ -70,10 +71,7 @@ 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")
+ f.DurationVar(&o.timeout, "timeout", getter.DefaultHTTPTimeout*time.Second, "time to wait for the index file download to complete")
return cmd
}
@@ -84,7 +82,7 @@ func (o *repoUpdateOptions) run(out io.Writer) error {
case isNotExist(err):
return errNoRepositories
case err != nil:
- return errors.Wrapf(err, "failed loading file: %s", o.repoFile)
+ return fmt.Errorf("failed loading file: %s: %w", o.repoFile, err)
case len(f.Repositories) == 0:
return errNoRepositories
}
@@ -101,7 +99,7 @@ func (o *repoUpdateOptions) run(out io.Writer) error {
for _, cfg := range f.Repositories {
if updateAllRepos || isRepoRequested(cfg.Name, o.names) {
- r, err := repo.NewChartRepository(cfg, getter.All(settings))
+ r, err := repo.NewChartRepository(cfg, getter.All(settings, getter.WithTimeout(o.timeout)))
if err != nil {
return err
}
@@ -112,29 +110,44 @@ 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))
+
+ writeMutex := sync.Mutex{}
for _, re := range repos {
wg.Add(1)
go func(re *repo.ChartRepository) {
defer wg.Done()
if _, err := re.DownloadIndexFile(); err != nil {
+ writeMutex.Lock()
+ defer writeMutex.Unlock()
fmt.Fprintf(out, "...Unable to get an update from the %q chart repository (%s):\n\t%s\n", re.Config.Name, re.Config.URL, err)
- repoFailList = append(repoFailList, re.Config.URL)
+ failRepoURLChan <- re.Config.URL
} else {
+ writeMutex.Lock()
+ defer writeMutex.Unlock()
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)
}
@@ -152,7 +165,7 @@ func checkRequestedRepos(requestedRepos []string, validRepos []*repo.Entry) erro
}
}
if !found {
- return errors.Errorf("no repositories found matching '%s'. Nothing will be updated", requestedRepo)
+ return fmt.Errorf("no repositories found matching '%s'. Nothing will be updated", requestedRepo)
}
}
return nil
diff --git a/cmd/helm/repo_update_test.go b/pkg/cmd/repo_update_test.go
similarity index 81%
rename from cmd/helm/repo_update_test.go
rename to pkg/cmd/repo_update_test.go
index 7e379da91..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"
@@ -34,7 +34,7 @@ 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)
}
@@ -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,7 +161,7 @@ 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)
@@ -172,40 +172,14 @@ func TestUpdateChartsFail(t *testing.T) {
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 := repotest.NewTempServer(
- t,
- repotest.WithChartSourceGlob("testdata/testserver/*.*"),
- )
- 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 94%
rename from cmd/helm/require/args.go
rename to pkg/cmd/require/args.go
index cfa8a0169..f5e0888f1 100644
--- a/cmd/helm/require/args.go
+++ b/pkg/cmd/require/args.go
@@ -16,14 +16,15 @@ limitations under the License.
package require
import (
- "github.com/pkg/errors"
+ "fmt"
+
"github.com/spf13/cobra"
)
// NoArgs returns an error if any args are included.
func NoArgs(cmd *cobra.Command, args []string) error {
if len(args) > 0 {
- return errors.Errorf(
+ return fmt.Errorf(
"%q accepts no arguments\n\nUsage: %s",
cmd.CommandPath(),
cmd.UseLine(),
@@ -36,7 +37,7 @@ func NoArgs(cmd *cobra.Command, args []string) error {
func ExactArgs(n int) cobra.PositionalArgs {
return func(cmd *cobra.Command, args []string) error {
if len(args) != n {
- return errors.Errorf(
+ return fmt.Errorf(
"%q requires %d %s\n\nUsage: %s",
cmd.CommandPath(),
n,
@@ -52,7 +53,7 @@ func ExactArgs(n int) cobra.PositionalArgs {
func MaximumNArgs(n int) cobra.PositionalArgs {
return func(cmd *cobra.Command, args []string) error {
if len(args) > n {
- return errors.Errorf(
+ return fmt.Errorf(
"%q accepts at most %d %s\n\nUsage: %s",
cmd.CommandPath(),
n,
@@ -68,7 +69,7 @@ func MaximumNArgs(n int) cobra.PositionalArgs {
func MinimumNArgs(n int) cobra.PositionalArgs {
return func(cmd *cobra.Command, args []string) error {
if len(args) < n {
- return errors.Errorf(
+ return fmt.Errorf(
"%q requires at least %d %s\n\nUsage: %s",
cmd.CommandPath(),
n,
diff --git a/cmd/helm/require/args_test.go b/pkg/cmd/require/args_test.go
similarity index 99%
rename from cmd/helm/require/args_test.go
rename to pkg/cmd/require/args_test.go
index cd5850650..b6c430fc0 100644
--- a/cmd/helm/require/args_test.go
+++ b/pkg/cmd/require/args_test.go
@@ -63,6 +63,7 @@ type testCase struct {
}
func runTestCases(t *testing.T, testCases []testCase) {
+ t.Helper()
for i, tc := range testCases {
t.Run(fmt.Sprint(i), func(t *testing.T) {
cmd := &cobra.Command{
diff --git a/cmd/helm/rollback.go b/pkg/cmd/rollback.go
similarity index 87%
rename from cmd/helm/rollback.go
rename to pkg/cmd/rollback.go
index a65f30a1f..6658d3fd6 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/v4/cmd/helm/require"
"helm.sh/helm/v4/pkg/action"
+ "helm.sh/helm/v4/pkg/cmd/require"
)
const rollbackDesc = `
@@ -77,14 +77,13 @@ func newRollbackCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
f := cmd.Flags()
f.BoolVar(&client.DryRun, "dry-run", false, "simulate a rollback")
- f.BoolVar(&client.Recreate, "recreate-pods", false, "performs pods restart for the resource if applicable")
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 a94327e07..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/v4/pkg/chart"
- "helm.sh/helm/v4/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 82%
rename from cmd/helm/root.go
rename to pkg/cmd/root.go
index dd3ddeab7..4eb5da494 100644
--- a/cmd/helm/root.go
+++ b/pkg/cmd/root.go
@@ -14,26 +14,33 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-package main // import "helm.sh/helm/v4/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/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
@@ -89,7 +96,40 @@ 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, logSetup func(bool)) (*cobra.Command, error) {
+ actionConfig := new(action.Configuration)
+ cmd, err := newRootCmdWithConfig(actionConfig, out, args, logSetup)
+ 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
+}
+
+// SetupLogging sets up Helm logging used by the Helm client.
+// This function is passed to the NewRootCmd function to enable logging. Any other
+// application that uses the NewRootCmd function to setup all the Helm commands may
+// use this function to setup logging or their own. Using a custom logging setup function
+// enables applications using Helm commands to integrate with their existing logging
+// system.
+// The debug argument is the value if Helm is set for debugging (i.e. --debug flag)
+func SetupLogging(debug bool) {
+ logger := logging.NewLogger(func() bool { return debug })
+ slog.SetDefault(logger)
+}
+
+func newRootCmdWithConfig(actionConfig *action.Configuration, out io.Writer, args []string, logSetup func(bool)) (*cobra.Command, error) {
cmd := &cobra.Command{
Use: "helm",
Short: "The Helm package manager for Kubernetes.",
@@ -106,11 +146,21 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string
}
},
}
+
flags := cmd.PersistentFlags()
settings.AddFlags(flags)
addKlogFlags(flags)
+ // We can safely ignore any errors that flags.Parse encounters since
+ // those errors will be caught later during the call to cmd.Execution.
+ // This call is required to gather configuration information prior to
+ // execution.
+ flags.ParseErrorsWhitelist.UnknownFlags = true
+ flags.Parse(args)
+
+ logSetup(settings.Debug)
+
// 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 {
@@ -158,13 +208,6 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string
log.Fatal(err)
}
- // We can safely ignore any errors that flags.Parse encounters since
- // those errors will be caught later during the call to cmd.Execution.
- // This call is required to gather configuration information prior to
- // execution.
- flags.ParseErrorsWhitelist.UnknownFlags = true
- flags.Parse(args)
-
registryClient, err := newDefaultRegistryClient(false, "", "")
if err != nil {
return nil, err
@@ -219,6 +262,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 {
@@ -326,6 +412,7 @@ func newRegistryClientWithTLS(
registry.ClientOptHTTPClient(&http.Client{
Transport: &http.Transport{
TLSClientConfig: tlsConf,
+ Proxy: http.ProxyFromEnvironment,
},
}),
registry.ClientOptBasicAuth(username, password),
diff --git a/cmd/helm/root_test.go b/pkg/cmd/root_test.go
similarity index 99%
rename from cmd/helm/root_test.go
rename to pkg/cmd/root_test.go
index e30850900..84e3d9ed2 100644
--- a/cmd/helm/root_test.go
+++ b/pkg/cmd/root_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 (
"os"
@@ -80,7 +80,7 @@ func TestRootCmd(t *testing.T) {
ensure.HelmHome(t)
for k, v := range tt.envvars {
- os.Setenv(k, v)
+ t.Setenv(k, v)
}
if _, _, err := executeActionCommand(tt.args); err != nil {
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 100%
rename from cmd/helm/search/search.go
rename to pkg/cmd/search/search.go
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 175491b36..7a4ba786b 100644
--- a/cmd/helm/search/search_test.go
+++ b/pkg/cmd/search/search_test.go
@@ -20,7 +20,7 @@ import (
"strings"
"testing"
- "helm.sh/helm/v4/pkg/chart"
+ chart "helm.sh/helm/v4/pkg/chart/v2"
"helm.sh/helm/v4/pkg/repo"
)
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 5bdb1092d..cfeeec59b 100644
--- a/cmd/helm/search_hub.go
+++ b/pkg/cmd/search_hub.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"
"io"
+ "log/slog"
"strings"
"github.com/gosuri/uitable"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
"helm.sh/helm/v4/internal/monocular"
@@ -83,13 +83,13 @@ func newSearchHubCmd(out io.Writer) *cobra.Command {
func (o *searchHubOptions) run(out io.Writer, args []string) error {
c, err := monocular.New(o.searchEndpoint)
if err != nil {
- return errors.Wrap(err, fmt.Sprintf("unable to create connection to %q", o.searchEndpoint))
+ return fmt.Errorf("unable to create connection to %q: %w", o.searchEndpoint, err)
}
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 36e8a8c58..dffa0d1c4 100644
--- a/cmd/helm/search_repo.go
+++ b/pkg/cmd/search_repo.go
@@ -14,24 +14,25 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-package main
+package cmd
import (
"bufio"
"bytes"
+ "errors"
"fmt"
"io"
+ "log/slog"
"os"
"path/filepath"
"strings"
"github.com/Masterminds/semver/v3"
"github.com/gosuri/uitable"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
- "helm.sh/helm/v4/cmd/helm/search"
"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"
)
@@ -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"
}
}
@@ -152,7 +153,7 @@ func (o *searchRepoOptions) applyConstraint(res []*search.Result) ([]*search.Res
constraint, err := semver.NewConstraint(o.version)
if err != nil {
- return res, errors.Wrap(err, "an invalid version/constraint format")
+ return res, fmt.Errorf("an invalid version/constraint format: %w", err)
}
data := res[: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 96%
rename from cmd/helm/show.go
rename to pkg/cmd/show.go
index 492de94f6..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/v4/cmd/helm/require"
"helm.sh/helm/v4/pkg/action"
+ "helm.sh/helm/v4/pkg/cmd/require"
)
const showDesc = `
@@ -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
}
diff --git a/cmd/helm/show_test.go b/pkg/cmd/show_test.go
similarity index 99%
rename from cmd/helm/show_test.go
rename to pkg/cmd/show_test.go
index 0598095b5..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"
diff --git a/cmd/helm/status.go b/pkg/cmd/status.go
similarity index 98%
rename from cmd/helm/status.go
rename to pkg/cmd/status.go
index fd3e4ce14..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/v4/cmd/helm/require"
"helm.sh/helm/v4/pkg/action"
- chartutil "helm.sh/helm/v4/pkg/chart/util"
+ chartutil "helm.sh/helm/v4/pkg/chart/v2/util"
"helm.sh/helm/v4/pkg/cli/output"
- "helm.sh/helm/v4/pkg/release"
+ "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.
diff --git a/cmd/helm/status_test.go b/pkg/cmd/status_test.go
similarity index 98%
rename from cmd/helm/status_test.go
rename to pkg/cmd/status_test.go
index 1973fe068..cb4e23c59 100644
--- a/cmd/helm/status_test.go
+++ b/pkg/cmd/status_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"
"time"
- "helm.sh/helm/v4/pkg/chart"
- "helm.sh/helm/v4/pkg/release"
+ chart "helm.sh/helm/v4/pkg/chart/v2"
+ release "helm.sh/helm/v4/pkg/release/v1"
helmtime "helm.sh/helm/v4/pkg/time"
)
diff --git a/cmd/helm/template.go b/pkg/cmd/template.go
similarity index 95%
rename from cmd/helm/template.go
rename to pkg/cmd/template.go
index 1a6265eba..ac20a45b3 100644
--- a/cmd/helm/template.go
+++ b/pkg/cmd/template.go
@@ -14,12 +14,14 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-package main
+package cmd
import (
"bytes"
+ "errors"
"fmt"
"io"
+ "io/fs"
"os"
"path"
"path/filepath"
@@ -28,15 +30,15 @@ import (
"sort"
"strings"
- "helm.sh/helm/v4/pkg/release"
+ release "helm.sh/helm/v4/pkg/release/v1"
"github.com/spf13/cobra"
- "helm.sh/helm/v4/cmd/helm/require"
"helm.sh/helm/v4/pkg/action"
- chartutil "helm.sh/helm/v4/pkg/chart/util"
+ chartutil "helm.sh/helm/v4/pkg/chart/v2/util"
"helm.sh/helm/v4/pkg/cli/values"
- "helm.sh/helm/v4/pkg/releaseutil"
+ "helm.sh/helm/v4/pkg/cmd/require"
+ releaseutil "helm.sh/helm/v4/pkg/release/util"
)
const templateDesc = `
@@ -199,7 +201,7 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
f.BoolVar(&skipTests, "skip-tests", false, "skip tests from templated output")
f.BoolVar(&client.IsUpgrade, "is-upgrade", false, "set .Release.IsUpgrade instead of .Release.IsInstall")
f.StringVar(&kubeVersion, "kube-version", "", "Kubernetes version used for Capabilities.KubeVersion")
- f.StringSliceVarP(&extraAPIs, "api-versions", "a", []string{}, "Kubernetes api versions used for Capabilities.APIVersions")
+ f.StringSliceVarP(&extraAPIs, "api-versions", "a", []string{}, "Kubernetes api versions used for Capabilities.APIVersions (multiple can be specified)")
f.BoolVar(&client.UseReleaseName, "release-name", false, "use release name in the output-dir path.")
bindPostRenderFlag(cmd, &client.PostRenderer)
@@ -230,7 +232,7 @@ func writeToFile(outputDir string, name string, data string, appendData bool) er
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
@@ -250,7 +252,7 @@ func createOrOpenFile(filename string, appendData bool) (*os.File, error) {
func ensureDirectoryForFile(file string) error {
baseDir := path.Dir(file)
_, err := os.Stat(baseDir)
- if err != nil && !os.IsNotExist(err) {
+ if err != nil && !errors.Is(err, fs.ErrNotExist) {
return err
}
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 28e24ce63..5bcccf5d0 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,18 +22,6 @@ 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) {
@@ -95,7 +83,12 @@ func TestTemplateCmd(t *testing.T) {
},
{
name: "check kube api versions",
- cmd: fmt.Sprintf("template --api-versions helm.k8s.io/test '%s'", chartPath),
+ cmd: fmt.Sprintf("template --api-versions helm.k8s.io/test,helm.k8s.io/test2 '%s'", chartPath),
+ golden: "output/template-with-api-version.txt",
+ },
+ {
+ name: "check kube api versions",
+ cmd: fmt.Sprintf("template --api-versions helm.k8s.io/test --api-versions helm.k8s.io/test2 '%s'", chartPath),
golden: "output/template-with-api-version.txt",
},
{
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 100%
rename from cmd/helm/testdata/output/chart-with-subchart-update.txt
rename to pkg/cmd/testdata/output/chart-with-subchart-update.txt
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 100%
rename from cmd/helm/testdata/output/deprecated-chart.txt
rename to pkg/cmd/testdata/output/deprecated-chart.txt
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/cmd/helm/testdata/output/get-metadata.json b/pkg/cmd/testdata/output/get-metadata.json
similarity index 100%
rename from cmd/helm/testdata/output/get-metadata.json
rename to pkg/cmd/testdata/output/get-metadata.json
diff --git a/cmd/helm/testdata/output/get-metadata.txt b/pkg/cmd/testdata/output/get-metadata.txt
similarity index 100%
rename from cmd/helm/testdata/output/get-metadata.txt
rename to pkg/cmd/testdata/output/get-metadata.txt
diff --git a/cmd/helm/testdata/output/get-metadata.yaml b/pkg/cmd/testdata/output/get-metadata.yaml
similarity index 100%
rename from cmd/helm/testdata/output/get-metadata.yaml
rename to pkg/cmd/testdata/output/get-metadata.yaml
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 100%
rename from cmd/helm/testdata/output/get-release.txt
rename to pkg/cmd/testdata/output/get-release.txt
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 100%
rename from cmd/helm/testdata/output/install-and-replace.txt
rename to pkg/cmd/testdata/output/install-and-replace.txt
diff --git a/cmd/helm/testdata/output/install-and-take-ownership.txt b/pkg/cmd/testdata/output/install-and-take-ownership.txt
similarity index 100%
rename from cmd/helm/testdata/output/install-and-take-ownership.txt
rename to pkg/cmd/testdata/output/install-and-take-ownership.txt
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 100%
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
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 100%
rename from cmd/helm/testdata/output/install-dry-run-with-secret.txt
rename to pkg/cmd/testdata/output/install-dry-run-with-secret.txt
diff --git a/pkg/cmd/testdata/output/install-hide-secret.txt b/pkg/cmd/testdata/output/install-hide-secret.txt
new file mode 100644
index 000000000..165f14f73
--- /dev/null
+++ b/pkg/cmd/testdata/output/install-hide-secret.txt
@@ -0,0 +1 @@
+Error: INSTALLATION FAILED: hiding Kubernetes secrets requires a dry-run mode
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 100%
rename from cmd/helm/testdata/output/install-name-template.txt
rename to pkg/cmd/testdata/output/install-name-template.txt
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 100%
rename from cmd/helm/testdata/output/install-no-hooks.txt
rename to pkg/cmd/testdata/output/install-no-hooks.txt
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 100%
rename from cmd/helm/testdata/output/install-with-multiple-values-files.txt
rename to pkg/cmd/testdata/output/install-with-multiple-values-files.txt
diff --git a/cmd/helm/testdata/output/install-with-multiple-values.txt b/pkg/cmd/testdata/output/install-with-multiple-values.txt
similarity index 100%
rename from cmd/helm/testdata/output/install-with-multiple-values.txt
rename to pkg/cmd/testdata/output/install-with-multiple-values.txt
diff --git a/cmd/helm/testdata/output/install-with-timeout.txt b/pkg/cmd/testdata/output/install-with-timeout.txt
similarity index 100%
rename from cmd/helm/testdata/output/install-with-timeout.txt
rename to pkg/cmd/testdata/output/install-with-timeout.txt
diff --git a/cmd/helm/testdata/output/install-with-values-file.txt b/pkg/cmd/testdata/output/install-with-values-file.txt
similarity index 100%
rename from cmd/helm/testdata/output/install-with-values-file.txt
rename to pkg/cmd/testdata/output/install-with-values-file.txt
diff --git a/cmd/helm/testdata/output/install-with-values.txt b/pkg/cmd/testdata/output/install-with-values.txt
similarity index 100%
rename from cmd/helm/testdata/output/install-with-values.txt
rename to pkg/cmd/testdata/output/install-with-values.txt
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 100%
rename from cmd/helm/testdata/output/install-with-wait-for-jobs.txt
rename to pkg/cmd/testdata/output/install-with-wait-for-jobs.txt
diff --git a/cmd/helm/testdata/output/install-with-wait.txt b/pkg/cmd/testdata/output/install-with-wait.txt
similarity index 100%
rename from cmd/helm/testdata/output/install-with-wait.txt
rename to pkg/cmd/testdata/output/install-with-wait.txt
diff --git a/cmd/helm/testdata/output/install.txt b/pkg/cmd/testdata/output/install.txt
similarity index 100%
rename from cmd/helm/testdata/output/install.txt
rename to pkg/cmd/testdata/output/install.txt
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/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/pkg/cmd/testdata/output/repo-list-empty.txt b/pkg/cmd/testdata/output/repo-list-empty.txt
new file mode 100644
index 000000000..c6edb659a
--- /dev/null
+++ b/pkg/cmd/testdata/output/repo-list-empty.txt
@@ -0,0 +1 @@
+no repositories to show
diff --git a/pkg/cmd/testdata/output/repo-list.txt b/pkg/cmd/testdata/output/repo-list.txt
new file mode 100644
index 000000000..edbd0ecc1
--- /dev/null
+++ b/pkg/cmd/testdata/output/repo-list.txt
@@ -0,0 +1,4 @@
+NAME URL
+charts https://charts.helm.sh/stable
+firstexample http://firstexample.com
+secondexample http://secondexample.com
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 100%
rename from cmd/helm/testdata/output/schema.txt
rename to pkg/cmd/testdata/output/schema.txt
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 100%
rename from cmd/helm/testdata/output/status-with-notes.txt
rename to pkg/cmd/testdata/output/status-with-notes.txt
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 100%
rename from cmd/helm/testdata/output/status-with-resources.txt
rename to pkg/cmd/testdata/output/status-with-resources.txt
diff --git a/cmd/helm/testdata/output/status-with-test-suite.txt b/pkg/cmd/testdata/output/status-with-test-suite.txt
similarity index 100%
rename from cmd/helm/testdata/output/status-with-test-suite.txt
rename to pkg/cmd/testdata/output/status-with-test-suite.txt
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 100%
rename from cmd/helm/testdata/output/status.txt
rename to pkg/cmd/testdata/output/status.txt
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 100%
rename from cmd/helm/testdata/output/subchart-schema-cli.txt
rename to pkg/cmd/testdata/output/subchart-schema-cli.txt
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 98%
rename from cmd/helm/testdata/output/template-with-api-version.txt
rename to pkg/cmd/testdata/output/template-with-api-version.txt
index 7e1c35001..8b6074cdb 100644
--- a/cmd/helm/testdata/output/template-with-api-version.txt
+++ b/pkg/cmd/testdata/output/template-with-api-version.txt
@@ -75,6 +75,7 @@ metadata:
kube-version/minor: "20"
kube-version/version: "v1.20.0"
kube-api-version/test: v1
+ kube-api-version/test2: v2
spec:
type: ClusterIP
ports:
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/cmd/helm/testdata/output/upgrade-and-take-ownership.txt b/pkg/cmd/testdata/output/upgrade-and-take-ownership.txt
similarity index 100%
rename from cmd/helm/testdata/output/upgrade-and-take-ownership.txt
rename to pkg/cmd/testdata/output/upgrade-and-take-ownership.txt
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 100%
rename from cmd/helm/testdata/output/upgrade-uninstalled-with-keep-history.txt
rename to pkg/cmd/testdata/output/upgrade-uninstalled-with-keep-history.txt
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 100%
rename from cmd/helm/testdata/output/upgrade-with-dependency-update.txt
rename to pkg/cmd/testdata/output/upgrade-with-dependency-update.txt
diff --git a/cmd/helm/testdata/output/upgrade-with-install-timeout.txt b/pkg/cmd/testdata/output/upgrade-with-install-timeout.txt
similarity index 100%
rename from cmd/helm/testdata/output/upgrade-with-install-timeout.txt
rename to pkg/cmd/testdata/output/upgrade-with-install-timeout.txt
diff --git a/cmd/helm/testdata/output/upgrade-with-install.txt b/pkg/cmd/testdata/output/upgrade-with-install.txt
similarity index 100%
rename from cmd/helm/testdata/output/upgrade-with-install.txt
rename to pkg/cmd/testdata/output/upgrade-with-install.txt
diff --git a/cmd/helm/testdata/output/upgrade-with-missing-dependencies.txt b/pkg/cmd/testdata/output/upgrade-with-missing-dependencies.txt
similarity index 69%
rename from cmd/helm/testdata/output/upgrade-with-missing-dependencies.txt
rename to pkg/cmd/testdata/output/upgrade-with-missing-dependencies.txt
index adf2ae899..b2c154a80 100644
--- a/cmd/helm/testdata/output/upgrade-with-missing-dependencies.txt
+++ b/pkg/cmd/testdata/output/upgrade-with-missing-dependencies.txt
@@ -1 +1 @@
-Error: An error occurred while checking for chart dependencies. You may need to run `helm dependency build` to fetch missing dependencies: found in Chart.yaml, but missing in charts/ directory: reqsubchart2
+Error: an error occurred while checking for chart dependencies. You may need to run `helm dependency build` to fetch missing dependencies: found in Chart.yaml, but missing in charts/ directory: reqsubchart2
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 100%
rename from cmd/helm/testdata/output/upgrade-with-reset-values.txt
rename to pkg/cmd/testdata/output/upgrade-with-reset-values.txt
diff --git a/cmd/helm/testdata/output/upgrade-with-reset-values2.txt b/pkg/cmd/testdata/output/upgrade-with-reset-values2.txt
similarity index 100%
rename from cmd/helm/testdata/output/upgrade-with-reset-values2.txt
rename to pkg/cmd/testdata/output/upgrade-with-reset-values2.txt
diff --git a/cmd/helm/testdata/output/upgrade-with-timeout.txt b/pkg/cmd/testdata/output/upgrade-with-timeout.txt
similarity index 100%
rename from cmd/helm/testdata/output/upgrade-with-timeout.txt
rename to pkg/cmd/testdata/output/upgrade-with-timeout.txt
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 100%
rename from cmd/helm/testdata/output/upgrade-with-wait-for-jobs.txt
rename to pkg/cmd/testdata/output/upgrade-with-wait-for-jobs.txt
diff --git a/cmd/helm/testdata/output/upgrade-with-wait.txt b/pkg/cmd/testdata/output/upgrade-with-wait.txt
similarity index 100%
rename from cmd/helm/testdata/output/upgrade-with-wait.txt
rename to pkg/cmd/testdata/output/upgrade-with-wait.txt
diff --git a/cmd/helm/testdata/output/upgrade.txt b/pkg/cmd/testdata/output/upgrade.txt
similarity index 100%
rename from cmd/helm/testdata/output/upgrade.txt
rename to pkg/cmd/testdata/output/upgrade.txt
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/cmd/helm/testdata/output/version-client-shorthand.txt b/pkg/cmd/testdata/output/version-client-shorthand.txt
similarity index 100%
rename from cmd/helm/testdata/output/version-client-shorthand.txt
rename to pkg/cmd/testdata/output/version-client-shorthand.txt
diff --git a/cmd/helm/testdata/output/version-client.txt b/pkg/cmd/testdata/output/version-client.txt
similarity index 100%
rename from cmd/helm/testdata/output/version-client.txt
rename to pkg/cmd/testdata/output/version-client.txt
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/cmd/helm/testdata/output/version-short.txt b/pkg/cmd/testdata/output/version-short.txt
similarity index 100%
rename from cmd/helm/testdata/output/version-short.txt
rename to pkg/cmd/testdata/output/version-short.txt
diff --git a/cmd/helm/testdata/output/version-template.txt b/pkg/cmd/testdata/output/version-template.txt
similarity index 100%
rename from cmd/helm/testdata/output/version-template.txt
rename to pkg/cmd/testdata/output/version-template.txt
diff --git a/cmd/helm/testdata/output/version.txt b/pkg/cmd/testdata/output/version.txt
similarity index 100%
rename from cmd/helm/testdata/output/version.txt
rename to pkg/cmd/testdata/output/version.txt
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/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/chart/util/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/chart/util/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/chart/util/testdata/subpop/charts/subchart1/charts/subchartA/Chart.yaml b/pkg/cmd/testdata/testcharts/subchart/charts/subchartA/Chart.yaml
similarity index 100%
rename from pkg/chart/util/testdata/subpop/charts/subchart1/charts/subchartA/Chart.yaml
rename to pkg/cmd/testdata/testcharts/subchart/charts/subchartA/Chart.yaml
diff --git a/pkg/chart/util/testdata/subpop/charts/subchart2/templates/service.yaml b/pkg/cmd/testdata/testcharts/subchart/charts/subchartA/templates/service.yaml
similarity index 100%
rename from pkg/chart/util/testdata/subpop/charts/subchart2/templates/service.yaml
rename to pkg/cmd/testdata/testcharts/subchart/charts/subchartA/templates/service.yaml
diff --git a/pkg/chart/util/testdata/subpop/charts/subchart1/charts/subchartA/values.yaml b/pkg/cmd/testdata/testcharts/subchart/charts/subchartA/values.yaml
similarity index 100%
rename from pkg/chart/util/testdata/subpop/charts/subchart1/charts/subchartA/values.yaml
rename to pkg/cmd/testdata/testcharts/subchart/charts/subchartA/values.yaml
diff --git a/pkg/chart/util/testdata/subpop/charts/subchart2/charts/subchartB/Chart.yaml b/pkg/cmd/testdata/testcharts/subchart/charts/subchartB/Chart.yaml
similarity index 100%
rename from pkg/chart/util/testdata/subpop/charts/subchart2/charts/subchartB/Chart.yaml
rename to pkg/cmd/testdata/testcharts/subchart/charts/subchartB/Chart.yaml
diff --git a/pkg/chart/util/testdata/subpop/noreqs/templates/service.yaml b/pkg/cmd/testdata/testcharts/subchart/charts/subchartB/templates/service.yaml
similarity index 100%
rename from pkg/chart/util/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/chart/util/testdata/subpop/charts/subchart1/templates/NOTES.txt b/pkg/cmd/testdata/testcharts/subchart/templates/NOTES.txt
similarity index 100%
rename from pkg/chart/util/testdata/subpop/charts/subchart1/templates/NOTES.txt
rename to pkg/cmd/testdata/testcharts/subchart/templates/NOTES.txt
diff --git a/pkg/chart/util/testdata/subpop/charts/subchart1/templates/service.yaml b/pkg/cmd/testdata/testcharts/subchart/templates/service.yaml
similarity index 88%
rename from pkg/chart/util/testdata/subpop/charts/subchart1/templates/service.yaml
rename to pkg/cmd/testdata/testcharts/subchart/templates/service.yaml
index fee94dced..19c931cc3 100644
--- a/pkg/chart/util/testdata/subpop/charts/subchart1/templates/service.yaml
+++ b/pkg/cmd/testdata/testcharts/subchart/templates/service.yaml
@@ -11,6 +11,9 @@ metadata:
{{- if .Capabilities.APIVersions.Has "helm.k8s.io/test" }}
kube-api-version/test: v1
{{- end }}
+{{- if .Capabilities.APIVersions.Has "helm.k8s.io/test2" }}
+ kube-api-version/test2: v2
+{{- end }}
spec:
type: {{ .Values.service.type }}
ports:
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/chart/util/testdata/subpop/charts/subchart1/templates/subdir/rolebinding.yaml b/pkg/cmd/testdata/testcharts/subchart/templates/subdir/rolebinding.yaml
similarity index 100%
rename from pkg/chart/util/testdata/subpop/charts/subchart1/templates/subdir/rolebinding.yaml
rename to pkg/cmd/testdata/testcharts/subchart/templates/subdir/rolebinding.yaml
diff --git a/pkg/chart/util/testdata/subpop/charts/subchart1/templates/subdir/serviceaccount.yaml b/pkg/cmd/testdata/testcharts/subchart/templates/subdir/serviceaccount.yaml
similarity index 100%
rename from pkg/chart/util/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 94%
rename from cmd/helm/uninstall.go
rename to pkg/cmd/uninstall.go
index 9c5e25c87..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/v4/cmd/helm/require"
"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 f9bc71ec2..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/v4/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 91%
rename from cmd/helm/upgrade.go
rename to pkg/cmd/upgrade.go
index 6684f9ebf..d4e7b4852 100644
--- a/cmd/helm/upgrade.go
+++ b/pkg/cmd/upgrade.go
@@ -14,29 +14,29 @@ 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"
"time"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
- "helm.sh/helm/v4/cmd/helm/require"
"helm.sh/helm/v4/pkg/action"
- "helm.sh/helm/v4/pkg/chart/loader"
+ "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"
- "helm.sh/helm/v4/pkg/release"
+ release "helm.sh/helm/v4/pkg/release/v1"
"helm.sh/helm/v4/pkg/storage/driver"
)
@@ -136,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
@@ -173,11 +173,11 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
}
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
}
@@ -199,12 +199,12 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
}
if req := ch.Metadata.Dependencies; req != nil {
if err := action.CheckDependencies(ch, req); err != nil {
- err = errors.Wrap(err, "An error occurred while checking for chart dependencies. You may need to run `helm dependency build` to fetch missing dependencies")
+ err = fmt.Errorf("an error occurred while checking for chart dependencies. You may need to run `helm dependency build` to fetch missing dependencies: %w", err)
if client.DependencyUpdate {
man := &downloader.Manager{
Out: out,
ChartPath: chartPath,
- Keyring: client.ChartPathOptions.Keyring,
+ Keyring: client.Keyring,
SkipUpdate: false,
Getters: p,
RepositoryConfig: settings.RepositoryConfig,
@@ -216,7 +216,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
}
// Reload the chart with the updated Chart.lock file.
if ch, err = loader.Load(chartPath); err != nil {
- return errors.Wrap(err, "failed reloading chart after repo update")
+ return fmt.Errorf("failed reloading chart after repo update: %w", err)
}
} else {
return err
@@ -225,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
@@ -245,7 +245,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
rel, err := client.RunWithContext(ctx, args[0], ch, vals)
if err != nil {
- return errors.Wrap(err, "UPGRADE FAILED")
+ return fmt.Errorf("UPGRADE FAILED: %w", err)
}
if outfmt == output.Table {
@@ -268,8 +268,6 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
f.StringVar(&client.DryRunOption, "dry-run", "", "simulate an install. If --dry-run is set with no option being specified or as '--dry-run=client', it will not attempt cluster connections. Setting '--dry-run=server' allows attempting cluster connections.")
f.BoolVar(&client.HideSecret, "hide-secret", false, "hide Kubernetes Secrets when also using the --dry-run flag")
f.Lookup("dry-run").NoOptDefVal = "client"
- f.BoolVar(&client.Recreate, "recreate-pods", false, "performs pods restart for the resource if applicable")
- f.MarkDeprecated("recreate-pods", "functionality will no longer be updated. Consult the documentation for other methods to recreate pods")
f.BoolVar(&client.Force, "force", false, "force resource updates through a replacement strategy")
f.BoolVar(&client.DisableHooks, "no-hooks", false, "disable pre/post upgrade hooks")
f.BoolVar(&client.DisableOpenAPIValidation, "disable-openapi-validation", false, "if set, the upgrade process will not validate rendered templates against the Kubernetes OpenAPI Schema")
@@ -278,9 +276,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")
@@ -295,6 +292,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
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 {
diff --git a/cmd/helm/upgrade_test.go b/pkg/cmd/upgrade_test.go
similarity index 95%
rename from cmd/helm/upgrade_test.go
rename to pkg/cmd/upgrade_test.go
index f97a4a26b..d7375dcad 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/v4/pkg/chart"
- "helm.sh/helm/v4/pkg/chart/loader"
- chartutil "helm.sh/helm/v4/pkg/chart/util"
- "helm.sh/helm/v4/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) {
@@ -193,7 +193,7 @@ func TestUpgradeCmd(t *testing.T) {
func TestUpgradeWithValue(t *testing.T) {
releaseName := "funny-bunny-v2"
- relMock, ch, chartPath := prepareMockRelease(releaseName, t)
+ relMock, ch, chartPath := prepareMockRelease(t, releaseName)
defer resetEnv()()
@@ -220,7 +220,7 @@ func TestUpgradeWithValue(t *testing.T) {
func TestUpgradeWithStringValue(t *testing.T) {
releaseName := "funny-bunny-v3"
- relMock, ch, chartPath := prepareMockRelease(releaseName, t)
+ relMock, ch, chartPath := prepareMockRelease(t, releaseName)
defer resetEnv()()
@@ -248,7 +248,7 @@ func TestUpgradeWithStringValue(t *testing.T) {
func TestUpgradeInstallWithSubchartNotes(t *testing.T) {
releaseName := "wacky-bunny-v1"
- relMock, ch, _ := prepareMockRelease(releaseName, t)
+ relMock, ch, _ := prepareMockRelease(t, releaseName)
defer resetEnv()()
@@ -280,7 +280,7 @@ func TestUpgradeInstallWithSubchartNotes(t *testing.T) {
func TestUpgradeWithValuesFile(t *testing.T) {
releaseName := "funny-bunny-v4"
- relMock, ch, chartPath := prepareMockRelease(releaseName, t)
+ relMock, ch, chartPath := prepareMockRelease(t, releaseName)
defer resetEnv()()
@@ -308,7 +308,7 @@ func TestUpgradeWithValuesFile(t *testing.T) {
func TestUpgradeWithValuesFromStdin(t *testing.T) {
releaseName := "funny-bunny-v5"
- relMock, ch, chartPath := prepareMockRelease(releaseName, t)
+ relMock, ch, chartPath := prepareMockRelease(t, releaseName)
defer resetEnv()()
@@ -340,7 +340,7 @@ func TestUpgradeWithValuesFromStdin(t *testing.T) {
func TestUpgradeInstallWithValuesFromStdin(t *testing.T) {
releaseName := "funny-bunny-v6"
- _, _, chartPath := prepareMockRelease(releaseName, t)
+ _, _, chartPath := prepareMockRelease(t, releaseName)
defer resetEnv()()
@@ -368,7 +368,8 @@ func TestUpgradeInstallWithValuesFromStdin(t *testing.T) {
}
-func prepareMockRelease(releaseName string, t *testing.T) (func(n string, v int, ch *chart.Chart) *release.Release, *chart.Chart, string) {
+func prepareMockRelease(t *testing.T, releaseName string) (func(n string, v int, ch *chart.Chart) *release.Release, *chart.Chart, string) {
+ t.Helper()
tmpChart := t.TempDir()
configmapData, err := os.ReadFile("testdata/testcharts/upgradetest/templates/configmap.yaml")
if err != nil {
@@ -445,7 +446,7 @@ func TestUpgradeFileCompletion(t *testing.T) {
func TestUpgradeInstallWithLabels(t *testing.T) {
releaseName := "funny-bunny-labels"
- _, _, chartPath := prepareMockRelease(releaseName, t)
+ _, _, chartPath := prepareMockRelease(t, releaseName)
defer resetEnv()()
@@ -471,7 +472,8 @@ func TestUpgradeInstallWithLabels(t *testing.T) {
}
}
-func prepareMockReleaseWithSecret(releaseName string, t *testing.T) (func(n string, v int, ch *chart.Chart) *release.Release, *chart.Chart, string) {
+func prepareMockReleaseWithSecret(t *testing.T, releaseName string) (func(n string, v int, ch *chart.Chart) *release.Release, *chart.Chart, string) {
+ t.Helper()
tmpChart := t.TempDir()
configmapData, err := os.ReadFile("testdata/testcharts/chart-with-secret/templates/configmap.yaml")
if err != nil {
@@ -512,7 +514,7 @@ func prepareMockReleaseWithSecret(releaseName string, t *testing.T) (func(n stri
func TestUpgradeWithDryRun(t *testing.T) {
releaseName := "funny-bunny-labels"
- _, _, chartPath := prepareMockReleaseWithSecret(releaseName, t)
+ _, _, chartPath := prepareMockReleaseWithSecret(t, releaseName)
defer resetEnv()()
diff --git a/cmd/helm/verify.go b/pkg/cmd/verify.go
similarity index 97%
rename from cmd/helm/verify.go
rename to pkg/cmd/verify.go
index 197a164b6..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/v4/cmd/helm/require"
"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 98%
rename from cmd/helm/version.go
rename to pkg/cmd/version.go
index 030ce2dcd..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/v4/cmd/helm/require"
"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 f5d1deac9..04c56e614 100644
--- a/pkg/downloader/chart_downloader.go
+++ b/pkg/downloader/chart_downloader.go
@@ -16,15 +16,15 @@ limitations under the License.
package downloader
import (
+ "errors"
"fmt"
"io"
+ "io/fs"
"net/url"
"os"
"path/filepath"
"strings"
- "github.com/pkg/errors"
-
"helm.sh/helm/v4/internal/fileutil"
"helm.sh/helm/v4/internal/urlutil"
"helm.sh/helm/v4/pkg/getter"
@@ -120,7 +120,7 @@ func (c *ChartDownloader) DownloadTo(ref, version, dest string) (string, *proven
body, err := g.Get(u.String() + ".prov")
if err != nil {
if c.Verify == VerifyAlways {
- return destfile, ver, errors.Errorf("failed to fetch provenance %q", u.String()+".prov")
+ return destfile, ver, fmt.Errorf("failed to fetch provenance %q", u.String()+".prov")
}
fmt.Fprintf(c.Out, "WARNING: Verification not found for %s: %s\n", ref, err)
return destfile, ver, nil
@@ -160,7 +160,7 @@ func (c *ChartDownloader) DownloadTo(ref, version, dest string) (string, *proven
func (c *ChartDownloader) ResolveChartVersion(ref, version string) (*url.URL, error) {
u, err := url.Parse(ref)
if err != nil {
- return nil, errors.Errorf("invalid chart URL format: %s", ref)
+ return nil, fmt.Errorf("invalid chart URL format: %s", ref)
}
if registry.IsOCI(u.String()) {
@@ -213,13 +213,12 @@ func (c *ChartDownloader) ResolveChartVersion(ref, version string) (*url.URL, er
// See if it's of the form: repo/path_to_chart
p := strings.SplitN(u.Path, "/", 2)
if len(p) < 2 {
- return u, errors.Errorf("non-absolute URLs should be in form of repo_name/path_to_chart, got: %s", u)
+ return u, fmt.Errorf("non-absolute URLs should be in form of repo_name/path_to_chart, got: %s", u)
}
repoName := p[0]
chartName := p[1]
rc, err := pickChartRepositoryConfigByName(repoName, rf.Repositories)
-
if err != nil {
return u, err
}
@@ -249,23 +248,22 @@ func (c *ChartDownloader) ResolveChartVersion(ref, version string) (*url.URL, er
idxFile := filepath.Join(c.RepositoryCache, helmpath.CacheIndexFile(r.Config.Name))
i, err := repo.LoadIndexFile(idxFile)
if err != nil {
- return u, errors.Wrap(err, "no cached repo found. (try 'helm repo update')")
+ return u, fmt.Errorf("no cached repo found. (try 'helm repo update'): %w", err)
}
cv, err := i.Get(chartName, version)
if err != nil {
- return u, errors.Wrapf(err, "chart %q matching %s not found in %s index. (try 'helm repo update')", chartName, version, r.Config.Name)
+ return u, fmt.Errorf("chart %q matching %s not found in %s index. (try 'helm repo update'): %w", chartName, version, r.Config.Name, err)
}
if len(cv.URLs) == 0 {
- return u, errors.Errorf("chart %q has no downloadable URLs", ref)
+ return u, fmt.Errorf("chart %q has no downloadable URLs", ref)
}
// TODO: Seems that picking first URL is not fully correct
resolvedURL, err := repo.ResolveReferenceURL(rc.URL, cv.URLs[0])
-
if err != nil {
- return u, errors.Errorf("invalid chart URL format: %s", ref)
+ return u, fmt.Errorf("invalid chart URL format: %s", ref)
}
return url.Parse(resolvedURL)
@@ -288,12 +286,12 @@ func VerifyChart(path, keyring string) (*provenance.Verification, error) {
provfile := path + ".prov"
if _, err := os.Stat(provfile); err != nil {
- return nil, errors.Wrapf(err, "could not load provenance file %s", provfile)
+ return nil, fmt.Errorf("could not load provenance file %s: %w", provfile, err)
}
sig, err := provenance.NewFromKeyring(keyring, "")
if err != nil {
- return nil, errors.Wrap(err, "failed to load keyring")
+ return nil, fmt.Errorf("failed to load keyring: %w", err)
}
return sig.Verify(path, provfile)
}
@@ -310,12 +308,12 @@ func pickChartRepositoryConfigByName(name string, cfgs []*repo.Entry) (*repo.Ent
for _, rc := range cfgs {
if rc.Name == name {
if rc.URL == "" {
- return nil, errors.Errorf("no URL found for repository %s", name)
+ return nil, fmt.Errorf("no URL found for repository %s", name)
}
return rc, nil
}
}
- return nil, errors.Errorf("repo %s not found", name)
+ return nil, fmt.Errorf("repo %s not found", name)
}
// scanReposForURL scans all repos to find which repo contains the given URL.
@@ -348,7 +346,7 @@ func (c *ChartDownloader) scanReposForURL(u string, rf *repo.File) (*repo.Entry,
idxFile := filepath.Join(c.RepositoryCache, helmpath.CacheIndexFile(r.Config.Name))
i, err := repo.LoadIndexFile(idxFile)
if err != nil {
- return nil, errors.Wrap(err, "no cached repo found. (try 'helm repo update')")
+ return nil, fmt.Errorf("no cached repo found. (try 'helm repo update'): %w", err)
}
for _, entry := range i.Entries {
@@ -367,7 +365,7 @@ func (c *ChartDownloader) scanReposForURL(u string, rf *repo.File) (*repo.Entry,
func loadRepoConfig(file string) (*repo.File, error) {
r, err := repo.LoadFile(file)
- if err != nil && !os.IsNotExist(errors.Cause(err)) {
+ if err != nil && !errors.Is(err, fs.ErrNotExist) {
return nil, err
}
return r, nil
diff --git a/pkg/downloader/chart_downloader_test.go b/pkg/downloader/chart_downloader_test.go
index 26dcc58ff..766afede1 100644
--- a/pkg/downloader/chart_downloader_test.go
+++ b/pkg/downloader/chart_downloader_test.go
@@ -46,6 +46,7 @@ func TestResolveChartRef(t *testing.T) {
{name: "reference, querystring repo", ref: "testing-querystring/alpine", expect: "http://example.com/alpine-1.2.3.tgz?key=value"},
{name: "reference, testing-relative repo", ref: "testing-relative/foo", expect: "http://example.com/helm/charts/foo-1.2.3.tgz"},
{name: "reference, testing-relative repo", ref: "testing-relative/bar", expect: "http://example.com/helm/bar-1.2.3.tgz"},
+ {name: "reference, testing-relative repo", ref: "testing-relative/baz", expect: "http://example.com/path/to/baz-1.2.3.tgz"},
{name: "reference, testing-relative-trailing-slash repo", ref: "testing-relative-trailing-slash/foo", expect: "http://example.com/helm/charts/foo-1.2.3.tgz"},
{name: "reference, testing-relative-trailing-slash repo", ref: "testing-relative-trailing-slash/bar", expect: "http://example.com/helm/bar-1.2.3.tgz"},
{name: "encoded URL", ref: "encoded-url/foobar", expect: "http://example.com/with%2Fslash/charts/foobar-4.2.1.tgz"},
diff --git a/pkg/downloader/manager.go b/pkg/downloader/manager.go
index e5dac8b10..b43165975 100644
--- a/pkg/downloader/manager.go
+++ b/pkg/downloader/manager.go
@@ -18,27 +18,27 @@ package downloader
import (
"crypto"
"encoding/hex"
+ "errors"
"fmt"
"io"
+ stdfs "io/fs"
"log"
"net/url"
"os"
- "path"
"path/filepath"
"regexp"
"strings"
"sync"
"github.com/Masterminds/semver/v3"
- "github.com/pkg/errors"
"sigs.k8s.io/yaml"
"helm.sh/helm/v4/internal/resolver"
"helm.sh/helm/v4/internal/third_party/dep/fs"
"helm.sh/helm/v4/internal/urlutil"
- "helm.sh/helm/v4/pkg/chart"
- "helm.sh/helm/v4/pkg/chart/loader"
- chartutil "helm.sh/helm/v4/pkg/chart/util"
+ 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"
@@ -220,7 +220,7 @@ func (m *Manager) Update() error {
func (m *Manager) loadChartDir() (*chart.Chart, error) {
if fi, err := os.Stat(m.ChartPath); err != nil {
- return nil, errors.Wrapf(err, "could not find %s", m.ChartPath)
+ return nil, fmt.Errorf("could not find %s: %w", m.ChartPath, err)
} else if !fi.IsDir() {
return nil, errors.New("only unpacked charts can be updated")
}
@@ -251,9 +251,9 @@ func (m *Manager) downloadAll(deps []*chart.Dependency) error {
// Check if 'charts' directory is not actually a directory. If it does not exist, create it.
if fi, err := os.Stat(destPath); err == nil {
if !fi.IsDir() {
- return errors.Errorf("%q is not a directory", destPath)
+ return fmt.Errorf("%q is not a directory", destPath)
}
- } else if os.IsNotExist(err) {
+ } else if errors.Is(err, stdfs.ErrNotExist) {
if err := os.MkdirAll(destPath, 0755); err != nil {
return err
}
@@ -314,7 +314,7 @@ func (m *Manager) downloadAll(deps []*chart.Dependency) error {
// https://github.com/helm/helm/issues/1439
churl, username, password, insecureskiptlsverify, passcredentialsall, caFile, certFile, keyFile, err := m.findChartURL(dep.Name, dep.Version, dep.Repository, repos)
if err != nil {
- saveError = errors.Wrapf(err, "could not find %s", churl)
+ saveError = fmt.Errorf("could not find %s: %w", churl, err)
break
}
@@ -345,7 +345,7 @@ func (m *Manager) downloadAll(deps []*chart.Dependency) error {
if registry.IsOCI(churl) {
churl, version, err = parseOCIRef(churl)
if err != nil {
- return errors.Wrapf(err, "could not parse OCI reference")
+ return fmt.Errorf("could not parse OCI reference: %w", err)
}
dl.Options = append(dl.Options,
getter.WithRegistryClient(m.RegistryClient),
@@ -353,7 +353,7 @@ func (m *Manager) downloadAll(deps []*chart.Dependency) error {
}
if _, _, err = dl.DownloadTo(churl, version, tmpPath); err != nil {
- saveError = errors.Wrapf(err, "could not download %s", churl)
+ saveError = fmt.Errorf("could not download %s: %w", churl, err)
break
}
@@ -377,7 +377,7 @@ func parseOCIRef(chartRef string) (string, string, error) {
refTagRegexp := regexp.MustCompile(`^(oci://[^:]+(:[0-9]{1,5})?[^:]+):(.*)$`)
caps := refTagRegexp.FindStringSubmatch(chartRef)
if len(caps) != 4 {
- return "", "", errors.Errorf("improperly formatted oci chart reference: %s", chartRef)
+ return "", "", fmt.Errorf("improperly formatted oci chart reference: %s", chartRef)
}
chartRef = caps[1]
tag := caps[3]
@@ -385,7 +385,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.
@@ -559,7 +559,7 @@ func (m *Manager) ensureMissingRepos(repoNames map[string]string, deps []*chart.
func (m *Manager) resolveRepoNames(deps []*chart.Dependency) (map[string]string, error) {
rf, err := loadRepoConfig(m.RepositoryConfig)
if err != nil {
- if os.IsNotExist(err) {
+ if errors.Is(err, stdfs.ErrNotExist) {
return make(map[string]string), nil
}
return nil, err
@@ -727,7 +727,6 @@ func (m *Manager) findChartURL(name, version, repoURL string, repos map[string]*
}
for _, cr := range repos {
-
if urlutil.Equal(repoURL, cr.Config.URL) {
var entry repo.ChartVersions
entry, err = findEntryByName(name, cr)
@@ -744,7 +743,7 @@ func (m *Manager) findChartURL(name, version, repoURL string, repos map[string]*
//nolint:nakedret
return
}
- url, err = normalizeURL(repoURL, ve.URLs[0])
+ url, err = repo.ResolveReferenceURL(repoURL, ve.URLs[0])
if err != nil {
//nolint:nakedret
return
@@ -764,7 +763,7 @@ func (m *Manager) findChartURL(name, version, repoURL string, repos map[string]*
if err == nil {
return url, username, password, false, false, "", "", "", err
}
- err = errors.Errorf("chart %s not found in %s: %s", name, repoURL, err)
+ err = fmt.Errorf("chart %s not found in %s: %w", name, repoURL, err)
return url, username, password, false, false, "", "", "", err
}
@@ -810,24 +809,6 @@ func versionEquals(v1, v2 string) bool {
return sv1.Equal(sv2)
}
-func normalizeURL(baseURL, urlOrPath string) (string, error) {
- u, err := url.Parse(urlOrPath)
- if err != nil {
- return urlOrPath, err
- }
- if u.IsAbs() {
- return u.String(), nil
- }
- u2, err := url.Parse(baseURL)
- if err != nil {
- return urlOrPath, errors.Wrap(err, "base URL failed to parse")
- }
-
- u2.RawPath = path.Join(u2.RawPath, urlOrPath)
- u2.Path = path.Join(u2.Path, urlOrPath)
- return u2.String(), nil
-}
-
// loadChartRepositories reads the repositories.yaml, and then builds a map of
// ChartRepositories.
//
@@ -838,7 +819,7 @@ func (m *Manager) loadChartRepositories() (map[string]*repo.ChartRepository, err
// Load repositories.yaml file
rf, err := loadRepoConfig(m.RepositoryConfig)
if err != nil {
- return indices, errors.Wrapf(err, "failed to load %s", m.RepositoryConfig)
+ return indices, fmt.Errorf("failed to load %s: %w", m.RepositoryConfig, err)
}
for _, re := range rf.Repositories {
@@ -870,13 +851,27 @@ func writeLock(chartpath string, lock *chart.Lock, legacyLockfile bool) error {
lockfileName = "requirements.lock"
}
dest := filepath.Join(chartpath, lockfileName)
+
+ info, err := os.Lstat(dest)
+ if err != nil && !os.IsNotExist(err) {
+ return fmt.Errorf("error getting info for %q: %w", dest, err)
+ } else if err == nil {
+ if info.Mode()&os.ModeSymlink != 0 {
+ link, err := os.Readlink(dest)
+ if err != nil {
+ return fmt.Errorf("error reading symlink for %q: %w", dest, err)
+ }
+ return fmt.Errorf("the %s file is a symlink to %q", lockfileName, link)
+ }
+ }
+
return os.WriteFile(dest, data, 0644)
}
// archive a dep chart from local directory and save it into destPath
func tarFromLocalDir(chartpath, name, repo, version, destPath string) (string, error) {
if !strings.HasPrefix(repo, "file://") {
- return "", errors.Errorf("wrong format: chart %s repository %s", name, repo)
+ return "", fmt.Errorf("wrong format: chart %s repository %s", name, repo)
}
origPath, err := resolver.GetLocalPath(repo, chartpath)
@@ -891,7 +886,7 @@ func tarFromLocalDir(chartpath, name, repo, version, destPath string) (string, e
constraint, err := semver.NewConstraint(version)
if err != nil {
- return "", errors.Wrapf(err, "dependency %s has an invalid version/constraint format", name)
+ return "", fmt.Errorf("dependency %s has an invalid version/constraint format: %w", name, err)
}
v, err := semver.NewVersion(ch.Metadata.Version)
@@ -904,7 +899,7 @@ func tarFromLocalDir(chartpath, name, repo, version, destPath string) (string, e
return ch.Metadata.Version, err
}
- return "", errors.Errorf("can't get a valid version for dependency %s", name)
+ return "", fmt.Errorf("can't get a valid version for dependency %s", name)
}
// The prefix to use for cache keys created by the manager for repo names
diff --git a/pkg/downloader/manager_test.go b/pkg/downloader/manager_test.go
index 8731fb003..f01a5d7ad 100644
--- a/pkg/downloader/manager_test.go
+++ b/pkg/downloader/manager_test.go
@@ -17,16 +17,20 @@ package downloader
import (
"bytes"
+ "errors"
+ "io/fs"
"os"
"path/filepath"
"reflect"
"testing"
+ "time"
"github.com/stretchr/testify/assert"
+ "sigs.k8s.io/yaml"
- "helm.sh/helm/v4/pkg/chart"
- "helm.sh/helm/v4/pkg/chart/loader"
- chartutil "helm.sh/helm/v4/pkg/chart/util"
+ 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"
@@ -51,26 +55,6 @@ func TestVersionEquals(t *testing.T) {
}
}
-func TestNormalizeURL(t *testing.T) {
- tests := []struct {
- name, base, path, expect string
- }{
- {name: "basic URL", base: "https://example.com", path: "http://helm.sh/foo", expect: "http://helm.sh/foo"},
- {name: "relative path", base: "https://helm.sh/charts", path: "foo", expect: "https://helm.sh/charts/foo"},
- {name: "Encoded path", base: "https://helm.sh/a%2Fb/charts", path: "foo", expect: "https://helm.sh/a%2Fb/charts/foo"},
- }
-
- for _, tt := range tests {
- got, err := normalizeURL(tt.base, tt.path)
- if err != nil {
- t.Errorf("%s: error %s", tt.name, err)
- continue
- } else if got != tt.expect {
- t.Errorf("%s: expected %q, got %q", tt.name, tt.expect, got)
- }
- }
-}
-
func TestFindChartURL(t *testing.T) {
var b bytes.Buffer
m := &Manager{
@@ -132,6 +116,31 @@ func TestFindChartURL(t *testing.T) {
if passcredentialsall != false {
t.Errorf("Unexpected passcredentialsall %t", passcredentialsall)
}
+
+ name = "foo"
+ version = "1.2.3"
+ repoURL = "http://example.com/helm"
+
+ churl, username, password, insecureSkipTLSVerify, passcredentialsall, _, _, _, err = m.findChartURL(name, version, repoURL, repos)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if churl != "http://example.com/helm/charts/foo-1.2.3.tgz" {
+ t.Errorf("Unexpected URL %q", churl)
+ }
+ if username != "" {
+ t.Errorf("Unexpected username %q", username)
+ }
+ if password != "" {
+ t.Errorf("Unexpected password %q", password)
+ }
+ if passcredentialsall != false {
+ t.Errorf("Unexpected passcredentialsall %t", passcredentialsall)
+ }
+ if insecureSkipTLSVerify {
+ t.Errorf("Unexpected insecureSkipTLSVerify %t", insecureSkipTLSVerify)
+ }
}
func TestGetRepoNames(t *testing.T) {
@@ -262,7 +271,7 @@ func TestDownloadAll(t *testing.T) {
t.Error(err)
}
- if _, err := os.Stat(filepath.Join(chartPath, "charts", "signtest-0.1.0.tgz")); os.IsNotExist(err) {
+ if _, err := os.Stat(filepath.Join(chartPath, "charts", "signtest-0.1.0.tgz")); errors.Is(err, fs.ErrNotExist) {
t.Error(err)
}
@@ -435,6 +444,7 @@ func TestUpdateWithNoRepo(t *testing.T) {
// Parent chart includes local-subchart 0.1.0 subchart from a fake repository, by default.
// If each of these main fields (name, version, repository) is not supplied by dep param, default value will be used.
func checkBuildWithOptionalFields(t *testing.T, chartName string, dep chart.Dependency) {
+ t.Helper()
// Set up a fake repo
srv := repotest.NewTempServer(
t,
@@ -664,3 +674,94 @@ func TestDedupeRepos(t *testing.T) {
})
}
}
+
+func TestWriteLock(t *testing.T) {
+ fixedTime, err := time.Parse(time.RFC3339, "2025-07-04T00:00:00Z")
+ assert.NoError(t, err)
+ lock := &chart.Lock{
+ Generated: fixedTime,
+ Digest: "sha256:12345",
+ Dependencies: []*chart.Dependency{
+ {
+ Name: "fantastic-chart",
+ Version: "1.2.3",
+ Repository: "https://example.com/charts",
+ },
+ },
+ }
+ expectedContent, err := yaml.Marshal(lock)
+ assert.NoError(t, err)
+
+ t.Run("v2 lock file", func(t *testing.T) {
+ dir := t.TempDir()
+ err := writeLock(dir, lock, false)
+ assert.NoError(t, err)
+
+ lockfilePath := filepath.Join(dir, "Chart.lock")
+ _, err = os.Stat(lockfilePath)
+ assert.NoError(t, err, "Chart.lock should exist")
+
+ content, err := os.ReadFile(lockfilePath)
+ assert.NoError(t, err)
+ assert.Equal(t, expectedContent, content)
+
+ // Check that requirements.lock does not exist
+ _, err = os.Stat(filepath.Join(dir, "requirements.lock"))
+ assert.Error(t, err)
+ assert.True(t, os.IsNotExist(err))
+ })
+
+ t.Run("v1 lock file", func(t *testing.T) {
+ dir := t.TempDir()
+ err := writeLock(dir, lock, true)
+ assert.NoError(t, err)
+
+ lockfilePath := filepath.Join(dir, "requirements.lock")
+ _, err = os.Stat(lockfilePath)
+ assert.NoError(t, err, "requirements.lock should exist")
+
+ content, err := os.ReadFile(lockfilePath)
+ assert.NoError(t, err)
+ assert.Equal(t, expectedContent, content)
+
+ // Check that Chart.lock does not exist
+ _, err = os.Stat(filepath.Join(dir, "Chart.lock"))
+ assert.Error(t, err)
+ assert.True(t, os.IsNotExist(err))
+ })
+
+ t.Run("overwrite existing lock file", func(t *testing.T) {
+ dir := t.TempDir()
+ lockfilePath := filepath.Join(dir, "Chart.lock")
+ assert.NoError(t, os.WriteFile(lockfilePath, []byte("old content"), 0644))
+
+ err = writeLock(dir, lock, false)
+ assert.NoError(t, err)
+
+ content, err := os.ReadFile(lockfilePath)
+ assert.NoError(t, err)
+ assert.Equal(t, expectedContent, content)
+ })
+
+ t.Run("lock file is a symlink", func(t *testing.T) {
+ dir := t.TempDir()
+ dummyFile := filepath.Join(dir, "dummy.txt")
+ assert.NoError(t, os.WriteFile(dummyFile, []byte("dummy"), 0644))
+
+ lockfilePath := filepath.Join(dir, "Chart.lock")
+ assert.NoError(t, os.Symlink(dummyFile, lockfilePath))
+
+ err = writeLock(dir, lock, false)
+ assert.Error(t, err)
+ assert.Contains(t, err.Error(), "the Chart.lock file is a symlink to")
+ })
+
+ t.Run("chart path is not a directory", func(t *testing.T) {
+ dir := t.TempDir()
+ filePath := filepath.Join(dir, "not-a-dir")
+ assert.NoError(t, os.WriteFile(filePath, []byte("file"), 0644))
+
+ err = writeLock(filePath, lock, false)
+ assert.Error(t, err)
+ })
+}
diff --git a/pkg/downloader/testdata/repository/testing-relative-index.yaml b/pkg/downloader/testdata/repository/testing-relative-index.yaml
index ba27ed257..9524daf6e 100644
--- a/pkg/downloader/testdata/repository/testing-relative-index.yaml
+++ b/pkg/downloader/testdata/repository/testing-relative-index.yaml
@@ -26,3 +26,16 @@ entries:
version: 1.2.3
checksum: 0e6661f193211d7a5206918d42f5c2a9470b737d
apiVersion: v2
+ baz:
+ - name: baz
+ description: Baz Chart With Absolute Path
+ home: https://helm.sh/helm
+ keywords: []
+ maintainers: []
+ sources:
+ - https://github.com/helm/charts
+ urls:
+ - /path/to/baz-1.2.3.tgz
+ version: 1.2.3
+ checksum: 0e6661f193211d7a5206918d42f5c2a9470b737d
+ apiVersion: v2
diff --git a/pkg/engine/engine.go b/pkg/engine/engine.go
index 0229b1833..6e47a0e39 100644
--- a/pkg/engine/engine.go
+++ b/pkg/engine/engine.go
@@ -17,8 +17,10 @@ limitations under the License.
package engine
import (
+ "errors"
"fmt"
- "log"
+ "log/slog"
+ "maps"
"path"
"path/filepath"
"regexp"
@@ -26,13 +28,24 @@ import (
"strings"
"text/template"
- "github.com/pkg/errors"
"k8s.io/client-go/rest"
- "helm.sh/helm/v4/pkg/chart"
- chartutil "helm.sh/helm/v4/pkg/chart/util"
+ chart "helm.sh/helm/v4/pkg/chart/v2"
+ chartutil "helm.sh/helm/v4/pkg/chart/v2/util"
)
+// taken from https://cs.opensource.google/go/go/+/refs/tags/go1.23.6:src/text/template/exec.go;l=141
+// > "template: %s: executing %q at <%s>: %s"
+var execErrFmt = regexp.MustCompile(`^template: (?P(?U).+): executing (?P(?U).+) at (?P(?U).+): (?P(?U).+)(?P( template:.*)?)$`)
+
+// taken from https://cs.opensource.google/go/go/+/refs/tags/go1.23.6:src/text/template/exec.go;l=138
+// > "template: %s: %s"
+var execErrFmtWithoutTemplate = regexp.MustCompile(`^template: (?P(?U).+): (?P.*)(?P( template:.*)?)$`)
+
+// taken from https://cs.opensource.google/go/go/+/refs/tags/go1.23.6:src/text/template/exec.go;l=191
+// > "template: no template %q associated with template %q"
+var execErrNoTemplateAssociated = regexp.MustCompile(`^template: no template (?P.*) associated with template (?P(.*)?)$`)
+
// Engine is an implementation of the Helm rendering implementation for templates.
type Engine struct {
// If strict is enabled, template rendering will fail if a template references
@@ -44,6 +57,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.
@@ -131,7 +146,9 @@ func includeFun(t *template.Template, includedNames map[string]int) func(string,
var buf strings.Builder
if v, ok := includedNames[name]; ok {
if v > recursionMaxNums {
- return "", errors.Wrapf(fmt.Errorf("unable to execute template"), "rendering template has a nested reference name: %s", name)
+ return "", fmt.Errorf(
+ "rendering template has a nested reference name: %s: %w",
+ name, errors.New("unable to execute template"))
}
includedNames[name]++
} else {
@@ -149,7 +166,7 @@ func tplFun(parent *template.Template, includedNames map[string]int, strict bool
return func(tpl string, vals interface{}) (string, error) {
t, err := parent.Clone()
if err != nil {
- return "", errors.Wrapf(err, "cannot clone template")
+ return "", fmt.Errorf("cannot clone template: %w", err)
}
// Re-inject the missingkey option, see text/template issue https://github.com/golang/go/issues/43022
@@ -176,12 +193,12 @@ func tplFun(parent *template.Template, includedNames map[string]int, strict bool
// text string. (Maybe we could use a hash appended to the name?)
t, err = t.New(parent.Name()).Parse(tpl)
if err != nil {
- return "", errors.Wrapf(err, "cannot parse template %q", tpl)
+ return "", fmt.Errorf("cannot parse template %q: %w", tpl, err)
}
var buf strings.Builder
if err := t.Execute(&buf, vals); err != nil {
- return "", errors.Wrapf(err, "error during tpl function execution for %q", tpl)
+ return "", fmt.Errorf("error during tpl function execution for %q: %w", tpl, err)
}
// See comment in renderWithReferences explaining the hack.
@@ -203,7 +220,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))
@@ -211,7 +228,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))
@@ -224,7 +241,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))
@@ -244,6 +261,9 @@ func (e Engine) initFunMap(t *template.Template) {
}
}
+ // Set custom template funcs
+ maps.Copy(funcMap, e.CustomTemplateFuncs)
+
t.Funcs(funcMap)
}
@@ -258,7 +278,7 @@ func (e Engine) render(tpls map[string]renderable) (rendered map[string]string,
// template engine.
defer func() {
if r := recover(); r != nil {
- err = errors.Errorf("rendering template failed: %v", r)
+ err = fmt.Errorf("rendering template failed: %v", r)
}
}()
t := template.New("gotpl")
@@ -295,7 +315,7 @@ func (e Engine) render(tpls map[string]renderable) (rendered map[string]string,
vals["Template"] = chartutil.Values{"Name": filename, "BasePath": tpls[filename].basePath}
var buf strings.Builder
if err := t.ExecuteTemplate(&buf, filename, vals); err != nil {
- return map[string]string{}, cleanupExecError(filename, err)
+ return map[string]string{}, reformatExecErrorMsg(filename, err)
}
// Work around the issue where Go will emit "" even if Options(missing=zero)
@@ -321,7 +341,33 @@ func cleanupParseError(filename string, err error) error {
return fmt.Errorf("parse error at (%s): %s", string(location), errMsg)
}
-func cleanupExecError(filename string, err error) error {
+type TraceableError struct {
+ location string
+ message string
+ executedFunction string
+}
+
+func (t TraceableError) String() string {
+ var errorString strings.Builder
+ if t.location != "" {
+ fmt.Fprintf(&errorString, "%s\n ", t.location)
+ }
+ if t.executedFunction != "" {
+ fmt.Fprintf(&errorString, "%s\n ", t.executedFunction)
+ }
+ if t.message != "" {
+ fmt.Fprintf(&errorString, "%s\n", t.message)
+ }
+ return errorString.String()
+}
+
+// reformatExecErrorMsg takes an error message for template rendering and formats it into a formatted
+// multi-line error string
+func reformatExecErrorMsg(filename string, err error) error {
+ // This function matches the error message against regex's for the text/template package.
+ // If the regex's can parse out details from that error message such as the line number, template it failed on,
+ // and error description, then it will construct a new error that displays these details in a structured way.
+ // If there are issues with parsing the error message, the err passed into the function should return instead.
if _, isExecError := err.(template.ExecError); !isExecError {
return err
}
@@ -340,8 +386,46 @@ func cleanupExecError(filename string, err error) error {
if len(parts) >= 2 {
return fmt.Errorf("execution error at (%s): %s", string(location), parts[1])
}
+ current := err
+ fileLocations := []TraceableError{}
+ for current != nil {
+ var traceable TraceableError
+ if matches := execErrFmt.FindStringSubmatch(current.Error()); matches != nil {
+ templateName := matches[execErrFmt.SubexpIndex("templateName")]
+ functionName := matches[execErrFmt.SubexpIndex("functionName")]
+ locationName := matches[execErrFmt.SubexpIndex("location")]
+ errMsg := matches[execErrFmt.SubexpIndex("errMsg")]
+ traceable = TraceableError{
+ location: templateName,
+ message: errMsg,
+ executedFunction: "executing " + functionName + " at " + locationName + ":",
+ }
+ } else if matches := execErrFmtWithoutTemplate.FindStringSubmatch(current.Error()); matches != nil {
+ templateName := matches[execErrFmt.SubexpIndex("templateName")]
+ errMsg := matches[execErrFmt.SubexpIndex("errMsg")]
+ traceable = TraceableError{
+ location: templateName,
+ message: errMsg,
+ }
+ } else if matches := execErrNoTemplateAssociated.FindStringSubmatch(current.Error()); matches != nil {
+ traceable = TraceableError{
+ message: current.Error(),
+ }
+ } else {
+ return err
+ }
+ if len(fileLocations) == 0 || fileLocations[len(fileLocations)-1] != traceable {
+ fileLocations = append(fileLocations, traceable)
+ }
+ current = errors.Unwrap(current)
+ }
+
+ var finalErrorString strings.Builder
+ for _, fileLocation := range fileLocations {
+ fmt.Fprintf(&finalErrorString, "%s", fileLocation.String())
+ }
- return err
+ return errors.New(strings.TrimSpace(finalErrorString.String()))
}
func sortTemplates(tpls map[string]renderable) []string {
diff --git a/pkg/engine/engine_test.go b/pkg/engine/engine_test.go
index 4c28c2965..f4228fbd7 100644
--- a/pkg/engine/engine_test.go
+++ b/pkg/engine/engine_test.go
@@ -24,14 +24,16 @@ import (
"testing"
"text/template"
+ "github.com/stretchr/testify/assert"
+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/dynamic/fake"
- "helm.sh/helm/v4/pkg/chart"
- chartutil "helm.sh/helm/v4/pkg/chart/util"
+ chart "helm.sh/helm/v4/pkg/chart/v2"
+ chartutil "helm.sh/helm/v4/pkg/chart/v2/util"
)
func TestSortTemplates(t *testing.T) {
@@ -1289,14 +1291,140 @@ func TestRenderTplMissingKeyString(t *testing.T) {
t.Errorf("Expected error, got %v", out)
return
}
- switch err.(type) {
- case (template.ExecError):
- errTxt := fmt.Sprint(err)
- if !strings.Contains(errTxt, "noSuchKey") {
- t.Errorf("Expected error to contain 'noSuchKey', got %s", errTxt)
- }
- default:
- // Some unexpected error.
+ errTxt := fmt.Sprint(err)
+ if !strings.Contains(errTxt, "noSuchKey") {
+ t.Errorf("Expected error to contain 'noSuchKey', got %s", errTxt)
+ }
+
+}
+
+func TestNestedHelpersProducesMultilineStacktrace(t *testing.T) {
+ c := &chart.Chart{
+ Metadata: &chart.Metadata{Name: "NestedHelperFunctions"},
+ Templates: []*chart.File{
+ {Name: "templates/svc.yaml", Data: []byte(
+ `name: {{ include "nested_helper.name" . }}`,
+ )},
+ {Name: "templates/_helpers_1.tpl", Data: []byte(
+ `{{- define "nested_helper.name" -}}{{- include "common.names.get_name" . -}}{{- end -}}`,
+ )},
+ {Name: "charts/common/templates/_helpers_2.tpl", Data: []byte(
+ `{{- define "common.names.get_name" -}}{{- .Values.nonexistant.key | trunc 63 | trimSuffix "-" -}}{{- end -}}`,
+ )},
+ },
+ }
+
+ expectedErrorMessage := `NestedHelperFunctions/templates/svc.yaml:1:9
+ executing "NestedHelperFunctions/templates/svc.yaml" at :
+ error calling include:
+NestedHelperFunctions/templates/_helpers_1.tpl:1:39
+ executing "nested_helper.name" at :
+ error calling include:
+NestedHelperFunctions/charts/common/templates/_helpers_2.tpl:1:49
+ executing "common.names.get_name" at <.Values.nonexistant.key>:
+ nil pointer evaluating interface {}.key`
+
+ v := chartutil.Values{}
+
+ val, _ := chartutil.CoalesceValues(c, v)
+ vals := map[string]interface{}{
+ "Values": val.AsMap(),
+ }
+ _, err := Render(c, vals)
+
+ assert.NotNil(t, err)
+ assert.Equal(t, expectedErrorMessage, err.Error())
+}
+
+func TestMultilineNoTemplateAssociatedError(t *testing.T) {
+ c := &chart.Chart{
+ Metadata: &chart.Metadata{Name: "multiline"},
+ Templates: []*chart.File{
+ {Name: "templates/svc.yaml", Data: []byte(
+ `name: {{ include "nested_helper.name" . }}`,
+ )},
+ {Name: "templates/test.yaml", Data: []byte(
+ `{{ toYaml .Values }}`,
+ )},
+ {Name: "charts/common/templates/_helpers_2.tpl", Data: []byte(
+ `{{ toYaml .Values }}`,
+ )},
+ },
+ }
+
+ expectedErrorMessage := `multiline/templates/svc.yaml:1:9
+ executing "multiline/templates/svc.yaml" at :
+ error calling include:
+template: no template "nested_helper.name" associated with template "gotpl"`
+
+ v := chartutil.Values{}
+
+ val, _ := chartutil.CoalesceValues(c, v)
+ vals := map[string]interface{}{
+ "Values": val.AsMap(),
+ }
+ _, err := Render(c, vals)
+
+ assert.NotNil(t, err)
+ assert.Equal(t, expectedErrorMessage, err.Error())
+}
+
+func TestRenderCustomTemplateFuncs(t *testing.T) {
+ // 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 dc6a67de8..87166728c 100644
--- a/pkg/engine/files.go
+++ b/pkg/engine/files.go
@@ -23,7 +23,7 @@ import (
"github.com/gobwas/glob"
- "helm.sh/helm/v4/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.go b/pkg/engine/funcs.go
index d03a818c2..a97f8f104 100644
--- a/pkg/engine/funcs.go
+++ b/pkg/engine/funcs.go
@@ -19,6 +19,7 @@ package engine
import (
"bytes"
"encoding/json"
+ "maps"
"strings"
"text/template"
@@ -51,10 +52,12 @@ func funcMap() template.FuncMap {
"toToml": toTOML,
"fromToml": fromTOML,
"toYaml": toYAML,
+ "mustToYaml": mustToYAML,
"toYamlPretty": toYAMLPretty,
"fromYaml": fromYAML,
"fromYamlArray": fromYAMLArray,
"toJson": toJSON,
+ "mustToJson": mustToJSON,
"fromJson": fromJSON,
"fromJsonArray": fromJSONArray,
@@ -71,9 +74,7 @@ func funcMap() template.FuncMap {
},
}
- for k, v := range extra {
- f[k] = v
- }
+ maps.Copy(f, extra)
return f
}
@@ -91,6 +92,19 @@ func toYAML(v interface{}) string {
return strings.TrimSuffix(string(data), "\n")
}
+// mustToYAML takes an interface, marshals it to yaml, and returns a string.
+// It will panic if there is an error.
+//
+// This is designed to be called from a template when need to ensure that the
+// output YAML is valid.
+func mustToYAML(v interface{}) string {
+ data, err := yaml.Marshal(v)
+ if err != nil {
+ panic(err)
+ }
+ return strings.TrimSuffix(string(data), "\n")
+}
+
func toYAMLPretty(v interface{}) string {
var data bytes.Buffer
encoder := goYaml.NewEncoder(&data)
@@ -176,6 +190,19 @@ func toJSON(v interface{}) string {
return string(data)
}
+// mustToJSON takes an interface, marshals it to json, and returns a string.
+// It will panic if there is an error.
+//
+// This is designed to be called from a template when need to ensure that the
+// output JSON is valid.
+func mustToJSON(v interface{}) string {
+ data, err := json.Marshal(v)
+ if err != nil {
+ panic(err)
+ }
+ return string(data)
+}
+
// fromJSON converts a JSON document into a map[string]interface{}.
//
// This is not a general-purpose JSON parser, and will not parse all valid
diff --git a/pkg/engine/funcs_test.go b/pkg/engine/funcs_test.go
index a4f4d604f..71a72e2e4 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 . }}`,
@@ -135,6 +135,43 @@ keyInElement1 = "valueInElement1"`,
assert.NoError(t, err)
assert.Equal(t, tt.expect, b.String(), tt.tpl)
}
+
+ loopMap := map[string]interface{}{
+ "foo": "bar",
+ }
+ loopMap["loop"] = []interface{}{loopMap}
+
+ mustFuncsTests := []struct {
+ tpl string
+ expect interface{}
+ vars interface{}
+ }{{
+ tpl: `{{ mustToYaml . }}`,
+ vars: loopMap,
+ }, {
+ tpl: `{{ mustToJson . }}`,
+ vars: loopMap,
+ }, {
+ tpl: `{{ toYaml . }}`,
+ expect: "", // should return empty string and swallow error
+ vars: loopMap,
+ }, {
+ tpl: `{{ toJson . }}`,
+ expect: "", // should return empty string and swallow error
+ vars: loopMap,
+ },
+ }
+
+ for _, tt := range mustFuncsTests {
+ var b strings.Builder
+ err := template.Must(template.New("test").Funcs(funcMap()).Parse(tt.tpl)).Execute(&b, tt.vars)
+ if tt.expect != nil {
+ assert.NoError(t, err)
+ assert.Equal(t, tt.expect, b.String(), tt.tpl)
+ } else {
+ assert.Error(t, err)
+ }
+ }
}
// This test to check a function provided by sprig is due to a change in a
diff --git a/pkg/engine/lookup_func.go b/pkg/engine/lookup_func.go
index 75e85098d..605b43a48 100644
--- a/pkg/engine/lookup_func.go
+++ b/pkg/engine/lookup_func.go
@@ -18,10 +18,10 @@ package engine
import (
"context"
- "log"
+ "fmt"
+ "log/slog"
"strings"
- "github.com/pkg/errors"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
@@ -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,8 +98,8 @@ 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)
- return nil, false, errors.Wrapf(err, "unable to get apiresource from unstructured: %s", gvk.String())
+ 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{
Group: apiRes.Group,
@@ -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/gates/gates_test.go b/pkg/gates/gates_test.go
index 6bdd17ed6..4d77199e6 100644
--- a/pkg/gates/gates_test.go
+++ b/pkg/gates/gates_test.go
@@ -23,14 +23,13 @@ import (
const name string = "HELM_EXPERIMENTAL_FEATURE"
func TestIsEnabled(t *testing.T) {
- os.Unsetenv(name)
g := Gate(name)
if g.IsEnabled() {
t.Errorf("feature gate shows as available, but the environment variable %s was not set", name)
}
- os.Setenv(name, "1")
+ t.Setenv(name, "1")
if !g.IsEnabled() {
t.Errorf("feature gate shows as disabled, but the environment variable %s was set", name)
diff --git a/pkg/getter/getter.go b/pkg/getter/getter.go
index 5014784bc..5605e043f 100644
--- a/pkg/getter/getter.go
+++ b/pkg/getter/getter.go
@@ -18,11 +18,11 @@ package getter
import (
"bytes"
+ "fmt"
"net/http"
+ "slices"
"time"
- "github.com/pkg/errors"
-
"helm.sh/helm/v4/pkg/cli"
"helm.sh/helm/v4/pkg/registry"
)
@@ -164,12 +164,7 @@ type Provider struct {
// Provides returns true if the given scheme is supported by this Provider.
func (p Provider) Provides(scheme string) bool {
- for _, i := range p.Schemes {
- if i == scheme {
- return true
- }
- }
- return false
+ return slices.Contains(p.Schemes, scheme)
}
// Providers is a collection of Provider objects.
@@ -184,7 +179,7 @@ func (p Providers) ByScheme(scheme string) (Getter, error) {
return pp.New()
}
}
- return nil, errors.Errorf("scheme %q not supported", scheme)
+ return nil, fmt.Errorf("scheme %q not supported", scheme)
}
const (
@@ -196,24 +191,32 @@ const (
var defaultOptions = []Option{WithTimeout(time.Second * DefaultHTTPTimeout)}
-var httpProvider = Provider{
- Schemes: []string{"http", "https"},
- New: func(options ...Option) (Getter, error) {
- options = append(options, defaultOptions...)
- return NewHTTPGetter(options...)
- },
-}
-
-var ociProvider = Provider{
- Schemes: []string{registry.OCIScheme},
- New: NewOCIGetter,
+func Getters(extraOpts ...Option) Providers {
+ return Providers{
+ Provider{
+ Schemes: []string{"http", "https"},
+ New: func(options ...Option) (Getter, error) {
+ options = append(options, defaultOptions...)
+ options = append(options, extraOpts...)
+ return NewHTTPGetter(options...)
+ },
+ },
+ Provider{
+ Schemes: []string{registry.OCIScheme},
+ New: func(options ...Option) (Getter, error) {
+ options = append(options, defaultOptions...)
+ options = append(options, extraOpts...)
+ return NewOCIGetter(options...)
+ },
+ },
+ }
}
// All finds all of the registered getters as a list of Provider instances.
// Currently, the built-in getters and the discovered plugins with downloader
// notations are collected.
-func All(settings *cli.EnvSettings) Providers {
- result := Providers{httpProvider, ociProvider}
+func All(settings *cli.EnvSettings, opts ...Option) Providers {
+ result := Getters(opts...)
pluginDownloaders, _ := collectPlugins(settings)
result = append(result, pluginDownloaders...)
return result
diff --git a/pkg/getter/getter_test.go b/pkg/getter/getter_test.go
index a14301900..83920e809 100644
--- a/pkg/getter/getter_test.go
+++ b/pkg/getter/getter_test.go
@@ -17,6 +17,7 @@ package getter
import (
"testing"
+ "time"
"helm.sh/helm/v4/pkg/cli"
)
@@ -52,6 +53,23 @@ func TestProviders(t *testing.T) {
}
}
+func TestProvidersWithTimeout(t *testing.T) {
+ want := time.Hour
+ getters := Getters(WithTimeout(want))
+ getter, err := getters.ByScheme("http")
+ if err != nil {
+ t.Error(err)
+ }
+ client, err := getter.(*HTTPGetter).httpClient()
+ if err != nil {
+ t.Error(err)
+ }
+ got := client.Timeout
+ if got != want {
+ t.Errorf("Expected %q, got %q", want, got)
+ }
+}
+
func TestAll(t *testing.T) {
env := cli.New()
env.PluginsDirectory = pluginDir
diff --git a/pkg/getter/httpgetter.go b/pkg/getter/httpgetter.go
index 37d80cda7..925df201e 100644
--- a/pkg/getter/httpgetter.go
+++ b/pkg/getter/httpgetter.go
@@ -18,15 +18,13 @@ package getter
import (
"bytes"
"crypto/tls"
+ "fmt"
"io"
"net/http"
"net/url"
"sync"
- "github.com/pkg/errors"
-
"helm.sh/helm/v4/internal/tlsutil"
- "helm.sh/helm/v4/internal/urlutil"
"helm.sh/helm/v4/internal/version"
)
@@ -66,11 +64,11 @@ func (g *HTTPGetter) get(href string) (*bytes.Buffer, error) {
// with the basic auth is the one being fetched.
u1, err := url.Parse(g.opts.url)
if err != nil {
- return nil, errors.Wrap(err, "Unable to parse getter URL")
+ return nil, fmt.Errorf("unable to parse getter URL: %w", err)
}
u2, err := url.Parse(href)
if err != nil {
- return nil, errors.Wrap(err, "Unable to parse URL getting from")
+ return nil, fmt.Errorf("unable to parse URL getting from: %w", err)
}
// Host on URL (returned from url.Parse) contains the port if present.
@@ -93,7 +91,7 @@ func (g *HTTPGetter) get(href string) (*bytes.Buffer, error) {
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
- return nil, errors.Errorf("failed to fetch %s : %s", href, resp.Status)
+ return nil, fmt.Errorf("failed to fetch %s : %s", href, resp.Status)
}
buf := bytes.NewBuffer(nil)
@@ -134,14 +132,8 @@ func (g *HTTPGetter) httpClient() (*http.Client, error) {
tlsutil.WithCAFile(g.opts.caFile),
)
if err != nil {
- return nil, errors.Wrap(err, "can't create TLS config for client")
- }
-
- sni, err := urlutil.ExtractHostname(g.opts.url)
- if err != nil {
- return nil, err
+ return nil, fmt.Errorf("can't create TLS config for client: %w", err)
}
- tlsConf.ServerName = sni
g.transport.TLSClientConfig = tlsConf
}
diff --git a/pkg/getter/httpgetter_test.go b/pkg/getter/httpgetter_test.go
index 24e670f6e..a997c7f03 100644
--- a/pkg/getter/httpgetter_test.go
+++ b/pkg/getter/httpgetter_test.go
@@ -28,8 +28,6 @@ import (
"testing"
"time"
- "github.com/pkg/errors"
-
"helm.sh/helm/v4/internal/tlsutil"
"helm.sh/helm/v4/internal/version"
"helm.sh/helm/v4/pkg/cli"
@@ -317,7 +315,7 @@ func TestDownloadTLS(t *testing.T) {
tlsutil.WithCAFile(ca),
)
if err != nil {
- t.Fatal(errors.Wrap(err, "can't create TLS config for client"))
+ t.Fatal(fmt.Errorf("can't create TLS config for client: %w", err))
}
tlsConf.ServerName = "helm.sh"
tlsSrv.TLS = tlsConf
@@ -358,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()
@@ -450,12 +573,10 @@ 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 {
+ t.Helper()
returnVal, err := g.httpClient()
if err != nil {
diff --git a/pkg/getter/plugingetter.go b/pkg/getter/plugingetter.go
index 2b734fdf0..3b8185543 100644
--- a/pkg/getter/plugingetter.go
+++ b/pkg/getter/plugingetter.go
@@ -23,8 +23,6 @@ import (
"path/filepath"
"strings"
- "github.com/pkg/errors"
-
"helm.sh/helm/v4/pkg/cli"
"helm.sh/helm/v4/pkg/plugin"
)
@@ -86,7 +84,7 @@ func (p *pluginGetter) Get(href string, options ...Option) (*bytes.Buffer, error
if err := prog.Run(); err != nil {
if eerr, ok := err.(*exec.ExitError); ok {
os.Stderr.Write(eerr.Stderr)
- return nil, errors.Errorf("plugin %q exited with error", p.command)
+ return nil, fmt.Errorf("plugin %q exited with error", p.command)
}
return nil, err
}
diff --git a/pkg/helmpath/home_unix_test.go b/pkg/helmpath/home_unix_test.go
index 6e4189bc9..a64c9bcd6 100644
--- a/pkg/helmpath/home_unix_test.go
+++ b/pkg/helmpath/home_unix_test.go
@@ -16,7 +16,6 @@
package helmpath
import (
- "os"
"runtime"
"testing"
@@ -24,9 +23,9 @@ import (
)
func TestHelmHome(t *testing.T) {
- os.Setenv(xdg.CacheHomeEnvVar, "/cache")
- os.Setenv(xdg.ConfigHomeEnvVar, "/config")
- os.Setenv(xdg.DataHomeEnvVar, "/data")
+ t.Setenv(xdg.CacheHomeEnvVar, "/cache")
+ t.Setenv(xdg.ConfigHomeEnvVar, "/config")
+ t.Setenv(xdg.DataHomeEnvVar, "/data")
isEq := func(t *testing.T, got, expected string) {
t.Helper()
if expected != got {
@@ -40,7 +39,7 @@ func TestHelmHome(t *testing.T) {
isEq(t, DataPath(), "/data/helm")
// test to see if lazy-loading environment variables at runtime works
- os.Setenv(xdg.CacheHomeEnvVar, "/cache2")
+ t.Setenv(xdg.CacheHomeEnvVar, "/cache2")
isEq(t, CachePath(), "/cache2/helm")
}
diff --git a/pkg/helmpath/lazypath_darwin_test.go b/pkg/helmpath/lazypath_darwin_test.go
index e04e20756..e3006d0d5 100644
--- a/pkg/helmpath/lazypath_darwin_test.go
+++ b/pkg/helmpath/lazypath_darwin_test.go
@@ -40,7 +40,7 @@ func TestDataPath(t *testing.T) {
t.Errorf("expected '%s', got '%s'", expected, lazy.dataPath(testFile))
}
- os.Setenv(xdg.DataHomeEnvVar, "/tmp")
+ t.Setenv(xdg.DataHomeEnvVar, "/tmp")
expected = filepath.Join("/tmp", appName, testFile)
@@ -58,7 +58,7 @@ func TestConfigPath(t *testing.T) {
t.Errorf("expected '%s', got '%s'", expected, lazy.configPath(testFile))
}
- os.Setenv(xdg.ConfigHomeEnvVar, "/tmp")
+ t.Setenv(xdg.ConfigHomeEnvVar, "/tmp")
expected = filepath.Join("/tmp", appName, testFile)
@@ -76,7 +76,7 @@ func TestCachePath(t *testing.T) {
t.Errorf("expected '%s', got '%s'", expected, lazy.cachePath(testFile))
}
- os.Setenv(xdg.CacheHomeEnvVar, "/tmp")
+ t.Setenv(xdg.CacheHomeEnvVar, "/tmp")
expected = filepath.Join("/tmp", appName, testFile)
diff --git a/pkg/helmpath/lazypath_unix_test.go b/pkg/helmpath/lazypath_unix_test.go
index 534735d10..4b0f2429b 100644
--- a/pkg/helmpath/lazypath_unix_test.go
+++ b/pkg/helmpath/lazypath_unix_test.go
@@ -16,7 +16,6 @@
package helmpath
import (
- "os"
"path/filepath"
"testing"
@@ -32,15 +31,13 @@ const (
)
func TestDataPath(t *testing.T) {
- os.Unsetenv(xdg.DataHomeEnvVar)
-
expected := filepath.Join(homedir.HomeDir(), ".local", "share", appName, testFile)
if lazy.dataPath(testFile) != expected {
t.Errorf("expected '%s', got '%s'", expected, lazy.dataPath(testFile))
}
- os.Setenv(xdg.DataHomeEnvVar, "/tmp")
+ t.Setenv(xdg.DataHomeEnvVar, "/tmp")
expected = filepath.Join("/tmp", appName, testFile)
@@ -50,15 +47,13 @@ func TestDataPath(t *testing.T) {
}
func TestConfigPath(t *testing.T) {
- os.Unsetenv(xdg.ConfigHomeEnvVar)
-
expected := filepath.Join(homedir.HomeDir(), ".config", appName, testFile)
if lazy.configPath(testFile) != expected {
t.Errorf("expected '%s', got '%s'", expected, lazy.configPath(testFile))
}
- os.Setenv(xdg.ConfigHomeEnvVar, "/tmp")
+ t.Setenv(xdg.ConfigHomeEnvVar, "/tmp")
expected = filepath.Join("/tmp", appName, testFile)
@@ -68,15 +63,13 @@ func TestConfigPath(t *testing.T) {
}
func TestCachePath(t *testing.T) {
- os.Unsetenv(xdg.CacheHomeEnvVar)
-
expected := filepath.Join(homedir.HomeDir(), ".cache", appName, testFile)
if lazy.cachePath(testFile) != expected {
t.Errorf("expected '%s', got '%s'", expected, lazy.cachePath(testFile))
}
- os.Setenv(xdg.CacheHomeEnvVar, "/tmp")
+ t.Setenv(xdg.CacheHomeEnvVar, "/tmp")
expected = filepath.Join("/tmp", appName, testFile)
diff --git a/pkg/ignore/rules.go b/pkg/ignore/rules.go
index 88de407ad..3511c2d40 100644
--- a/pkg/ignore/rules.go
+++ b/pkg/ignore/rules.go
@@ -19,13 +19,12 @@ package ignore
import (
"bufio"
"bytes"
+ "errors"
"io"
- "log"
+ "log/slog"
"os"
"path/filepath"
"strings"
-
- "github.com/pkg/errors"
)
// HelmIgnore default name of an ignorefile.
@@ -102,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
}
@@ -171,13 +170,13 @@ func (r *Rules) parseRule(rule string) error {
rule = strings.TrimSuffix(rule, "/")
}
- if strings.HasPrefix(rule, "/") {
+ if after, ok := strings.CutPrefix(rule, "/"); ok {
// Require path matches the root path.
p.match = func(n string, _ os.FileInfo) bool {
- rule = strings.TrimPrefix(rule, "/")
+ rule = after
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
@@ -187,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
@@ -199,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 fd111c647..78ed4e088 100644
--- a/pkg/kube/client.go
+++ b/pkg/kube/client.go
@@ -20,41 +20,38 @@ import (
"bytes"
"context"
"encoding/json"
+ "errors"
"fmt"
"io"
+ "log/slog"
"os"
"path/filepath"
"reflect"
"strings"
"sync"
- "time"
- jsonpatch "github.com/evanphx/json-patch"
- "github.com/pkg/errors"
- batch "k8s.io/api/batch/v1"
+ jsonpatch "github.com/evanphx/json-patch/v5"
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 +76,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
+ 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,19 +102,71 @@ 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.Interface, error) {
var err error
@@ -126,20 +183,20 @@ func (c *Client) IsReachable() error {
if err == genericclioptions.ErrEmptyConfig {
// re-replace kubernetes ErrEmptyConfig error with a friendly error
// moar workarounds for Kubernetes API breaking.
- return errors.New("Kubernetes cluster unreachable")
+ return errors.New("kubernetes cluster unreachable")
}
if err != nil {
- return errors.Wrap(err, "Kubernetes cluster unreachable")
+ return fmt.Errorf("kubernetes cluster unreachable: %w", err)
}
if _, err := client.Discovery().ServerVersion(); err != nil {
- return errors.Wrap(err, "Kubernetes cluster unreachable")
+ return fmt.Errorf("kubernetes cluster unreachable: %w", err)
}
return nil
}
// 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 +248,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 +266,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 +338,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 +397,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) {
- updateErrors := []string{}
+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
@@ -399,7 +410,7 @@ func (c *Client) Update(original, target ResourceList, force bool) (*Result, err
helper := resource.NewHelper(info.Client, info.Mapping).WithFieldManager(getManagedFieldsManager())
if _, err := helper.Get(info.Namespace, info.Name); err != nil {
if !apierrors.IsNotFound(err) {
- return errors.Wrap(err, "could not get information about the resource")
+ return fmt.Errorf("could not get information about the resource: %w", err)
}
// Append the created resource to the results, even if something fails
@@ -407,23 +418,23 @@ func (c *Client) Update(original, target ResourceList, force bool) (*Result, err
// Since the resource does not exist, create it.
if err := createResource(info); err != nil {
- return errors.Wrap(err, "failed to create resource")
+ return fmt.Errorf("failed to create resource: %w", 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
}
originalInfo := original.Get(info)
if originalInfo == nil {
kind := info.Mapping.GroupVersionKind.Kind
- return errors.Errorf("no %s with the name %q found", kind, info.Name)
+ 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)
- updateErrors = append(updateErrors, err.Error())
+ 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
res.Updated = append(res.Updated, info)
@@ -435,26 +446,26 @@ func (c *Client) Update(original, target ResourceList, force bool) (*Result, err
case err != nil:
return res, err
case len(updateErrors) != 0:
- return res, errors.New(strings.Join(updateErrors, " && "))
+ return res, joinErrors(updateErrors, " && ")
}
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 +473,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 +514,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 +548,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 {
@@ -595,10 +585,14 @@ func batchPerform(infos ResourceList, fn func(*resource.Info) error, errs chan<-
}
}
+var createMutex sync.Mutex
+
func createResource(info *resource.Info) error {
return retry.RetryOnConflict(
retry.DefaultRetry,
func() error {
+ createMutex.Lock()
+ defer createMutex.Unlock()
obj, err := resource.NewHelper(info.Client, info.Mapping).WithFieldManager(getManagedFieldsManager()).Create(info.Namespace, true, info.Object)
if err != nil {
return err
@@ -617,27 +611,27 @@ 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, errors.Wrap(err, "serializing current configuration")
+ return nil, types.StrategicMergePatchType, fmt.Errorf("serializing current configuration: %w", err)
}
newData, err := json.Marshal(target.Object)
if err != nil {
- return nil, types.StrategicMergePatchType, errors.Wrap(err, "serializing target configuration")
+ return nil, types.StrategicMergePatchType, fmt.Errorf("serializing target configuration: %w", err)
}
// Fetch the current object for the three way merge
helper := resource.NewHelper(target.Client, target.Mapping).WithFieldManager(getManagedFieldsManager())
currentObj, err := helper.Get(target.Namespace, target.Name)
if err != nil && !apierrors.IsNotFound(err) {
- return nil, types.StrategicMergePatchType, errors.Wrapf(err, "unable to get data for current object %s/%s", target.Namespace, target.Name)
+ return nil, types.StrategicMergePatchType, fmt.Errorf("unable to get data for current object %s/%s: %w", target.Namespace, target.Name, err)
}
// Even if currentObj is nil (because it was not found), it will marshal just fine
currentData, err := json.Marshal(currentObj)
if err != nil {
- return nil, types.StrategicMergePatchType, errors.Wrap(err, "serializing live configuration")
+ return nil, types.StrategicMergePatchType, fmt.Errorf("serializing live configuration: %w", err)
}
// Get a versioned object
@@ -645,7 +639,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 +647,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
@@ -660,14 +667,14 @@ func createPatch(target *resource.Info, current runtime.Object) ([]byte, types.P
patchMeta, err := strategicpatch.NewPatchMetaFromStruct(versionedObject)
if err != nil {
- return nil, types.StrategicMergePatchType, errors.Wrap(err, "unable to create patch metadata from object")
+ return nil, types.StrategicMergePatchType, fmt.Errorf("unable to create patch metadata from object: %w", err)
}
patch, err := strategicpatch.CreateThreeWayMergePatch(oldData, newData, currentData, patchMeta, true)
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())
@@ -679,29 +686,29 @@ func updateResource(c *Client, target *resource.Info, currentObj runtime.Object,
var err error
obj, err = helper.Replace(target.Namespace, target.Name, true, target.Object)
if err != nil {
- return errors.Wrap(err, "failed to replace object")
+ 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 errors.Wrap(err, "failed to create patch")
+ 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 {
- return errors.Wrap(err, "failed to refresh resource information")
+ return fmt.Errorf("failed to refresh resource information: %w", err)
}
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 errors.Wrapf(err, "cannot patch %q with kind %s", target.Name, kind)
+ return fmt.Errorf("cannot patch %q with kind %s: %w", target.Name, kind, err)
}
}
@@ -709,109 +716,6 @@ 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))
- 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.
- 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, errors.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 (c *Client) waitForJob(obj runtime.Object, name string) (bool, error) {
- o, ok := obj.(*batch.Job)
- if !ok {
- return true, errors.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, errors.Errorf("job %s failed: %s", name, c.Reason)
- }
- }
-
- 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
-}
-
-// 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, errors.Errorf("expected %s to be a *v1.Pod, got %T", name, obj)
- }
-
- switch o.Status.Phase {
- case v1.PodSucceeded:
- c.Log("Pod %s succeeded", o.Name)
- return true, nil
- case v1.PodFailed:
- return true, errors.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)
- }
-
- return false, nil
-}
-
// 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)
@@ -841,15 +745,12 @@ func (c *Client) OutputContainerLogsForPodList(podList *v1.PodList, namespace st
func copyRequestStreamToWriter(request *rest.Request, podName, containerName string, writer io.Writer) error {
readCloser, err := request.Stream(context.Background())
if err != nil {
- return errors.Errorf("Failed to stream pod logs for pod: %s, container: %s", podName, containerName)
+ return fmt.Errorf("failed to stream pod logs for pod: %s, container: %s", podName, containerName)
}
defer readCloser.Close()
_, err = io.Copy(writer, readCloser)
if err != nil {
- return errors.Errorf("Failed to copy IO from logs for pod: %s, container: %s", podName, containerName)
- }
- if err != nil {
- return errors.Errorf("Failed to close reader for pod: %s, container: %s", podName, containerName)
+ return fmt.Errorf("failed to copy IO from logs for pod: %s, container: %s", podName, containerName)
}
return nil
}
@@ -866,3 +767,27 @@ func scrubValidationError(err error) error {
}
return err
}
+
+type joinedErrors struct {
+ errs []error
+ sep string
+}
+
+func joinErrors(errs []error, sep string) error {
+ return &joinedErrors{
+ errs: errs,
+ sep: sep,
+ }
+}
+
+func (e *joinedErrors) Error() string {
+ errs := make([]string, 0, len(e.errs))
+ for _, err := range e.errs {
+ errs = append(errs, err.Error())
+ }
+ return strings.Join(errs, e.sep)
+}
+
+func (e *joinedErrors) Unwrap() []error {
+ return e.errs
+}
diff --git a/pkg/kube/client_test.go b/pkg/kube/client_test.go
index ff1335f0f..cd83a7f9e 100644
--- a/pkg/kube/client_test.go
+++ b/pkg/kube/client_test.go
@@ -27,8 +27,13 @@ import (
"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"
@@ -36,8 +41,10 @@ import (
cmdtesting "k8s.io/kubectl/pkg/cmd/testing"
)
-var unstructuredSerializer = resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer
-var codec = scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
+var (
+ unstructuredSerializer = resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer
+ codec = scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
+)
func objBody(obj runtime.Object) io.ReadCloser {
return io.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(codec, obj))))
@@ -102,12 +109,12 @@ func newResponseJSON(code int, json []byte) (*http.Response, error) {
}
func newTestClient(t *testing.T) *Client {
+ t.Helper()
testFactory := cmdtesting.NewTestFactory()
t.Cleanup(testFactory.Cleanup)
return &Client{
Factory: testFactory.WithNamespace("default"),
- Log: nopLogger,
}
}
@@ -134,15 +141,15 @@ func TestCreate(t *testing.T) {
actions = append(actions, path+":"+method)
t.Logf("got request %s %s", path, method)
switch {
- case path == "/namespaces/default/pods" && method == "POST":
+ case path == "/namespaces/default/pods" && method == http.MethodPost:
if strings.Contains(body, "starfish") {
if iterationCounter < 2 {
iterationCounter++
- return newResponseJSON(409, resourceQuotaConflict)
+ return newResponseJSON(http.StatusConflict, resourceQuotaConflict)
}
- return newResponse(200, &listA.Items[0])
+ return newResponse(http.StatusOK, &listA.Items[0])
}
- return newResponseJSON(409, resourceQuotaConflict)
+ return newResponseJSON(http.StatusConflict, resourceQuotaConflict)
default:
t.Fatalf("unexpected request: %s %s", method, path)
return nil, nil
@@ -208,7 +215,8 @@ func TestCreate(t *testing.T) {
})
}
-func TestUpdate(t *testing.T) {
+func testUpdate(t *testing.T, threeWayMerge bool) {
+ t.Helper()
listA := newPodList("starfish", "otter", "squid")
listB := newPodList("starfish", "otter", "dolphin")
listC := newPodList("starfish", "otter", "dolphin")
@@ -226,11 +234,11 @@ func TestUpdate(t *testing.T) {
actions = append(actions, p+":"+m)
t.Logf("got request %s %s", p, m)
switch {
- case p == "/namespaces/default/pods/starfish" && m == "GET":
- return newResponse(200, &listA.Items[0])
- case p == "/namespaces/default/pods/otter" && m == "GET":
- return newResponse(200, &listA.Items[1])
- case p == "/namespaces/default/pods/otter" && m == "PATCH":
+ case p == "/namespaces/default/pods/starfish" && m == http.MethodGet:
+ return newResponse(http.StatusOK, &listA.Items[0])
+ case p == "/namespaces/default/pods/otter" && m == http.MethodGet:
+ return newResponse(http.StatusOK, &listA.Items[1])
+ case p == "/namespaces/default/pods/otter" && m == http.MethodPatch:
data, err := io.ReadAll(req.Body)
if err != nil {
t.Fatalf("could not dump request: %s", err)
@@ -240,10 +248,10 @@ func TestUpdate(t *testing.T) {
if string(data) != expected {
t.Errorf("expected patch\n%s\ngot\n%s", expected, string(data))
}
- return newResponse(200, &listB.Items[0])
- case p == "/namespaces/default/pods/dolphin" && m == "GET":
- return newResponse(404, notFoundBody())
- case p == "/namespaces/default/pods/starfish" && m == "PATCH":
+ return newResponse(http.StatusOK, &listB.Items[0])
+ case p == "/namespaces/default/pods/dolphin" && m == http.MethodGet:
+ return newResponse(http.StatusNotFound, notFoundBody())
+ case p == "/namespaces/default/pods/starfish" && m == http.MethodPatch:
data, err := io.ReadAll(req.Body)
if err != nil {
t.Fatalf("could not dump request: %s", err)
@@ -253,17 +261,17 @@ func TestUpdate(t *testing.T) {
if string(data) != expected {
t.Errorf("expected patch\n%s\ngot\n%s", expected, string(data))
}
- return newResponse(200, &listB.Items[0])
- case p == "/namespaces/default/pods" && m == "POST":
+ return newResponse(http.StatusOK, &listB.Items[0])
+ case p == "/namespaces/default/pods" && m == http.MethodPost:
if iterationCounter < 2 {
iterationCounter++
- return newResponseJSON(409, resourceQuotaConflict)
+ return newResponseJSON(http.StatusConflict, resourceQuotaConflict)
}
- return newResponse(200, &listB.Items[1])
- case p == "/namespaces/default/pods/squid" && m == "DELETE":
- return newResponse(200, &listB.Items[1])
- case p == "/namespaces/default/pods/squid" && m == "GET":
- return newResponse(200, &listB.Items[2])
+ return newResponse(http.StatusOK, &listB.Items[1])
+ case p == "/namespaces/default/pods/squid" && m == http.MethodDelete:
+ return newResponse(http.StatusOK, &listB.Items[1])
+ case p == "/namespaces/default/pods/squid" && m == http.MethodGet:
+ return newResponse(http.StatusOK, &listB.Items[2])
default:
t.Fatalf("unexpected request: %s %s", req.Method, req.URL.Path)
return nil, nil
@@ -279,7 +287,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)
}
@@ -328,6 +341,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
@@ -468,7 +489,7 @@ func TestWait(t *testing.T) {
p, m := req.URL.Path, req.Method
t.Logf("got request %s %s", p, m)
switch {
- case p == "/api/v1/namespaces/default/pods/starfish" && m == "GET":
+ case p == "/api/v1/namespaces/default/pods/starfish" && m == http.MethodGet:
pod := &podList.Items[0]
if created != nil && time.Since(*created) >= time.Second*5 {
pod.Status.Conditions = []v1.PodCondition{
@@ -478,8 +499,8 @@ func TestWait(t *testing.T) {
},
}
}
- return newResponse(200, pod)
- case p == "/api/v1/namespaces/default/pods/otter" && m == "GET":
+ return newResponse(http.StatusOK, pod)
+ case p == "/api/v1/namespaces/default/pods/otter" && m == http.MethodGet:
pod := &podList.Items[1]
if created != nil && time.Since(*created) >= time.Second*5 {
pod.Status.Conditions = []v1.PodCondition{
@@ -489,8 +510,8 @@ func TestWait(t *testing.T) {
},
}
}
- return newResponse(200, pod)
- case p == "/api/v1/namespaces/default/pods/squid" && m == "GET":
+ return newResponse(http.StatusOK, pod)
+ case p == "/api/v1/namespaces/default/pods/squid" && m == http.MethodGet:
pod := &podList.Items[2]
if created != nil && time.Since(*created) >= time.Second*5 {
pod.Status.Conditions = []v1.PodCondition{
@@ -500,21 +521,26 @@ func TestWait(t *testing.T) {
},
}
}
- return newResponse(200, pod)
- case p == "/namespaces/default/pods" && m == "POST":
+ return newResponse(http.StatusOK, pod)
+ case p == "/namespaces/default/pods" && m == http.MethodPost:
resources, err := c.Build(req.Body, false)
if err != nil {
t.Fatal(err)
}
now := time.Now()
created = &now
- return newResponse(200, resources[0].Object)
+ return newResponse(http.StatusOK, resources[0].Object)
default:
t.Fatalf("unexpected request: %s %s", req.Method, req.URL.Path)
return nil, nil
}
}),
}
+ 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)
@@ -548,25 +574,30 @@ func TestWaitJob(t *testing.T) {
p, m := req.URL.Path, req.Method
t.Logf("got request %s %s", p, m)
switch {
- case p == "/apis/batch/v1/namespaces/default/jobs/starfish" && m == "GET":
+ case p == "/apis/batch/v1/namespaces/default/jobs/starfish" && m == http.MethodGet:
if created != nil && time.Since(*created) >= time.Second*5 {
job.Status.Succeeded = 1
}
- return newResponse(200, job)
- case p == "/namespaces/default/jobs" && m == "POST":
+ return newResponse(http.StatusOK, job)
+ case p == "/namespaces/default/jobs" && m == http.MethodPost:
resources, err := c.Build(req.Body, false)
if err != nil {
t.Fatal(err)
}
now := time.Now()
created = &now
- return newResponse(200, resources[0].Object)
+ return newResponse(http.StatusOK, resources[0].Object)
default:
t.Fatalf("unexpected request: %s %s", req.Method, req.URL.Path)
return nil, nil
}
}),
}
+ 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)
@@ -600,27 +631,32 @@ func TestWaitDelete(t *testing.T) {
p, m := req.URL.Path, req.Method
t.Logf("got request %s %s", p, m)
switch {
- case p == "/namespaces/default/pods/starfish" && m == "GET":
+ case p == "/namespaces/default/pods/starfish" && m == http.MethodGet:
if deleted != nil && time.Since(*deleted) >= time.Second*5 {
- return newResponse(404, notFoundBody())
+ return newResponse(http.StatusNotFound, notFoundBody())
}
- return newResponse(200, &pod)
- case p == "/namespaces/default/pods/starfish" && m == "DELETE":
+ return newResponse(http.StatusOK, &pod)
+ case p == "/namespaces/default/pods/starfish" && m == http.MethodDelete:
now := time.Now()
deleted = &now
- return newResponse(200, &pod)
- case p == "/namespaces/default/pods" && m == "POST":
+ return newResponse(http.StatusOK, &pod)
+ case p == "/namespaces/default/pods" && m == http.MethodPost:
resources, err := c.Build(req.Body, false)
if err != nil {
t.Fatal(err)
}
- return newResponse(200, resources[0].Object)
+ return newResponse(http.StatusOK, resources[0].Object)
default:
t.Fatalf("unexpected request: %s %s", req.Method, req.URL.Path)
return nil, nil
}
}),
}
+ 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)
@@ -686,7 +722,6 @@ func TestReal(t *testing.T) {
}
func TestGetPodList(t *testing.T) {
-
namespace := "some-namespace"
names := []string{"dave", "jimmy"}
var responsePodList v1.PodList
@@ -701,7 +736,6 @@ func TestGetPodList(t *testing.T) {
clientAssertions := assert.New(t)
clientAssertions.NoError(err)
clientAssertions.Equal(&responsePodList, podList)
-
}
func TestOutputContainerLogsForPodList(t *testing.T) {
@@ -788,11 +822,11 @@ spec:
apiVersion: v1
kind: Service
metadata:
- name: redis-slave
+ name: redis-replica
labels:
app: redis
tier: backend
- role: slave
+ role: replica
spec:
ports:
# the port that this service should serve on
@@ -800,24 +834,24 @@ spec:
selector:
app: redis
tier: backend
- role: slave
+ role: replica
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
- name: redis-slave
+ name: redis-replica
spec:
replicas: 2
template:
metadata:
labels:
app: redis
- role: slave
+ role: replica
tier: backend
spec:
containers:
- - name: slave
- image: gcr.io/google_samples/gb-redisslave:v1
+ - name: replica
+ image: gcr.io/google_samples/gb-redisreplica:v1
resources:
requests:
cpu: 100m
@@ -898,3 +932,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: http.StatusOK,
+ 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/factory.go b/pkg/kube/factory.go
index 9dae398d7..1d237c307 100644
--- a/pkg/kube/factory.go
+++ b/pkg/kube/factory.go
@@ -20,6 +20,7 @@ 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 ceca3c113..a543a0f73 100644
--- a/pkg/kube/fake/fake.go
+++ b/pkg/kube/fake/fake.go
@@ -35,18 +35,30 @@ type FailingKubeClient struct {
PrintingKubeClient
CreateError error
GetError error
- WaitError error
DeleteError error
DeleteWithPropagationError error
- WatchUntilReadyError 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
func (f *FailingKubeClient) Create(resources kube.ResourceList) (*kube.Result, error) {
if f.CreateError != nil {
@@ -64,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
@@ -97,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
@@ -112,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
}
@@ -139,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"
@@ -146,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 dcce9a3be..f6659a904 100644
--- a/pkg/kube/fake/printer.go
+++ b/pkg/kube/fake/printer.go
@@ -37,6 +37,12 @@ type PrintingKubeClient struct {
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
func (p *PrintingKubeClient) IsReachable() error {
return nil
@@ -59,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
}
@@ -85,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))
@@ -140,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 c9776cacd..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,17 +46,41 @@ type Interface interface {
//
// Validates against OpenAPI schema if validate is true.
Build(reader io.Reader, validate bool) (ResourceList, 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 was 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.
@@ -118,7 +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 584b8853a..7a06c72f9 100644
--- a/pkg/kube/ready.go
+++ b/pkg/kube/ready.go
@@ -19,13 +19,11 @@ 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"
@@ -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 ced0a51a8..db0d02cbe 100644
--- a/pkg/kube/ready_test.go
+++ b/pkg/kube/ready_test.go
@@ -20,10 +20,8 @@ import (
"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,13 +55,12 @@ 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,
},
args: args{
- ctx: context.TODO(),
+ ctx: t.Context(),
resource: &resource.Info{Object: &corev1.Pod{}, Name: "foo", Namespace: defaultNamespace},
},
pod: newPodWithCondition("foo", corev1.ConditionTrue),
@@ -74,13 +70,12 @@ 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,
},
args: args{
- ctx: context.TODO(),
+ ctx: t.Context(),
resource: &resource.Info{Object: &corev1.Pod{}, Name: "foo", Namespace: defaultNamespace},
},
pod: newPodWithCondition("bar", corev1.ConditionTrue),
@@ -92,11 +87,10 @@ 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,
}
- if _, err := c.client.CoreV1().Pods(defaultNamespace).Create(context.TODO(), tt.pod, metav1.CreateOptions{}); err != nil {
+ if _, err := c.client.CoreV1().Pods(defaultNamespace).Create(t.Context(), tt.pod, metav1.CreateOptions{}); err != nil {
t.Errorf("Failed to create Pod error: %v", err)
return
}
@@ -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,13 +127,12 @@ 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,
},
args: args{
- ctx: context.TODO(),
+ ctx: t.Context(),
resource: &resource.Info{Object: &batchv1.Job{}, Name: "foo", Namespace: defaultNamespace},
},
job: newJob("bar", 1, intToInt32(1), 1, 0),
@@ -150,13 +142,12 @@ 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,
},
args: args{
- ctx: context.TODO(),
+ ctx: t.Context(),
resource: &resource.Info{Object: &batchv1.Job{}, Name: "foo", Namespace: defaultNamespace},
},
job: newJob("foo", 1, intToInt32(1), 1, 0),
@@ -168,11 +159,10 @@ 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,
}
- if _, err := c.client.BatchV1().Jobs(defaultNamespace).Create(context.TODO(), tt.job, metav1.CreateOptions{}); err != nil {
+ if _, err := c.client.BatchV1().Jobs(defaultNamespace).Create(t.Context(), tt.job, metav1.CreateOptions{}); err != nil {
t.Errorf("Failed to create Job error: %v", err)
return
}
@@ -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,13 +199,12 @@ 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,
},
args: args{
- ctx: context.TODO(),
+ ctx: t.Context(),
resource: &resource.Info{Object: &appsv1.Deployment{}, Name: "foo", Namespace: defaultNamespace},
},
replicaSet: newReplicaSet("foo", 0, 0, true),
@@ -227,13 +215,12 @@ 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,
},
args: args{
- ctx: context.TODO(),
+ ctx: t.Context(),
resource: &resource.Info{Object: &appsv1.Deployment{}, Name: "foo", Namespace: defaultNamespace},
},
replicaSet: newReplicaSet("foo", 0, 0, true),
@@ -246,15 +233,14 @@ 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,
}
- if _, err := c.client.AppsV1().Deployments(defaultNamespace).Create(context.TODO(), tt.deployment, metav1.CreateOptions{}); err != nil {
+ if _, err := c.client.AppsV1().Deployments(defaultNamespace).Create(t.Context(), tt.deployment, metav1.CreateOptions{}); err != nil {
t.Errorf("Failed to create Deployment error: %v", err)
return
}
- if _, err := c.client.AppsV1().ReplicaSets(defaultNamespace).Create(context.TODO(), tt.replicaSet, metav1.CreateOptions{}); err != nil {
+ if _, err := c.client.AppsV1().ReplicaSets(defaultNamespace).Create(t.Context(), tt.replicaSet, metav1.CreateOptions{}); err != nil {
t.Errorf("Failed to create ReplicaSet error: %v", err)
return
}
@@ -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,13 +276,12 @@ 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,
},
args: args{
- ctx: context.TODO(),
+ ctx: t.Context(),
resource: &resource.Info{Object: &corev1.PersistentVolumeClaim{}, Name: "foo", Namespace: defaultNamespace},
},
pvc: newPersistentVolumeClaim("foo", corev1.ClaimPending),
@@ -307,13 +291,12 @@ 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,
},
args: args{
- ctx: context.TODO(),
+ ctx: t.Context(),
resource: &resource.Info{Object: &corev1.PersistentVolumeClaim{}, Name: "foo", Namespace: defaultNamespace},
},
pvc: newPersistentVolumeClaim("bar", corev1.ClaimPending),
@@ -325,11 +308,10 @@ 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,
}
- if _, err := c.client.CoreV1().PersistentVolumeClaims(defaultNamespace).Create(context.TODO(), tt.pvc, metav1.CreateOptions{}); err != nil {
+ if _, err := c.client.CoreV1().PersistentVolumeClaims(defaultNamespace).Create(t.Context(), tt.pvc, metav1.CreateOptions{}); err != nil {
t.Errorf("Failed to create PersistentVolumeClaim error: %v", err)
return
}
@@ -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,13 +347,12 @@ 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,
},
args: args{
- ctx: context.TODO(),
+ ctx: t.Context(),
resource: &resource.Info{Object: &corev1.Service{}, Name: "foo", Namespace: defaultNamespace},
},
svc: newService("foo", corev1.ServiceSpec{Type: corev1.ServiceTypeLoadBalancer, ClusterIP: ""}),
@@ -382,13 +362,12 @@ 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,
},
args: args{
- ctx: context.TODO(),
+ ctx: t.Context(),
resource: &resource.Info{Object: &corev1.Service{}, Name: "foo", Namespace: defaultNamespace},
},
svc: newService("bar", corev1.ServiceSpec{Type: corev1.ServiceTypeExternalName, ClusterIP: ""}),
@@ -400,11 +379,10 @@ 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,
}
- if _, err := c.client.CoreV1().Services(defaultNamespace).Create(context.TODO(), tt.svc, metav1.CreateOptions{}); err != nil {
+ if _, err := c.client.CoreV1().Services(defaultNamespace).Create(t.Context(), tt.svc, metav1.CreateOptions{}); err != nil {
t.Errorf("Failed to create Service error: %v", err)
return
}
@@ -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,13 +418,12 @@ 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,
},
args: args{
- ctx: context.TODO(),
+ ctx: t.Context(),
resource: &resource.Info{Object: &appsv1.DaemonSet{}, Name: "foo", Namespace: defaultNamespace},
},
ds: newDaemonSet("foo", 0, 0, 1, 0, true),
@@ -457,13 +433,12 @@ 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,
},
args: args{
- ctx: context.TODO(),
+ ctx: t.Context(),
resource: &resource.Info{Object: &appsv1.DaemonSet{}, Name: "foo", Namespace: defaultNamespace},
},
ds: newDaemonSet("bar", 0, 1, 1, 1, true),
@@ -475,11 +450,10 @@ 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,
}
- if _, err := c.client.AppsV1().DaemonSets(defaultNamespace).Create(context.TODO(), tt.ds, metav1.CreateOptions{}); err != nil {
+ if _, err := c.client.AppsV1().DaemonSets(defaultNamespace).Create(t.Context(), tt.ds, metav1.CreateOptions{}); err != nil {
t.Errorf("Failed to create DaemonSet error: %v", err)
return
}
@@ -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},
+ ctx: t.Context(),
+ 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},
+ ctx: t.Context(),
+ resource: &resource.Info{Object: &appsv1.StatefulSet{}, Name: "foo", Namespace: defaultNamespace},
},
ss: newStatefulSet("bar", 1, 0, 1, 1, true),
want: false,
@@ -550,11 +521,10 @@ 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,
}
- if _, err := c.client.AppsV1().StatefulSets(defaultNamespace).Create(context.TODO(), tt.ss, metav1.CreateOptions{}); err != nil {
+ if _, err := c.client.AppsV1().StatefulSets(defaultNamespace).Create(t.Context(), tt.ss, metav1.CreateOptions{}); err != nil {
t.Errorf("Failed to create StatefulSet error: %v", err)
return
}
@@ -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,13 +560,12 @@ 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,
},
args: args{
- ctx: context.TODO(),
+ ctx: t.Context(),
resource: &resource.Info{Object: &corev1.ReplicationController{}, Name: "foo", Namespace: defaultNamespace},
},
rc: newReplicationController("foo", false),
@@ -607,13 +575,12 @@ 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,
},
args: args{
- ctx: context.TODO(),
+ ctx: t.Context(),
resource: &resource.Info{Object: &corev1.ReplicationController{}, Name: "foo", Namespace: defaultNamespace},
},
rc: newReplicationController("bar", false),
@@ -623,13 +590,12 @@ 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,
},
args: args{
- ctx: context.TODO(),
+ ctx: t.Context(),
resource: &resource.Info{Object: &corev1.ReplicationController{}, Name: "foo", Namespace: defaultNamespace},
},
rc: newReplicationController("foo", true),
@@ -641,11 +607,10 @@ 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,
}
- if _, err := c.client.CoreV1().ReplicationControllers(defaultNamespace).Create(context.TODO(), tt.rc, metav1.CreateOptions{}); err != nil {
+ if _, err := c.client.CoreV1().ReplicationControllers(defaultNamespace).Create(t.Context(), tt.rc, metav1.CreateOptions{}); err != nil {
t.Errorf("Failed to create ReplicationController error: %v", err)
return
}
@@ -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},
+ ctx: t.Context(),
+ 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},
+ ctx: t.Context(),
+ 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,14 +1012,14 @@ 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 {
+ if _, err := c.client.CoreV1().Pods(defaultNamespace).Create(t.Context(), &pod, metav1.CreateOptions{}); err != nil {
t.Errorf("Failed to create Pod error: %v", err)
return
}
}
- got, err := c.podsReadyForObject(context.TODO(), tt.args.namespace, tt.args.obj)
+ got, err := c.podsReadyForObject(t.Context(), tt.args.namespace, tt.args.obj)
if (err != nil) != tt.wantErr {
t.Errorf("podsReadyForObject() error = %v, wantErr %v", err, tt.wantErr)
return
@@ -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 600f256b3..d88b171f0 100644
--- a/pkg/kube/resource.go
+++ b/pkg/kube/resource.go
@@ -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 && a.Mapping.GroupVersionKind.Group == b.Mapping.GroupVersionKind.Group
+ return a.Name == b.Name && a.Namespace == b.Namespace && a.Mapping.GroupVersionKind == b.Mapping.GroupVersionKind
}
diff --git a/pkg/kube/resource_test.go b/pkg/kube/resource_test.go
index c405ca382..ccc613c1b 100644
--- a/pkg/kube/resource_test.go
+++ b/pkg/kube/resource_test.go
@@ -59,3 +59,42 @@ func TestResourceList(t *testing.T) {
t.Error("expected intersect to return bar")
}
}
+
+func TestIsMatchingInfo(t *testing.T) {
+ gvk := schema.GroupVersionKind{Group: "group1", Version: "version1", Kind: "pod"}
+ resourceInfo := resource.Info{Name: "name1", Namespace: "namespace1", Mapping: &meta.RESTMapping{GroupVersionKind: gvk}}
+
+ gvkDiffGroup := schema.GroupVersionKind{Group: "diff", Version: "version1", Kind: "pod"}
+ resourceInfoDiffGroup := resource.Info{Name: "name1", Namespace: "namespace1", Mapping: &meta.RESTMapping{GroupVersionKind: gvkDiffGroup}}
+ if isMatchingInfo(&resourceInfo, &resourceInfoDiffGroup) {
+ t.Error("expected resources not equal")
+ }
+
+ gvkDiffVersion := schema.GroupVersionKind{Group: "group1", Version: "diff", Kind: "pod"}
+ resourceInfoDiffVersion := resource.Info{Name: "name1", Namespace: "namespace1", Mapping: &meta.RESTMapping{GroupVersionKind: gvkDiffVersion}}
+ if isMatchingInfo(&resourceInfo, &resourceInfoDiffVersion) {
+ t.Error("expected resources not equal")
+ }
+
+ gvkDiffKind := schema.GroupVersionKind{Group: "group1", Version: "version1", Kind: "deployment"}
+ resourceInfoDiffKind := resource.Info{Name: "name1", Namespace: "namespace1", Mapping: &meta.RESTMapping{GroupVersionKind: gvkDiffKind}}
+ if isMatchingInfo(&resourceInfo, &resourceInfoDiffKind) {
+ t.Error("expected resources not equal")
+ }
+
+ resourceInfoDiffName := resource.Info{Name: "diff", Namespace: "namespace1", Mapping: &meta.RESTMapping{GroupVersionKind: gvk}}
+ if isMatchingInfo(&resourceInfo, &resourceInfoDiffName) {
+ t.Error("expected resources not equal")
+ }
+
+ resourceInfoDiffNamespace := resource.Info{Name: "name1", Namespace: "diff", Mapping: &meta.RESTMapping{GroupVersionKind: gvk}}
+ if isMatchingInfo(&resourceInfo, &resourceInfoDiffNamespace) {
+ t.Error("expected resources not equal")
+ }
+
+ gvkEqual := schema.GroupVersionKind{Group: "group1", Version: "version1", Kind: "pod"}
+ resourceInfoEqual := resource.Info{Name: "name1", Namespace: "namespace1", Mapping: &meta.RESTMapping{GroupVersionKind: gvkEqual}}
+ if !isMatchingInfo(&resourceInfo, &resourceInfoEqual) {
+ t.Error("expected resources to be equal")
+ }
+}
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..4b06da896
--- /dev/null
+++ b/pkg/kube/statuswait_test.go
@@ -0,0 +1,450 @@
+/*
+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 {
+ t.Helper()
+ 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 {
+ t.Helper()
+ 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 {
+ t.Helper()
+ 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 7eb931496..8a3bacdcc 100644
--- a/pkg/kube/wait.go
+++ b/pkg/kube/wait.go
@@ -18,11 +18,12 @@ package kube // import "helm.sh/helm/v4/pkg/kube"
import (
"context"
+ "errors"
"fmt"
+ "log/slog"
"net/http"
"time"
- "github.com/pkg/errors"
appsv1 "k8s.io/api/apps/v1"
appsv1beta1 "k8s.io/api/apps/v1beta1"
appsv1beta2 "k8s.io/api/apps/v1beta2"
@@ -31,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))
@@ -60,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
@@ -80,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)
+// WaitForDelete polls to check if all the resources are deleted or a timeout is reached
+func (hw *legacyWaiter) WaitForDelete(deleted ResourceList, timeout time.Duration) error {
+ 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) {
@@ -115,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
@@ -162,5 +190,161 @@ func SelectorsForObject(object runtime.Object) (selector labels.Selector, err er
return nil, fmt.Errorf("selector for %T not implemented", object)
}
- return selector, errors.Wrap(err, "invalid label selector")
+ if err != nil {
+ return selector, fmt.Errorf("invalid label selector: %w", err)
+ }
+
+ 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 b53400c87..a61d5e43f 100644
--- a/pkg/lint/lint.go
+++ b/pkg/lint/lint.go
@@ -19,7 +19,7 @@ package lint // import "helm.sh/helm/v4/pkg/lint"
import (
"path/filepath"
- chartutil "helm.sh/helm/v4/pkg/chart/util"
+ chartutil "helm.sh/helm/v4/pkg/chart/v2/util"
"helm.sh/helm/v4/pkg/lint/rules"
"helm.sh/helm/v4/pkg/lint/support"
)
diff --git a/pkg/lint/lint_test.go b/pkg/lint/lint_test.go
index bba024d59..888d3dfe6 100644
--- a/pkg/lint/lint_test.go
+++ b/pkg/lint/lint_test.go
@@ -21,7 +21,7 @@ import (
"testing"
"time"
- chartutil "helm.sh/helm/v4/pkg/chart/util"
+ chartutil "helm.sh/helm/v4/pkg/chart/v2/util"
"helm.sh/helm/v4/pkg/lint/support"
)
@@ -35,6 +35,7 @@ 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 := RunAll(badChartDir, values, namespace).Messages
@@ -90,6 +91,16 @@ 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 := RunAll(badValuesFileDir, values, namespace).Messages
if len(m) < 1 {
diff --git a/pkg/lint/rules/chartfile.go b/pkg/lint/rules/chartfile.go
index a1e08b58d..724c3f2ea 100644
--- a/pkg/lint/rules/chartfile.go
+++ b/pkg/lint/rules/chartfile.go
@@ -17,17 +17,17 @@ limitations under the License.
package rules // import "helm.sh/helm/v4/pkg/lint/rules"
import (
+ "errors"
"fmt"
"os"
"path/filepath"
"github.com/Masterminds/semver/v3"
"github.com/asaskevich/govalidator"
- "github.com/pkg/errors"
"sigs.k8s.io/yaml"
- "helm.sh/helm/v4/pkg/chart"
- chartutil "helm.sh/helm/v4/pkg/chart/util"
+ chart "helm.sh/helm/v4/pkg/chart/v2"
+ chartutil "helm.sh/helm/v4/pkg/chart/v2/util"
"helm.sh/helm/v4/pkg/lint/support"
)
@@ -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)
@@ -81,7 +84,7 @@ func isStringValue(data map[string]interface{}, key string) error {
}
valueType := fmt.Sprintf("%T", value)
if valueType != "string" {
- return errors.Errorf("%s should be of type string but it's of type %s", key, valueType)
+ return fmt.Errorf("%s should be of type string but it's of type %s", key, valueType)
}
return nil
}
@@ -97,7 +100,14 @@ func validateChartYamlNotDirectory(chartPath string) error {
func validateChartYamlFormat(chartFileError error) error {
if chartFileError != nil {
- return errors.Errorf("unable to parse YAML\n\t%s", chartFileError.Error())
+ return fmt.Errorf("unable to parse YAML\n\t%w", chartFileError)
+ }
+ return nil
+}
+
+func validateChartYamlStrictFormat(chartFileError error) error {
+ if chartFileError != nil {
+ return fmt.Errorf("failed to strictly parse chart metadata file\n\t%w", chartFileError)
}
return nil
}
@@ -131,9 +141,8 @@ func validateChartVersion(cf *chart.Metadata) error {
}
version, err := semver.NewVersion(cf.Version)
-
if err != nil {
- return errors.Errorf("version '%s' is not a valid SemVer", cf.Version)
+ return fmt.Errorf("version '%s' is not a valid SemVer", cf.Version)
}
c, err := semver.NewConstraint(">0.0.0-0")
@@ -143,7 +152,7 @@ func validateChartVersion(cf *chart.Metadata) error {
valid, msg := c.Validate(version)
if !valid && len(msg) > 0 {
- return errors.Errorf("version %v", msg[0])
+ return fmt.Errorf("version %v", msg[0])
}
return nil
@@ -154,9 +163,9 @@ func validateChartMaintainer(cf *chart.Metadata) error {
if maintainer.Name == "" {
return errors.New("each maintainer requires a name")
} else if maintainer.Email != "" && !govalidator.IsEmail(maintainer.Email) {
- return errors.Errorf("invalid email '%s' for maintainer '%s'", maintainer.Email, maintainer.Name)
+ return fmt.Errorf("invalid email '%s' for maintainer '%s'", maintainer.Email, maintainer.Name)
} else if maintainer.URL != "" && !govalidator.IsURL(maintainer.URL) {
- return errors.Errorf("invalid url '%s' for maintainer '%s'", maintainer.URL, maintainer.Name)
+ return fmt.Errorf("invalid url '%s' for maintainer '%s'", maintainer.URL, maintainer.Name)
}
}
return nil
@@ -165,7 +174,7 @@ func validateChartMaintainer(cf *chart.Metadata) error {
func validateChartSources(cf *chart.Metadata) error {
for _, source := range cf.Sources {
if source == "" || !govalidator.IsRequestURL(source) {
- return errors.Errorf("invalid source URL '%s'", source)
+ return fmt.Errorf("invalid source URL '%s'", source)
}
}
return nil
@@ -180,7 +189,7 @@ func validateChartIconPresence(cf *chart.Metadata) error {
func validateChartIconURL(cf *chart.Metadata) error {
if cf.Icon != "" && !govalidator.IsRequestURL(cf.Icon) {
- return errors.Errorf("invalid icon URL '%s'", cf.Icon)
+ return fmt.Errorf("invalid icon URL '%s'", cf.Icon)
}
return nil
}
diff --git a/pkg/lint/rules/chartfile_test.go b/pkg/lint/rules/chartfile_test.go
index 6d709790a..bbb14a5e8 100644
--- a/pkg/lint/rules/chartfile_test.go
+++ b/pkg/lint/rules/chartfile_test.go
@@ -17,15 +17,14 @@ limitations under the License.
package rules
import (
+ "errors"
"os"
"path/filepath"
"strings"
"testing"
- "github.com/pkg/errors"
-
- "helm.sh/helm/v4/pkg/chart"
- chartutil "helm.sh/helm/v4/pkg/chart/util"
+ chart "helm.sh/helm/v4/pkg/chart/v2"
+ chartutil "helm.sh/helm/v4/pkg/chart/v2/util"
"helm.sh/helm/v4/pkg/lint/support"
)
@@ -166,10 +165,30 @@ func TestValidateChartSources(t *testing.T) {
}
func TestValidateChartIconPresence(t *testing.T) {
- err := validateChartIconPresence(badChart)
- if err == nil {
- t.Errorf("validateChartIconPresence to return a linter error, got no error")
- }
+ t.Run("Icon absent", func(t *testing.T) {
+ testChart := &chart.Metadata{
+ Icon: "",
+ }
+
+ err := validateChartIconPresence(testChart)
+
+ if err == nil {
+ t.Errorf("validateChartIconPresence to return a linter error, got no error")
+ } else if !strings.Contains(err.Error(), "icon is recommended") {
+ t.Errorf("expected %q, got %q", "icon is recommended", err.Error())
+ }
+ })
+ t.Run("Icon present", func(t *testing.T) {
+ testChart := &chart.Metadata{
+ Icon: "http://example.org/icon.png",
+ }
+
+ err := validateChartIconPresence(testChart)
+
+ if err != nil {
+ t.Errorf("Unexpected error: %q", err.Error())
+ }
+ })
}
func TestValidateChartIconURL(t *testing.T) {
diff --git a/pkg/lint/rules/dependencies.go b/pkg/lint/rules/dependencies.go
index 5f71dd144..16c9d6435 100644
--- a/pkg/lint/rules/dependencies.go
+++ b/pkg/lint/rules/dependencies.go
@@ -20,10 +20,8 @@ import (
"fmt"
"strings"
- "github.com/pkg/errors"
-
- "helm.sh/helm/v4/pkg/chart"
- "helm.sh/helm/v4/pkg/chart/loader"
+ chart "helm.sh/helm/v4/pkg/chart/v2"
+ "helm.sh/helm/v4/pkg/chart/v2/loader"
"helm.sh/helm/v4/pkg/lint/support"
)
@@ -43,7 +41,7 @@ func Dependencies(linter *support.Linter) {
func validateChartFormat(chartError error) error {
if chartError != nil {
- return errors.Errorf("unable to load chart\n\t%s", chartError)
+ return fmt.Errorf("unable to load chart\n\t%w", chartError)
}
return nil
}
diff --git a/pkg/lint/rules/dependencies_test.go b/pkg/lint/rules/dependencies_test.go
index e946b1c01..1369b2372 100644
--- a/pkg/lint/rules/dependencies_test.go
+++ b/pkg/lint/rules/dependencies_test.go
@@ -19,8 +19,8 @@ import (
"path/filepath"
"testing"
- "helm.sh/helm/v4/pkg/chart"
- chartutil "helm.sh/helm/v4/pkg/chart/util"
+ chart "helm.sh/helm/v4/pkg/chart/v2"
+ chartutil "helm.sh/helm/v4/pkg/chart/v2/util"
"helm.sh/helm/v4/pkg/lint/support"
)
diff --git a/pkg/lint/rules/deprecations.go b/pkg/lint/rules/deprecations.go
index 0a8e642c9..c6d635a5e 100644
--- a/pkg/lint/rules/deprecations.go
+++ b/pkg/lint/rules/deprecations.go
@@ -25,7 +25,7 @@ import (
"k8s.io/apiserver/pkg/endpoints/deprecation"
kscheme "k8s.io/client-go/kubernetes/scheme"
- chartutil "helm.sh/helm/v4/pkg/chart/util"
+ chartutil "helm.sh/helm/v4/pkg/chart/v2/util"
)
var (
@@ -47,7 +47,7 @@ func (e deprecatedAPIError) Error() string {
return msg
}
-func validateNoDeprecations(resource *K8sYamlStruct, kubeVersion *chartutil.KubeVersion) error {
+func validateNoDeprecations(resource *k8sYamlStruct, kubeVersion *chartutil.KubeVersion) error {
// if `resource` does not have an APIVersion or Kind, we cannot test it for deprecation
if resource.APIVersion == "" {
return nil
@@ -92,7 +92,7 @@ func validateNoDeprecations(resource *K8sYamlStruct, kubeVersion *chartutil.Kube
}
}
-func resourceToRuntimeObject(resource *K8sYamlStruct) (runtime.Object, error) {
+func resourceToRuntimeObject(resource *k8sYamlStruct) (runtime.Object, error) {
scheme := runtime.NewScheme()
kscheme.AddToScheme(scheme)
diff --git a/pkg/lint/rules/deprecations_test.go b/pkg/lint/rules/deprecations_test.go
index c0e64d04f..6add843ce 100644
--- a/pkg/lint/rules/deprecations_test.go
+++ b/pkg/lint/rules/deprecations_test.go
@@ -19,7 +19,7 @@ package rules // import "helm.sh/helm/v4/pkg/lint/rules"
import "testing"
func TestValidateNoDeprecations(t *testing.T) {
- deprecated := &K8sYamlStruct{
+ deprecated := &k8sYamlStruct{
APIVersion: "extensions/v1beta1",
Kind: "Deployment",
}
@@ -32,7 +32,7 @@ func TestValidateNoDeprecations(t *testing.T) {
t.Fatalf("Expected error message to be non-blank: %v", err)
}
- if err := validateNoDeprecations(&K8sYamlStruct{
+ if err := validateNoDeprecations(&k8sYamlStruct{
APIVersion: "v1",
Kind: "Pod",
}, nil); err != nil {
diff --git a/pkg/lint/rules/template.go b/pkg/lint/rules/template.go
index ec434aba5..463bd5341 100644
--- a/pkg/lint/rules/template.go
+++ b/pkg/lint/rules/template.go
@@ -19,31 +19,26 @@ package rules
import (
"bufio"
"bytes"
+ "errors"
"fmt"
"io"
"os"
"path"
"path/filepath"
- "regexp"
+ "slices"
"strings"
- "github.com/pkg/errors"
"k8s.io/apimachinery/pkg/api/validation"
apipath "k8s.io/apimachinery/pkg/api/validation/path"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/apimachinery/pkg/util/yaml"
- "helm.sh/helm/v4/pkg/chart/loader"
- chartutil "helm.sh/helm/v4/pkg/chart/util"
+ "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"
)
-var (
- crdHookSearch = regexp.MustCompile(`"?helm\.sh/hook"?:\s+crd-install`)
- releaseTimeSearch = regexp.MustCompile(`\.Release\.Time`)
-)
-
// Templates lints the templates in the Linter.
func Templates(linter *support.Linter, values map[string]interface{}, namespace string, _ bool) {
TemplatesWithKubeVersion(linter, values, namespace, nil)
@@ -119,14 +114,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" {
@@ -148,9 +139,9 @@ func TemplatesWithSkipSchemaValidation(linter *support.Linter, values map[string
// Lint all resources if the file contains multiple documents separated by ---
for {
- // Even though K8sYamlStruct only defines a few fields, an error in any other
+ // Even though k8sYamlStruct only defines a few fields, an error in any other
// key will be raised as well
- var yamlStruct *K8sYamlStruct
+ var yamlStruct *k8sYamlStruct
err := decoder.Decode(&yamlStruct)
if err == io.EOF {
@@ -216,30 +207,31 @@ func validateAllowedExtension(fileName string) error {
ext := filepath.Ext(fileName)
validExtensions := []string{".yaml", ".yml", ".tpl", ".txt"}
- for _, b := range validExtensions {
- if b == ext {
- return nil
- }
+ if slices.Contains(validExtensions, ext) {
+ return nil
}
- return errors.Errorf("file extension '%s' not valid. Valid extensions are .yaml, .yml, .tpl, or .txt", ext)
+ return fmt.Errorf("file extension '%s' not valid. Valid extensions are .yaml, .yml, .tpl, or .txt", ext)
}
func validateYamlContent(err error) error {
- return errors.Wrap(err, "unable to parse YAML")
+ if err != nil {
+ return fmt.Errorf("unable to parse YAML: %w", err)
+ }
+ return nil
}
// validateMetadataName uses the correct validation function for the object
// Kind, or if not set, defaults to the standard definition of a subdomain in
// DNS (RFC 1123), used by most resources.
-func validateMetadataName(obj *K8sYamlStruct) error {
+func validateMetadataName(obj *k8sYamlStruct) error {
fn := validateMetadataNameFunc(obj)
allErrs := field.ErrorList{}
for _, msg := range fn(obj.Metadata.Name, false) {
allErrs = append(allErrs, field.Invalid(field.NewPath("metadata").Child("name"), obj.Metadata.Name, msg))
}
if len(allErrs) > 0 {
- return errors.Wrapf(allErrs.ToAggregate(), "object name does not conform to Kubernetes naming requirements: %q", obj.Metadata.Name)
+ return fmt.Errorf("object name does not conform to Kubernetes naming requirements: %q: %w", obj.Metadata.Name, allErrs.ToAggregate())
}
return nil
}
@@ -257,7 +249,7 @@ func validateMetadataName(obj *K8sYamlStruct) error {
// If no mapping is defined, returns NameIsDNSSubdomain. This is used by object
// kinds that don't have special requirements, so is the most likely to work if
// new kinds are added.
-func validateMetadataNameFunc(obj *K8sYamlStruct) validation.ValidateNameFunc {
+func validateMetadataNameFunc(obj *k8sYamlStruct) validation.ValidateNameFunc {
switch strings.ToLower(obj.Kind) {
case "pod", "node", "secret", "endpoints", "resourcequota", // core
"controllerrevision", "daemonset", "deployment", "replicaset", "statefulset", // apps
@@ -291,33 +283,20 @@ 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 {
+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)
}
}
return nil
}
-func validateListAnnotations(yamlStruct *K8sYamlStruct, manifest string) error {
+
+func validateListAnnotations(yamlStruct *k8sYamlStruct, manifest string) error {
if yamlStruct.Kind == "List" {
m := struct {
Items []struct {
@@ -333,18 +312,15 @@ func validateListAnnotations(yamlStruct *K8sYamlStruct, manifest string) error {
for _, i := range m.Items {
if _, ok := i.Metadata.Annotations["helm.sh/resource-policy"]; ok {
- return errors.New("Annotation 'helm.sh/resource-policy' within List objects are ignored")
+ return errors.New("annotation 'helm.sh/resource-policy' within List objects are ignored")
}
}
}
return nil
}
-// K8sYamlStruct stubs a Kubernetes YAML file.
-//
-// DEPRECATED: In Helm 4, this will be made a private type, as it is for use only within
-// the rules package.
-type K8sYamlStruct struct {
+// k8sYamlStruct stubs a Kubernetes YAML file.
+type k8sYamlStruct struct {
APIVersion string `json:"apiVersion"`
Kind string
Metadata k8sYamlMetadata
diff --git a/pkg/lint/rules/template_test.go b/pkg/lint/rules/template_test.go
index d3185d8c8..787bd6e4b 100644
--- a/pkg/lint/rules/template_test.go
+++ b/pkg/lint/rules/template_test.go
@@ -23,8 +23,8 @@ import (
"strings"
"testing"
- "helm.sh/helm/v4/pkg/chart"
- chartutil "helm.sh/helm/v4/pkg/chart/util"
+ chart "helm.sh/helm/v4/pkg/chart/v2"
+ chartutil "helm.sh/helm/v4/pkg/chart/v2/util"
"helm.sh/helm/v4/pkg/lint/support"
)
@@ -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)
@@ -121,76 +101,76 @@ func TestMultiTemplateFail(t *testing.T) {
func TestValidateMetadataName(t *testing.T) {
tests := []struct {
- obj *K8sYamlStruct
+ obj *k8sYamlStruct
wantErr bool
}{
// Most kinds use IsDNS1123Subdomain.
- {&K8sYamlStruct{Kind: "Pod", Metadata: k8sYamlMetadata{Name: ""}}, true},
- {&K8sYamlStruct{Kind: "Pod", Metadata: k8sYamlMetadata{Name: "foo"}}, false},
- {&K8sYamlStruct{Kind: "Pod", Metadata: k8sYamlMetadata{Name: "foo.bar1234baz.seventyone"}}, false},
- {&K8sYamlStruct{Kind: "Pod", Metadata: k8sYamlMetadata{Name: "FOO"}}, true},
- {&K8sYamlStruct{Kind: "Pod", Metadata: k8sYamlMetadata{Name: "123baz"}}, false},
- {&K8sYamlStruct{Kind: "Pod", Metadata: k8sYamlMetadata{Name: "foo.BAR.baz"}}, true},
- {&K8sYamlStruct{Kind: "Pod", Metadata: k8sYamlMetadata{Name: "one-two"}}, false},
- {&K8sYamlStruct{Kind: "Pod", Metadata: k8sYamlMetadata{Name: "-two"}}, true},
- {&K8sYamlStruct{Kind: "Pod", Metadata: k8sYamlMetadata{Name: "one_two"}}, true},
- {&K8sYamlStruct{Kind: "Pod", Metadata: k8sYamlMetadata{Name: "a..b"}}, true},
- {&K8sYamlStruct{Kind: "Pod", Metadata: k8sYamlMetadata{Name: "%^$%*@^*@^"}}, true},
- {&K8sYamlStruct{Kind: "Pod", Metadata: k8sYamlMetadata{Name: "operator:pod"}}, true},
- {&K8sYamlStruct{Kind: "ServiceAccount", Metadata: k8sYamlMetadata{Name: "foo"}}, false},
- {&K8sYamlStruct{Kind: "ServiceAccount", Metadata: k8sYamlMetadata{Name: "foo.bar1234baz.seventyone"}}, false},
- {&K8sYamlStruct{Kind: "ServiceAccount", Metadata: k8sYamlMetadata{Name: "FOO"}}, true},
- {&K8sYamlStruct{Kind: "ServiceAccount", Metadata: k8sYamlMetadata{Name: "operator:sa"}}, true},
+ {&k8sYamlStruct{Kind: "Pod", Metadata: k8sYamlMetadata{Name: ""}}, true},
+ {&k8sYamlStruct{Kind: "Pod", Metadata: k8sYamlMetadata{Name: "foo"}}, false},
+ {&k8sYamlStruct{Kind: "Pod", Metadata: k8sYamlMetadata{Name: "foo.bar1234baz.seventyone"}}, false},
+ {&k8sYamlStruct{Kind: "Pod", Metadata: k8sYamlMetadata{Name: "FOO"}}, true},
+ {&k8sYamlStruct{Kind: "Pod", Metadata: k8sYamlMetadata{Name: "123baz"}}, false},
+ {&k8sYamlStruct{Kind: "Pod", Metadata: k8sYamlMetadata{Name: "foo.BAR.baz"}}, true},
+ {&k8sYamlStruct{Kind: "Pod", Metadata: k8sYamlMetadata{Name: "one-two"}}, false},
+ {&k8sYamlStruct{Kind: "Pod", Metadata: k8sYamlMetadata{Name: "-two"}}, true},
+ {&k8sYamlStruct{Kind: "Pod", Metadata: k8sYamlMetadata{Name: "one_two"}}, true},
+ {&k8sYamlStruct{Kind: "Pod", Metadata: k8sYamlMetadata{Name: "a..b"}}, true},
+ {&k8sYamlStruct{Kind: "Pod", Metadata: k8sYamlMetadata{Name: "%^$%*@^*@^"}}, true},
+ {&k8sYamlStruct{Kind: "Pod", Metadata: k8sYamlMetadata{Name: "operator:pod"}}, true},
+ {&k8sYamlStruct{Kind: "ServiceAccount", Metadata: k8sYamlMetadata{Name: "foo"}}, false},
+ {&k8sYamlStruct{Kind: "ServiceAccount", Metadata: k8sYamlMetadata{Name: "foo.bar1234baz.seventyone"}}, false},
+ {&k8sYamlStruct{Kind: "ServiceAccount", Metadata: k8sYamlMetadata{Name: "FOO"}}, true},
+ {&k8sYamlStruct{Kind: "ServiceAccount", Metadata: k8sYamlMetadata{Name: "operator:sa"}}, true},
// Service uses IsDNS1035Label.
- {&K8sYamlStruct{Kind: "Service", Metadata: k8sYamlMetadata{Name: "foo"}}, false},
- {&K8sYamlStruct{Kind: "Service", Metadata: k8sYamlMetadata{Name: "123baz"}}, true},
- {&K8sYamlStruct{Kind: "Service", Metadata: k8sYamlMetadata{Name: "foo.bar"}}, true},
+ {&k8sYamlStruct{Kind: "Service", Metadata: k8sYamlMetadata{Name: "foo"}}, false},
+ {&k8sYamlStruct{Kind: "Service", Metadata: k8sYamlMetadata{Name: "123baz"}}, true},
+ {&k8sYamlStruct{Kind: "Service", Metadata: k8sYamlMetadata{Name: "foo.bar"}}, true},
// Namespace uses IsDNS1123Label.
- {&K8sYamlStruct{Kind: "Namespace", Metadata: k8sYamlMetadata{Name: "foo"}}, false},
- {&K8sYamlStruct{Kind: "Namespace", Metadata: k8sYamlMetadata{Name: "123baz"}}, false},
- {&K8sYamlStruct{Kind: "Namespace", Metadata: k8sYamlMetadata{Name: "foo.bar"}}, true},
- {&K8sYamlStruct{Kind: "Namespace", Metadata: k8sYamlMetadata{Name: "foo-bar"}}, false},
+ {&k8sYamlStruct{Kind: "Namespace", Metadata: k8sYamlMetadata{Name: "foo"}}, false},
+ {&k8sYamlStruct{Kind: "Namespace", Metadata: k8sYamlMetadata{Name: "123baz"}}, false},
+ {&k8sYamlStruct{Kind: "Namespace", Metadata: k8sYamlMetadata{Name: "foo.bar"}}, true},
+ {&k8sYamlStruct{Kind: "Namespace", Metadata: k8sYamlMetadata{Name: "foo-bar"}}, false},
// CertificateSigningRequest has no validation.
- {&K8sYamlStruct{Kind: "CertificateSigningRequest", Metadata: k8sYamlMetadata{Name: ""}}, false},
- {&K8sYamlStruct{Kind: "CertificateSigningRequest", Metadata: k8sYamlMetadata{Name: "123baz"}}, false},
- {&K8sYamlStruct{Kind: "CertificateSigningRequest", Metadata: k8sYamlMetadata{Name: "%^$%*@^*@^"}}, false},
+ {&k8sYamlStruct{Kind: "CertificateSigningRequest", Metadata: k8sYamlMetadata{Name: ""}}, false},
+ {&k8sYamlStruct{Kind: "CertificateSigningRequest", Metadata: k8sYamlMetadata{Name: "123baz"}}, false},
+ {&k8sYamlStruct{Kind: "CertificateSigningRequest", Metadata: k8sYamlMetadata{Name: "%^$%*@^*@^"}}, false},
// RBAC uses path validation.
- {&K8sYamlStruct{Kind: "Role", Metadata: k8sYamlMetadata{Name: "foo"}}, false},
- {&K8sYamlStruct{Kind: "Role", Metadata: k8sYamlMetadata{Name: "123baz"}}, false},
- {&K8sYamlStruct{Kind: "Role", Metadata: k8sYamlMetadata{Name: "foo.bar"}}, false},
- {&K8sYamlStruct{Kind: "Role", Metadata: k8sYamlMetadata{Name: "operator:role"}}, false},
- {&K8sYamlStruct{Kind: "Role", Metadata: k8sYamlMetadata{Name: "operator/role"}}, true},
- {&K8sYamlStruct{Kind: "Role", Metadata: k8sYamlMetadata{Name: "operator%role"}}, true},
- {&K8sYamlStruct{Kind: "ClusterRole", Metadata: k8sYamlMetadata{Name: "foo"}}, false},
- {&K8sYamlStruct{Kind: "ClusterRole", Metadata: k8sYamlMetadata{Name: "123baz"}}, false},
- {&K8sYamlStruct{Kind: "ClusterRole", Metadata: k8sYamlMetadata{Name: "foo.bar"}}, false},
- {&K8sYamlStruct{Kind: "ClusterRole", Metadata: k8sYamlMetadata{Name: "operator:role"}}, false},
- {&K8sYamlStruct{Kind: "ClusterRole", Metadata: k8sYamlMetadata{Name: "operator/role"}}, true},
- {&K8sYamlStruct{Kind: "ClusterRole", Metadata: k8sYamlMetadata{Name: "operator%role"}}, true},
- {&K8sYamlStruct{Kind: "RoleBinding", Metadata: k8sYamlMetadata{Name: "operator:role"}}, false},
- {&K8sYamlStruct{Kind: "ClusterRoleBinding", Metadata: k8sYamlMetadata{Name: "operator:role"}}, false},
+ {&k8sYamlStruct{Kind: "Role", Metadata: k8sYamlMetadata{Name: "foo"}}, false},
+ {&k8sYamlStruct{Kind: "Role", Metadata: k8sYamlMetadata{Name: "123baz"}}, false},
+ {&k8sYamlStruct{Kind: "Role", Metadata: k8sYamlMetadata{Name: "foo.bar"}}, false},
+ {&k8sYamlStruct{Kind: "Role", Metadata: k8sYamlMetadata{Name: "operator:role"}}, false},
+ {&k8sYamlStruct{Kind: "Role", Metadata: k8sYamlMetadata{Name: "operator/role"}}, true},
+ {&k8sYamlStruct{Kind: "Role", Metadata: k8sYamlMetadata{Name: "operator%role"}}, true},
+ {&k8sYamlStruct{Kind: "ClusterRole", Metadata: k8sYamlMetadata{Name: "foo"}}, false},
+ {&k8sYamlStruct{Kind: "ClusterRole", Metadata: k8sYamlMetadata{Name: "123baz"}}, false},
+ {&k8sYamlStruct{Kind: "ClusterRole", Metadata: k8sYamlMetadata{Name: "foo.bar"}}, false},
+ {&k8sYamlStruct{Kind: "ClusterRole", Metadata: k8sYamlMetadata{Name: "operator:role"}}, false},
+ {&k8sYamlStruct{Kind: "ClusterRole", Metadata: k8sYamlMetadata{Name: "operator/role"}}, true},
+ {&k8sYamlStruct{Kind: "ClusterRole", Metadata: k8sYamlMetadata{Name: "operator%role"}}, true},
+ {&k8sYamlStruct{Kind: "RoleBinding", Metadata: k8sYamlMetadata{Name: "operator:role"}}, false},
+ {&k8sYamlStruct{Kind: "ClusterRoleBinding", Metadata: k8sYamlMetadata{Name: "operator:role"}}, false},
// Unknown Kind
- {&K8sYamlStruct{Kind: "FutureKind", Metadata: k8sYamlMetadata{Name: ""}}, true},
- {&K8sYamlStruct{Kind: "FutureKind", Metadata: k8sYamlMetadata{Name: "foo"}}, false},
- {&K8sYamlStruct{Kind: "FutureKind", Metadata: k8sYamlMetadata{Name: "foo.bar1234baz.seventyone"}}, false},
- {&K8sYamlStruct{Kind: "FutureKind", Metadata: k8sYamlMetadata{Name: "FOO"}}, true},
- {&K8sYamlStruct{Kind: "FutureKind", Metadata: k8sYamlMetadata{Name: "123baz"}}, false},
- {&K8sYamlStruct{Kind: "FutureKind", Metadata: k8sYamlMetadata{Name: "foo.BAR.baz"}}, true},
- {&K8sYamlStruct{Kind: "FutureKind", Metadata: k8sYamlMetadata{Name: "one-two"}}, false},
- {&K8sYamlStruct{Kind: "FutureKind", Metadata: k8sYamlMetadata{Name: "-two"}}, true},
- {&K8sYamlStruct{Kind: "FutureKind", Metadata: k8sYamlMetadata{Name: "one_two"}}, true},
- {&K8sYamlStruct{Kind: "FutureKind", Metadata: k8sYamlMetadata{Name: "a..b"}}, true},
- {&K8sYamlStruct{Kind: "FutureKind", Metadata: k8sYamlMetadata{Name: "%^$%*@^*@^"}}, true},
- {&K8sYamlStruct{Kind: "FutureKind", Metadata: k8sYamlMetadata{Name: "operator:pod"}}, true},
+ {&k8sYamlStruct{Kind: "FutureKind", Metadata: k8sYamlMetadata{Name: ""}}, true},
+ {&k8sYamlStruct{Kind: "FutureKind", Metadata: k8sYamlMetadata{Name: "foo"}}, false},
+ {&k8sYamlStruct{Kind: "FutureKind", Metadata: k8sYamlMetadata{Name: "foo.bar1234baz.seventyone"}}, false},
+ {&k8sYamlStruct{Kind: "FutureKind", Metadata: k8sYamlMetadata{Name: "FOO"}}, true},
+ {&k8sYamlStruct{Kind: "FutureKind", Metadata: k8sYamlMetadata{Name: "123baz"}}, false},
+ {&k8sYamlStruct{Kind: "FutureKind", Metadata: k8sYamlMetadata{Name: "foo.BAR.baz"}}, true},
+ {&k8sYamlStruct{Kind: "FutureKind", Metadata: k8sYamlMetadata{Name: "one-two"}}, false},
+ {&k8sYamlStruct{Kind: "FutureKind", Metadata: k8sYamlMetadata{Name: "-two"}}, true},
+ {&k8sYamlStruct{Kind: "FutureKind", Metadata: k8sYamlMetadata{Name: "one_two"}}, true},
+ {&k8sYamlStruct{Kind: "FutureKind", Metadata: k8sYamlMetadata{Name: "a..b"}}, true},
+ {&k8sYamlStruct{Kind: "FutureKind", Metadata: k8sYamlMetadata{Name: "%^$%*@^*@^"}}, true},
+ {&k8sYamlStruct{Kind: "FutureKind", Metadata: k8sYamlMetadata{Name: "operator:pod"}}, true},
// No kind
- {&K8sYamlStruct{Metadata: k8sYamlMetadata{Name: "foo"}}, false},
- {&K8sYamlStruct{Metadata: k8sYamlMetadata{Name: "operator:pod"}}, true},
+ {&k8sYamlStruct{Metadata: k8sYamlMetadata{Name: "foo"}}, false},
+ {&k8sYamlStruct{Metadata: k8sYamlMetadata{Name: "operator:pod"}}, true},
}
for _, tt := range tests {
t.Run(fmt.Sprintf("%s/%s", tt.obj.Kind, tt.obj.Metadata.Name), func(t *testing.T) {
@@ -293,7 +273,7 @@ func TestStrictTemplateParsingMapError(t *testing.T) {
}
func TestValidateMatchSelector(t *testing.T) {
- md := &K8sYamlStruct{
+ md := &k8sYamlStruct{
APIVersion: "apps/v1",
Kind: "Deployment",
Metadata: k8sYamlMetadata{
@@ -421,7 +401,7 @@ func TestEmptyWithCommentsManifests(t *testing.T) {
}
}
func TestValidateListAnnotations(t *testing.T) {
- md := &K8sYamlStruct{
+ md := &k8sYamlStruct{
APIVersion: "v1",
Kind: "List",
Metadata: k8sYamlMetadata{
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 f430178c3..019e74fa7 100644
--- a/pkg/lint/rules/values.go
+++ b/pkg/lint/rules/values.go
@@ -17,12 +17,11 @@ limitations under the License.
package rules
import (
+ "fmt"
"os"
"path/filepath"
- "github.com/pkg/errors"
-
- chartutil "helm.sh/helm/v4/pkg/chart/util"
+ chartutil "helm.sh/helm/v4/pkg/chart/v2/util"
"helm.sh/helm/v4/pkg/lint/support"
)
@@ -47,7 +46,7 @@ func ValuesWithOverrides(linter *support.Linter, valueOverrides map[string]inter
func validateValuesFileExistence(valuesPath string) error {
_, err := os.Stat(valuesPath)
if err != nil {
- return errors.Errorf("file does not exist")
+ return fmt.Errorf("file does not exist")
}
return nil
}
@@ -55,7 +54,7 @@ func validateValuesFileExistence(valuesPath string) error {
func validateValuesFile(valuesPath string, overrides map[string]interface{}) error {
values, err := chartutil.ReadValuesFile(valuesPath)
if err != nil {
- return errors.Wrap(err, "unable to parse YAML")
+ return fmt.Errorf("unable to parse YAML: %w", err)
}
// Helm 3.0.0 carried over the values linting from Helm 2.x, which only tests the top
diff --git a/pkg/lint/rules/values_test.go b/pkg/lint/rules/values_test.go
index 8a2556a60..348695785 100644
--- a/pkg/lint/rules/values_test.go
+++ b/pkg/lint/rules/values_test.go
@@ -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/message_test.go b/pkg/lint/support/message_test.go
index 9e12a638b..ce5b5e42e 100644
--- a/pkg/lint/support/message_test.go
+++ b/pkg/lint/support/message_test.go
@@ -17,12 +17,10 @@ limitations under the License.
package support
import (
+ "errors"
"testing"
-
- "github.com/pkg/errors"
)
-var linter = Linter{}
var errLint = errors.New("lint failed")
func TestRunLinterRule(t *testing.T) {
@@ -46,6 +44,7 @@ func TestRunLinterRule(t *testing.T) {
{-1, errLint, 4, false, ErrorSev},
}
+ linter := Linter{}
for _, test := range tests {
isValid := linter.RunLinterRule(test.Severity, "chart", test.LintError)
if len(linter.Messages) != test.ExpectedMessages {
diff --git a/pkg/plugin/installer/base_test.go b/pkg/plugin/installer/base_test.go
index f4dd6d6be..732ac7927 100644
--- a/pkg/plugin/installer/base_test.go
+++ b/pkg/plugin/installer/base_test.go
@@ -14,7 +14,6 @@ limitations under the License.
package installer // import "helm.sh/helm/v4/pkg/plugin/installer"
import (
- "os"
"testing"
)
@@ -37,12 +36,11 @@ func TestPath(t *testing.T) {
for _, tt := range tests {
- os.Setenv("HELM_PLUGINS", tt.helmPluginsDir)
+ t.Setenv("HELM_PLUGINS", tt.helmPluginsDir)
baseIns := newBase(tt.source)
baseInsPath := baseIns.Path()
if baseInsPath != tt.expectPath {
t.Errorf("expected name %s, got %s", tt.expectPath, baseInsPath)
}
- os.Unsetenv("HELM_PLUGINS")
}
}
diff --git a/pkg/plugin/installer/http_installer.go b/pkg/plugin/installer/http_installer.go
index b900fa401..3bcf71208 100644
--- a/pkg/plugin/installer/http_installer.go
+++ b/pkg/plugin/installer/http_installer.go
@@ -19,15 +19,18 @@ import (
"archive/tar"
"bytes"
"compress/gzip"
+ "errors"
+ "fmt"
"io"
+ "log/slog"
"os"
"path"
"path/filepath"
"regexp"
+ "slices"
"strings"
securejoin "github.com/cyphar/filepath-securejoin"
- "github.com/pkg/errors"
"helm.sh/helm/v4/internal/third_party/dep/fs"
"helm.sh/helm/v4/pkg/cli"
@@ -78,7 +81,7 @@ func NewExtractor(source string) (Extractor, error) {
return extractor, nil
}
}
- return nil, errors.Errorf("no extractor implemented yet for %s", source)
+ return nil, fmt.Errorf("no extractor implemented yet for %s", source)
}
// NewHTTPInstaller creates a new HttpInstaller.
@@ -132,7 +135,7 @@ func (i *HTTPInstaller) Install() error {
}
if err := i.extractor.Extract(pluginData, i.CacheDir); err != nil {
- return errors.Wrap(err, "extracting files from archive")
+ return fmt.Errorf("extracting files from archive: %w", err)
}
if !isPlugin(i.CacheDir) {
@@ -144,19 +147,19 @@ 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())
}
// Update updates a local repository
// Not implemented for now since tarball most likely will be packaged by version
func (i *HTTPInstaller) Update() error {
- return errors.Errorf("method Update() not implemented for HttpInstaller")
+ return fmt.Errorf("method Update() not implemented for HttpInstaller")
}
// 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)
@@ -194,10 +197,8 @@ func cleanJoin(root, dest string) (string, error) {
// We want to alert the user that something bad was attempted. Cleaning it
// is not a good practice.
- for _, part := range strings.Split(dest, "/") {
- if part == ".." {
- return "", errors.New("path contains '..', which is illegal")
- }
+ if slices.Contains(strings.Split(dest, "/"), "..") {
+ return "", errors.New("path contains '..', which is illegal")
}
// If a path is absolute, the creator of the TAR is doing something shady.
@@ -264,7 +265,7 @@ func (g *TarGzExtractor) Extract(buffer *bytes.Buffer, targetDir string) error {
case tar.TypeXGlobalHeader, tar.TypeXHeader:
continue
default:
- return errors.Errorf("unknown type: %b in %s", header.Typeflag, header.Name)
+ return fmt.Errorf("unknown type: %b in %s", header.Typeflag, header.Name)
}
}
return nil
diff --git a/pkg/plugin/installer/http_installer_test.go b/pkg/plugin/installer/http_installer_test.go
index c5af1f2cc..ed4b73b35 100644
--- a/pkg/plugin/installer/http_installer_test.go
+++ b/pkg/plugin/installer/http_installer_test.go
@@ -20,7 +20,9 @@ import (
"bytes"
"compress/gzip"
"encoding/base64"
+ "errors"
"fmt"
+ "io/fs"
"net/http"
"net/http/httptest"
"os"
@@ -29,8 +31,6 @@ import (
"syscall"
"testing"
- "github.com/pkg/errors"
-
"helm.sh/helm/v4/internal/test/ensure"
"helm.sh/helm/v4/pkg/getter"
"helm.sh/helm/v4/pkg/helmpath"
@@ -150,7 +150,7 @@ func TestHTTPInstallerNonExistentVersion(t *testing.T) {
// inject fake http client responding with error
httpInstaller.getter = &TestHTTPGetter{
- MockError: errors.Errorf("failed to download plugin for some reason"),
+ MockError: fmt.Errorf("failed to download plugin for some reason"),
}
// attempt to install the plugin
@@ -276,7 +276,7 @@ func TestExtract(t *testing.T) {
pluginYAMLFullPath := filepath.Join(tempDir, "plugin.yaml")
if info, err := os.Stat(pluginYAMLFullPath); err != nil {
- if os.IsNotExist(err) {
+ if errors.Is(err, fs.ErrNotExist) {
t.Fatalf("Expected %s to exist but doesn't", pluginYAMLFullPath)
}
t.Fatal(err)
@@ -286,7 +286,7 @@ func TestExtract(t *testing.T) {
readmeFullPath := filepath.Join(tempDir, "README.md")
if info, err := os.Stat(readmeFullPath); err != nil {
- if os.IsNotExist(err) {
+ if errors.Is(err, fs.ErrNotExist) {
t.Fatalf("Expected %s to exist but doesn't", readmeFullPath)
}
t.Fatal(err)
diff --git a/pkg/plugin/installer/installer.go b/pkg/plugin/installer/installer.go
index 5fad58f99..d88737ebf 100644
--- a/pkg/plugin/installer/installer.go
+++ b/pkg/plugin/installer/installer.go
@@ -16,15 +16,12 @@ limitations under the License.
package installer
import (
- "fmt"
- "log"
+ "errors"
"net/http"
"os"
"path/filepath"
"strings"
- "github.com/pkg/errors"
-
"helm.sh/helm/v4/pkg/plugin"
)
@@ -125,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 a79ca7ec7..109f4f236 100644
--- a/pkg/plugin/installer/local_installer.go
+++ b/pkg/plugin/installer/local_installer.go
@@ -16,10 +16,11 @@ limitations under the License.
package installer // import "helm.sh/helm/v4/pkg/plugin/installer"
import (
+ "errors"
+ "fmt"
+ "log/slog"
"os"
"path/filepath"
-
- "github.com/pkg/errors"
)
// ErrPluginNotAFolder indicates that the plugin path is not a folder.
@@ -34,7 +35,7 @@ type LocalInstaller struct {
func NewLocalInstaller(source string) (*LocalInstaller, error) {
src, err := filepath.Abs(source)
if err != nil {
- return nil, errors.Wrap(err, "unable to get absolute path to plugin")
+ return nil, fmt.Errorf("unable to get absolute path to plugin: %w", err)
}
i := &LocalInstaller{
base: newBase(src),
@@ -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 b28920af4..9effcd2c4 100644
--- a/pkg/plugin/installer/local_installer_test.go
+++ b/pkg/plugin/installer/local_installer_test.go
@@ -20,12 +20,14 @@ import (
"path/filepath"
"testing"
+ "helm.sh/helm/v4/internal/test/ensure"
"helm.sh/helm/v4/pkg/helmpath"
)
var _ Installer = new(LocalInstaller)
func TestLocalInstaller(t *testing.T) {
+ ensure.HelmHome(t)
// Make a temp dir
tdir := t.TempDir()
if err := os.WriteFile(filepath.Join(tdir, "plugin.yaml"), []byte{}, 0644); err != nil {
diff --git a/pkg/plugin/installer/vcs_installer.go b/pkg/plugin/installer/vcs_installer.go
index 3967e46cd..3e53cbf11 100644
--- a/pkg/plugin/installer/vcs_installer.go
+++ b/pkg/plugin/installer/vcs_installer.go
@@ -16,12 +16,15 @@ limitations under the License.
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"
- "github.com/pkg/errors"
"helm.sh/helm/v4/internal/third_party/dep/fs"
"helm.sh/helm/v4/pkg/helmpath"
@@ -88,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")
}
@@ -128,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)
@@ -139,27 +142,27 @@ 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
}
}
- return "", errors.Errorf("requested version %q does not exist for plugin %q", i.Version, i.Repo.Remote())
+ return "", fmt.Errorf("requested version %q does not exist for plugin %q", i.Version, i.Repo.Remote())
}
// 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()); os.IsNotExist(err) {
- debug("cloning %s to %s", repo.Remote(), repo.LocalPath())
+ if _, err := os.Stat(repo.LocalPath()); errors.Is(err, stdfs.ErrNotExist) {
+ 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 fbb5d354e..491d58a3f 100644
--- a/pkg/plugin/installer/vcs_installer_test.go
+++ b/pkg/plugin/installer/vcs_installer_test.go
@@ -19,6 +19,7 @@ import (
"fmt"
"os"
"path/filepath"
+ "strings"
"testing"
"github.com/Masterminds/vcs"
@@ -119,6 +120,8 @@ func TestVCSInstallerNonExistentVersion(t *testing.T) {
if err := Install(i); err == nil {
t.Fatalf("expected error for version does not exists, got none")
+ } else if strings.Contains(err.Error(), "Could not resolve host: github.com") {
+ t.Skip("Unable to run test without Internet access")
} else if err.Error() != fmt.Sprintf("requested version %q does not exist for plugin %q", version, source) {
t.Fatalf("expected error for version does not exists, got (%v)", err)
}
@@ -146,7 +149,11 @@ func TestVCSInstallerUpdate(t *testing.T) {
// Install plugin before update
if err := Install(i); err != nil {
- t.Fatal(err)
+ if strings.Contains(err.Error(), "Could not resolve host: github.com") {
+ t.Skip("Unable to run test without Internet access")
+ } else {
+ t.Fatal(err)
+ }
}
// Test FindSource method for positive result
diff --git a/pkg/plugin/plugin.go b/pkg/plugin/plugin.go
index 3456664c1..9d79ab4fc 100644
--- a/pkg/plugin/plugin.go
+++ b/pkg/plugin/plugin.go
@@ -24,7 +24,6 @@ import (
"strings"
"unicode"
- "github.com/pkg/errors"
"sigs.k8s.io/yaml"
"helm.sh/helm/v4/pkg/cli"
@@ -313,12 +312,12 @@ func LoadDir(dirname string) (*Plugin, error) {
pluginfile := filepath.Join(dirname, PluginFileName)
data, err := os.ReadFile(pluginfile)
if err != nil {
- return nil, errors.Wrapf(err, "failed to read plugin at %q", pluginfile)
+ return nil, fmt.Errorf("failed to read plugin at %q: %w", pluginfile, err)
}
plug := &Plugin{Dir: dirname}
if err := yaml.UnmarshalStrict(data, &plug.Metadata); err != nil {
- return nil, errors.Wrapf(err, "failed to load plugin at %q", pluginfile)
+ return nil, fmt.Errorf("failed to load plugin at %q: %w", pluginfile, err)
}
return plug, validatePluginData(plug, pluginfile)
}
@@ -332,7 +331,7 @@ func LoadAll(basedir string) ([]*Plugin, error) {
scanpath := filepath.Join(basedir, "*", PluginFileName)
matches, err := filepath.Glob(scanpath)
if err != nil {
- return plugins, errors.Wrapf(err, "failed to find plugins in %q", scanpath)
+ return plugins, fmt.Errorf("failed to find plugins in %q: %w", scanpath, err)
}
if matches == nil {
diff --git a/pkg/postrender/exec.go b/pkg/postrender/exec.go
index 167e737d6..16d9c09ce 100644
--- a/pkg/postrender/exec.go
+++ b/pkg/postrender/exec.go
@@ -18,11 +18,10 @@ package postrender
import (
"bytes"
+ "fmt"
"io"
"os/exec"
"path/filepath"
-
- "github.com/pkg/errors"
)
type execRender struct {
@@ -61,7 +60,13 @@ func (p *execRender) Run(renderedManifests *bytes.Buffer) (*bytes.Buffer, error)
}()
err = cmd.Run()
if err != nil {
- return nil, errors.Wrapf(err, "error while running command %s. error output:\n%s", p.binaryPath, stderr.String())
+ 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, fmt.Errorf("post-renderer %q produced empty output", p.binaryPath)
}
return postRendered, nil
@@ -89,7 +94,7 @@ func getFullPath(binaryPath string) (string, error) {
// // The plugins variable can actually contain multiple paths, so loop through those
// for _, p := range filepath.SplitList(pluginDir) {
// _, err := os.Stat(filepath.Join(p, binaryPath))
- // if err != nil && !os.IsNotExist(err) {
+ // if err != nil && !errors.Is(err, fs.ErrNotExist) {
// return "", err
// } else if err == nil {
// binaryPath = filepath.Join(p, binaryPath)
@@ -102,7 +107,7 @@ func getFullPath(binaryPath string) (string, error) {
// the path and is executable
checkedPath, err := exec.LookPath(binaryPath)
if err != nil {
- return "", errors.Wrapf(err, "unable to find binary at %s", binaryPath)
+ return "", fmt.Errorf("unable to find binary at %s: %w", binaryPath, err)
}
return filepath.Abs(checkedPath)
diff --git a/pkg/postrender/exec_test.go b/pkg/postrender/exec_test.go
index 19a6ec6c4..a10ad2cc4 100644
--- a/pkg/postrender/exec_test.go
+++ b/pkg/postrender/exec_test.go
@@ -60,11 +60,7 @@ func TestGetFullPath(t *testing.T) {
t.Run("binary in PATH resolves correctly", func(t *testing.T) {
testpath := setupTestingScript(t)
- realPath := os.Getenv("PATH")
- os.Setenv("PATH", filepath.Dir(testpath))
- defer func() {
- os.Setenv("PATH", realPath)
- }()
+ t.Setenv("PATH", filepath.Dir(testpath))
fullPath, err := getFullPath(filepath.Base(testpath))
is.NoError(err)
@@ -121,6 +117,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
@@ -168,7 +179,7 @@ func setupTestingScript(t *testing.T) (filepath string) {
t.Fatalf("unable to write tempfile for testing: %s", err)
}
- err = f.Chmod(0755)
+ err = f.Chmod(0o755)
if err != nil {
t.Fatalf("unable to make tempfile executable for testing: %s", err)
}
diff --git a/pkg/provenance/sign.go b/pkg/provenance/sign.go
index bbb68322f..504bc6aa1 100644
--- a/pkg/provenance/sign.go
+++ b/pkg/provenance/sign.go
@@ -19,19 +19,20 @@ import (
"bytes"
"crypto"
"encoding/hex"
+ "errors"
+ "fmt"
"io"
"os"
"path/filepath"
"strings"
- "github.com/pkg/errors"
"golang.org/x/crypto/openpgp" //nolint
"golang.org/x/crypto/openpgp/clearsign" //nolint
"golang.org/x/crypto/openpgp/packet" //nolint
"sigs.k8s.io/yaml"
- hapi "helm.sh/helm/v4/pkg/chart"
- "helm.sh/helm/v4/pkg/chart/loader"
+ hapi "helm.sh/helm/v4/pkg/chart/v2"
+ "helm.sh/helm/v4/pkg/chart/v2/loader"
)
var defaultPGPConfig = packet.Config{
@@ -143,7 +144,7 @@ func NewFromKeyring(keyringfile, id string) (*Signatory, error) {
}
}
if vague {
- return s, errors.Errorf("more than one key contain the id %q", id)
+ return s, fmt.Errorf("more than one key contain the id %q", id)
}
s.Entity = candidate
@@ -236,12 +237,12 @@ func (s *Signatory) ClearSign(chartpath string) (string, error) {
// In other words, if we call Close here, there's a risk that there's an attempt to use the
// private key to sign garbage data (since we know that io.Copy failed, `w` won't contain
// anything useful).
- return "", errors.Wrap(err, "failed to write to clearsign encoder")
+ return "", fmt.Errorf("failed to write to clearsign encoder: %w", err)
}
err = w.Close()
if err != nil {
- return "", errors.Wrap(err, "failed to either sign or armor message block")
+ return "", fmt.Errorf("failed to either sign or armor message block: %w", err)
}
return out.String(), nil
@@ -254,14 +255,14 @@ func (s *Signatory) Verify(chartpath, sigpath string) (*Verification, error) {
if fi, err := os.Stat(fname); err != nil {
return ver, err
} else if fi.IsDir() {
- return ver, errors.Errorf("%s cannot be a directory", fname)
+ return ver, fmt.Errorf("%s cannot be a directory", fname)
}
}
// First verify the signature
sig, err := s.decodeSignature(sigpath)
if err != nil {
- return ver, errors.Wrap(err, "failed to decode signature")
+ return ver, fmt.Errorf("failed to decode signature: %w", err)
}
by, err := s.verifySignature(sig)
@@ -283,9 +284,9 @@ func (s *Signatory) Verify(chartpath, sigpath string) (*Verification, error) {
sum = "sha256:" + sum
basename := filepath.Base(chartpath)
if sha, ok := sums.Files[basename]; !ok {
- return ver, errors.Errorf("provenance does not contain a SHA for a file named %q", basename)
+ return ver, fmt.Errorf("provenance does not contain a SHA for a file named %q", basename)
} else if sha != sum {
- return ver, errors.Errorf("sha256 sum does not match for %s: %q != %q", basename, sha, sum)
+ return ver, fmt.Errorf("sha256 sum does not match for %s: %q != %q", basename, sha, sum)
}
ver.FileHash = sum
ver.FileName = basename
diff --git a/pkg/provenance/sign_test.go b/pkg/provenance/sign_test.go
index 69a6dad5b..9a60fd19c 100644
--- a/pkg/provenance/sign_test.go
+++ b/pkg/provenance/sign_test.go
@@ -276,7 +276,7 @@ func TestDecodeSignature(t *testing.T) {
t.Fatal(err)
}
- f, err := os.CreateTemp("", "helm-test-sig-")
+ f, err := os.CreateTemp(t.TempDir(), "helm-test-sig-")
if err != nil {
t.Fatal(err)
}
diff --git a/pkg/pusher/ocipusher.go b/pkg/pusher/ocipusher.go
index a5149d1c7..699d27caf 100644
--- a/pkg/pusher/ocipusher.go
+++ b/pkg/pusher/ocipusher.go
@@ -16,7 +16,9 @@ limitations under the License.
package pusher
import (
+ "errors"
"fmt"
+ "io/fs"
"net"
"net/http"
"os"
@@ -24,10 +26,8 @@ import (
"strings"
"time"
- "github.com/pkg/errors"
-
"helm.sh/helm/v4/internal/tlsutil"
- "helm.sh/helm/v4/pkg/chart/loader"
+ "helm.sh/helm/v4/pkg/chart/v2/loader"
"helm.sh/helm/v4/pkg/registry"
"helm.sh/helm/v4/pkg/time/ctime"
)
@@ -48,8 +48,8 @@ func (pusher *OCIPusher) Push(chartRef, href string, options ...Option) error {
func (pusher *OCIPusher) push(chartRef, href string) error {
stat, err := os.Stat(chartRef)
if err != nil {
- if os.IsNotExist(err) {
- return errors.Errorf("%s: no such file", chartRef)
+ if errors.Is(err, fs.ErrNotExist) {
+ return fmt.Errorf("%s: no such file", chartRef)
}
return err
}
@@ -117,7 +117,7 @@ func (pusher *OCIPusher) newRegistryClient() (*registry.Client, error) {
tlsutil.WithCAFile(pusher.opts.caFile),
)
if err != nil {
- return nil, errors.Wrap(err, "can't create TLS config for client")
+ return nil, fmt.Errorf("can't create TLS config for client: %w", err)
}
registryClient, err := registry.NewClient(
diff --git a/pkg/pusher/ocipusher_test.go b/pkg/pusher/ocipusher_test.go
index 760da8404..24f52a7ad 100644
--- a/pkg/pusher/ocipusher_test.go
+++ b/pkg/pusher/ocipusher_test.go
@@ -1,3 +1,5 @@
+//go:build !windows
+
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,7 +18,10 @@ limitations under the License.
package pusher
import (
+ "io"
+ "os"
"path/filepath"
+ "strings"
"testing"
"helm.sh/helm/v4/pkg/registry"
@@ -94,3 +99,330 @@ func TestNewOCIPusher(t *testing.T) {
t.Errorf("Expected NewOCIPusher to contain %p as RegistryClient, got %p", registryClient, op.opts.registryClient)
}
}
+
+func TestOCIPusher_Push_ErrorHandling(t *testing.T) {
+ tests := []struct {
+ name string
+ chartRef string
+ expectedError string
+ setupFunc func() string
+ }{
+ {
+ name: "non-existent file",
+ chartRef: "/non/existent/file.tgz",
+ expectedError: "no such file",
+ },
+ {
+ name: "directory instead of file",
+ expectedError: "cannot push directory, must provide chart archive (.tgz)",
+ setupFunc: func() string {
+ tempDir := t.TempDir()
+ return tempDir
+ },
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ pusher, err := NewOCIPusher()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ chartRef := tt.chartRef
+ if tt.setupFunc != nil {
+ chartRef = tt.setupFunc()
+ }
+
+ err = pusher.Push(chartRef, "oci://localhost:5000/test")
+ if err == nil {
+ t.Fatal("Expected error but got none")
+ }
+
+ if !strings.Contains(err.Error(), tt.expectedError) {
+ t.Errorf("Expected error containing %q, got %q", tt.expectedError, err.Error())
+ }
+ })
+ }
+}
+
+func TestOCIPusher_newRegistryClient(t *testing.T) {
+ cd := "../../testdata"
+ join := filepath.Join
+ ca, pub, priv := join(cd, "rootca.crt"), join(cd, "crt.pem"), join(cd, "key.pem")
+
+ tests := []struct {
+ name string
+ opts []Option
+ expectError bool
+ errorContains string
+ }{
+ {
+ name: "plain HTTP",
+ opts: []Option{WithPlainHTTP(true)},
+ },
+ {
+ name: "with TLS client config",
+ opts: []Option{
+ WithTLSClientConfig(pub, priv, ca),
+ },
+ },
+ {
+ name: "with insecure skip TLS verify",
+ opts: []Option{
+ WithInsecureSkipTLSVerify(true),
+ },
+ },
+ {
+ name: "with cert and key only",
+ opts: []Option{
+ WithTLSClientConfig(pub, priv, ""),
+ },
+ },
+ {
+ name: "with CA file only",
+ opts: []Option{
+ WithTLSClientConfig("", "", ca),
+ },
+ },
+ {
+ name: "default client without options",
+ opts: []Option{},
+ },
+ {
+ name: "invalid cert file",
+ opts: []Option{
+ WithTLSClientConfig("/non/existent/cert.pem", priv, ca),
+ },
+ expectError: true,
+ errorContains: "can't create TLS config",
+ },
+ {
+ name: "invalid key file",
+ opts: []Option{
+ WithTLSClientConfig(pub, "/non/existent/key.pem", ca),
+ },
+ expectError: true,
+ errorContains: "can't create TLS config",
+ },
+ {
+ name: "invalid CA file",
+ opts: []Option{
+ WithTLSClientConfig("", "", "/non/existent/ca.crt"),
+ },
+ expectError: true,
+ errorContains: "can't create TLS config",
+ },
+ {
+ name: "combined TLS options",
+ opts: []Option{
+ WithTLSClientConfig(pub, priv, ca),
+ WithInsecureSkipTLSVerify(true),
+ },
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ pusher, err := NewOCIPusher(tt.opts...)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ op, ok := pusher.(*OCIPusher)
+ if !ok {
+ t.Fatal("Expected *OCIPusher")
+ }
+
+ client, err := op.newRegistryClient()
+ if tt.expectError {
+ if err == nil {
+ t.Fatal("Expected error but got none")
+ }
+ if tt.errorContains != "" && !strings.Contains(err.Error(), tt.errorContains) {
+ t.Errorf("Expected error containing %q, got %q", tt.errorContains, err.Error())
+ }
+ } else {
+ if err != nil {
+ t.Fatalf("Unexpected error: %v", err)
+ }
+ if client == nil {
+ t.Fatal("Expected non-nil registry client")
+ }
+ }
+ })
+ }
+}
+
+func TestOCIPusher_Push_ChartOperations(t *testing.T) {
+ // Path to test charts
+ chartPath := "../../pkg/cmd/testdata/testcharts/compressedchart-0.1.0.tgz"
+ chartWithProvPath := "../../pkg/cmd/testdata/testcharts/signtest-0.1.0.tgz"
+
+ tests := []struct {
+ name string
+ chartRef string
+ href string
+ options []Option
+ setupFunc func(t *testing.T) (string, func())
+ expectError bool
+ errorContains string
+ }{
+ {
+ name: "invalid chart file",
+ chartRef: "../../pkg/action/testdata/charts/corrupted-compressed-chart.tgz",
+ href: "oci://localhost:5000/test",
+ expectError: true,
+ errorContains: "does not appear to be a gzipped archive",
+ },
+ {
+ name: "chart read error",
+ setupFunc: func(t *testing.T) (string, func()) {
+ t.Helper()
+ // Create a valid chart file that we'll make unreadable
+ tempDir := t.TempDir()
+ tempChart := filepath.Join(tempDir, "temp-chart.tgz")
+
+ // Copy a valid chart
+ src, err := os.Open(chartPath)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer src.Close()
+
+ dst, err := os.Create(tempChart)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if _, err := io.Copy(dst, src); err != nil {
+ t.Fatal(err)
+ }
+ dst.Close()
+
+ // Make the file unreadable
+ if err := os.Chmod(tempChart, 0000); err != nil {
+ t.Fatal(err)
+ }
+
+ return tempChart, func() {
+ os.Chmod(tempChart, 0644) // Restore permissions for cleanup
+ }
+ },
+ href: "oci://localhost:5000/test",
+ expectError: true,
+ errorContains: "permission denied",
+ },
+ {
+ name: "push with provenance file - loading phase",
+ chartRef: chartWithProvPath,
+ href: "oci://registry.example.com/charts",
+ setupFunc: func(t *testing.T) (string, func()) {
+ t.Helper()
+ // Copy chart and create a .prov file for it
+ tempDir := t.TempDir()
+ tempChart := filepath.Join(tempDir, "signtest-0.1.0.tgz")
+ tempProv := filepath.Join(tempDir, "signtest-0.1.0.tgz.prov")
+
+ // Copy chart file
+ src, err := os.Open(chartWithProvPath)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer src.Close()
+
+ dst, err := os.Create(tempChart)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if _, err := io.Copy(dst, src); err != nil {
+ t.Fatal(err)
+ }
+ dst.Close()
+
+ // Create provenance file
+ if err := os.WriteFile(tempProv, []byte("test provenance data"), 0644); err != nil {
+ t.Fatal(err)
+ }
+
+ return tempChart, func() {}
+ },
+ expectError: true, // Will fail at the registry push step
+ errorContains: "", // Error depends on registry client behavior
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ chartRef := tt.chartRef
+ var cleanup func()
+
+ if tt.setupFunc != nil {
+ chartRef, cleanup = tt.setupFunc(t)
+ if cleanup != nil {
+ defer cleanup()
+ }
+ }
+
+ // Skip test if chart file doesn't exist and we're not expecting an error
+ if _, err := os.Stat(chartRef); err != nil && !tt.expectError {
+ t.Skipf("Test chart %s not found, skipping test", chartRef)
+ }
+
+ pusher, err := NewOCIPusher(tt.options...)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ err = pusher.Push(chartRef, tt.href)
+
+ if tt.expectError {
+ if err == nil {
+ t.Fatal("Expected error but got none")
+ }
+ if tt.errorContains != "" && !strings.Contains(err.Error(), tt.errorContains) {
+ t.Errorf("Expected error containing %q, got %q", tt.errorContains, err.Error())
+ }
+ } else {
+ if err != nil {
+ t.Fatalf("Unexpected error: %v", err)
+ }
+ }
+ })
+ }
+}
+
+func TestOCIPusher_Push_MultipleOptions(t *testing.T) {
+ chartPath := "../../pkg/cmd/testdata/testcharts/compressedchart-0.1.0.tgz"
+
+ // Skip test if chart file doesn't exist
+ if _, err := os.Stat(chartPath); err != nil {
+ t.Skipf("Test chart %s not found, skipping test", chartPath)
+ }
+
+ pusher, err := NewOCIPusher()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Test that multiple options are applied correctly
+ err = pusher.Push(chartPath, "oci://localhost:5000/test",
+ WithPlainHTTP(true),
+ WithInsecureSkipTLSVerify(true),
+ )
+
+ // We expect an error since we're not actually pushing to a registry
+ if err == nil {
+ t.Fatal("Expected error when pushing without a valid registry")
+ }
+
+ // Verify options were applied
+ op := pusher.(*OCIPusher)
+ if !op.opts.plainHTTP {
+ t.Error("Expected plainHTTP option to be applied")
+ }
+ if !op.opts.insecureSkipTLSverify {
+ t.Error("Expected insecureSkipTLSverify option to be applied")
+ }
+}
diff --git a/pkg/pusher/pusher.go b/pkg/pusher/pusher.go
index 0e07ad2d7..e3c767be9 100644
--- a/pkg/pusher/pusher.go
+++ b/pkg/pusher/pusher.go
@@ -17,7 +17,8 @@ limitations under the License.
package pusher
import (
- "github.com/pkg/errors"
+ "fmt"
+ "slices"
"helm.sh/helm/v4/pkg/cli"
"helm.sh/helm/v4/pkg/registry"
@@ -86,12 +87,7 @@ type Provider struct {
// Provides returns true if the given scheme is supported by this Provider.
func (p Provider) Provides(scheme string) bool {
- for _, i := range p.Schemes {
- if i == scheme {
- return true
- }
- }
- return false
+ return slices.Contains(p.Schemes, scheme)
}
// Providers is a collection of Provider objects.
@@ -106,7 +102,7 @@ func (p Providers) ByScheme(scheme string) (Pusher, error) {
return pp.New()
}
}
- return nil, errors.Errorf("scheme %q not supported", scheme)
+ return nil, fmt.Errorf("scheme %q not supported", scheme)
}
var ociProvider = Provider{
diff --git a/pkg/registry/client.go b/pkg/registry/client.go
index 01e5dff7b..3ea68f181 100644
--- a/pkg/registry/client.go
+++ b/pkg/registry/client.go
@@ -21,6 +21,7 @@ import (
"crypto/tls"
"crypto/x509"
"encoding/json"
+ "errors"
"fmt"
"io"
"net/http"
@@ -31,10 +32,8 @@ import (
"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"
- "github.com/pkg/errors"
"oras.land/oras-go/v2"
"oras.land/oras-go/v2/content"
"oras.land/oras-go/v2/content/memory"
@@ -45,7 +44,7 @@ import (
"oras.land/oras-go/v2/registry/remote/retry"
"helm.sh/helm/v4/internal/version"
- "helm.sh/helm/v4/pkg/chart"
+ chart "helm.sh/helm/v4/pkg/chart/v2"
"helm.sh/helm/v4/pkg/helmpath"
)
@@ -56,8 +55,6 @@ storing semantic versions, Helm adopts the convention of changing plus (+) to
an underscore (_) in chart version tags when pushing to a registry and back to
a plus (+) when pulling from a registry.`
-var errDeprecatedRemote = errors.New("providing github.com/containerd/containerd/remotes.Resolver via ClientOptResolver is no longer suported")
-
type (
// RemoteClient shadows the ORAS remote.Client interface
// (hiding the ORAS type from Helm client visibility)
@@ -103,27 +100,8 @@ func NewClient(options ...ClientOption) (*Client, error) {
client.credentialsFile = helmpath.ConfigPath(CredentialsFileBasename)
}
if client.httpClient == nil {
- type cloner[T any] interface {
- Clone() T
- }
-
- // 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()
- }
-
client.httpClient = &http.Client{
- Transport: retry.NewTransport(transport),
+ Transport: NewTransport(client.debug),
}
}
@@ -231,12 +209,6 @@ func ClientOptPlainHTTP() ClientOption {
}
}
-func ClientOptResolver(_ remotes.Resolver) ClientOption {
- return func(c *Client) {
- c.err = errDeprecatedRemote
- }
-}
-
type (
// LoginOption allows specifying various settings on login
LoginOption func(*loginOperation)
@@ -258,19 +230,20 @@ func (c *Client) Login(host string, options ...LoginOption) error {
return err
}
reg.PlainHTTP = c.plainHTTP
+ cred := auth.Credential{Username: c.username, Password: c.password}
+ c.authorizer.ForceAttemptOAuth2 = true
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 := reg.Ping(ctx); err != nil {
- return fmt.Errorf("authenticating to %q: %w", host, err)
+ c.authorizer.ForceAttemptOAuth2 = false
+ if err := reg.Ping(ctx); err != nil {
+ return fmt.Errorf("authenticating to %q: %w", host, err)
+ }
}
key := credentials.ServerAddressFromRegistry(host)
+ key = credentials.ServerAddressFromHostname(key)
if err := c.credentialsStore.Put(ctx, key, cred); err != nil {
return err
}
@@ -305,6 +278,11 @@ func ensureTLSConfig(client *auth.Client) (*tls.Config, error) {
switch t := t.Base.(type) {
case *http.Transport:
transport = t
+ case *LoggingTransport:
+ switch t := t.RoundTripper.(type) {
+ case *http.Transport:
+ transport = t
+ }
}
}
@@ -472,7 +450,7 @@ func (c *Client) Pull(ref string, options ...PullOption) (*PullResult, error) {
PreCopy: func(_ context.Context, desc ocispec.Descriptor) error {
mediaType := desc.MediaType
if i := sort.SearchStrings(allowedMediaTypes, mediaType); i >= len(allowedMediaTypes) || allowedMediaTypes[i] != mediaType {
- return errors.Errorf("media type %q is not allowed, found in descriptor with digest: %q", mediaType, desc.Digest)
+ return oras.SkipNode
}
mu.Lock()
@@ -486,7 +464,6 @@ func (c *Client) Pull(ref string, options ...PullOption) (*PullResult, error) {
return nil, err
}
- descriptors = append(descriptors, manifest)
descriptors = append(descriptors, layers...)
numDescriptors := len(descriptors)
@@ -694,19 +671,9 @@ func (c *Client) Push(data []byte, ref string, options ...PushOption) (*PushResu
})
ociAnnotations := generateOCIAnnotations(meta, operation.creationTime)
- manifest := ocispec.Manifest{
- Versioned: specs.Versioned{SchemaVersion: 2},
- Config: configDescriptor,
- Layers: layers,
- Annotations: ociAnnotations,
- }
- manifestData, err := json.Marshal(manifest)
- if err != nil {
- return nil, err
- }
-
- manifestDescriptor, err := oras.TagBytes(ctx, memoryStore, ocispec.MediaTypeImageManifest, manifestData, ref)
+ manifestDescriptor, err := c.tagManifest(ctx, memoryStore, configDescriptor,
+ layers, ociAnnotations, parsedRef)
if err != nil {
return nil, err
}
@@ -771,7 +738,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
@@ -855,7 +822,7 @@ func (c *Client) ValidateReference(ref, version string, u *url.URL) (*url.URL, e
version = registryReference.Tag
} else {
if registryReference.Tag != "" && registryReference.Tag != version {
- return nil, errors.Errorf("chart reference and version mismatch: %s is not %s", version, registryReference.Tag)
+ return nil, fmt.Errorf("chart reference and version mismatch: %s is not %s", version, registryReference.Tag)
}
}
@@ -874,7 +841,7 @@ func (c *Client) ValidateReference(ref, version string, u *url.URL) (*url.URL, e
return u, nil
}
if desc.Digest.String() != registryReference.Digest {
- return nil, errors.Errorf("chart reference digest mismatch: %s is not %s", 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
}
@@ -890,7 +857,7 @@ func (c *Client) ValidateReference(ref, version string, u *url.URL) (*url.URL, e
return nil, err
}
if len(tags) == 0 {
- return nil, errors.Errorf("Unable to locate any tags in provided repository: %s", ref)
+ return nil, fmt.Errorf("unable to locate any tags in provided repository: %s", ref)
}
// Determine if version provided
@@ -907,3 +874,24 @@ func (c *Client) ValidateReference(ref, version string, u *url.URL) (*url.URL, e
return u, err
}
+
+// tagManifest prepares and tags a manifest in memory storage
+func (c *Client) tagManifest(ctx context.Context, memoryStore *memory.Store,
+ configDescriptor ocispec.Descriptor, layers []ocispec.Descriptor,
+ ociAnnotations map[string]string, parsedRef reference) (ocispec.Descriptor, error) {
+
+ manifest := ocispec.Manifest{
+ Versioned: specs.Versioned{SchemaVersion: 2},
+ Config: configDescriptor,
+ Layers: layers,
+ Annotations: ociAnnotations,
+ }
+
+ manifestData, err := json.Marshal(manifest)
+ if err != nil {
+ return ocispec.Descriptor{}, err
+ }
+
+ return oras.TagBytes(ctx, memoryStore, ocispec.MediaTypeImageManifest,
+ manifestData, parsedRef.String())
+}
diff --git a/pkg/registry/client_test.go b/pkg/registry/client_test.go
index 4c5a78849..2ffd691c2 100644
--- a/pkg/registry/client_test.go
+++ b/pkg/registry/client_test.go
@@ -17,17 +17,37 @@ limitations under the License.
package registry
import (
+ "io"
"testing"
- "github.com/containerd/containerd/remotes"
- "github.com/stretchr/testify/assert"
+ ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/stretchr/testify/require"
+ "oras.land/oras-go/v2/content/memory"
)
-func TestNewClientResolverNotSupported(t *testing.T) {
- var r remotes.Resolver
+// Inspired by oras test
+// https://github.com/oras-project/oras-go/blob/05a2b09cbf2eab1df691411884dc4df741ec56ab/content_test.go#L1802
+func TestTagManifestTransformsReferences(t *testing.T) {
+ memStore := memory.New()
+ client := &Client{out: io.Discard}
+ ctx := t.Context()
- client, err := NewClient(ClientOptResolver(r))
- require.Equal(t, err, errDeprecatedRemote)
- assert.Nil(t, client)
+ refWithPlus := "test-registry.io/charts/test:1.0.0+metadata"
+ expectedRef := "test-registry.io/charts/test:1.0.0_metadata" // + becomes _
+
+ configDesc := ocispec.Descriptor{MediaType: ConfigMediaType, Digest: "sha256:config", Size: 100}
+ layers := []ocispec.Descriptor{{MediaType: ChartLayerMediaType, Digest: "sha256:layer", Size: 200}}
+
+ parsedRef, err := newReference(refWithPlus)
+ require.NoError(t, err)
+
+ desc, err := client.tagManifest(ctx, memStore, configDesc, layers, nil, parsedRef)
+ require.NoError(t, err)
+
+ transformedDesc, err := memStore.Resolve(ctx, expectedRef)
+ require.NoError(t, err, "Should find the reference with _ instead of +")
+ require.Equal(t, desc.Digest, transformedDesc.Digest)
+
+ _, err = memStore.Resolve(ctx, refWithPlus)
+ require.Error(t, err, "Should NOT find the reference with the original +")
}
diff --git a/pkg/registry/reference_test.go b/pkg/registry/reference_test.go
index 31317d18f..b6872cc37 100644
--- a/pkg/registry/reference_test.go
+++ b/pkg/registry/reference_test.go
@@ -19,6 +19,7 @@ package registry
import "testing"
func verify(t *testing.T, actual reference, registry, repository, tag, digest string) {
+ t.Helper()
if registry != actual.orasReference.Registry {
t.Errorf("Oras reference registry expected %v actual %v", registry, actual.Registry)
}
diff --git a/pkg/registry/transport.go b/pkg/registry/transport.go
new file mode 100644
index 000000000..7b9c6744b
--- /dev/null
+++ b/pkg/registry/transport.go
@@ -0,0 +1,175 @@
+/*
+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 (
+ "bytes"
+ "fmt"
+ "io"
+ "log/slog"
+ "mime"
+ "net/http"
+ "strings"
+ "sync/atomic"
+
+ "oras.land/oras-go/v2/registry/remote/retry"
+)
+
+var (
+ // requestCount records the number of logged request-response pairs and will
+ // be used as the unique id for the next pair.
+ requestCount uint64
+
+ // toScrub is a set of headers that should be scrubbed from the log.
+ toScrub = []string{
+ "Authorization",
+ "Set-Cookie",
+ }
+)
+
+// payloadSizeLimit limits the maximum size of the response body to be printed.
+const payloadSizeLimit int64 = 16 * 1024 // 16 KiB
+
+// LoggingTransport is an http.RoundTripper that keeps track of the in-flight
+// request and add hooks to report HTTP tracing events.
+type LoggingTransport struct {
+ http.RoundTripper
+}
+
+// NewTransport creates and returns a new instance of LoggingTransport
+func NewTransport(debug bool) *retry.Transport {
+ type cloner[T any] interface {
+ Clone() T
+ }
+
+ // 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 debug {
+ transport = &LoggingTransport{RoundTripper: transport}
+ }
+
+ return retry.NewTransport(transport)
+}
+
+// RoundTrip calls base round trip while keeping track of the current request.
+func (t *LoggingTransport) RoundTrip(req *http.Request) (resp *http.Response, err error) {
+ id := atomic.AddUint64(&requestCount, 1) - 1
+
+ slog.Debug("Request", "id", id, "url", req.URL, "method", req.Method, "header", logHeader(req.Header))
+ resp, err = t.RoundTripper.RoundTrip(req)
+ if err != nil {
+ slog.Debug("Response", "id", id, "error", err)
+ } else if resp != nil {
+ slog.Debug("Response", "id", id, "status", resp.Status, "header", logHeader(resp.Header), "body", logResponseBody(resp))
+ } else {
+ slog.Debug("Response", "id", id, "response", "nil")
+ }
+
+ return resp, err
+}
+
+// logHeader prints out the provided header keys and values, with auth header scrubbed.
+func logHeader(header http.Header) string {
+ if len(header) > 0 {
+ headers := []string{}
+ for k, v := range header {
+ for _, h := range toScrub {
+ if strings.EqualFold(k, h) {
+ v = []string{"*****"}
+ }
+ }
+ headers = append(headers, fmt.Sprintf(" %q: %q", k, strings.Join(v, ", ")))
+ }
+ return strings.Join(headers, "\n")
+ }
+ return " Empty header"
+}
+
+// logResponseBody prints out the response body if it is printable and within size limit.
+func logResponseBody(resp *http.Response) string {
+ if resp.Body == nil || resp.Body == http.NoBody {
+ return " No response body to print"
+ }
+
+ // non-applicable body is not printed and remains untouched for subsequent processing
+ contentType := resp.Header.Get("Content-Type")
+ if contentType == "" {
+ return " Response body without a content type is not printed"
+ }
+ if !isPrintableContentType(contentType) {
+ return fmt.Sprintf(" Response body of content type %q is not printed", contentType)
+ }
+
+ buf := bytes.NewBuffer(nil)
+ body := resp.Body
+ // restore the body by concatenating the read body with the remaining body
+ resp.Body = struct {
+ io.Reader
+ io.Closer
+ }{
+ Reader: io.MultiReader(buf, body),
+ Closer: body,
+ }
+ // read the body up to limit+1 to check if the body exceeds the limit
+ if _, err := io.CopyN(buf, body, payloadSizeLimit+1); err != nil && err != io.EOF {
+ return fmt.Sprintf(" Error reading response body: %v", err)
+ }
+
+ readBody := buf.String()
+ if len(readBody) == 0 {
+ return " Response body is empty"
+ }
+ if containsCredentials(readBody) {
+ return " Response body redacted due to potential credentials"
+ }
+ if len(readBody) > int(payloadSizeLimit) {
+ return readBody[:payloadSizeLimit] + "\n...(truncated)"
+ }
+ return readBody
+}
+
+// isPrintableContentType returns true if the contentType is printable.
+func isPrintableContentType(contentType string) bool {
+ mediaType, _, err := mime.ParseMediaType(contentType)
+ if err != nil {
+ return false
+ }
+
+ switch mediaType {
+ case "application/json", // JSON types
+ "text/plain", "text/html": // text types
+ return true
+ }
+ return strings.HasSuffix(mediaType, "+json")
+}
+
+// containsCredentials returns true if the body contains potential credentials.
+func containsCredentials(body string) bool {
+ return strings.Contains(body, `"token"`) || strings.Contains(body, `"access_token"`)
+}
diff --git a/pkg/registry/transport_test.go b/pkg/registry/transport_test.go
new file mode 100644
index 000000000..b4990c526
--- /dev/null
+++ b/pkg/registry/transport_test.go
@@ -0,0 +1,399 @@
+/*
+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 (
+ "bytes"
+ "errors"
+ "io"
+ "net/http"
+ "testing"
+)
+
+var errMockRead = errors.New("mock read error")
+
+type errorReader struct{}
+
+func (e *errorReader) Read(_ []byte) (n int, err error) {
+ return 0, errMockRead
+}
+
+func Test_isPrintableContentType(t *testing.T) {
+ tests := []struct {
+ name string
+ contentType string
+ want bool
+ }{
+ {
+ name: "Empty content type",
+ contentType: "",
+ want: false,
+ },
+ {
+ name: "General JSON type",
+ contentType: "application/json",
+ want: true,
+ },
+ {
+ name: "General JSON type with charset",
+ contentType: "application/json; charset=utf-8",
+ want: true,
+ },
+ {
+ name: "Random type with application/json prefix",
+ contentType: "application/jsonwhatever",
+ want: false,
+ },
+ {
+ name: "Manifest type in JSON",
+ contentType: "application/vnd.oci.image.manifest.v1+json",
+ want: true,
+ },
+ {
+ name: "Manifest type in JSON with charset",
+ contentType: "application/vnd.oci.image.manifest.v1+json; charset=utf-8",
+ want: true,
+ },
+ {
+ name: "Random content type in JSON",
+ contentType: "application/whatever+json",
+ want: true,
+ },
+ {
+ name: "Plain text type",
+ contentType: "text/plain",
+ want: true,
+ },
+ {
+ name: "Plain text type with charset",
+ contentType: "text/plain; charset=utf-8",
+ want: true,
+ },
+ {
+ name: "Random type with text/plain prefix",
+ contentType: "text/plainnnnn",
+ want: false,
+ },
+ {
+ name: "HTML type",
+ contentType: "text/html",
+ want: true,
+ },
+ {
+ name: "Plain text type with charset",
+ contentType: "text/html; charset=utf-8",
+ want: true,
+ },
+ {
+ name: "Random type with text/html prefix",
+ contentType: "text/htmlllll",
+ want: false,
+ },
+ {
+ name: "Binary type",
+ contentType: "application/octet-stream",
+ want: false,
+ },
+ {
+ name: "Unknown type",
+ contentType: "unknown/unknown",
+ want: false,
+ },
+ {
+ name: "Invalid type",
+ contentType: "text/",
+ want: false,
+ },
+ {
+ name: "Random string",
+ contentType: "random123!@#",
+ want: false,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ if got := isPrintableContentType(tt.contentType); got != tt.want {
+ t.Errorf("isPrintableContentType() = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
+
+func Test_logResponseBody(t *testing.T) {
+ tests := []struct {
+ name string
+ resp *http.Response
+ want string
+ wantData []byte
+ }{
+ {
+ name: "Nil body",
+ resp: &http.Response{
+ Body: nil,
+ Header: http.Header{"Content-Type": []string{"application/json"}},
+ },
+ want: " No response body to print",
+ },
+ {
+ name: "No body",
+ wantData: nil,
+ resp: &http.Response{
+ Body: http.NoBody,
+ ContentLength: 100, // in case of HEAD response, the content length is set but the body is empty
+ Header: http.Header{"Content-Type": []string{"application/json"}},
+ },
+ want: " No response body to print",
+ },
+ {
+ name: "Empty body",
+ wantData: []byte(""),
+ resp: &http.Response{
+ Body: io.NopCloser(bytes.NewReader([]byte(""))),
+ ContentLength: 0,
+ Header: http.Header{"Content-Type": []string{"text/plain"}},
+ },
+ want: " Response body is empty",
+ },
+ {
+ name: "Unknown content length",
+ wantData: []byte("whatever"),
+ resp: &http.Response{
+ Body: io.NopCloser(bytes.NewReader([]byte("whatever"))),
+ ContentLength: -1,
+ Header: http.Header{"Content-Type": []string{"text/plain"}},
+ },
+ want: "whatever",
+ },
+ {
+ name: "Missing content type header",
+ wantData: []byte("whatever"),
+ resp: &http.Response{
+ Body: io.NopCloser(bytes.NewReader([]byte("whatever"))),
+ ContentLength: 8,
+ },
+ want: " Response body without a content type is not printed",
+ },
+ {
+ name: "Empty content type header",
+ wantData: []byte("whatever"),
+ resp: &http.Response{
+ Body: io.NopCloser(bytes.NewReader([]byte("whatever"))),
+ ContentLength: 8,
+ Header: http.Header{"Content-Type": []string{""}},
+ },
+ want: " Response body without a content type is not printed",
+ },
+ {
+ name: "Non-printable content type",
+ wantData: []byte("binary data"),
+ resp: &http.Response{
+ Body: io.NopCloser(bytes.NewReader([]byte("binary data"))),
+ ContentLength: 11,
+ Header: http.Header{"Content-Type": []string{"application/octet-stream"}},
+ },
+ want: " Response body of content type \"application/octet-stream\" is not printed",
+ },
+ {
+ name: "Body at the limit",
+ wantData: bytes.Repeat([]byte("a"), int(payloadSizeLimit)),
+ resp: &http.Response{
+ Body: io.NopCloser(bytes.NewReader(bytes.Repeat([]byte("a"), int(payloadSizeLimit)))),
+ ContentLength: payloadSizeLimit,
+ Header: http.Header{"Content-Type": []string{"text/plain"}},
+ },
+ want: string(bytes.Repeat([]byte("a"), int(payloadSizeLimit))),
+ },
+ {
+ name: "Body larger than limit",
+ wantData: bytes.Repeat([]byte("a"), int(payloadSizeLimit+1)),
+ resp: &http.Response{
+ Body: io.NopCloser(bytes.NewReader(bytes.Repeat([]byte("a"), int(payloadSizeLimit+1)))), // 1 byte larger than limit
+ ContentLength: payloadSizeLimit + 1,
+ Header: http.Header{"Content-Type": []string{"text/plain"}},
+ },
+ want: string(bytes.Repeat([]byte("a"), int(payloadSizeLimit))) + "\n...(truncated)",
+ },
+ {
+ name: "Printable content type within limit",
+ wantData: []byte("data"),
+ resp: &http.Response{
+ Body: io.NopCloser(bytes.NewReader([]byte("data"))),
+ ContentLength: 4,
+ Header: http.Header{"Content-Type": []string{"text/plain"}},
+ },
+ want: "data",
+ },
+ {
+ name: "Actual body size is larger than content length",
+ wantData: []byte("data"),
+ resp: &http.Response{
+ Body: io.NopCloser(bytes.NewReader([]byte("data"))),
+ ContentLength: 3, // mismatched content length
+ Header: http.Header{"Content-Type": []string{"text/plain"}},
+ },
+ want: "data",
+ },
+ {
+ name: "Actual body size is larger than content length and exceeds limit",
+ wantData: bytes.Repeat([]byte("a"), int(payloadSizeLimit+1)),
+ resp: &http.Response{
+ Body: io.NopCloser(bytes.NewReader(bytes.Repeat([]byte("a"), int(payloadSizeLimit+1)))), // 1 byte larger than limit
+ ContentLength: 1, // mismatched content length
+ Header: http.Header{"Content-Type": []string{"text/plain"}},
+ },
+ want: string(bytes.Repeat([]byte("a"), int(payloadSizeLimit))) + "\n...(truncated)",
+ },
+ {
+ name: "Actual body size is smaller than content length",
+ wantData: []byte("data"),
+ resp: &http.Response{
+ Body: io.NopCloser(bytes.NewReader([]byte("data"))),
+ ContentLength: 5, // mismatched content length
+ Header: http.Header{"Content-Type": []string{"text/plain"}},
+ },
+ want: "data",
+ },
+ {
+ name: "Body contains token",
+ resp: &http.Response{
+ Body: io.NopCloser(bytes.NewReader([]byte(`{"token":"12345"}`))),
+ ContentLength: 17,
+ Header: http.Header{"Content-Type": []string{"application/json"}},
+ },
+ wantData: []byte(`{"token":"12345"}`),
+ want: " Response body redacted due to potential credentials",
+ },
+ {
+ name: "Body contains access_token",
+ resp: &http.Response{
+ Body: io.NopCloser(bytes.NewReader([]byte(`{"access_token":"12345"}`))),
+ ContentLength: 17,
+ Header: http.Header{"Content-Type": []string{"application/json"}},
+ },
+ wantData: []byte(`{"access_token":"12345"}`),
+ want: " Response body redacted due to potential credentials",
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ if got := logResponseBody(tt.resp); got != tt.want {
+ t.Errorf("logResponseBody() = %v, want %v", got, tt.want)
+ }
+ // validate the response body
+ if tt.resp.Body != nil {
+ readBytes, err := io.ReadAll(tt.resp.Body)
+ if err != nil {
+ t.Errorf("failed to read body after logResponseBody(), err= %v", err)
+ }
+ if !bytes.Equal(readBytes, tt.wantData) {
+ t.Errorf("resp.Body after logResponseBody() = %v, want %v", readBytes, tt.wantData)
+ }
+ if closeErr := tt.resp.Body.Close(); closeErr != nil {
+ t.Errorf("failed to close body after logResponseBody(), err= %v", closeErr)
+ }
+ }
+ })
+ }
+}
+
+func Test_logResponseBody_error(t *testing.T) {
+ tests := []struct {
+ name string
+ resp *http.Response
+ want string
+ }{
+ {
+ name: "Error reading body",
+ resp: &http.Response{
+ Body: io.NopCloser(&errorReader{}),
+ ContentLength: 10,
+ Header: http.Header{"Content-Type": []string{"text/plain"}},
+ },
+ want: " Error reading response body: mock read error",
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ if got := logResponseBody(tt.resp); got != tt.want {
+ t.Errorf("logResponseBody() = %v, want %v", got, tt.want)
+ }
+ if closeErr := tt.resp.Body.Close(); closeErr != nil {
+ t.Errorf("failed to close body after logResponseBody(), err= %v", closeErr)
+ }
+ })
+ }
+}
+
+func Test_containsCredentials(t *testing.T) {
+ tests := []struct {
+ name string
+ body string
+ want bool
+ }{
+ {
+ name: "Contains token keyword",
+ body: `{"token": "12345"}`,
+ want: true,
+ },
+ {
+ name: "Contains quoted token keyword",
+ body: `whatever "token" blah`,
+ want: true,
+ },
+ {
+ name: "Contains unquoted token keyword",
+ body: `whatever token blah`,
+ want: false,
+ },
+ {
+ name: "Contains access_token keyword",
+ body: `{"access_token": "12345"}`,
+ want: true,
+ },
+ {
+ name: "Contains quoted access_token keyword",
+ body: `whatever "access_token" blah`,
+ want: true,
+ },
+ {
+ name: "Contains unquoted access_token keyword",
+ body: `whatever access_token blah`,
+ want: false,
+ },
+ {
+ name: "Does not contain credentials",
+ body: `{"key": "value"}`,
+ want: false,
+ },
+ {
+ name: "Empty body",
+ body: ``,
+ want: false,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ if got := containsCredentials(tt.body); got != tt.want {
+ t.Errorf("containsCredentials() = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
diff --git a/pkg/registry/util.go b/pkg/registry/util.go
index 8c0106fb9..b31ab63fe 100644
--- a/pkg/registry/util.go
+++ b/pkg/registry/util.go
@@ -21,17 +21,17 @@ import (
"fmt"
"io"
"net/http"
+ "slices"
"strings"
"time"
"helm.sh/helm/v4/internal/tlsutil"
- "helm.sh/helm/v4/pkg/chart"
- "helm.sh/helm/v4/pkg/chart/loader"
+ 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/pkg/errors"
)
var immutableOciAnnotations = []string{
@@ -46,12 +46,7 @@ func IsOCI(url string) bool {
// ContainsTag determines whether a tag is found in a provided list of tags
func ContainsTag(tags []string, tag string) bool {
- for _, t := range tags {
- if tag == t {
- return true
- }
- }
- return false
+ return slices.Contains(tags, tag)
}
func GetTagMatchingVersionOrConstraint(tags []string, versionString string) (string, error) {
@@ -87,7 +82,7 @@ func GetTagMatchingVersionOrConstraint(tags []string, versionString string) (str
}
}
- return "", errors.Errorf("Could not locate a version matching provided version string %s", versionString)
+ return "", fmt.Errorf("could not locate a version matching provided version string %s", versionString)
}
// extractChartMeta is used to extract a chart metadata from a byte array
diff --git a/pkg/registry/util_test.go b/pkg/registry/util_test.go
index 93a11a998..c8ce4e4a4 100644
--- a/pkg/registry/util_test.go
+++ b/pkg/registry/util_test.go
@@ -23,7 +23,7 @@ import (
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
- "helm.sh/helm/v4/pkg/chart"
+ chart "helm.sh/helm/v4/pkg/chart/v2"
helmtime "helm.sh/helm/v4/pkg/time"
)
diff --git a/pkg/registry/utils_test.go b/pkg/registry/utils_test.go
index 8e6943222..e8fcba4e3 100644
--- a/pkg/registry/utils_test.go
+++ b/pkg/registry/utils_test.go
@@ -141,7 +141,7 @@ func setup(suite *TestSuite, tlsEnabled, insecure bool) *registry.Registry {
suite.Nil(err, "no error creating mock DNS server")
suite.srv.PatchNet(net.DefaultResolver)
- 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{}{}}
@@ -182,11 +182,9 @@ func initCompromisedRegistryTestServer() string {
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if strings.Contains(r.URL.Path, "manifests") {
w.Header().Set("Content-Type", "application/vnd.oci.image.manifest.v1+json")
- w.WriteHeader(200)
+ w.WriteHeader(http.StatusOK)
- // 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
@@ -198,19 +196,19 @@ 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)
+ w.WriteHeader(http.StatusOK)
w.Write([]byte("{\"name\":\"mychart\",\"version\":\"0.1.0\",\"description\":\"A Helm chart for Kubernetes\\n" +
"an 'application' or a 'library' chart.\",\"apiVersion\":\"v2\",\"appVersion\":\"1.16.0\",\"type\":" +
"\"application\"}"))
} else if r.URL.Path == "/v2/testrepo/supposedlysafechart/blobs/sha256:ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb" {
w.Header().Set("Content-Type", ChartLayerMediaType)
- w.WriteHeader(200)
+ w.WriteHeader(http.StatusOK)
w.Write([]byte("b"))
} else {
- w.WriteHeader(500)
+ w.WriteHeader(http.StatusInternalServerError)
}
}))
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 d600d43e8..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/v4/pkg/releaseutil"
+package util // import "helm.sh/helm/v4/pkg/release/util"
-import rspb "helm.sh/helm/v4/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 9fc5ce9b1..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/v4/pkg/releaseutil"
+package util // import "helm.sh/helm/v4/pkg/release/util"
import (
"testing"
- rspb "helm.sh/helm/v4/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 ec51d50d8..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/v4/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 f7745d64d..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/v4/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 96%
rename from pkg/releaseutil/manifest_sorter.go
rename to pkg/release/util/manifest_sorter.go
index 2d9a14bf1..be93ad1ed 100644
--- a/pkg/releaseutil/manifest_sorter.go
+++ b/pkg/release/util/manifest_sorter.go
@@ -14,20 +14,20 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-package releaseutil
+package util
import (
- "log"
+ "fmt"
+ "log/slog"
"path"
"sort"
"strconv"
"strings"
- "github.com/pkg/errors"
"sigs.k8s.io/yaml"
- chartutil "helm.sh/helm/v4/pkg/chart/util"
- "helm.sh/helm/v4/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.
@@ -149,7 +149,7 @@ func (file *manifestFile) sort(result *result) error {
var entry SimpleHead
if err := yaml.Unmarshal([]byte(m), &entry); err != nil {
- return errors.Wrapf(err, "YAML parse error on %s", file.path)
+ return fmt.Errorf("YAML parse error on %s: %w", file.path, err)
}
if !hasAnyAnnotation(entry) {
@@ -196,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
}
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 3bd196c12..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/v4/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 8e05b76dc..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/v4/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 a2135d68f..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/v4/pkg/releaseutil"
+package util // import "helm.sh/helm/v4/pkg/release/util"
import (
"sort"
- rspb "helm.sh/helm/v4/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 96%
rename from pkg/releaseutil/sorter_test.go
rename to pkg/release/util/sorter_test.go
index bef261ee8..7ca540441 100644
--- a/pkg/releaseutil/sorter_test.go
+++ b/pkg/release/util/sorter_test.go
@@ -14,13 +14,13 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-package releaseutil // import "helm.sh/helm/v4/pkg/releaseutil"
+package util // import "helm.sh/helm/v4/pkg/release/util"
import (
"testing"
"time"
- rspb "helm.sh/helm/v4/pkg/release"
+ rspb "helm.sh/helm/v4/pkg/release/v1"
helmtime "helm.sh/helm/v4/pkg/time"
)
@@ -43,6 +43,7 @@ func tsRelease(name string, vers int, dur time.Duration, status rspb.Status) *rs
}
func check(t *testing.T, by string, fn func(int, int) bool) {
+ t.Helper()
for i := len(releases) - 1; i > 0; i-- {
if fn(i, i-1) {
t.Errorf("release at positions '(%d,%d)' not sorted by %s", i-1, i, by)
diff --git a/pkg/release/hook.go b/pkg/release/v1/hook.go
similarity index 99%
rename from pkg/release/hook.go
rename to pkg/release/v1/hook.go
index 5ff61fdaa..1ef5c1eb8 100644
--- a/pkg/release/hook.go
+++ b/pkg/release/v1/hook.go
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-package release
+package v1
import (
"helm.sh/helm/v4/pkg/time"
diff --git a/pkg/release/info.go b/pkg/release/v1/info.go
similarity index 98%
rename from pkg/release/info.go
rename to pkg/release/v1/info.go
index 514807a35..ff98ab63e 100644
--- a/pkg/release/info.go
+++ b/pkg/release/v1/info.go
@@ -13,7 +13,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-package release
+package v1
import (
"k8s.io/apimachinery/pkg/runtime"
diff --git a/pkg/release/mock.go b/pkg/release/v1/mock.go
similarity index 98%
rename from pkg/release/mock.go
rename to pkg/release/v1/mock.go
index ab21d3cf2..9ca57284c 100644
--- a/pkg/release/mock.go
+++ b/pkg/release/v1/mock.go
@@ -14,13 +14,13 @@ 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/v4/pkg/chart"
+ chart "helm.sh/helm/v4/pkg/chart/v2"
"helm.sh/helm/v4/pkg/time"
)
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 2a4963d9b..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/v4/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 3629bd24b..e41226fa4 100644
--- a/pkg/repo/chartrepo.go
+++ b/pkg/repo/chartrepo.go
@@ -22,19 +22,14 @@ import (
"encoding/json"
"fmt"
"io"
- "log"
+ "log/slog"
"net/url"
"os"
"path/filepath"
"strings"
- "github.com/pkg/errors"
- "sigs.k8s.io/yaml"
-
- "helm.sh/helm/v4/pkg/chart/loader"
"helm.sh/helm/v4/pkg/getter"
"helm.sh/helm/v4/pkg/helmpath"
- "helm.sh/helm/v4/pkg/provenance"
)
// Entry represents a collection of parameters for chart repository
@@ -52,23 +47,22 @@ 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
func NewChartRepository(cfg *Entry, getters getter.Providers) (*ChartRepository, error) {
u, err := url.Parse(cfg.URL)
if err != nil {
- return nil, errors.Errorf("invalid chart URL format: %s", cfg.URL)
+ return nil, fmt.Errorf("invalid chart URL format: %s", cfg.URL)
}
client, err := getters.ByScheme(u.Scheme)
if err != nil {
- return nil, errors.Errorf("could not find protocol handler for: %s", u.Scheme)
+ return nil, fmt.Errorf("could not find protocol handler for: %s", u.Scheme)
}
return &ChartRepository{
@@ -79,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 errors.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")
@@ -156,46 +116,6 @@ 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()
-}
-
-func (r *ChartRepository) saveIndexFile() error {
- index, err := yaml.Marshal(r.IndexFile)
- if err != nil {
- return err
- }
- 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 errors.Wrapf(err, "failed adding to %s to index", path)
- }
- }
- // TODO: If a chart exists, but has a different Digest, should we error?
- }
- r.IndexFile.SortEntries()
- return nil
-}
-
type findChartInRepoURLOptions struct {
Username string
Password string
@@ -278,7 +198,7 @@ func FindChartInRepoURL(repoURL string, chartName string, getters getter.Provide
}
idx, err := r.DownloadIndexFile()
if err != nil {
- return "", errors.Wrapf(err, "looks like %q is not a valid chart repository or cannot be reached", repoURL)
+ return "", fmt.Errorf("looks like %q is not a valid chart repository or cannot be reached: %w", repoURL, err)
}
defer func() {
os.RemoveAll(filepath.Join(r.CachePath, helmpath.CacheChartsFile(r.Config.Name)))
@@ -297,18 +217,21 @@ func FindChartInRepoURL(repoURL string, chartName string, getters getter.Provide
}
cv, err := repoIndex.Get(chartName, opts.ChartVersion)
if err != nil {
- return "", errors.Errorf("%s not found in %s repository", errMsg, repoURL)
+ return "", ChartNotFoundError{
+ Chart: errMsg,
+ RepoURL: repoURL,
+ }
}
if len(cv.URLs) == 0 {
- return "", errors.Errorf("%s has no downloadable URLs", errMsg)
+ return "", fmt.Errorf("%s has no downloadable URLs", errMsg)
}
chartURL := cv.URLs[0]
absoluteChartURL, err := ResolveReferenceURL(repoURL, chartURL)
if err != nil {
- return "", errors.Wrap(err, "failed to make chart URL absolute")
+ return "", fmt.Errorf("failed to make chart URL absolute: %w", err)
}
return absoluteChartURL, nil
@@ -319,7 +242,7 @@ func FindChartInRepoURL(repoURL string, chartName string, getters getter.Provide
func ResolveReferenceURL(baseURL, refURL string) (string, error) {
parsedRefURL, err := url.Parse(refURL)
if err != nil {
- return "", errors.Wrapf(err, "failed to parse %s as URL", refURL)
+ return "", fmt.Errorf("failed to parse %s as URL: %w", refURL, err)
}
if parsedRefURL.IsAbs() {
@@ -328,7 +251,7 @@ func ResolveReferenceURL(baseURL, refURL string) (string, error) {
parsedBaseURL, err := url.Parse(baseURL)
if err != nil {
- return "", errors.Wrapf(err, "failed to parse %s as URL", baseURL)
+ return "", fmt.Errorf("failed to parse %s as URL: %w", baseURL, err)
}
// We need a trailing slash for ResolveReference to work, but make sure there isn't already one
@@ -343,7 +266,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 e3330b8eb..05e034dd8 100644
--- a/pkg/repo/chartrepo_test.go
+++ b/pkg/repo/chartrepo_test.go
@@ -18,11 +18,10 @@ package repo
import (
"bytes"
+ "errors"
"net/http"
"net/http/httptest"
"os"
- "path/filepath"
- "reflect"
"runtime"
"strings"
"testing"
@@ -30,87 +29,10 @@ import (
"sigs.k8s.io/yaml"
- "helm.sh/helm/v4/pkg/chart"
"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
}
@@ -148,7 +70,7 @@ func TestIndexCustomSchemeDownload(t *testing.T) {
}
repo.CachePath = t.TempDir()
- tempIndexFile, err := os.CreateTemp("", "test-repo")
+ tempIndexFile, err := os.CreateTemp(t.TempDir(), "test-repo")
if err != nil {
t.Fatalf("Failed to create temp index file: %v", err)
}
@@ -169,97 +91,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 {
@@ -372,6 +203,9 @@ func TestErrorFindChartInRepoURL(t *testing.T) {
} 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 !errors.Is(err, ChartNotFoundError{}) {
+ t.Errorf("error is not of correct error type structure")
+ }
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")
@@ -390,11 +224,15 @@ func TestResolveReferenceURL(t *testing.T) {
for _, tt := range []struct {
baseURL, refURL, chartURL string
}{
+ {"http://localhost:8123/", "/nginx-0.2.0.tgz", "http://localhost:8123/nginx-0.2.0.tgz"},
{"http://localhost:8123/charts/", "nginx-0.2.0.tgz", "http://localhost:8123/charts/nginx-0.2.0.tgz"},
+ {"http://localhost:8123/charts/", "/nginx-0.2.0.tgz", "http://localhost:8123/nginx-0.2.0.tgz"},
{"http://localhost:8123/charts-with-no-trailing-slash", "nginx-0.2.0.tgz", "http://localhost:8123/charts-with-no-trailing-slash/nginx-0.2.0.tgz"},
{"http://localhost:8123", "https://charts.helm.sh/stable/nginx-0.2.0.tgz", "https://charts.helm.sh/stable/nginx-0.2.0.tgz"},
{"http://localhost:8123/charts%2fwith%2fescaped%2fslash", "nginx-0.2.0.tgz", "http://localhost:8123/charts%2fwith%2fescaped%2fslash/nginx-0.2.0.tgz"},
+ {"http://localhost:8123/charts%2fwith%2fescaped%2fslash", "/nginx-0.2.0.tgz", "http://localhost:8123/nginx-0.2.0.tgz"},
{"http://localhost:8123/charts?with=queryparameter", "nginx-0.2.0.tgz", "http://localhost:8123/charts/nginx-0.2.0.tgz?with=queryparameter"},
+ {"http://localhost:8123/charts?with=queryparameter", "/nginx-0.2.0.tgz", "http://localhost:8123/nginx-0.2.0.tgz?with=queryparameter"},
} {
chartURL, err := ResolveReferenceURL(tt.baseURL, tt.refURL)
if err != nil {
diff --git a/cmd/helm/repo_list_test.go b/pkg/repo/error.go
similarity index 65%
rename from cmd/helm/repo_list_test.go
rename to pkg/repo/error.go
index 90149ebda..16264ed26 100644
--- a/cmd/helm/repo_list_test.go
+++ b/pkg/repo/error.go
@@ -14,16 +14,22 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-package main
+package repo
import (
- "testing"
+ "fmt"
)
-func TestRepoListOutputCompletion(t *testing.T) {
- outputFlagCompletionTest(t, "repo list")
+type ChartNotFoundError struct {
+ RepoURL string
+ Chart string
}
-func TestRepoListFileCompletion(t *testing.T) {
- checkFileCompletion(t, "repo list", false)
+func (e ChartNotFoundError) Error() string {
+ return fmt.Sprintf("%s not found in %s repository", e.Chart, e.RepoURL)
+}
+
+func (e ChartNotFoundError) Is(err error) bool {
+ _, ok := err.(ChartNotFoundError)
+ return ok
}
diff --git a/pkg/repo/index.go b/pkg/repo/index.go
index 2526cba1b..c26d7581c 100644
--- a/pkg/repo/index.go
+++ b/pkg/repo/index.go
@@ -19,7 +19,9 @@ package repo
import (
"bytes"
"encoding/json"
- "log"
+ "errors"
+ "fmt"
+ "log/slog"
"os"
"path"
"path/filepath"
@@ -28,18 +30,15 @@ import (
"time"
"github.com/Masterminds/semver/v3"
- "github.com/pkg/errors"
"sigs.k8s.io/yaml"
"helm.sh/helm/v4/internal/fileutil"
"helm.sh/helm/v4/internal/urlutil"
- "helm.sh/helm/v4/pkg/chart"
- "helm.sh/helm/v4/pkg/chart/loader"
+ 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"
@@ -110,7 +109,7 @@ func LoadIndexFile(path string) (*IndexFile, error) {
}
i, err := loadIndex(b, path)
if err != nil {
- return nil, errors.Wrapf(err, "error loading %s", path)
+ return nil, fmt.Errorf("error loading %s: %w", path, err)
}
return i, nil
}
@@ -126,7 +125,7 @@ func (i IndexFile) MustAdd(md *chart.Metadata, filename, baseURL, digest string)
md.APIVersion = chart.APIVersionV1
}
if err := md.Validate(); err != nil {
- return errors.Wrapf(err, "validate failed for %s", filename)
+ return fmt.Errorf("validate failed for %s: %w", filename, err)
}
u := filename
@@ -154,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)
}
}
@@ -219,7 +218,7 @@ func (i IndexFile) Get(name, version string) (*ChartVersion, error) {
return ver, nil
}
}
- return nil, errors.Errorf("no chart version found for %s-%s", name, version)
+ return nil, fmt.Errorf("no chart version found for %s-%s", name, version)
}
// WriteFile writes an index file to the given destination path.
@@ -332,7 +331,7 @@ func IndexDirectory(dir, baseURL string) (*IndexFile, error) {
return index, err
}
if err := index.MustAdd(c.Metadata, fname, parentURL, hash); err != nil {
- return index, errors.Wrapf(err, "failed adding to %s to index", fname)
+ return index, fmt.Errorf("failed adding to %s to index: %w", fname, err)
}
}
return index, nil
@@ -356,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
@@ -367,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 8c22bdc3e..d40719b12 100644
--- a/pkg/repo/index_test.go
+++ b/pkg/repo/index_test.go
@@ -28,7 +28,7 @@ import (
"strings"
"testing"
- "helm.sh/helm/v4/pkg/chart"
+ 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"
@@ -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")
}
}
@@ -352,6 +352,7 @@ func TestDownloadIndexFile(t *testing.T) {
}
func verifyLocalIndex(t *testing.T, i *IndexFile) {
+ t.Helper()
numEntries := len(i.Entries)
if numEntries != 3 {
t.Errorf("Expected 3 entries in index file but got %d", numEntries)
@@ -450,6 +451,7 @@ func verifyLocalIndex(t *testing.T, i *IndexFile) {
}
func verifyLocalChartsFile(t *testing.T, chartsContent []byte, indexContent *IndexFile) {
+ t.Helper()
var expected, reald []string
for chart := range indexContent.Entries {
expected = append(expected, chart)
diff --git a/pkg/repo/repo.go b/pkg/repo/repo.go
index 203dfb4de..48c0e0193 100644
--- a/pkg/repo/repo.go
+++ b/pkg/repo/repo.go
@@ -17,11 +17,11 @@ limitations under the License.
package repo // import "helm.sh/helm/v4/pkg/repo"
import (
+ "fmt"
"os"
"path/filepath"
"time"
- "github.com/pkg/errors"
"sigs.k8s.io/yaml"
)
@@ -48,7 +48,7 @@ func LoadFile(path string) (*File, error) {
r := new(File)
b, err := os.ReadFile(path)
if err != nil {
- return r, errors.Wrapf(err, "couldn't load repositories file (%s)", path)
+ return r, fmt.Errorf("couldn't load repositories file (%s): %w", path, err)
}
err = yaml.Unmarshal(b, r)
diff --git a/pkg/repo/repo_test.go b/pkg/repo/repo_test.go
index c2087ebbe..bdaa61eda 100644
--- a/pkg/repo/repo_test.go
+++ b/pkg/repo/repo_test.go
@@ -197,7 +197,7 @@ func TestWriteFile(t *testing.T) {
},
)
- file, err := os.CreateTemp("", "helm-repo")
+ file, err := os.CreateTemp(t.TempDir(), "helm-repo")
if err != nil {
t.Errorf("failed to create test-file (%v)", err)
}
diff --git a/pkg/repo/repotest/server.go b/pkg/repo/repotest/server.go
index 7b004dbc3..ea9d5290c 100644
--- a/pkg/repo/repotest/server.go
+++ b/pkg/repo/repotest/server.go
@@ -16,7 +16,6 @@ limitations under the License.
package repotest
import (
- "context"
"crypto/tls"
"fmt"
"net/http"
@@ -34,14 +33,15 @@ import (
"golang.org/x/crypto/bcrypt"
"sigs.k8s.io/yaml"
- "helm.sh/helm/v4/pkg/chart"
- "helm.sh/helm/v4/pkg/chart/loader"
- chartutil "helm.sh/helm/v4/pkg/chart/util"
+ 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"
)
func BasicAuthMiddleware(t *testing.T) http.HandlerFunc {
+ t.Helper()
return http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) {
username, password, ok := r.BasicAuth()
if !ok || username != "username" || password != "password" {
@@ -89,11 +89,8 @@ type Server struct {
//
// The temp dir will be removed by testing package automatically when test finished.
func NewTempServer(t *testing.T, options ...ServerOption) *Server {
-
- docrootTempDir, err := os.MkdirTemp("", "helm-repotest-")
- if err != nil {
- t.Fatal(err)
- }
+ t.Helper()
+ docrootTempDir := t.TempDir()
srv := newServer(t, docrootTempDir, options...)
@@ -110,6 +107,7 @@ func NewTempServer(t *testing.T, options ...ServerOption) *Server {
// Create the server, but don't yet start it
func newServer(t *testing.T, docroot string, options ...ServerOption) *Server {
+ t.Helper()
absdocroot, err := filepath.Abs(docroot)
if err != nil {
t.Fatal(err)
@@ -162,6 +160,7 @@ func WithDependingChart(c *chart.Chart) OCIServerOpt {
}
func NewOCIServer(t *testing.T, dir string) (*OCIServer, error) {
+ t.Helper()
testHtpasswdFileBasename := "authtest.htpasswd"
testUsername, testPassword := "username", "password"
@@ -170,7 +169,7 @@ func NewOCIServer(t *testing.T, dir string) (*OCIServer, error) {
t.Fatal("error generating bcrypt password for test htpasswd file")
}
htpasswdPath := filepath.Join(dir, testHtpasswdFileBasename)
- err = os.WriteFile(htpasswdPath, []byte(fmt.Sprintf("%s:%s\n", testUsername, string(pwBytes))), 0644)
+ err = os.WriteFile(htpasswdPath, []byte(fmt.Sprintf("%s:%s\n", testUsername, string(pwBytes))), 0o644)
if err != nil {
t.Fatalf("error creating test htpasswd file")
}
@@ -194,7 +193,7 @@ func NewOCIServer(t *testing.T, dir string) (*OCIServer, error) {
registryURL := fmt.Sprintf("localhost:%d", port)
- r, err := registry.NewRegistry(context.Background(), config)
+ r, err := registry.NewRegistry(t.Context(), config)
if err != nil {
t.Fatal(err)
}
@@ -209,6 +208,7 @@ func NewOCIServer(t *testing.T, dir string) (*OCIServer, error) {
}
func (srv *OCIServer) Run(t *testing.T, opts ...OCIServerOpt) {
+ t.Helper()
cfg := &OCIServerRunConfig{}
for _, fn := range opts {
fn(cfg)
@@ -327,7 +327,7 @@ func (s *Server) CopyCharts(origin string) ([]string, error) {
if err != nil {
return []string{}, err
}
- if err := os.WriteFile(newname, data, 0644); err != nil {
+ if err := os.WriteFile(newname, data, 0o644); err != nil {
return []string{}, err
}
copied[i] = newname
@@ -351,7 +351,7 @@ func (s *Server) CreateIndex() error {
}
ifile := filepath.Join(s.docroot, "index.yaml")
- return os.WriteFile(ifile, d, 0644)
+ return os.WriteFile(ifile, d, 0o644)
}
func (s *Server) start() {
@@ -403,5 +403,5 @@ func setTestingRepository(url, fname string) error {
Name: "test",
URL: url,
})
- return r.WriteFile(fname, 0640)
+ return r.WriteFile(fname, 0o640)
}
diff --git a/pkg/repo/repotest/server_test.go b/pkg/repo/repotest/server_test.go
index cf68e5110..4d62ef8ed 100644
--- a/pkg/repo/repotest/server_test.go
+++ b/pkg/repo/repotest/server_test.go
@@ -92,7 +92,7 @@ func TestServer(t *testing.T) {
if err != nil {
t.Fatal(err)
}
- if res.StatusCode != 404 {
+ if res.StatusCode != http.StatusNotFound {
t.Fatalf("Expected 404, got %d", res.StatusCode)
}
}
@@ -140,7 +140,7 @@ func TestNewTempServer(t *testing.T) {
res.Body.Close()
- if res.StatusCode != 200 {
+ if res.StatusCode != http.StatusOK {
t.Errorf("Expected 200, got %d", res.StatusCode)
}
@@ -153,7 +153,7 @@ func TestNewTempServer(t *testing.T) {
}
res.Body.Close()
- if res.StatusCode != 200 {
+ if res.StatusCode != http.StatusOK {
t.Errorf("Expected 200, got %d", res.StatusCode)
}
}
@@ -198,7 +198,7 @@ func TestNewTempServer(t *testing.T) {
if err != nil {
t.Fatal(err)
}
- if res.StatusCode != 404 {
+ if res.StatusCode != http.StatusNotFound {
t.Fatalf("Expected 404, got %d", res.StatusCode)
}
})
diff --git a/pkg/repo/repotest/tlsconfig.go b/pkg/repo/repotest/tlsconfig.go
index 3914a4d3f..3ea7338ff 100644
--- a/pkg/repo/repotest/tlsconfig.go
+++ b/pkg/repo/repotest/tlsconfig.go
@@ -26,6 +26,7 @@ import (
)
func MakeTestTLSConfig(t *testing.T, path string) *tls.Config {
+ t.Helper()
ca, pub, priv := filepath.Join(path, "rootca.crt"), filepath.Join(path, "crt.pem"), filepath.Join(path, "key.pem")
insecure := false
diff --git a/pkg/storage/driver/cfgmaps.go b/pkg/storage/driver/cfgmaps.go
index 48edc3172..de097f294 100644
--- a/pkg/storage/driver/cfgmaps.go
+++ b/pkg/storage/driver/cfgmaps.go
@@ -19,11 +19,11 @@ package driver // import "helm.sh/helm/v4/pkg/storage/driver"
import (
"context"
"fmt"
+ "log/slog"
"strconv"
"strings"
"time"
- "github.com/pkg/errors"
v1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -31,7 +31,7 @@ import (
"k8s.io/apimachinery/pkg/util/validation"
corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
- rspb "helm.sh/helm/v4/pkg/release"
+ rspb "helm.sh/helm/v4/pkg/release/v1"
)
var _ Driver = (*ConfigMaps)(nil)
@@ -43,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
@@ -51,7 +50,6 @@ type ConfigMaps struct {
func NewConfigMaps(impl corev1.ConfigMapInterface) *ConfigMaps {
return &ConfigMaps{
impl: impl,
- Log: func(_ string, _ ...interface{}) {},
}
}
@@ -70,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
}
@@ -93,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
}
@@ -104,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)
@@ -123,7 +121,7 @@ func (cfgmaps *ConfigMaps) Query(labels map[string]string) ([]*rspb.Release, err
ls := kblabels.Set{}
for k, v := range labels {
if errs := validation.IsValidLabelValue(v); len(errs) != 0 {
- return nil, errors.Errorf("invalid label value: %q: %s", v, strings.Join(errs, "; "))
+ return nil, fmt.Errorf("invalid label value: %q: %s", v, strings.Join(errs, "; "))
}
ls[k] = v
}
@@ -132,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
}
@@ -144,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
@@ -166,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
@@ -175,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
@@ -194,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 c93ef8ea4..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/v4/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 2987ba38e..4f9d63928 100644
--- a/pkg/storage/driver/driver.go
+++ b/pkg/storage/driver/driver.go
@@ -17,11 +17,10 @@ limitations under the License.
package driver // import "helm.sh/helm/v4/pkg/storage/driver"
import (
+ "errors"
"fmt"
- "github.com/pkg/errors"
-
- rspb "helm.sh/helm/v4/pkg/release"
+ rspb "helm.sh/helm/v4/pkg/release/v1"
)
var (
diff --git a/pkg/storage/driver/memory.go b/pkg/storage/driver/memory.go
index 430ab215f..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/v4/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 999649635..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/v4/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 359f2d079..7dba5fea2 100644
--- a/pkg/storage/driver/mock_test.go
+++ b/pkg/storage/driver/mock_test.go
@@ -31,7 +31,7 @@ import (
kblabels "k8s.io/apimachinery/pkg/labels"
corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
- rspb "helm.sh/helm/v4/pkg/release"
+ rspb "helm.sh/helm/v4/pkg/release/v1"
)
func releaseStub(name string, vers int, namespace string, status rspb.Status) *rspb.Release {
@@ -52,6 +52,7 @@ func testKey(name string, vers int) string {
}
func tsFixtureMemory(t *testing.T) *Memory {
+ t.Helper()
hs := []*rspb.Release{
// rls-a
releaseStub("rls-a", 4, "default", rspb.StatusDeployed),
@@ -83,6 +84,7 @@ func tsFixtureMemory(t *testing.T) *Memory {
// newTestFixtureCfgMaps initializes a MockConfigMapsInterface.
// ConfigMaps are created for each release provided.
func newTestFixtureCfgMaps(t *testing.T, releases ...*rspb.Release) *ConfigMaps {
+ t.Helper()
var mock MockConfigMapsInterface
mock.Init(t, releases...)
@@ -98,6 +100,7 @@ type MockConfigMapsInterface struct {
// Init initializes the MockConfigMapsInterface with the set of releases.
func (mock *MockConfigMapsInterface) Init(t *testing.T, releases ...*rspb.Release) {
+ t.Helper()
mock.objects = map[string]*v1.ConfigMap{}
for _, rls := range releases {
@@ -130,7 +133,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 +142,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 +152,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,9 +169,10 @@ 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 {
+ t.Helper()
var mock MockSecretsInterface
mock.Init(t, releases...)
@@ -184,6 +188,7 @@ type MockSecretsInterface struct {
// Init initializes the MockSecretsInterface with the set of releases.
func (mock *MockSecretsInterface) Init(t *testing.T, releases ...*rspb.Release) {
+ t.Helper()
mock.objects = map[string]*v1.Secret{}
for _, rls := range releases {
@@ -216,7 +221,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 +230,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 +240,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)
}
@@ -254,6 +259,7 @@ func (mock *MockSecretsInterface) Delete(_ context.Context, name string, _ metav
// newTestFixtureSQL mocks the SQL database (for testing purposes)
func newTestFixtureSQL(t *testing.T, _ ...*rspb.Release) (*SQL, sqlmock.Sqlmock) {
+ t.Helper()
sqlDB, mock, err := sqlmock.New()
if err != nil {
t.Fatalf("error when opening stub database connection: %v", err)
@@ -262,7 +268,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 3b8f078fd..6b4efef3a 100644
--- a/pkg/storage/driver/records.go
+++ b/pkg/storage/driver/records.go
@@ -20,7 +20,7 @@ import (
"sort"
"strconv"
- rspb "helm.sh/helm/v4/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 b1bb051d5..34b2fb80c 100644
--- a/pkg/storage/driver/records_test.go
+++ b/pkg/storage/driver/records_test.go
@@ -20,7 +20,7 @@ import (
"reflect"
"testing"
- rspb "helm.sh/helm/v4/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 eb215a755..23a8f5cab 100644
--- a/pkg/storage/driver/secrets.go
+++ b/pkg/storage/driver/secrets.go
@@ -19,11 +19,11 @@ package driver // import "helm.sh/helm/v4/pkg/storage/driver"
import (
"context"
"fmt"
+ "log/slog"
"strconv"
"strings"
"time"
- "github.com/pkg/errors"
v1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -31,7 +31,7 @@ import (
"k8s.io/apimachinery/pkg/util/validation"
corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
- rspb "helm.sh/helm/v4/pkg/release"
+ rspb "helm.sh/helm/v4/pkg/release/v1"
)
var _ Driver = (*Secrets)(nil)
@@ -43,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
@@ -51,7 +50,6 @@ type Secrets struct {
func NewSecrets(impl corev1.SecretInterface) *Secrets {
return &Secrets{
impl: impl,
- Log: func(_ string, _ ...interface{}) {},
}
}
@@ -69,12 +67,15 @@ func (secrets *Secrets) Get(key string) (*rspb.Release, error) {
if apierrors.IsNotFound(err) {
return nil, ErrReleaseNotFound
}
- return nil, errors.Wrapf(err, "get: failed to get %q", key)
+ return nil, fmt.Errorf("get: failed to get %q: %w", key, err)
}
// found the secret, decode the base64 data string
r, err := decodeRelease(string(obj.Data["release"]))
- r.Labels = filterSystemLabels(obj.ObjectMeta.Labels)
- return r, errors.Wrapf(err, "get: failed to decode data %q", key)
+ if err != nil {
+ return r, fmt.Errorf("get: failed to decode data %q: %w", key, err)
+ }
+ r.Labels = filterSystemLabels(obj.Labels)
+ return r, nil
}
// List fetches all releases and returns the list releases such
@@ -86,7 +87,7 @@ func (secrets *Secrets) List(filter func(*rspb.Release) bool) ([]*rspb.Release,
list, err := secrets.impl.List(context.Background(), opts)
if err != nil {
- return nil, errors.Wrap(err, "list: failed to list")
+ return nil, fmt.Errorf("list: failed to list: %w", err)
}
var results []*rspb.Release
@@ -96,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)
@@ -115,7 +116,7 @@ func (secrets *Secrets) Query(labels map[string]string) ([]*rspb.Release, error)
ls := kblabels.Set{}
for k, v := range labels {
if errs := validation.IsValidLabelValue(v); len(errs) != 0 {
- return nil, errors.Errorf("invalid label value: %q: %s", v, strings.Join(errs, "; "))
+ return nil, fmt.Errorf("invalid label value: %q: %s", v, strings.Join(errs, "; "))
}
ls[k] = v
}
@@ -124,7 +125,7 @@ func (secrets *Secrets) Query(labels map[string]string) ([]*rspb.Release, error)
list, err := secrets.impl.List(context.Background(), opts)
if err != nil {
- return nil, errors.Wrap(err, "query: failed to query with labels")
+ return nil, fmt.Errorf("query: failed to query with labels: %w", err)
}
if len(list.Items) == 0 {
@@ -135,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
@@ -157,7 +158,7 @@ func (secrets *Secrets) Create(key string, rls *rspb.Release) error {
// create a new secret to hold the release
obj, err := newSecretsObject(key, rls, lbs)
if err != nil {
- return errors.Wrapf(err, "create: failed to encode release %q", rls.Name)
+ return fmt.Errorf("create: failed to encode release %q: %w", rls.Name, err)
}
// push the secret object out into the kubiverse
if _, err := secrets.impl.Create(context.Background(), obj, metav1.CreateOptions{}); err != nil {
@@ -165,7 +166,7 @@ func (secrets *Secrets) Create(key string, rls *rspb.Release) error {
return ErrReleaseExists
}
- return errors.Wrap(err, "create: failed to create")
+ return fmt.Errorf("create: failed to create: %w", err)
}
return nil
}
@@ -183,11 +184,14 @@ func (secrets *Secrets) Update(key string, rls *rspb.Release) error {
// create a new secret object to hold the release
obj, err := newSecretsObject(key, rls, lbs)
if err != nil {
- return errors.Wrapf(err, "update: failed to encode release %q", rls.Name)
+ return fmt.Errorf("update: failed to encode release %q: %w", rls.Name, err)
}
// push the secret object out into the kubiverse
_, err = secrets.impl.Update(context.Background(), obj, metav1.UpdateOptions{})
- return errors.Wrap(err, "update: failed to update")
+ if err != nil {
+ return fmt.Errorf("update: failed to update: %w", err)
+ }
+ return nil
}
// Delete deletes the Secret holding the release named by key.
@@ -198,7 +202,10 @@ func (secrets *Secrets) Delete(key string) (rls *rspb.Release, err error) {
}
// delete the release
err = secrets.impl.Delete(context.Background(), key, metav1.DeleteOptions{})
- return rls, err
+ if err != nil {
+ return nil, err
+ }
+ return rls, nil
}
// newSecretsObject constructs a kubernetes Secret object
diff --git a/pkg/storage/driver/secrets_test.go b/pkg/storage/driver/secrets_test.go
index 37ecc20dd..9e45bae67 100644
--- a/pkg/storage/driver/secrets_test.go
+++ b/pkg/storage/driver/secrets_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/v4/pkg/release"
+ rspb "helm.sh/helm/v4/pkg/release/v1"
)
func TestSecretName(t *testing.T) {
@@ -242,10 +243,8 @@ 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 !reflect.DeepEqual(ErrReleaseNotFound, err) {
+ 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 d5ab6b0a1..46f6c6b2e 100644
--- a/pkg/storage/driver/sql.go
+++ b/pkg/storage/driver/sql.go
@@ -18,6 +18,8 @@ package driver // import "helm.sh/helm/v4/pkg/storage/driver"
import (
"fmt"
+ "log/slog"
+ "maps"
"sort"
"strconv"
"time"
@@ -30,7 +32,7 @@ import (
// Import pq for postgres dialect
_ "github.com/lib/pq"
- rspb "helm.sh/helm/v4/pkg/release"
+ rspb "helm.sh/helm/v4/pkg/release/v1"
)
var _ Driver = (*SQL)(nil)
@@ -86,8 +88,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 +108,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 +122,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 +276,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 +284,6 @@ func NewSQL(connectionString string, logger func(string, ...interface{}), namesp
driver := &SQL{
db: db,
- Log: logger,
statementBuilder: sq.StatementBuilder.PlaceholderFormat(sq.Dollar),
}
@@ -309,24 +308,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 +346,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,17 +360,15 @@ 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) {
- release.Labels[k] = v
- }
+ maps.Copy(release.Labels, getReleaseSystemLabels(release))
if filter(release) {
releases = append(releases, release)
@@ -396,7 +393,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 +406,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 +424,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 +453,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 +488,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 +502,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 +535,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 +560,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 +577,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 +593,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 +604,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 +629,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 +651,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 8d7b88475..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/v4/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 7f1bc716c..ca8e23cc2 100644
--- a/pkg/storage/driver/util.go
+++ b/pkg/storage/driver/util.go
@@ -22,8 +22,9 @@ import (
"encoding/base64"
"encoding/json"
"io"
+ "slices"
- rspb "helm.sh/helm/v4/pkg/release"
+ rspb "helm.sh/helm/v4/pkg/release/v1"
)
var b64 = base64.StdEncoding
@@ -88,12 +89,7 @@ func decodeRelease(data string) (*rspb.Release, error) {
// Checks if label is system
func isSystemLabel(key string) bool {
- for _, v := range GetSystemLabels() {
- if key == v {
- return true
- }
- }
- return false
+ return slices.Contains(GetSystemLabels(), key)
}
// Removes system labels from labels map
diff --git a/pkg/storage/storage.go b/pkg/storage/storage.go
index af339b85e..b43f7c0f2 100644
--- a/pkg/storage/storage.go
+++ b/pkg/storage/storage.go
@@ -17,13 +17,13 @@ limitations under the License.
package storage // import "helm.sh/helm/v4/pkg/storage"
import (
+ "errors"
"fmt"
+ "log/slog"
"strings"
- "github.com/pkg/errors"
-
- rspb "helm.sh/helm/v4/pkg/release"
- relutil "helm.sh/helm/v4/pkg/releaseutil"
+ relutil "helm.sh/helm/v4/pkg/release/util"
+ rspb "helm.sh/helm/v4/pkg/release/v1"
"helm.sh/helm/v4/pkg/storage/driver"
)
@@ -42,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))
}
@@ -58,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 &&
@@ -73,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)
}
@@ -81,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)
})
}
@@ -104,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)
})
}
@@ -132,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",
@@ -151,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
@@ -206,14 +204,14 @@ func (s *Storage) removeLeastRecent(name string, maximum 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
case 1:
return errs[0]
default:
- return errors.Errorf("encountered %d deletion errors. First is: %s", c, errs[0])
+ return fmt.Errorf("encountered %d deletion errors. First is: %w", c, errs[0])
}
}
@@ -221,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
@@ -229,13 +227,13 @@ 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
}
if len(h) == 0 {
- return nil, errors.Errorf("no revision for release %q", name)
+ return nil, fmt.Errorf("no revision for release %q", name)
}
relutil.Reverse(h, relutil.SortByRevision)
@@ -261,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 80011520e..d3025eca3 100644
--- a/pkg/storage/storage_test.go
+++ b/pkg/storage/storage_test.go
@@ -17,13 +17,12 @@ limitations under the License.
package storage // import "helm.sh/helm/v4/pkg/storage"
import (
+ "errors"
"fmt"
"reflect"
"testing"
- "github.com/pkg/errors"
-
- rspb "helm.sh/helm/v4/pkg/release"
+ rspb "helm.sh/helm/v4/pkg/release/v1"
"helm.sh/helm/v4/pkg/storage/driver"
)
@@ -312,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
@@ -338,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
@@ -395,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"
@@ -476,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/strvals/literal_parser.go b/pkg/strvals/literal_parser.go
index f75655811..d34e5e854 100644
--- a/pkg/strvals/literal_parser.go
+++ b/pkg/strvals/literal_parser.go
@@ -20,8 +20,6 @@ import (
"fmt"
"io"
"strconv"
-
- "github.com/pkg/errors"
)
// ParseLiteral parses a set line interpreting the value as a literal string.
@@ -102,7 +100,7 @@ func (t *literalParser) key(data map[string]interface{}, nestedNameLevel int) (r
if len(key) == 0 {
return err
}
- return errors.Errorf("key %q has no value", string(key))
+ return fmt.Errorf("key %q has no value", string(key))
case lastRune == '=':
// found end of key: swallow the '=' and get the value
@@ -129,7 +127,7 @@ func (t *literalParser) key(data map[string]interface{}, nestedNameLevel int) (r
// recurse on sub-tree with remaining data
err := t.key(inner, nestedNameLevel)
if err == nil && len(inner) == 0 {
- return errors.Errorf("key map %q has no value", string(key))
+ return fmt.Errorf("key map %q has no value", string(key))
}
if len(inner) != 0 {
set(data, string(key), inner)
@@ -140,7 +138,7 @@ func (t *literalParser) key(data map[string]interface{}, nestedNameLevel int) (r
// We are in a list index context, so we need to set an index.
i, err := t.keyIndex()
if err != nil {
- return errors.Wrap(err, "error parsing index")
+ return fmt.Errorf("error parsing index: %w", err)
}
kk := string(key)
@@ -178,7 +176,7 @@ func (t *literalParser) listItem(list []interface{}, i, nestedNameLevel int) ([]
switch key, lastRune, err := runesUntilLiteral(t.sc, stop); {
case len(key) > 0:
- return list, errors.Errorf("unexpected data at end of array index: %q", key)
+ return list, fmt.Errorf("unexpected data at end of array index: %q", key)
case err != nil:
return list, err
@@ -214,7 +212,7 @@ func (t *literalParser) listItem(list []interface{}, i, nestedNameLevel int) ([]
// now we have a nested list. Read the index and handle.
nextI, err := t.keyIndex()
if err != nil {
- return list, errors.Wrap(err, "error parsing index")
+ return list, fmt.Errorf("error parsing index: %w", err)
}
var crtList []interface{}
if len(list) > i {
@@ -233,7 +231,7 @@ func (t *literalParser) listItem(list []interface{}, i, nestedNameLevel int) ([]
return setIndex(list, i, list2)
default:
- return nil, errors.Errorf("parse error: unexpected token %v", lastRune)
+ return nil, fmt.Errorf("parse error: unexpected token %v", lastRune)
}
}
diff --git a/pkg/strvals/parser.go b/pkg/strvals/parser.go
index a0e8d66d1..c65e98c84 100644
--- a/pkg/strvals/parser.go
+++ b/pkg/strvals/parser.go
@@ -18,13 +18,13 @@ package strvals
import (
"bytes"
"encoding/json"
+ "errors"
"fmt"
"io"
"strconv"
"strings"
"unicode"
- "github.com/pkg/errors"
"sigs.k8s.io/yaml"
)
@@ -189,14 +189,14 @@ func (t *parser) key(data map[string]interface{}, nestedNameLevel int) (reterr e
if len(k) == 0 {
return err
}
- return errors.Errorf("key %q has no value", string(k))
+ return fmt.Errorf("key %q has no value", string(k))
//set(data, string(k), "")
//return err
case last == '[':
// We are in a list index context, so we need to set an index.
i, err := t.keyIndex()
if err != nil {
- return errors.Wrap(err, "error parsing index")
+ return fmt.Errorf("error parsing index: %w", err)
}
kk := string(k)
// Find or create target list
@@ -261,7 +261,7 @@ func (t *parser) key(data map[string]interface{}, nestedNameLevel int) (reterr e
case last == ',':
// No value given. Set the value to empty string. Return error.
set(data, string(k), "")
- return errors.Errorf("key %q has no value (cannot end with ,)", string(k))
+ return fmt.Errorf("key %q has no value (cannot end with ,)", string(k))
case last == '.':
// Check value name is within the maximum nested name level
nestedNameLevel++
@@ -278,7 +278,7 @@ func (t *parser) key(data map[string]interface{}, nestedNameLevel int) (reterr e
// Recurse
e := t.key(inner, nestedNameLevel)
if e == nil && len(inner) == 0 {
- return errors.Errorf("key map %q has no value", string(k))
+ return fmt.Errorf("key map %q has no value", string(k))
}
if len(inner) != 0 {
set(data, string(k), inner)
@@ -332,6 +332,7 @@ func (t *parser) keyIndex() (int, error) {
return strconv.Atoi(string(v))
}
+
func (t *parser) listItem(list []interface{}, i, nestedNameLevel int) ([]interface{}, error) {
if i < 0 {
return list, fmt.Errorf("negative %d index not allowed", i)
@@ -339,7 +340,7 @@ func (t *parser) listItem(list []interface{}, i, nestedNameLevel int) ([]interfa
stop := runeSet([]rune{'[', '.', '='})
switch k, last, err := runesUntil(t.sc, stop); {
case len(k) > 0:
- return list, errors.Errorf("unexpected data at end of array index: %q", k)
+ return list, fmt.Errorf("unexpected data at end of array index: %q", k)
case err != nil:
return list, err
case last == '=':
@@ -394,7 +395,7 @@ func (t *parser) listItem(list []interface{}, i, nestedNameLevel int) ([]interfa
// now we have a nested list. Read the index and handle.
nextI, err := t.keyIndex()
if err != nil {
- return list, errors.Wrap(err, "error parsing index")
+ return list, fmt.Errorf("error parsing index: %w", err)
}
var crtList []interface{}
if len(list) > i {
@@ -430,7 +431,7 @@ func (t *parser) listItem(list []interface{}, i, nestedNameLevel int) ([]interfa
}
return setIndex(list, i, inner)
default:
- return nil, errors.Errorf("parse error: unexpected token %v", last)
+ return nil, fmt.Errorf("parse error: unexpected token %v", last)
}
}
diff --git a/pkg/time/time.go b/pkg/time/time.go
index 13b1211e6..16973b455 100644
--- a/pkg/time/time.go
+++ b/pkg/time/time.go
@@ -41,7 +41,7 @@ func Now() Time {
}
func (t Time) MarshalJSON() ([]byte, error) {
- if t.Time.IsZero() {
+ if t.IsZero() {
return []byte(emptyString), nil
}
@@ -65,6 +65,7 @@ func Parse(layout, value string) (Time, error) {
t, err := time.Parse(layout, value)
return Time{Time: t}, err
}
+
func ParseInLocation(layout, value string, loc *time.Location) (Time, error) {
t, err := time.ParseInLocation(layout, value, loc)
return Time{Time: t}, err
diff --git a/pkg/time/time_test.go b/pkg/time/time_test.go
index 20f0f8e29..342ca4a10 100644
--- a/pkg/time/time_test.go
+++ b/pkg/time/time_test.go
@@ -20,64 +20,134 @@ import (
"encoding/json"
"testing"
"time"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
var (
- testingTime, _ = Parse(time.RFC3339, "1977-09-02T22:04:05Z")
- testingTimeString = `"1977-09-02T22:04:05Z"`
+ timeParseString = `"1977-09-02T22:04:05Z"`
+ timeString = "1977-09-02 22:04:05 +0000 UTC"
)
-func TestNonZeroValueMarshal(t *testing.T) {
+func givenTime(t *testing.T) Time {
+ t.Helper()
+ result, err := Parse(time.RFC3339, "1977-09-02T22:04:05Z")
+ require.NoError(t, err)
+ return result
+}
+
+func TestDate(t *testing.T) {
+ testingTime := givenTime(t)
+ got := Date(1977, 9, 2, 22, 04, 05, 0, time.UTC)
+ assert.Equal(t, timeString, got.String())
+ assert.True(t, testingTime.Equal(got))
+ assert.True(t, got.Equal(testingTime))
+}
+
+func TestNow(t *testing.T) {
+ testingTime := givenTime(t)
+ got := Now()
+ assert.True(t, testingTime.Before(got))
+ assert.True(t, got.After(testingTime))
+}
+
+func TestTime_Add(t *testing.T) {
+ testingTime := givenTime(t)
+ got := testingTime.Add(time.Hour)
+ assert.Equal(t, timeString, testingTime.String())
+ assert.Equal(t, "1977-09-02 23:04:05 +0000 UTC", got.String())
+}
+
+func TestTime_AddDate(t *testing.T) {
+ testingTime := givenTime(t)
+ got := testingTime.AddDate(1, 1, 1)
+ assert.Equal(t, "1978-10-03 22:04:05 +0000 UTC", got.String())
+}
+
+func TestTime_In(t *testing.T) {
+ testingTime := givenTime(t)
+ edt, err := time.LoadLocation("America/New_York")
+ assert.NoError(t, err)
+ got := testingTime.In(edt)
+ assert.Equal(t, "America/New_York", got.Location().String())
+}
+
+func TestTime_MarshalJSONNonZero(t *testing.T) {
+ testingTime := givenTime(t)
res, err := json.Marshal(testingTime)
- if err != nil {
- t.Fatal(err)
- }
- if testingTimeString != string(res) {
- t.Errorf("expected a marshaled value of %s, got %s", testingTimeString, res)
- }
+ assert.NoError(t, err)
+ assert.Equal(t, timeParseString, string(res))
}
-func TestZeroValueMarshal(t *testing.T) {
+func TestTime_MarshalJSONZeroValue(t *testing.T) {
res, err := json.Marshal(Time{})
- if err != nil {
- t.Fatal(err)
- }
- if string(res) != emptyString {
- t.Errorf("expected zero value to marshal to empty string, got %s", res)
- }
+ assert.NoError(t, err)
+ assert.Equal(t, `""`, string(res))
}
-func TestNonZeroValueUnmarshal(t *testing.T) {
+func TestTime_Round(t *testing.T) {
+ testingTime := givenTime(t)
+ got := testingTime.Round(time.Hour)
+ assert.Equal(t, timeString, testingTime.String())
+ assert.Equal(t, "1977-09-02 22:00:00 +0000 UTC", got.String())
+}
+
+func TestTime_Sub(t *testing.T) {
+ testingTime := givenTime(t)
+ before, err := Parse(time.RFC3339, "1977-09-01T22:04:05Z")
+ require.NoError(t, err)
+ got := testingTime.Sub(before)
+ assert.Equal(t, "24h0m0s", got.String())
+}
+
+func TestTime_Truncate(t *testing.T) {
+ testingTime := givenTime(t)
+ got := testingTime.Truncate(time.Hour)
+ assert.Equal(t, timeString, testingTime.String())
+ assert.Equal(t, "1977-09-02 22:00:00 +0000 UTC", got.String())
+}
+
+func TestTime_UTC(t *testing.T) {
+ edtTime, err := Parse(time.RFC3339, "1977-09-03T05:04:05+07:00")
+ require.NoError(t, err)
+ got := edtTime.UTC()
+ assert.Equal(t, timeString, got.String())
+}
+
+func TestTime_UnmarshalJSONNonZeroValue(t *testing.T) {
+ testingTime := givenTime(t)
var myTime Time
- err := json.Unmarshal([]byte(testingTimeString), &myTime)
- if err != nil {
- t.Fatal(err)
- }
- if !myTime.Equal(testingTime) {
- t.Errorf("expected time to be equal to %v, got %v", testingTime, myTime)
- }
+ err := json.Unmarshal([]byte(timeParseString), &myTime)
+ assert.NoError(t, err)
+ assert.True(t, testingTime.Equal(myTime))
}
-func TestEmptyStringUnmarshal(t *testing.T) {
+func TestTime_UnmarshalJSONEmptyString(t *testing.T) {
var myTime Time
err := json.Unmarshal([]byte(emptyString), &myTime)
- if err != nil {
- t.Fatal(err)
- }
- if !myTime.IsZero() {
- t.Errorf("expected time to be equal to zero value, got %v", myTime)
- }
+ assert.NoError(t, err)
+ assert.True(t, myTime.IsZero())
+}
+
+func TestTime_UnmarshalJSONNullString(t *testing.T) {
+ var myTime Time
+ err := json.Unmarshal([]byte("null"), &myTime)
+ assert.NoError(t, err)
+ assert.True(t, myTime.IsZero())
}
-func TestZeroValueUnmarshal(t *testing.T) {
+func TestTime_UnmarshalJSONZeroValue(t *testing.T) {
// This test ensures that we can unmarshal any time value that was output
// with the current go default value of "0001-01-01T00:00:00Z"
var myTime Time
err := json.Unmarshal([]byte(`"0001-01-01T00:00:00Z"`), &myTime)
- if err != nil {
- t.Fatal(err)
- }
- if !myTime.IsZero() {
- t.Errorf("expected time to be equal to zero value, got %v", myTime)
- }
+ assert.NoError(t, err)
+ assert.True(t, myTime.IsZero())
+}
+
+func TestUnix(t *testing.T) {
+ got := Unix(242085845, 0)
+ assert.Equal(t, int64(242085845), got.Unix())
+ assert.Equal(t, timeString, got.UTC().String())
}
diff --git a/pkg/uploader/chart_uploader.go b/pkg/uploader/chart_uploader.go
index 41dfd4455..b3d612e38 100644
--- a/pkg/uploader/chart_uploader.go
+++ b/pkg/uploader/chart_uploader.go
@@ -20,8 +20,6 @@ import (
"io"
"net/url"
- "github.com/pkg/errors"
-
"helm.sh/helm/v4/pkg/pusher"
"helm.sh/helm/v4/pkg/registry"
)
@@ -42,7 +40,7 @@ type ChartUploader struct {
func (c *ChartUploader) UploadTo(ref, remote string) error {
u, err := url.Parse(remote)
if err != nil {
- return errors.Errorf("invalid chart URL format: %s", remote)
+ return fmt.Errorf("invalid chart URL format: %s", remote)
}
if u.Scheme == "" {
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/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