Signed-off-by: Austin Abro <AustinAbro321@gmail.com>
pull/13604/head
Austin Abro 7 months ago
commit 11eeb4a6b1
No known key found for this signature in database
GPG Key ID: 92EB5159E403F9D6

@ -21,6 +21,6 @@ jobs:
go-version: '1.23' go-version: '1.23'
check-latest: true check-latest: true
- name: golangci-lint - name: golangci-lint
uses: golangci/golangci-lint-action@e60da84bfae8c7920a47be973d75e15710aa8bd7 #pin@6.3.0 uses: golangci/golangci-lint-action@2226d7cb06a077cd73e56eedd38eecad18e5d837 #pin@6.5.0
with: with:
version: v1.62 version: v1.62

@ -33,7 +33,7 @@ jobs:
persist-credentials: false persist-credentials: false
- name: "Run analysis" - name: "Run analysis"
uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # v2.4.0 uses: ossf/scorecard-action@f49aabe0b5af0936a0987cfb85d86b75731b0186 # v2.4.1
with: with:
results_file: results.sarif results_file: results.sarif
results_format: 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 # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
# format to the repository Actions tab. # format to the repository Actions tab.
- name: "Upload artifact" - name: "Upload artifact"
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
with: with:
name: SARIF file name: SARIF file
path: results.sarif path: results.sarif

@ -276,12 +276,26 @@ Like any good open source project, we use Pull Requests (PRs) to track code chan
or explicitly request another OWNER do that for them. or explicitly request another OWNER do that for them.
- If the owner of a PR is _not_ listed in `OWNERS`, any core maintainer may merge the PR. - If the owner of a PR is _not_ listed in `OWNERS`, any core maintainer may merge the PR.
#### Documentation PRs ### Documentation PRs
Documentation PRs should be made on the docs repo: <https://github.com/helm/helm-www>. Keeping Helm's documentation up to date is highly desirable, and is recommended for all user facing changes. Accurate and helpful documentation is critical for effectively communicating Helm's behavior to a wide audience. Documentation PRs should be made on the docs repo: <https://github.com/helm/helm-www>. Keeping Helm's documentation up to date is highly desirable, and is recommended for all user facing changes. Accurate and helpful documentation is critical for effectively communicating Helm's behavior to a wide audience.
Small, ad-hoc changes/PRs to Helm which introduce user facing changes, which would benefit from documentation changes, should apply the `docs needed` label. Larger changes associated with a HIP should track docs via that HIP. The `docs needed` label doesn't block PRs, and maintainers/PR reviewers should apply discretion judging in whether the `docs needed` label should be applied. Small, ad-hoc changes/PRs to Helm which introduce user facing changes, which would benefit from documentation changes, should apply the `docs needed` label. Larger changes associated with a HIP should track docs via that HIP. The `docs needed` label doesn't block PRs, and maintainers/PR reviewers should apply discretion judging in whether the `docs needed` label should be applied.
### Profiling PRs
If your contribution requires profiling to check memory and/or CPU usage, you can set `HELM_PPROF_CPU_PROFILE=/path/to/cpu.prof` and/or `HELM_PPROF_MEM_PROFILE=/path/to/mem.prof` environment variables to collect runtime profiling data for analysis. You can use Golang's [pprof](https://github.com/google/pprof/blob/main/doc/README.md) tool to inspect the results.
Example analysing collected profiling data
```
HELM_PPROF_CPU_PROFILE=cpu.prof HELM_PPROF_MEM_PROFILE=mem.prof helm show all bitnami/nginx
# Visualize graphs. You need to have installed graphviz package in your system
go tool pprof -http=":8000" cpu.prof
go tool pprof -http=":8001" mem.prof
```
## The Triager ## The Triager
Each week, one of the core maintainers will serve as the designated "triager" starting after the Each week, one of the core maintainers will serve as the designated "triager" starting after the

21
KEYS

@ -1037,3 +1037,24 @@ nk38BkgHg3LHjCbCNEVkSK2TMT69A58iwpY9WUQlphsiz4WBpafSPbv/jSlsm7uK
TNWtbFGBRpJyEg== TNWtbFGBRpJyEg==
=w141 =w141
-----END PGP PUBLIC KEY BLOCK----- -----END PGP PUBLIC KEY BLOCK-----
pub ed25519 2024-07-09 [SC]
7FEC81FACC7FFB2A010ADD13C2D40F4D8196E874
uid [ultimate] Robert Sirchia (I like turtles.) <rsirchia@outlook.com>
sig 3 C2D40F4D8196E874 2024-07-09 [self-signature]
sub cv25519 2024-07-09 [E]
sig C2D40F4D8196E874 2024-07-09 [self-signature]
-----BEGIN PGP PUBLIC KEY BLOCK-----
mDMEZo2C6xYJKwYBBAHaRw8BAQdA8kCWaI+FlCabcTw8EVeiMkokyWDalgl/Inbn
ACcGN1e0N1JvYmVydCBTaXJjaGlhIChJIGxpa2UgdHVydGxlcy4pIDxyc2lyY2hp
YUBvdXRsb29rLmNvbT6IkwQTFgoAOxYhBH/sgfrMf/sqAQrdE8LUD02Bluh0BQJm
jYLrAhsDBQsJCAcCAiICBhUKCQgLAgQWAgMBAh4HAheAAAoJEMLUD02Bluh0dyYA
/i7RB6m3MXNA8ei7GD8uQVpLfCRgEFsqSS/AzAOu8NGhAQCbw1kWL3AUll7KKtiQ
UE96nhCk+HnkQeVkWYS+MZ1tALg4BGaNgusSCisGAQQBl1UBBQEBB0CCA6Au4krL
YinQq9aAs29fFeRu/ye3PqQuz5jZ2r1ScAMBCAeIeAQYFgoAIBYhBH/sgfrMf/sq
AQrdE8LUD02Bluh0BQJmjYLrAhsMAAoJEMLUD02Bluh0KH4BAMSwEIGkoQl10LN3
K6V08VpFmniENmCDHshXYq0gGiTDAP9FsXl2UtmFU5xuYxH4fRKIxgmxJRAFMWI8
u3Rdu/s+DQ==
=smBO
-----END PGP PUBLIC KEY BLOCK-----

@ -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.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/lint/rules.k8sVersionMinor=$(K8S_MODULES_MINOR_VER)
LDFLAGS += -X helm.sh/helm/v4/pkg/chartutil.k8sVersionMajor=$(K8S_MODULES_MAJOR_VER) LDFLAGS += -X helm.sh/helm/v4/pkg/chart/util.k8sVersionMajor=$(K8S_MODULES_MAJOR_VER)
LDFLAGS += -X helm.sh/helm/v4/pkg/chartutil.k8sVersionMinor=$(K8S_MODULES_MINOR_VER) LDFLAGS += -X helm.sh/helm/v4/pkg/chart/util.k8sVersionMinor=$(K8S_MODULES_MINOR_VER)
.PHONY: all .PHONY: all
all: build all: build

@ -25,7 +25,7 @@ import (
"helm.sh/helm/v4/cmd/helm/require" "helm.sh/helm/v4/cmd/helm/require"
"helm.sh/helm/v4/pkg/chart" "helm.sh/helm/v4/pkg/chart"
"helm.sh/helm/v4/pkg/chartutil" chartutil "helm.sh/helm/v4/pkg/chart/util"
"helm.sh/helm/v4/pkg/helmpath" "helm.sh/helm/v4/pkg/helmpath"
) )

@ -25,7 +25,7 @@ import (
"helm.sh/helm/v4/internal/test/ensure" "helm.sh/helm/v4/internal/test/ensure"
"helm.sh/helm/v4/pkg/chart" "helm.sh/helm/v4/pkg/chart"
"helm.sh/helm/v4/pkg/chart/loader" "helm.sh/helm/v4/pkg/chart/loader"
"helm.sh/helm/v4/pkg/chartutil" chartutil "helm.sh/helm/v4/pkg/chart/util"
"helm.sh/helm/v4/pkg/helmpath" "helm.sh/helm/v4/pkg/helmpath"
) )

@ -22,18 +22,18 @@ import (
"strings" "strings"
"testing" "testing"
"helm.sh/helm/v4/pkg/chartutil" chartutil "helm.sh/helm/v4/pkg/chart/util"
"helm.sh/helm/v4/pkg/provenance" "helm.sh/helm/v4/pkg/provenance"
"helm.sh/helm/v4/pkg/repo" "helm.sh/helm/v4/pkg/repo"
"helm.sh/helm/v4/pkg/repo/repotest" "helm.sh/helm/v4/pkg/repo/repotest"
) )
func TestDependencyBuildCmd(t *testing.T) { func TestDependencyBuildCmd(t *testing.T) {
srv, err := repotest.NewTempServerWithCleanup(t, "testdata/testcharts/*.tgz") srv := repotest.NewTempServer(
t,
repotest.WithChartSourceGlob("testdata/testcharts/*.tgz"),
)
defer srv.Stop() defer srv.Stop()
if err != nil {
t.Fatal(err)
}
rootDir := srv.Root() rootDir := srv.Root()
srv.LinkIndices() srv.LinkIndices()

@ -24,7 +24,7 @@ import (
"helm.sh/helm/v4/internal/test/ensure" "helm.sh/helm/v4/internal/test/ensure"
"helm.sh/helm/v4/pkg/chart" "helm.sh/helm/v4/pkg/chart"
"helm.sh/helm/v4/pkg/chartutil" chartutil "helm.sh/helm/v4/pkg/chart/util"
"helm.sh/helm/v4/pkg/helmpath" "helm.sh/helm/v4/pkg/helmpath"
"helm.sh/helm/v4/pkg/provenance" "helm.sh/helm/v4/pkg/provenance"
"helm.sh/helm/v4/pkg/repo" "helm.sh/helm/v4/pkg/repo"
@ -32,10 +32,10 @@ import (
) )
func TestDependencyUpdateCmd(t *testing.T) { func TestDependencyUpdateCmd(t *testing.T) {
srv, err := repotest.NewTempServerWithCleanup(t, "testdata/testcharts/*.tgz") srv := repotest.NewTempServer(
if err != nil { t,
t.Fatal(err) repotest.WithChartSourceGlob("testdata/testcharts/*.tgz"),
} )
defer srv.Stop() defer srv.Stop()
t.Logf("Listening on directory %s", srv.Root()) t.Logf("Listening on directory %s", srv.Root())
@ -151,10 +151,10 @@ func TestDependencyUpdateCmd_DoNotDeleteOldChartsOnError(t *testing.T) {
defer resetEnv()() defer resetEnv()()
ensure.HelmHome(t) ensure.HelmHome(t)
srv, err := repotest.NewTempServerWithCleanup(t, "testdata/testcharts/*.tgz") srv := repotest.NewTempServer(
if err != nil { t,
t.Fatal(err) repotest.WithChartSourceGlob("testdata/testcharts/*.tgz"),
} )
defer srv.Stop() defer srv.Stop()
t.Logf("Listening on directory %s", srv.Root()) t.Logf("Listening on directory %s", srv.Root())
@ -248,10 +248,10 @@ func TestDependencyUpdateCmd_WithRepoThatWasNotAdded(t *testing.T) {
} }
func setupMockRepoServer(t *testing.T) *repotest.Server { func setupMockRepoServer(t *testing.T) *repotest.Server {
srv, err := repotest.NewTempServerWithCleanup(t, "testdata/testcharts/*.tgz") srv := repotest.NewTempServer(
if err != nil { t,
t.Fatal(err) repotest.WithChartSourceGlob("testdata/testcharts/*.tgz"),
} )
t.Logf("Listening on directory %s", srv.Root()) t.Logf("Listening on directory %s", srv.Root())

@ -48,7 +48,7 @@ func addValueOptionsFlags(f *pflag.FlagSet, v *values.Options) {
f.StringArrayVar(&v.Values, "set", []string{}, "set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)") f.StringArrayVar(&v.Values, "set", []string{}, "set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)")
f.StringArrayVar(&v.StringValues, "set-string", []string{}, "set STRING values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)") f.StringArrayVar(&v.StringValues, "set-string", []string{}, "set STRING values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)")
f.StringArrayVar(&v.FileValues, "set-file", []string{}, "set values from respective files specified via the command line (can specify multiple or separate values with commas: key1=path1,key2=path2)") f.StringArrayVar(&v.FileValues, "set-file", []string{}, "set values from respective files specified via the command line (can specify multiple or separate values with commas: key1=path1,key2=path2)")
f.StringArrayVar(&v.JSONValues, "set-json", []string{}, "set JSON values on the command line (can specify multiple or separate values with commas: key1=jsonval1,key2=jsonval2)") f.StringArrayVar(&v.JSONValues, "set-json", []string{}, "set JSON values on the command line (can specify multiple or separate values with commas: key1=jsonval1,key2=jsonval2 or using json format: {\"key1\": jsonval1, \"key2\": \"jsonval2\"})")
f.StringArrayVar(&v.LiteralValues, "set-literal", []string{}, "set a literal STRING value on the command line") f.StringArrayVar(&v.LiteralValues, "set-literal", []string{}, "set a literal STRING value on the command line")
} }

@ -57,6 +57,11 @@ func warning(format string, v ...interface{}) {
fmt.Fprintf(os.Stderr, format, v...) fmt.Fprintf(os.Stderr, format, v...)
} }
// hookOutputWriter provides the writer for writing hook logs.
func hookOutputWriter(_, _, _ string) io.Writer {
return log.Writer()
}
func main() { func main() {
// Setting the name of the app for managedFields in the Kubernetes client. // 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 // It is set here to the full name of "helm" so that renaming of helm to
@ -80,6 +85,7 @@ func main() {
if helmDriver == "memory" { if helmDriver == "memory" {
loadReleasesInMemory(actionConfig) loadReleasesInMemory(actionConfig)
} }
actionConfig.SetHookOutputFunc(hookOutputWriter)
}) })
if err := cmd.Execute(); err != nil { if err := cmd.Execute(); err != nil {

@ -30,7 +30,7 @@ import (
"helm.sh/helm/v4/internal/test" "helm.sh/helm/v4/internal/test"
"helm.sh/helm/v4/pkg/action" "helm.sh/helm/v4/pkg/action"
"helm.sh/helm/v4/pkg/chartutil" chartutil "helm.sh/helm/v4/pkg/chart/util"
"helm.sh/helm/v4/pkg/cli" "helm.sh/helm/v4/pkg/cli"
kubefake "helm.sh/helm/v4/pkg/kube/fake" kubefake "helm.sh/helm/v4/pkg/kube/fake"
"helm.sh/helm/v4/pkg/release" "helm.sh/helm/v4/pkg/release"

@ -30,7 +30,7 @@ import (
"helm.sh/helm/v4/pkg/chart" "helm.sh/helm/v4/pkg/chart"
"helm.sh/helm/v4/pkg/cli/output" "helm.sh/helm/v4/pkg/cli/output"
"helm.sh/helm/v4/pkg/release" "helm.sh/helm/v4/pkg/release"
"helm.sh/helm/v4/pkg/releaseutil" releaseutil "helm.sh/helm/v4/pkg/release/util"
helmtime "helm.sh/helm/v4/pkg/time" helmtime "helm.sh/helm/v4/pkg/time"
) )

@ -52,7 +52,7 @@ or use the '--set' flag and pass configuration from the command line, to force
a string value use '--set-string'. You can use '--set-file' to set individual a string value use '--set-string'. You can use '--set-file' to set individual
values from a file when the value itself is too long for the command line values from a file when the value itself is too long for the command line
or is dynamically generated. You can also use '--set-json' to set json values or is dynamically generated. You can also use '--set-json' to set json values
(scalars/objects/arrays) from the command line. (scalars/objects/arrays) from the command line. Additionally, you can use '--set-json' and passing json object as a string.
$ helm install -f myvalues.yaml myredis ./redis $ helm install -f myvalues.yaml myredis ./redis
@ -72,6 +72,9 @@ or
$ helm install --set-json 'master.sidecars=[{"name":"sidecar","image":"myImage","imagePullPolicy":"Always","ports":[{"name":"portname","containerPort":1234}]}]' myredis ./redis $ helm install --set-json 'master.sidecars=[{"name":"sidecar","image":"myImage","imagePullPolicy":"Always","ports":[{"name":"portname","containerPort":1234}]}]' myredis ./redis
or
$ helm install --set-json '{"master":{"sidecars":[{"name":"sidecar","image":"myImage","imagePullPolicy":"Always","ports":[{"name":"portname","containerPort":1234}]}]}}' myredis ./redis
You can specify the '--values'/'-f' flag multiple times. The priority will be given to the You can specify the '--values'/'-f' flag multiple times. The priority will be given to the
last (right-most) file specified. For example, if both myvalues.yaml and override.yaml last (right-most) file specified. For example, if both myvalues.yaml and override.yaml

@ -27,19 +27,13 @@ import (
) )
func TestInstall(t *testing.T) { func TestInstall(t *testing.T) {
srv, err := repotest.NewTempServerWithCleanup(t, "testdata/testcharts/*.tgz*") srv := repotest.NewTempServer(
if err != nil { t,
t.Fatal(err) repotest.WithChartSourceGlob("testdata/testcharts/*.tgz*"),
} repotest.WithMiddleware(repotest.BasicAuthMiddleware(t)),
)
defer srv.Stop() defer srv.Stop()
srv.WithMiddleware(http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) {
username, password, ok := r.BasicAuth()
if !ok || username != "username" || password != "password" {
t.Errorf("Expected request to use basic auth and for username == 'username' and password == 'password', got '%v', '%s', '%s'", ok, username, password)
}
}))
srv2 := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { srv2 := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.FileServer(http.Dir(srv.Root())).ServeHTTP(w, r) http.FileServer(http.Dir(srv.Root())).ServeHTTP(w, r)
})) }))

@ -27,7 +27,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"helm.sh/helm/v4/pkg/action" "helm.sh/helm/v4/pkg/action"
"helm.sh/helm/v4/pkg/chartutil" chartutil "helm.sh/helm/v4/pkg/chart/util"
"helm.sh/helm/v4/pkg/cli/values" "helm.sh/helm/v4/pkg/cli/values"
"helm.sh/helm/v4/pkg/getter" "helm.sh/helm/v4/pkg/getter"
"helm.sh/helm/v4/pkg/lint/support" "helm.sh/helm/v4/pkg/lint/support"

@ -0,0 +1,91 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"errors"
"fmt"
"os"
"runtime"
"runtime/pprof"
)
var (
cpuProfileFile *os.File
cpuProfilePath string
memProfilePath string
)
func init() {
cpuProfilePath = os.Getenv("HELM_PPROF_CPU_PROFILE")
memProfilePath = os.Getenv("HELM_PPROF_MEM_PROFILE")
}
// startProfiling starts profiling CPU usage if HELM_PPROF_CPU_PROFILE is set
// to a file path. It returns an error if the file could not be created or
// CPU profiling could not be started.
func startProfiling() error {
if cpuProfilePath != "" {
var err error
cpuProfileFile, err = os.Create(cpuProfilePath)
if err != nil {
return fmt.Errorf("could not create CPU profile: %w", err)
}
if err := pprof.StartCPUProfile(cpuProfileFile); err != nil {
cpuProfileFile.Close()
cpuProfileFile = nil
return fmt.Errorf("could not start CPU profile: %w", err)
}
}
return nil
}
// stopProfiling stops profiling CPU and memory usage.
// It writes memory profile to the file path specified in HELM_PPROF_MEM_PROFILE
// environment variable.
func stopProfiling() error {
errs := []error{}
// Stop CPU profiling if it was started
if cpuProfileFile != nil {
pprof.StopCPUProfile()
err := cpuProfileFile.Close()
if err != nil {
errs = append(errs, err)
}
cpuProfileFile = nil
}
if memProfilePath != "" {
f, err := os.Create(memProfilePath)
if err != nil {
errs = append(errs, err)
}
defer f.Close()
runtime.GC() // get up-to-date statistics
if err := pprof.WriteHeapProfile(f); err != nil {
errs = append(errs, err)
}
}
if err := errors.Join(errs...); err != nil {
return fmt.Errorf("error(s) while stopping profiling: %w", err)
}
return nil
}

@ -28,10 +28,10 @@ import (
) )
func TestPullCmd(t *testing.T) { func TestPullCmd(t *testing.T) {
srv, err := repotest.NewTempServerWithCleanup(t, "testdata/testcharts/*.tgz*") srv := repotest.NewTempServer(
if err != nil { t,
t.Fatal(err) repotest.WithChartSourceGlob("testdata/testcharts/*.tgz*"),
} )
defer srv.Stop() defer srv.Stop()
ociSrv, err := repotest.NewOCIServer(t, srv.Root()) ociSrv, err := repotest.NewOCIServer(t, srv.Root())
@ -257,19 +257,13 @@ func TestPullCmd(t *testing.T) {
} }
func TestPullWithCredentialsCmd(t *testing.T) { func TestPullWithCredentialsCmd(t *testing.T) {
srv, err := repotest.NewTempServerWithCleanup(t, "testdata/testcharts/*.tgz*") srv := repotest.NewTempServer(
if err != nil { t,
t.Fatal(err) repotest.WithChartSourceGlob("testdata/testcharts/*.tgz*"),
} repotest.WithMiddleware(repotest.BasicAuthMiddleware(t)),
)
defer srv.Stop() defer srv.Stop()
srv.WithMiddleware(http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) {
username, password, ok := r.BasicAuth()
if !ok || username != "username" || password != "password" {
t.Errorf("Expected request to use basic auth and for username == 'username' and password == 'password', got '%v', '%s', '%s'", ok, username, password)
}
}))
srv2 := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { srv2 := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.FileServer(http.Dir(srv.Root())).ServeHTTP(w, r) http.FileServer(http.Dir(srv.Root())).ServeHTTP(w, r)
})) }))

@ -34,22 +34,21 @@ import (
) )
func TestRepoAddCmd(t *testing.T) { func TestRepoAddCmd(t *testing.T) {
srv, err := repotest.NewTempServerWithCleanup(t, "testdata/testserver/*.*") srv := repotest.NewTempServer(
if err != nil { t,
t.Fatal(err) repotest.WithChartSourceGlob("testdata/testserver/*.*"),
} )
defer srv.Stop() defer srv.Stop()
// A second test server is setup to verify URL changing // A second test server is setup to verify URL changing
srv2, err := repotest.NewTempServerWithCleanup(t, "testdata/testserver/*.*") srv2 := repotest.NewTempServer(
if err != nil { t,
t.Fatal(err) repotest.WithChartSourceGlob("testdata/testserver/*.*"),
} )
defer srv2.Stop() defer srv2.Stop()
tmpdir := filepath.Join(t.TempDir(), "path-component.yaml/data") tmpdir := filepath.Join(t.TempDir(), "path-component.yaml/data")
err = os.MkdirAll(tmpdir, 0777) if err := os.MkdirAll(tmpdir, 0777); err != nil {
if err != nil {
t.Fatal(err) t.Fatal(err)
} }
repoFile := filepath.Join(tmpdir, "repositories.yaml") repoFile := filepath.Join(tmpdir, "repositories.yaml")
@ -81,10 +80,10 @@ func TestRepoAddCmd(t *testing.T) {
} }
func TestRepoAdd(t *testing.T) { func TestRepoAdd(t *testing.T) {
ts, err := repotest.NewTempServerWithCleanup(t, "testdata/testserver/*.*") ts := repotest.NewTempServer(
if err != nil { t,
t.Fatal(err) repotest.WithChartSourceGlob("testdata/testserver/*.*"),
} )
defer ts.Stop() defer ts.Stop()
rootDir := t.TempDir() rootDir := t.TempDir()
@ -134,10 +133,10 @@ func TestRepoAdd(t *testing.T) {
} }
func TestRepoAddCheckLegalName(t *testing.T) { func TestRepoAddCheckLegalName(t *testing.T) {
ts, err := repotest.NewTempServerWithCleanup(t, "testdata/testserver/*.*") ts := repotest.NewTempServer(
if err != nil { t,
t.Fatal(err) repotest.WithChartSourceGlob("testdata/testserver/*.*"),
} )
defer ts.Stop() defer ts.Stop()
defer resetEnv()() defer resetEnv()()
@ -190,10 +189,10 @@ func TestRepoAddConcurrentHiddenFile(t *testing.T) {
} }
func repoAddConcurrent(t *testing.T, testName, repoFile string) { func repoAddConcurrent(t *testing.T, testName, repoFile string) {
ts, err := repotest.NewTempServerWithCleanup(t, "testdata/testserver/*.*") ts := repotest.NewTempServer(
if err != nil { t,
t.Fatal(err) repotest.WithChartSourceGlob("testdata/testserver/*.*"),
} )
defer ts.Stop() defer ts.Stop()
var wg sync.WaitGroup var wg sync.WaitGroup
@ -240,7 +239,11 @@ func TestRepoAddFileCompletion(t *testing.T) {
} }
func TestRepoAddWithPasswordFromStdin(t *testing.T) { func TestRepoAddWithPasswordFromStdin(t *testing.T) {
srv := repotest.NewTempServerWithCleanupAndBasicAuth(t, "testdata/testserver/*.*") srv := repotest.NewTempServer(
t,
repotest.WithChartSourceGlob("testdata/testserver/*.*"),
repotest.WithMiddleware(repotest.BasicAuthMiddleware(t)),
)
defer srv.Stop() defer srv.Stop()
defer resetEnv()() defer resetEnv()()

@ -30,10 +30,10 @@ import (
) )
func TestRepoRemove(t *testing.T) { func TestRepoRemove(t *testing.T) {
ts, err := repotest.NewTempServerWithCleanup(t, "testdata/testserver/*.*") ts := repotest.NewTempServer(
if err != nil { t,
t.Fatal(err) repotest.WithChartSourceGlob("testdata/testserver/*.*"),
} )
defer ts.Stop() defer ts.Stop()
rootDir := t.TempDir() rootDir := t.TempDir()
@ -162,10 +162,11 @@ func testCacheFiles(t *testing.T, cacheIndexFile string, cacheChartsFile string,
} }
func TestRepoRemoveCompletion(t *testing.T) { func TestRepoRemoveCompletion(t *testing.T) {
ts, err := repotest.NewTempServerWithCleanup(t, "testdata/testserver/*.*") ts := repotest.NewTempServer(
if err != nil { t,
t.Fatal(err) repotest.WithChartSourceGlob("testdata/testserver/*.*"),
} )
defer ts.Stop() defer ts.Stop()
rootDir := t.TempDir() rootDir := t.TempDir()

@ -106,10 +106,11 @@ func TestUpdateCustomCacheCmd(t *testing.T) {
cachePath := filepath.Join(rootDir, "updcustomcache") cachePath := filepath.Join(rootDir, "updcustomcache")
os.Mkdir(cachePath, os.ModePerm) os.Mkdir(cachePath, os.ModePerm)
ts, err := repotest.NewTempServerWithCleanup(t, "testdata/testserver/*.*") ts := repotest.NewTempServer(
if err != nil { t,
t.Fatal(err) repotest.WithChartSourceGlob("testdata/testserver/*.*"),
} )
defer ts.Stop() defer ts.Stop()
o := &repoUpdateOptions{ o := &repoUpdateOptions{
@ -130,10 +131,9 @@ func TestUpdateCharts(t *testing.T) {
defer resetEnv()() defer resetEnv()()
ensure.HelmHome(t) ensure.HelmHome(t)
ts, err := repotest.NewTempServerWithCleanup(t, "testdata/testserver/*.*") ts := repotest.NewTempServer(t,
if err != nil { repotest.WithChartSourceGlob("testdata/testserver/*.*"),
t.Fatal(err) )
}
defer ts.Stop() defer ts.Stop()
r, err := repo.NewChartRepository(&repo.Entry{ r, err := repo.NewChartRepository(&repo.Entry{
@ -165,10 +165,10 @@ func TestUpdateChartsFail(t *testing.T) {
defer resetEnv()() defer resetEnv()()
ensure.HelmHome(t) ensure.HelmHome(t)
ts, err := repotest.NewTempServerWithCleanup(t, "testdata/testserver/*.*") ts := repotest.NewTempServer(
if err != nil { t,
t.Fatal(err) repotest.WithChartSourceGlob("testdata/testserver/*.*"),
} )
defer ts.Stop() defer ts.Stop()
var invalidURL = ts.URL() + "55" var invalidURL = ts.URL() + "55"
@ -198,10 +198,10 @@ func TestUpdateChartsFailWithError(t *testing.T) {
defer resetEnv()() defer resetEnv()()
ensure.HelmHome(t) ensure.HelmHome(t)
ts, err := repotest.NewTempServerWithCleanup(t, "testdata/testserver/*.*") ts := repotest.NewTempServer(
if err != nil { t,
t.Fatal(err) repotest.WithChartSourceGlob("testdata/testserver/*.*"),
} )
defer ts.Stop() defer ts.Stop()
var invalidURL = ts.URL() + "55" var invalidURL = ts.URL() + "55"

@ -71,7 +71,8 @@ func runTestCases(t *testing.T, testCases []testCase) {
Args: tc.validateFunc, Args: tc.validateFunc,
} }
cmd.SetArgs(tc.args) cmd.SetArgs(tc.args)
cmd.SetOutput(io.Discard) cmd.SetOut(io.Discard)
cmd.SetErr(io.Discard)
err := cmd.Execute() err := cmd.Execute()
if tc.wantError == "" { if tc.wantError == "" {

@ -95,6 +95,16 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string
Short: "The Helm package manager for Kubernetes.", Short: "The Helm package manager for Kubernetes.",
Long: globalUsage, Long: globalUsage,
SilenceUsage: true, SilenceUsage: true,
PersistentPreRun: func(_ *cobra.Command, _ []string) {
if err := startProfiling(); err != nil {
log.Printf("Warning: Failed to start profiling: %v", err)
}
},
PersistentPostRun: func(_ *cobra.Command, _ []string) {
if err := stopProfiling(); err != nil {
log.Printf("Warning: Failed to stop profiling: %v", err)
}
},
} }
flags := cmd.PersistentFlags() flags := cmd.PersistentFlags()

@ -26,10 +26,10 @@ import (
) )
func TestShowPreReleaseChart(t *testing.T) { func TestShowPreReleaseChart(t *testing.T) {
srv, err := repotest.NewTempServerWithCleanup(t, "testdata/testcharts/*.tgz*") srv := repotest.NewTempServer(
if err != nil { t,
t.Fatal(err) repotest.WithChartSourceGlob("testdata/testcharts/*.tgz*"),
} )
defer srv.Stop() defer srv.Stop()
if err := srv.LinkIndices(); err != nil { if err := srv.LinkIndices(); err != nil {

@ -30,7 +30,7 @@ import (
"helm.sh/helm/v4/cmd/helm/require" "helm.sh/helm/v4/cmd/helm/require"
"helm.sh/helm/v4/pkg/action" "helm.sh/helm/v4/pkg/action"
"helm.sh/helm/v4/pkg/chartutil" chartutil "helm.sh/helm/v4/pkg/chart/util"
"helm.sh/helm/v4/pkg/cli/output" "helm.sh/helm/v4/pkg/cli/output"
"helm.sh/helm/v4/pkg/release" "helm.sh/helm/v4/pkg/release"
) )

@ -34,9 +34,9 @@ import (
"helm.sh/helm/v4/cmd/helm/require" "helm.sh/helm/v4/cmd/helm/require"
"helm.sh/helm/v4/pkg/action" "helm.sh/helm/v4/pkg/action"
"helm.sh/helm/v4/pkg/chartutil" chartutil "helm.sh/helm/v4/pkg/chart/util"
"helm.sh/helm/v4/pkg/cli/values" "helm.sh/helm/v4/pkg/cli/values"
"helm.sh/helm/v4/pkg/releaseutil" releaseutil "helm.sh/helm/v4/pkg/release/util"
) )
const templateDesc = ` const templateDesc = `

@ -53,7 +53,7 @@ or use the '--set' flag and pass configuration from the command line, to force s
values, use '--set-string'. You can use '--set-file' to set individual values, use '--set-string'. You can use '--set-file' to set individual
values from a file when the value itself is too long for the command line values from a file when the value itself is too long for the command line
or is dynamically generated. You can also use '--set-json' to set json values or is dynamically generated. You can also use '--set-json' to set json values
(scalars/objects/arrays) from the command line. (scalars/objects/arrays) from the command line. Additionally, you can use '--set-json' and passing json object as a string.
You can specify the '--values'/'-f' flag multiple times. The priority will be given to the You can specify the '--values'/'-f' flag multiple times. The priority will be given to the
last (right-most) file specified. For example, if both myvalues.yaml and override.yaml last (right-most) file specified. For example, if both myvalues.yaml and override.yaml

@ -26,7 +26,7 @@ import (
"helm.sh/helm/v4/pkg/chart" "helm.sh/helm/v4/pkg/chart"
"helm.sh/helm/v4/pkg/chart/loader" "helm.sh/helm/v4/pkg/chart/loader"
"helm.sh/helm/v4/pkg/chartutil" chartutil "helm.sh/helm/v4/pkg/chart/util"
"helm.sh/helm/v4/pkg/release" "helm.sh/helm/v4/pkg/release"
) )

@ -13,7 +13,7 @@ require (
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2
github.com/containerd/containerd v1.7.25 github.com/containerd/containerd v1.7.25
github.com/cyphar/filepath-securejoin v0.4.1 github.com/cyphar/filepath-securejoin v0.4.1
github.com/distribution/distribution/v3 v3.0.0-rc.2 github.com/distribution/distribution/v3 v3.0.0-rc.3
github.com/evanphx/json-patch v5.9.11+incompatible github.com/evanphx/json-patch v5.9.11+incompatible
github.com/foxcpp/go-mockdns v1.1.0 github.com/foxcpp/go-mockdns v1.1.0
github.com/gobwas/glob v0.2.3 github.com/gobwas/glob v0.2.3
@ -29,21 +29,22 @@ require (
github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
github.com/rubenv/sql-migrate v1.7.1 github.com/rubenv/sql-migrate v1.7.1
github.com/spf13/cobra v1.8.1 github.com/spf13/cobra v1.9.1
github.com/spf13/pflag v1.0.6 github.com/spf13/pflag v1.0.6
github.com/stretchr/testify v1.10.0 github.com/stretchr/testify v1.10.0
github.com/xeipuuv/gojsonschema v1.2.0 github.com/xeipuuv/gojsonschema v1.2.0
golang.org/x/crypto v0.32.0 golang.org/x/crypto v0.35.0
golang.org/x/term v0.29.0 golang.org/x/term v0.29.0
golang.org/x/text v0.22.0 golang.org/x/text v0.22.0
k8s.io/api v0.32.1 gopkg.in/yaml.v3 v3.0.1
k8s.io/apiextensions-apiserver v0.32.1 k8s.io/api v0.32.2
k8s.io/apimachinery v0.32.1 k8s.io/apiextensions-apiserver v0.32.2
k8s.io/apiserver v0.32.1 k8s.io/apimachinery v0.32.2
k8s.io/cli-runtime v0.32.1 k8s.io/apiserver v0.32.2
k8s.io/client-go v0.32.1 k8s.io/cli-runtime v0.32.2
k8s.io/client-go v0.32.2
k8s.io/klog/v2 v2.130.1 k8s.io/klog/v2 v2.130.1
k8s.io/kubectl v0.32.1 k8s.io/kubectl v0.32.2
oras.land/oras-go/v2 v2.5.0 oras.land/oras-go/v2 v2.5.0
sigs.k8s.io/cli-utils v0.37.2 sigs.k8s.io/cli-utils v0.37.2
sigs.k8s.io/controller-runtime v0.20.1 sigs.k8s.io/controller-runtime v0.20.1
@ -65,7 +66,7 @@ require (
github.com/containerd/log v0.1.0 // indirect github.com/containerd/log v0.1.0 // indirect
github.com/containerd/platforms v0.2.1 // indirect github.com/containerd/platforms v0.2.1 // indirect
github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/distribution/reference v0.6.0 // indirect github.com/distribution/reference v0.6.0 // indirect
@ -177,8 +178,7 @@ require (
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/component-base v0.32.2 // indirect
k8s.io/component-base v0.32.1 // indirect
k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect

@ -58,8 +58,8 @@ github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpS
github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= 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 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 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.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
@ -71,8 +71,8 @@ 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/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 h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/distribution/distribution/v3 v3.0.0-rc.2 h1:tTrzntanYMbd20SyvdeR83Ya1l/aBwDcA3NCIpmwemc= github.com/distribution/distribution/v3 v3.0.0-rc.3 h1:JRJso9IVLoooKX76oWR+DWCCdZlK5m4nRtDWvzB1ITg=
github.com/distribution/distribution/v3 v3.0.0-rc.2/go.mod h1:H2zIRRXS20ylnv2HTuKILAWuANjuA60GB7MLOsQag7Y= github.com/distribution/distribution/v3 v3.0.0-rc.3/go.mod h1:offoOgrnYs+CFwis8nE0hyzYZqRCZj5EFc5kgfszwiE=
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= 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/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo= github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo=
@ -317,9 +317,8 @@ github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w=
github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 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.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@ -409,8 +408,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
@ -527,26 +526,26 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
k8s.io/api v0.32.1 h1:f562zw9cy+GvXzXf0CKlVQ7yHJVYzLfL6JAS4kOAaOc= k8s.io/api v0.32.2 h1:bZrMLEkgizC24G9eViHGOPbW+aRo9duEISRIJKfdJuw=
k8s.io/api v0.32.1/go.mod h1:/Yi/BqkuueW1BgpoePYBRdDYfjPF5sgTr5+YqDZra5k= k8s.io/api v0.32.2/go.mod h1:hKlhk4x1sJyYnHENsrdCWw31FEmCijNGPJO5WzHiJ6Y=
k8s.io/apiextensions-apiserver v0.32.1 h1:hjkALhRUeCariC8DiVmb5jj0VjIc1N0DREP32+6UXZw= k8s.io/apiextensions-apiserver v0.32.2 h1:2YMk285jWMk2188V2AERy5yDwBYrjgWYggscghPCvV4=
k8s.io/apiextensions-apiserver v0.32.1/go.mod h1:sxWIGuGiYov7Io1fAS2X06NjMIk5CbRHc2StSmbaQto= k8s.io/apiextensions-apiserver v0.32.2/go.mod h1:GPwf8sph7YlJT3H6aKUWtd0E+oyShk/YHWQHf/OOgCA=
k8s.io/apimachinery v0.32.1 h1:683ENpaCBjma4CYqsmZyhEzrGz6cjn1MY/X2jB2hkZs= k8s.io/apimachinery v0.32.2 h1:yoQBR9ZGkA6Rgmhbp/yuT9/g+4lxtsGYwW6dR6BDPLQ=
k8s.io/apimachinery v0.32.1/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= k8s.io/apimachinery v0.32.2/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE=
k8s.io/apiserver v0.32.1 h1:oo0OozRos66WFq87Zc5tclUX2r0mymoVHRq8JmR7Aak= k8s.io/apiserver v0.32.2 h1:WzyxAu4mvLkQxwD9hGa4ZfExo3yZZaYzoYvvVDlM6vw=
k8s.io/apiserver v0.32.1/go.mod h1:UcB9tWjBY7aryeI5zAgzVJB/6k7E97bkr1RgqDz0jPw= k8s.io/apiserver v0.32.2/go.mod h1:PEwREHiHNU2oFdte7BjzA1ZyjWjuckORLIK/wLV5goM=
k8s.io/cli-runtime v0.32.1 h1:19nwZPlYGJPUDbhAxDIS2/oydCikvKMHsxroKNGA2mM= k8s.io/cli-runtime v0.32.2 h1:aKQR4foh9qeyckKRkNXUccP9moxzffyndZAvr+IXMks=
k8s.io/cli-runtime v0.32.1/go.mod h1:NJPbeadVFnV2E7B7vF+FvU09mpwYlZCu8PqjzfuOnkY= k8s.io/cli-runtime v0.32.2/go.mod h1:a/JpeMztz3xDa7GCyyShcwe55p8pbcCVQxvqZnIwXN8=
k8s.io/client-go v0.32.1 h1:otM0AxdhdBIaQh7l1Q0jQpmo7WOFIk5FFa4bg6YMdUU= k8s.io/client-go v0.32.2 h1:4dYCD4Nz+9RApM2b/3BtVvBHw54QjMFUl1OLcJG5yOA=
k8s.io/client-go v0.32.1/go.mod h1:aTTKZY7MdxUaJ/KiUs8D+GssR9zJZi77ZqtzcGXIiDg= k8s.io/client-go v0.32.2/go.mod h1:fpZ4oJXclZ3r2nDOv+Ux3XcJutfrwjKTCHz2H3sww94=
k8s.io/component-base v0.32.1 h1:/5IfJ0dHIKBWysGV0yKTFfacZ5yNV1sulPh3ilJjRZk= k8s.io/component-base v0.32.2 h1:1aUL5Vdmu7qNo4ZsE+569PV5zFatM9hl+lb3dEea2zU=
k8s.io/component-base v0.32.1/go.mod h1:j1iMMHi/sqAHeG5z+O9BFNCF698a1u0186zkjMZQ28w= k8s.io/component-base v0.32.2/go.mod h1:PXJ61Vx9Lg+P5mS8TLd7bCIr+eMJRQTyXe8KvkrvJq0=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f h1:GA7//TjRY9yWGy1poLzYYJJ4JRdzg3+O6e8I+e+8T5Y= 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/kube-openapi v0.0.0-20241105132330-32ad38e42d3f/go.mod h1:R/HEjbvWI0qdfb8viZUeVZm0X6IZnxAydC7YU42CMw4=
k8s.io/kubectl v0.32.1 h1:/btLtXLQUU1rWx8AEvX9jrb9LaI6yeezt3sFALhB8M8= k8s.io/kubectl v0.32.2 h1:TAkag6+XfSBgkqK9I7ZvwtF0WVtUAvK8ZqTt+5zi1Us=
k8s.io/kubectl v0.32.1/go.mod h1:sezNuyWi1STk4ZNPVRIFfgjqMI6XMf+oCVLjZen/pFQ= 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 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro=
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= 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 h1:o8Me9kLY74Vp5uw07QXPiitjsw7qNXi8Twd+19Zf02c=

@ -19,6 +19,7 @@ package action
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"io"
"os" "os"
"path" "path"
"path/filepath" "path/filepath"
@ -33,13 +34,13 @@ import (
"k8s.io/client-go/rest" "k8s.io/client-go/rest"
"helm.sh/helm/v4/pkg/chart" "helm.sh/helm/v4/pkg/chart"
"helm.sh/helm/v4/pkg/chartutil" chartutil "helm.sh/helm/v4/pkg/chart/util"
"helm.sh/helm/v4/pkg/engine" "helm.sh/helm/v4/pkg/engine"
"helm.sh/helm/v4/pkg/kube" "helm.sh/helm/v4/pkg/kube"
"helm.sh/helm/v4/pkg/postrender" "helm.sh/helm/v4/pkg/postrender"
"helm.sh/helm/v4/pkg/registry" "helm.sh/helm/v4/pkg/registry"
"helm.sh/helm/v4/pkg/release" "helm.sh/helm/v4/pkg/release"
"helm.sh/helm/v4/pkg/releaseutil" releaseutil "helm.sh/helm/v4/pkg/release/util"
"helm.sh/helm/v4/pkg/storage" "helm.sh/helm/v4/pkg/storage"
"helm.sh/helm/v4/pkg/storage/driver" "helm.sh/helm/v4/pkg/storage/driver"
"helm.sh/helm/v4/pkg/time" "helm.sh/helm/v4/pkg/time"
@ -95,6 +96,9 @@ type Configuration struct {
Capabilities *chartutil.Capabilities Capabilities *chartutil.Capabilities
Log func(string, ...interface{}) Log func(string, ...interface{})
// HookOutputFunc called with container name and returns and expects writer that will receive the log output.
HookOutputFunc func(namespace, pod, container string) io.Writer
} }
// renderResources renders the templates in a chart // renderResources renders the templates in a chart
@ -122,7 +126,7 @@ func (cfg *Configuration) renderResources(ch *chart.Chart, values chartutil.Valu
var err2 error var err2 error
// A `helm template` should not talk to the remote cluster. However, commands with the flag // A `helm template` should not talk to the remote cluster. However, commands with the flag
//`--dry-run` with the value of `false`, `none`, or `server` should try to interact with the cluster. // `--dry-run` with the value of `false`, `none`, or `server` should try to interact with the cluster.
// It may break in interesting and exotic ways because other data (e.g. discovery) is mocked. // It may break in interesting and exotic ways because other data (e.g. discovery) is mocked.
if interactWithRemote && cfg.RESTClientGetter != nil { if interactWithRemote && cfg.RESTClientGetter != nil {
restConfig, err := cfg.RESTClientGetter.ToRESTConfig() restConfig, err := cfg.RESTClientGetter.ToRESTConfig()
@ -425,6 +429,12 @@ func (cfg *Configuration) Init(getter genericclioptions.RESTClientGetter, namesp
cfg.KubeClient = kc cfg.KubeClient = kc
cfg.Releases = store cfg.Releases = store
cfg.Log = log cfg.Log = log
cfg.HookOutputFunc = func(_, _, _ string) io.Writer { return io.Discard }
return nil return nil
} }
// SetHookOutputFunc sets the HookOutputFunc on the Configuration.
func (cfg *Configuration) SetHookOutputFunc(hookOutputFunc func(_, _, _ string) io.Writer) {
cfg.HookOutputFunc = hookOutputFunc
}

@ -25,7 +25,7 @@ import (
fakeclientset "k8s.io/client-go/kubernetes/fake" fakeclientset "k8s.io/client-go/kubernetes/fake"
"helm.sh/helm/v4/pkg/chart" "helm.sh/helm/v4/pkg/chart"
"helm.sh/helm/v4/pkg/chartutil" chartutil "helm.sh/helm/v4/pkg/chart/util"
kubefake "helm.sh/helm/v4/pkg/kube/fake" kubefake "helm.sh/helm/v4/pkg/kube/fake"
"helm.sh/helm/v4/pkg/registry" "helm.sh/helm/v4/pkg/registry"
"helm.sh/helm/v4/pkg/release" "helm.sh/helm/v4/pkg/release"
@ -111,6 +111,14 @@ type chartOptions struct {
type chartOption func(*chartOptions) type chartOption func(*chartOptions)
func buildChart(opts ...chartOption) *chart.Chart { func buildChart(opts ...chartOption) *chart.Chart {
defaultTemplates := []*chart.File{
{Name: "templates/hello", Data: []byte("hello: world")},
{Name: "templates/hooks", Data: []byte(manifestWithHook)},
}
return buildChartWithTemplates(defaultTemplates, opts...)
}
func buildChartWithTemplates(templates []*chart.File, opts ...chartOption) *chart.Chart {
c := &chartOptions{ c := &chartOptions{
Chart: &chart.Chart{ Chart: &chart.Chart{
// TODO: This should be more complete. // TODO: This should be more complete.
@ -119,18 +127,13 @@ func buildChart(opts ...chartOption) *chart.Chart {
Name: "hello", Name: "hello",
Version: "0.1.0", Version: "0.1.0",
}, },
// This adds a basic template and hooks. Templates: templates,
Templates: []*chart.File{
{Name: "templates/hello", Data: []byte("hello: world")},
{Name: "templates/hooks", Data: []byte(manifestWithHook)},
},
}, },
} }
for _, opt := range opts { for _, opt := range opts {
opt(c) opt(c)
} }
return c.Chart return c.Chart
} }

@ -26,7 +26,7 @@ import (
"helm.sh/helm/v4/internal/test" "helm.sh/helm/v4/internal/test"
"helm.sh/helm/v4/pkg/chart" "helm.sh/helm/v4/pkg/chart"
"helm.sh/helm/v4/pkg/chartutil" chartutil "helm.sh/helm/v4/pkg/chart/util"
) )
func TestList(t *testing.T) { func TestList(t *testing.T) {

@ -17,7 +17,7 @@ limitations under the License.
package action package action
import ( import (
"helm.sh/helm/v4/pkg/chartutil" chartutil "helm.sh/helm/v4/pkg/chart/util"
) )
// GetValues is the action for checking a given release's values. // GetValues is the action for checking a given release's values.

@ -19,7 +19,7 @@ package action
import ( import (
"github.com/pkg/errors" "github.com/pkg/errors"
"helm.sh/helm/v4/pkg/chartutil" chartutil "helm.sh/helm/v4/pkg/chart/util"
"helm.sh/helm/v4/pkg/release" "helm.sh/helm/v4/pkg/release"
) )

@ -17,10 +17,18 @@ package action
import ( import (
"bytes" "bytes"
"fmt"
"log"
"slices"
"sort" "sort"
"time" "time"
"helm.sh/helm/v4/pkg/kube"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/pkg/errors" "github.com/pkg/errors"
"gopkg.in/yaml.v3"
"helm.sh/helm/v4/pkg/release" "helm.sh/helm/v4/pkg/release"
helmtime "helm.sh/helm/v4/pkg/time" helmtime "helm.sh/helm/v4/pkg/time"
@ -86,10 +94,16 @@ func (cfg *Configuration) execHook(rl *release.Release, hook release.HookEvent,
// Mark hook as succeeded or failed // Mark hook as succeeded or failed
if err != nil { if err != nil {
h.LastRun.Phase = release.HookPhaseFailed h.LastRun.Phase = release.HookPhaseFailed
// If a hook is failed, check the annotation of the hook to determine if we should copy the logs client side
if errOutputting := cfg.outputLogsByPolicy(h, rl.Namespace, release.HookOutputOnFailed); errOutputting != nil {
// We log the error here as we want to propagate the hook failure upwards to the release object.
log.Printf("error outputting logs for hook failure: %v", errOutputting)
}
// If a hook is failed, check the annotation of the hook to determine whether the hook should be deleted // 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 // under failed condition. If so, then clear the corresponding resource object in the hook
if err := cfg.deleteHookByPolicy(h, release.HookFailed, timeout); err != nil { if errDeleting := cfg.deleteHookByPolicy(h, release.HookFailed, timeout); errDeleting != nil {
return err // 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)
} }
return err return err
} }
@ -97,9 +111,13 @@ func (cfg *Configuration) execHook(rl *release.Release, hook release.HookEvent,
} }
// If all hooks are successful, check the annotation of each hook to determine whether the hook should be deleted // If all hooks are successful, check the annotation of each hook to determine whether the hook should be deleted
// under succeeded condition. If so, then clear the corresponding resource object in each hook // or output should be logged under succeeded condition. If so, then clear the corresponding resource object in each hook
for i := len(executingHooks) - 1; i >= 0; i-- { for i := len(executingHooks) - 1; i >= 0; i-- {
h := executingHooks[i] h := executingHooks[i]
if err := cfg.outputLogsByPolicy(h, rl.Namespace, release.HookOutputOnSucceeded); err != nil {
// We log here as we still want to attempt hook resource deletion even if output logging fails.
log.Printf("error outputting logs for hook failure: %v", err)
}
if err := cfg.deleteHookByPolicy(h, release.HookSucceeded, timeout); err != nil { if err := cfg.deleteHookByPolicy(h, release.HookSucceeded, timeout); err != nil {
return err return err
} }
@ -154,3 +172,57 @@ func hookHasDeletePolicy(h *release.Hook, policy release.HookDeletePolicy) bool
} }
return false return false
} }
// outputLogsByPolicy outputs a pods logs if the hook policy instructs it to
func (cfg *Configuration) outputLogsByPolicy(h *release.Hook, releaseNamespace string, policy release.HookOutputLogPolicy) error {
if !hookHasOutputLogPolicy(h, policy) {
return nil
}
namespace, err := cfg.deriveNamespace(h, releaseNamespace)
if err != nil {
return err
}
switch h.Kind {
case "Job":
return cfg.outputContainerLogsForListOptions(namespace, metav1.ListOptions{LabelSelector: fmt.Sprintf("job-name=%s", h.Name)})
case "Pod":
return cfg.outputContainerLogsForListOptions(namespace, metav1.ListOptions{FieldSelector: fmt.Sprintf("metadata.name=%s", h.Name)})
default:
return nil
}
}
func (cfg *Configuration) outputContainerLogsForListOptions(namespace string, listOptions metav1.ListOptions) error {
// TODO Helm 4: Remove this check when GetPodList and OutputContainerLogsForPodList are moved from InterfaceLogs to Interface
if kubeClient, ok := cfg.KubeClient.(kube.InterfaceLogs); ok {
podList, err := kubeClient.GetPodList(namespace, listOptions)
if err != nil {
return err
}
err = kubeClient.OutputContainerLogsForPodList(podList, namespace, cfg.HookOutputFunc)
return err
}
return nil
}
func (cfg *Configuration) deriveNamespace(h *release.Hook, namespace string) (string, error) {
tmp := struct {
Metadata struct {
Namespace string
}
}{}
err := yaml.Unmarshal([]byte(h.Manifest), &tmp)
if err != nil {
return "", errors.Wrapf(err, "unable to parse metadata.namespace from kubernetes manifest for output logs hook %s", h.Path)
}
if tmp.Metadata.Namespace == "" {
return namespace, nil
}
return tmp.Metadata.Namespace, nil
}
// hookHasOutputLogPolicy determines whether the defined hook output log policy matches the hook output log policies
// supported by helm.
func hookHasOutputLogPolicy(h *release.Hook, policy release.HookOutputLogPolicy) bool {
return slices.Contains(h.OutputLogPolicies, policy)
}

@ -0,0 +1,208 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package action
import (
"bytes"
"fmt"
"io"
"testing"
"github.com/stretchr/testify/assert"
"helm.sh/helm/v4/pkg/chart"
kubefake "helm.sh/helm/v4/pkg/kube/fake"
"helm.sh/helm/v4/pkg/release"
)
func podManifestWithOutputLogs(hookDefinitions []release.HookOutputLogPolicy) string {
hookDefinitionString := convertHooksToCommaSeparated(hookDefinitions)
return fmt.Sprintf(`kind: Pod
metadata:
name: finding-sharky,
annotations:
"helm.sh/hook": pre-install
"helm.sh/hook-output-log-policy": %s
spec:
containers:
- name: sharky-test
image: fake-image
cmd: fake-command`, hookDefinitionString)
}
func podManifestWithOutputLogWithNamespace(hookDefinitions []release.HookOutputLogPolicy) string {
hookDefinitionString := convertHooksToCommaSeparated(hookDefinitions)
return fmt.Sprintf(`kind: Pod
metadata:
name: finding-george
namespace: sneaky-namespace
annotations:
"helm.sh/hook": pre-install
"helm.sh/hook-output-log-policy": %s
spec:
containers:
- name: george-test
image: fake-image
cmd: fake-command`, hookDefinitionString)
}
func jobManifestWithOutputLog(hookDefinitions []release.HookOutputLogPolicy) string {
hookDefinitionString := convertHooksToCommaSeparated(hookDefinitions)
return fmt.Sprintf(`kind: Job
apiVersion: batch/v1
metadata:
name: losing-religion
annotations:
"helm.sh/hook": pre-install
"helm.sh/hook-output-log-policy": %s
spec:
completions: 1
parallelism: 1
activeDeadlineSeconds: 30
template:
spec:
containers:
- name: religion-container
image: religion-image
cmd: religion-command`, hookDefinitionString)
}
func jobManifestWithOutputLogWithNamespace(hookDefinitions []release.HookOutputLogPolicy) string {
hookDefinitionString := convertHooksToCommaSeparated(hookDefinitions)
return fmt.Sprintf(`kind: Job
apiVersion: batch/v1
metadata:
name: losing-religion
namespace: rem-namespace
annotations:
"helm.sh/hook": pre-install
"helm.sh/hook-output-log-policy": %s
spec:
completions: 1
parallelism: 1
activeDeadlineSeconds: 30
template:
spec:
containers:
- name: religion-container
image: religion-image
cmd: religion-command`, hookDefinitionString)
}
func convertHooksToCommaSeparated(hookDefinitions []release.HookOutputLogPolicy) string {
var commaSeparated string
for i, policy := range hookDefinitions {
if i+1 == len(hookDefinitions) {
commaSeparated += policy.String()
} else {
commaSeparated += policy.String() + ","
}
}
return commaSeparated
}
func TestInstallRelease_HookOutputLogsOnFailure(t *testing.T) {
// Should output on failure with expected namespace if hook-failed is set
runInstallForHooksWithFailure(t, podManifestWithOutputLogs([]release.HookOutputLogPolicy{release.HookOutputOnFailed}), "spaced", true)
runInstallForHooksWithFailure(t, podManifestWithOutputLogWithNamespace([]release.HookOutputLogPolicy{release.HookOutputOnFailed}), "sneaky-namespace", true)
runInstallForHooksWithFailure(t, jobManifestWithOutputLog([]release.HookOutputLogPolicy{release.HookOutputOnFailed}), "spaced", true)
runInstallForHooksWithFailure(t, jobManifestWithOutputLogWithNamespace([]release.HookOutputLogPolicy{release.HookOutputOnFailed}), "rem-namespace", true)
// Should not output on failure with expected namespace if hook-succeed is set
runInstallForHooksWithFailure(t, podManifestWithOutputLogs([]release.HookOutputLogPolicy{release.HookOutputOnSucceeded}), "", false)
runInstallForHooksWithFailure(t, podManifestWithOutputLogWithNamespace([]release.HookOutputLogPolicy{release.HookOutputOnSucceeded}), "", false)
runInstallForHooksWithFailure(t, jobManifestWithOutputLog([]release.HookOutputLogPolicy{release.HookOutputOnSucceeded}), "", false)
runInstallForHooksWithFailure(t, jobManifestWithOutputLogWithNamespace([]release.HookOutputLogPolicy{release.HookOutputOnSucceeded}), "", false)
}
func TestInstallRelease_HookOutputLogsOnSuccess(t *testing.T) {
// Should output on success with expected namespace if hook-succeeded is set
runInstallForHooksWithSuccess(t, podManifestWithOutputLogs([]release.HookOutputLogPolicy{release.HookOutputOnSucceeded}), "spaced", true)
runInstallForHooksWithSuccess(t, podManifestWithOutputLogWithNamespace([]release.HookOutputLogPolicy{release.HookOutputOnSucceeded}), "sneaky-namespace", true)
runInstallForHooksWithSuccess(t, jobManifestWithOutputLog([]release.HookOutputLogPolicy{release.HookOutputOnSucceeded}), "spaced", true)
runInstallForHooksWithSuccess(t, jobManifestWithOutputLogWithNamespace([]release.HookOutputLogPolicy{release.HookOutputOnSucceeded}), "rem-namespace", true)
// Should not output on success if hook-failed is set
runInstallForHooksWithSuccess(t, podManifestWithOutputLogs([]release.HookOutputLogPolicy{release.HookOutputOnFailed}), "", false)
runInstallForHooksWithSuccess(t, podManifestWithOutputLogWithNamespace([]release.HookOutputLogPolicy{release.HookOutputOnFailed}), "", false)
runInstallForHooksWithSuccess(t, jobManifestWithOutputLog([]release.HookOutputLogPolicy{release.HookOutputOnFailed}), "", false)
runInstallForHooksWithSuccess(t, jobManifestWithOutputLogWithNamespace([]release.HookOutputLogPolicy{release.HookOutputOnFailed}), "", false)
}
func TestInstallRelease_HooksOutputLogsOnSuccessAndFailure(t *testing.T) {
// Should output on success with expected namespace if hook-succeeded and hook-failed is set
runInstallForHooksWithSuccess(t, podManifestWithOutputLogs([]release.HookOutputLogPolicy{release.HookOutputOnSucceeded, release.HookOutputOnFailed}), "spaced", true)
runInstallForHooksWithSuccess(t, podManifestWithOutputLogWithNamespace([]release.HookOutputLogPolicy{release.HookOutputOnSucceeded, release.HookOutputOnFailed}), "sneaky-namespace", true)
runInstallForHooksWithSuccess(t, jobManifestWithOutputLog([]release.HookOutputLogPolicy{release.HookOutputOnSucceeded, release.HookOutputOnFailed}), "spaced", true)
runInstallForHooksWithSuccess(t, jobManifestWithOutputLogWithNamespace([]release.HookOutputLogPolicy{release.HookOutputOnSucceeded, release.HookOutputOnFailed}), "rem-namespace", true)
// Should output on failure if hook-succeeded and hook-failed is set
runInstallForHooksWithFailure(t, podManifestWithOutputLogs([]release.HookOutputLogPolicy{release.HookOutputOnSucceeded, release.HookOutputOnFailed}), "spaced", true)
runInstallForHooksWithFailure(t, podManifestWithOutputLogWithNamespace([]release.HookOutputLogPolicy{release.HookOutputOnSucceeded, release.HookOutputOnFailed}), "sneaky-namespace", true)
runInstallForHooksWithFailure(t, jobManifestWithOutputLog([]release.HookOutputLogPolicy{release.HookOutputOnSucceeded, release.HookOutputOnFailed}), "spaced", true)
runInstallForHooksWithFailure(t, jobManifestWithOutputLogWithNamespace([]release.HookOutputLogPolicy{release.HookOutputOnSucceeded, release.HookOutputOnFailed}), "rem-namespace", true)
}
func runInstallForHooksWithSuccess(t *testing.T, manifest, expectedNamespace string, shouldOutput bool) {
var expectedOutput string
if shouldOutput {
expectedOutput = fmt.Sprintf("attempted to output logs for namespace: %s", expectedNamespace)
}
is := assert.New(t)
instAction := installAction(t)
instAction.ReleaseName = "failed-hooks"
outBuffer := &bytes.Buffer{}
instAction.cfg.KubeClient = &kubefake.PrintingKubeClient{Out: io.Discard, LogOutput: outBuffer}
templates := []*chart.File{
{Name: "templates/hello", Data: []byte("hello: world")},
{Name: "templates/hooks", Data: []byte(manifest)},
}
vals := map[string]interface{}{}
res, err := instAction.Run(buildChartWithTemplates(templates), vals)
is.NoError(err)
is.Equal(expectedOutput, outBuffer.String())
is.Equal(release.StatusDeployed, res.Info.Status)
}
func runInstallForHooksWithFailure(t *testing.T, manifest, expectedNamespace string, shouldOutput bool) {
var expectedOutput string
if shouldOutput {
expectedOutput = fmt.Sprintf("attempted to output logs for namespace: %s", expectedNamespace)
}
is := assert.New(t)
instAction := installAction(t)
instAction.ReleaseName = "failed-hooks"
failingClient := instAction.cfg.KubeClient.(*kubefake.FailingKubeClient)
failingClient.WatchUntilReadyError = fmt.Errorf("failed watch")
instAction.cfg.KubeClient = failingClient
outBuffer := &bytes.Buffer{}
failingClient.PrintingKubeClient = kubefake.PrintingKubeClient{Out: io.Discard, LogOutput: outBuffer}
templates := []*chart.File{
{Name: "templates/hello", Data: []byte("hello: world")},
{Name: "templates/hooks", Data: []byte(manifest)},
}
vals := map[string]interface{}{}
res, err := instAction.Run(buildChartWithTemplates(templates), vals)
is.Error(err)
is.Contains(res.Info.Description, "failed pre-install")
is.Equal(expectedOutput, outBuffer.String())
is.Equal(release.StatusFailed, res.Info.Status)
}

@ -40,7 +40,7 @@ import (
"sigs.k8s.io/yaml" "sigs.k8s.io/yaml"
"helm.sh/helm/v4/pkg/chart" "helm.sh/helm/v4/pkg/chart"
"helm.sh/helm/v4/pkg/chartutil" chartutil "helm.sh/helm/v4/pkg/chart/util"
"helm.sh/helm/v4/pkg/cli" "helm.sh/helm/v4/pkg/cli"
"helm.sh/helm/v4/pkg/downloader" "helm.sh/helm/v4/pkg/downloader"
"helm.sh/helm/v4/pkg/getter" "helm.sh/helm/v4/pkg/getter"
@ -49,7 +49,7 @@ import (
"helm.sh/helm/v4/pkg/postrender" "helm.sh/helm/v4/pkg/postrender"
"helm.sh/helm/v4/pkg/registry" "helm.sh/helm/v4/pkg/registry"
"helm.sh/helm/v4/pkg/release" "helm.sh/helm/v4/pkg/release"
"helm.sh/helm/v4/pkg/releaseutil" releaseutil "helm.sh/helm/v4/pkg/release/util"
"helm.sh/helm/v4/pkg/repo" "helm.sh/helm/v4/pkg/repo"
"helm.sh/helm/v4/pkg/storage" "helm.sh/helm/v4/pkg/storage"
"helm.sh/helm/v4/pkg/storage/driver" "helm.sh/helm/v4/pkg/storage/driver"

@ -17,6 +17,7 @@ limitations under the License.
package action package action
import ( import (
"bytes"
"context" "context"
"fmt" "fmt"
"io" "io"
@ -33,7 +34,7 @@ import (
"helm.sh/helm/v4/internal/test" "helm.sh/helm/v4/internal/test"
"helm.sh/helm/v4/pkg/chart" "helm.sh/helm/v4/pkg/chart"
"helm.sh/helm/v4/pkg/chartutil" chartutil "helm.sh/helm/v4/pkg/chart/util"
"helm.sh/helm/v4/pkg/kube" "helm.sh/helm/v4/pkg/kube"
kubefake "helm.sh/helm/v4/pkg/kube/fake" kubefake "helm.sh/helm/v4/pkg/kube/fake"
"helm.sh/helm/v4/pkg/release" "helm.sh/helm/v4/pkg/release"
@ -355,11 +356,14 @@ func TestInstallRelease_FailedHooks(t *testing.T) {
failer := instAction.cfg.KubeClient.(*kubefake.FailingKubeClient) failer := instAction.cfg.KubeClient.(*kubefake.FailingKubeClient)
failer.WatchUntilReadyError = fmt.Errorf("Failed watch") failer.WatchUntilReadyError = fmt.Errorf("Failed watch")
instAction.cfg.KubeClient = failer instAction.cfg.KubeClient = failer
outBuffer := &bytes.Buffer{}
failer.PrintingKubeClient = kubefake.PrintingKubeClient{Out: io.Discard, LogOutput: outBuffer}
vals := map[string]interface{}{} vals := map[string]interface{}{}
res, err := instAction.Run(buildChart(), vals) res, err := instAction.Run(buildChart(), vals)
is.Error(err) is.Error(err)
is.Contains(res.Info.Description, "failed post-install") is.Contains(res.Info.Description, "failed post-install")
is.Equal("", outBuffer.String())
is.Equal(release.StatusFailed, res.Info.Status) is.Equal(release.StatusFailed, res.Info.Status)
} }

@ -23,7 +23,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"helm.sh/helm/v4/pkg/chartutil" chartutil "helm.sh/helm/v4/pkg/chart/util"
"helm.sh/helm/v4/pkg/lint" "helm.sh/helm/v4/pkg/lint"
"helm.sh/helm/v4/pkg/lint/support" "helm.sh/helm/v4/pkg/lint/support"
) )

@ -23,7 +23,7 @@ import (
"k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/labels"
"helm.sh/helm/v4/pkg/release" "helm.sh/helm/v4/pkg/release"
"helm.sh/helm/v4/pkg/releaseutil" releaseutil "helm.sh/helm/v4/pkg/release/util"
) )
// ListStates represents zero or more status codes that a list item may have set // ListStates represents zero or more status codes that a list item may have set

@ -27,7 +27,7 @@ import (
"golang.org/x/term" "golang.org/x/term"
"helm.sh/helm/v4/pkg/chart/loader" "helm.sh/helm/v4/pkg/chart/loader"
"helm.sh/helm/v4/pkg/chartutil" chartutil "helm.sh/helm/v4/pkg/chart/util"
"helm.sh/helm/v4/pkg/provenance" "helm.sh/helm/v4/pkg/provenance"
) )

@ -24,7 +24,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"helm.sh/helm/v4/pkg/chartutil" chartutil "helm.sh/helm/v4/pkg/chart/util"
"helm.sh/helm/v4/pkg/cli" "helm.sh/helm/v4/pkg/cli"
"helm.sh/helm/v4/pkg/downloader" "helm.sh/helm/v4/pkg/downloader"
"helm.sh/helm/v4/pkg/getter" "helm.sh/helm/v4/pkg/getter"

@ -20,13 +20,14 @@ import (
"context" "context"
"fmt" "fmt"
"io" "io"
"slices"
"sort" "sort"
"time" "time"
"github.com/pkg/errors" "github.com/pkg/errors"
v1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
"helm.sh/helm/v4/pkg/chartutil" chartutil "helm.sh/helm/v4/pkg/chart/util"
"helm.sh/helm/v4/pkg/release" "helm.sh/helm/v4/pkg/release"
) )
@ -75,7 +76,7 @@ func (r *ReleaseTesting) Run(name string) (*release.Release, error) {
executingHooks := []*release.Hook{} executingHooks := []*release.Hook{}
if len(r.Filters[ExcludeNameFilter]) != 0 { if len(r.Filters[ExcludeNameFilter]) != 0 {
for _, h := range rel.Hooks { for _, h := range rel.Hooks {
if contains(r.Filters[ExcludeNameFilter], h.Name) { if slices.Contains(r.Filters[ExcludeNameFilter], h.Name) {
skippedHooks = append(skippedHooks, h) skippedHooks = append(skippedHooks, h)
} else { } else {
executingHooks = append(executingHooks, h) executingHooks = append(executingHooks, h)
@ -86,7 +87,7 @@ func (r *ReleaseTesting) Run(name string) (*release.Release, error) {
if len(r.Filters[IncludeNameFilter]) != 0 { if len(r.Filters[IncludeNameFilter]) != 0 {
executingHooks = nil executingHooks = nil
for _, h := range rel.Hooks { for _, h := range rel.Hooks {
if contains(r.Filters[IncludeNameFilter], h.Name) { if slices.Contains(r.Filters[IncludeNameFilter], h.Name) {
executingHooks = append(executingHooks, h) executingHooks = append(executingHooks, h)
} else { } else {
skippedHooks = append(skippedHooks, h) skippedHooks = append(skippedHooks, h)
@ -119,10 +120,10 @@ func (r *ReleaseTesting) GetPodLogs(out io.Writer, rel *release.Release) error {
for _, h := range hooksByWight { for _, h := range hooksByWight {
for _, e := range h.Events { for _, e := range h.Events {
if e == release.HookTest { if e == release.HookTest {
if contains(r.Filters[ExcludeNameFilter], h.Name) { if slices.Contains(r.Filters[ExcludeNameFilter], h.Name) {
continue continue
} }
if len(r.Filters[IncludeNameFilter]) > 0 && !contains(r.Filters[IncludeNameFilter], h.Name) { if len(r.Filters[IncludeNameFilter]) > 0 && !slices.Contains(r.Filters[IncludeNameFilter], h.Name) {
continue continue
} }
req := client.CoreV1().Pods(r.Namespace).GetLogs(h.Name, &v1.PodLogOptions{}) req := client.CoreV1().Pods(r.Namespace).GetLogs(h.Name, &v1.PodLogOptions{})
@ -142,12 +143,3 @@ func (r *ReleaseTesting) GetPodLogs(out io.Writer, rel *release.Release) error {
} }
return nil return nil
} }
func contains(arr []string, value string) bool {
for _, item := range arr {
if item == value {
return true
}
}
return false
}

@ -20,7 +20,7 @@ import (
"strings" "strings"
"helm.sh/helm/v4/pkg/kube" "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) { func filterManifestsToKeep(manifests []releaseutil.Manifest) (keep, remaining []releaseutil.Manifest) {

@ -24,7 +24,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"helm.sh/helm/v4/pkg/chartutil" chartutil "helm.sh/helm/v4/pkg/chart/util"
"helm.sh/helm/v4/pkg/kube" "helm.sh/helm/v4/pkg/kube"
"helm.sh/helm/v4/pkg/release" "helm.sh/helm/v4/pkg/release"
helmtime "helm.sh/helm/v4/pkg/time" helmtime "helm.sh/helm/v4/pkg/time"

@ -27,7 +27,7 @@ import (
"helm.sh/helm/v4/pkg/chart" "helm.sh/helm/v4/pkg/chart"
"helm.sh/helm/v4/pkg/chart/loader" "helm.sh/helm/v4/pkg/chart/loader"
"helm.sh/helm/v4/pkg/chartutil" chartutil "helm.sh/helm/v4/pkg/chart/util"
"helm.sh/helm/v4/pkg/registry" "helm.sh/helm/v4/pkg/registry"
) )

@ -25,10 +25,10 @@ import (
v1 "k8s.io/apimachinery/pkg/apis/meta/v1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"helm.sh/helm/v4/pkg/chartutil" chartutil "helm.sh/helm/v4/pkg/chart/util"
"helm.sh/helm/v4/pkg/kube" "helm.sh/helm/v4/pkg/kube"
"helm.sh/helm/v4/pkg/release" "helm.sh/helm/v4/pkg/release"
"helm.sh/helm/v4/pkg/releaseutil" releaseutil "helm.sh/helm/v4/pkg/release/util"
helmtime "helm.sh/helm/v4/pkg/time" helmtime "helm.sh/helm/v4/pkg/time"
) )

@ -29,12 +29,12 @@ import (
"k8s.io/cli-runtime/pkg/resource" "k8s.io/cli-runtime/pkg/resource"
"helm.sh/helm/v4/pkg/chart" "helm.sh/helm/v4/pkg/chart"
"helm.sh/helm/v4/pkg/chartutil" chartutil "helm.sh/helm/v4/pkg/chart/util"
"helm.sh/helm/v4/pkg/kube" "helm.sh/helm/v4/pkg/kube"
"helm.sh/helm/v4/pkg/postrender" "helm.sh/helm/v4/pkg/postrender"
"helm.sh/helm/v4/pkg/registry" "helm.sh/helm/v4/pkg/registry"
"helm.sh/helm/v4/pkg/release" "helm.sh/helm/v4/pkg/release"
"helm.sh/helm/v4/pkg/releaseutil" releaseutil "helm.sh/helm/v4/pkg/release/util"
"helm.sh/helm/v4/pkg/storage/driver" "helm.sh/helm/v4/pkg/storage/driver"
) )

@ -17,14 +17,17 @@ limitations under the License.
package loader package loader
import ( import (
"bufio"
"bytes" "bytes"
"encoding/json" "encoding/json"
"io"
"log" "log"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/pkg/errors" "github.com/pkg/errors"
utilyaml "k8s.io/apimachinery/pkg/util/yaml"
"sigs.k8s.io/yaml" "sigs.k8s.io/yaml"
"helm.sh/helm/v4/pkg/chart" "helm.sh/helm/v4/pkg/chart"
@ -104,13 +107,11 @@ func LoadFiles(files []*BufferedFile) (*chart.Chart, error) {
return c, errors.Wrap(err, "cannot load Chart.lock") return c, errors.Wrap(err, "cannot load Chart.lock")
} }
case f.Name == "values.yaml": case f.Name == "values.yaml":
c.Values = make(map[string]interface{}) values, err := LoadValues(bytes.NewReader(f.Data))
if err := yaml.Unmarshal(f.Data, &c.Values, func(d *json.Decoder) *json.Decoder { if err != nil {
d.UseNumber()
return d
}); err != nil {
return c, errors.Wrap(err, "cannot load values.yaml") return c, errors.Wrap(err, "cannot load values.yaml")
} }
c.Values = values
case f.Name == "values.schema.json": case f.Name == "values.schema.json":
c.Schema = f.Data c.Schema = f.Data
@ -205,3 +206,51 @@ func LoadFiles(files []*BufferedFile) (*chart.Chart, error) {
return c, nil return c, nil
} }
// LoadValues loads values from a reader.
//
// The reader is expected to contain one or more YAML documents, the values of which are merged.
// And the values can be either a chart's default values or a user-supplied values.
func LoadValues(data io.Reader) (map[string]interface{}, error) {
values := map[string]interface{}{}
reader := utilyaml.NewYAMLReader(bufio.NewReader(data))
for {
currentMap := map[string]interface{}{}
raw, err := reader.Read()
if err != nil {
if err == io.EOF {
break
}
return nil, errors.Wrap(err, "error reading yaml document")
}
if err := yaml.Unmarshal(raw, &currentMap, func(d *json.Decoder) *json.Decoder {
d.UseNumber()
return d
}); err != nil {
return nil, errors.Wrap(err, "cannot unmarshal yaml document")
}
values = MergeMaps(values, currentMap)
}
return values, nil
}
// MergeMaps merges two maps. If a key exists in both maps, the value from b will be used.
// If the value is a map, the maps will be merged recursively.
func MergeMaps(a, b map[string]interface{}) map[string]interface{} {
out := make(map[string]interface{}, len(a))
for k, v := range a {
out[k] = v
}
for k, v := range b {
if v, ok := v.(map[string]interface{}); ok {
if bv, ok := out[k]; ok {
if bv, ok := bv.(map[string]interface{}); ok {
out[k] = MergeMaps(bv, v)
continue
}
}
}
out[k] = v
}
return out
}

@ -24,6 +24,7 @@ import (
"log" "log"
"os" "os"
"path/filepath" "path/filepath"
"reflect"
"runtime" "runtime"
"strings" "strings"
"testing" "testing"
@ -488,6 +489,115 @@ func TestLoadInvalidArchive(t *testing.T) {
} }
} }
func TestLoadValues(t *testing.T) {
testCases := map[string]struct {
data []byte
expctedValues map[string]interface{}
}{
"It should load values correctly": {
data: []byte(`
foo:
image: foo:v1
bar:
version: v2
`),
expctedValues: map[string]interface{}{
"foo": map[string]interface{}{
"image": "foo:v1",
},
"bar": map[string]interface{}{
"version": "v2",
},
},
},
"It should load values correctly with multiple documents in one file": {
data: []byte(`
foo:
image: foo:v1
bar:
version: v2
---
foo:
image: foo:v2
`),
expctedValues: map[string]interface{}{
"foo": map[string]interface{}{
"image": "foo:v2",
},
"bar": map[string]interface{}{
"version": "v2",
},
},
},
}
for testName, testCase := range testCases {
t.Run(testName, func(tt *testing.T) {
values, err := LoadValues(bytes.NewReader(testCase.data))
if err != nil {
tt.Fatal(err)
}
if !reflect.DeepEqual(values, testCase.expctedValues) {
tt.Errorf("Expected values: %v, got %v", testCase.expctedValues, values)
}
})
}
}
func TestMergeValues(t *testing.T) {
nestedMap := map[string]interface{}{
"foo": "bar",
"baz": map[string]string{
"cool": "stuff",
},
}
anotherNestedMap := map[string]interface{}{
"foo": "bar",
"baz": map[string]string{
"cool": "things",
"awesome": "stuff",
},
}
flatMap := map[string]interface{}{
"foo": "bar",
"baz": "stuff",
}
anotherFlatMap := map[string]interface{}{
"testing": "fun",
}
testMap := MergeMaps(flatMap, nestedMap)
equal := reflect.DeepEqual(testMap, nestedMap)
if !equal {
t.Errorf("Expected a nested map to overwrite a flat value. Expected: %v, got %v", nestedMap, testMap)
}
testMap = MergeMaps(nestedMap, flatMap)
equal = reflect.DeepEqual(testMap, flatMap)
if !equal {
t.Errorf("Expected a flat value to overwrite a map. Expected: %v, got %v", flatMap, testMap)
}
testMap = MergeMaps(nestedMap, anotherNestedMap)
equal = reflect.DeepEqual(testMap, anotherNestedMap)
if !equal {
t.Errorf("Expected a nested map to overwrite another nested map. Expected: %v, got %v", anotherNestedMap, testMap)
}
testMap = MergeMaps(anotherFlatMap, anotherNestedMap)
expectedMap := map[string]interface{}{
"testing": "fun",
"foo": "bar",
"baz": map[string]string{
"cool": "things",
"awesome": "stuff",
},
}
equal = reflect.DeepEqual(testMap, expectedMap)
if !equal {
t.Errorf("Expected a map with different keys to merge properly with another map. Expected: %v, got %v", expectedMap, testMap)
}
}
func verifyChart(t *testing.T, c *chart.Chart) { func verifyChart(t *testing.T, c *chart.Chart) {
t.Helper() t.Helper()
if c.Name() == "" { if c.Name() == "" {

@ -13,7 +13,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package chartutil package util
import ( import (
"fmt" "fmt"

@ -13,7 +13,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package chartutil package util
import ( import (
"testing" "testing"

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package chartutil package util
import ( import (
"os" "os"

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package chartutil package util
import ( import (
"testing" "testing"

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package chartutil package util
import ( import (
"fmt" "fmt"

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package chartutil package util
import ( import (
"encoding/json" "encoding/json"

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package chartutil package util
import "github.com/Masterminds/semver/v3" import "github.com/Masterminds/semver/v3"

@ -15,7 +15,7 @@ limitations under the License.
*/ */
// Package version represents the current version of the project. // Package version represents the current version of the project.
package chartutil package util
import "testing" import "testing"

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package chartutil package util
import ( import (
"fmt" "fmt"

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package chartutil package util
import ( import (
"bytes" "bytes"

@ -13,7 +13,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package chartutil package util
import ( import (
"log" "log"

@ -12,7 +12,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package chartutil package util
import ( import (
"encoding/json" "encoding/json"

@ -15,7 +15,7 @@ limitations under the License.
*/ */
/* /*
Package chartutil contains tools for working with charts. package util contains tools for working with charts.
Charts are described in the chart package (pkg/chart). Charts are described in the chart package (pkg/chart).
This package provides utilities for serializing and deserializing charts. This package provides utilities for serializing and deserializing charts.
@ -42,4 +42,4 @@ into a Chart.
When creating charts in memory, use the 'helm.sh/helm/pkg/chart' When creating charts in memory, use the 'helm.sh/helm/pkg/chart'
package directly. package directly.
*/ */
package chartutil // import "helm.sh/helm/v4/pkg/chartutil" package util // import chartutil "helm.sh/helm/v4/pkg/chart/util"

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package chartutil package util
import ( import (
"fmt" "fmt"

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package chartutil package util
import ( import (
"testing" "testing"

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package chartutil package util
import ( import (
"io" "io"

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package chartutil package util
import ( import (
"os" "os"

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package chartutil package util
import ( import (
"bytes" "bytes"

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package chartutil package util
import ( import (
"os" "os"

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package chartutil package util
import ( import (
"archive/tar" "archive/tar"

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package chartutil package util
import ( import (
"archive/tar" "archive/tar"

Before

Width:  |  Height:  |  Size: 374 B

After

Width:  |  Height:  |  Size: 374 B

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

Loading…
Cancel
Save