Merge branch 'main' into rm_chart_repo_load_func

Signed-off-by: George Jenkins <gvjenkins@gmail.com>
pull/13578/head
George Jenkins 7 months ago committed by GitHub
commit e354dd296d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -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@051d91933864810ecd5e2ea2cfd98f6a5bca5347 #pin@6.3.2 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

@ -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/v2/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/v2/util.k8sVersionMinor=$(K8S_MODULES_MINOR_VER)
.PHONY: all .PHONY: all
all: build all: build

@ -17,12 +17,10 @@ limitations under the License.
package main // import "helm.sh/helm/v4/cmd/helm" package main // import "helm.sh/helm/v4/cmd/helm"
import ( import (
"fmt"
"io" "io"
"log" "log"
"os" "os"
"strings" "strings"
"time"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"sigs.k8s.io/yaml" "sigs.k8s.io/yaml"
@ -32,9 +30,10 @@ import (
"helm.sh/helm/v4/pkg/action" "helm.sh/helm/v4/pkg/action"
"helm.sh/helm/v4/pkg/cli" "helm.sh/helm/v4/pkg/cli"
helmcmd "helm.sh/helm/v4/pkg/cmd"
"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" release "helm.sh/helm/v4/pkg/release/v1"
"helm.sh/helm/v4/pkg/storage/driver" "helm.sh/helm/v4/pkg/storage/driver"
) )
@ -44,17 +43,9 @@ func init() {
log.SetFlags(log.Lshortfile) log.SetFlags(log.Lshortfile)
} }
func debug(format string, v ...interface{}) { // hookOutputWriter provides the writer for writing hook logs.
if settings.Debug { func hookOutputWriter(_, _, _ string) io.Writer {
timeNow := time.Now().String() return log.Writer()
format = fmt.Sprintf("%s [debug] %s\n", timeNow, format)
log.Output(2, fmt.Sprintf(format, v...))
}
}
func warning(format string, v ...interface{}) {
format = fmt.Sprintf("WARNING: %s\n", format)
fmt.Fprintf(os.Stderr, format, v...)
} }
func main() { func main() {
@ -65,28 +56,28 @@ func main() {
kube.ManagedFieldsManager = "helm" kube.ManagedFieldsManager = "helm"
actionConfig := new(action.Configuration) actionConfig := new(action.Configuration)
cmd, err := newRootCmd(actionConfig, os.Stdout, os.Args[1:]) cmd, err := helmcmd.NewRootCmd(actionConfig, os.Stdout, os.Args[1:])
if err != nil { if err != nil {
warning("%+v", err) helmcmd.Warning("%+v", err)
os.Exit(1) os.Exit(1)
} }
// run when each command's execute method is called
cobra.OnInitialize(func() { cobra.OnInitialize(func() {
helmDriver := os.Getenv("HELM_DRIVER") helmDriver := os.Getenv("HELM_DRIVER")
if err := actionConfig.Init(settings.RESTClientGetter(), settings.Namespace(), helmDriver, debug); err != nil { if err := actionConfig.Init(settings.RESTClientGetter(), settings.Namespace(), helmDriver, helmcmd.Debug); err != nil {
log.Fatal(err) log.Fatal(err)
} }
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 {
debug("%+v", err) helmcmd.Debug("%+v", err)
switch e := err.(type) { switch e := err.(type) {
case pluginError: case helmcmd.PluginError:
os.Exit(e.code) os.Exit(e.Code)
default: default:
os.Exit(1) os.Exit(1)
} }

@ -18,153 +18,12 @@ package main
import ( import (
"bytes" "bytes"
"io"
"os" "os"
"os/exec" "os/exec"
"runtime" "runtime"
"strings"
"testing" "testing"
shellwords "github.com/mattn/go-shellwords"
"github.com/spf13/cobra"
"helm.sh/helm/v4/internal/test"
"helm.sh/helm/v4/pkg/action"
"helm.sh/helm/v4/pkg/chartutil"
"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) { func TestPluginExitCode(t *testing.T) {
if os.Getenv("RUN_MAIN_FOR_TESTING") == "1" { if os.Getenv("RUN_MAIN_FOR_TESTING") == "1" {
os.Args = []string{"helm", "exitwith", "2"} os.Args = []string{"helm", "exitwith", "2"}
@ -190,10 +49,8 @@ func TestPluginExitCode(t *testing.T) {
"RUN_MAIN_FOR_TESTING=1", "RUN_MAIN_FOR_TESTING=1",
// See pkg/cli/environment.go for which envvars can be used for configuring these passes // 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. // 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 // This mimics the "exitwith" test case in TestLoadPlugins using envvars
"HELM_PLUGINS=testdata/helmhome/helm/plugins", "HELM_PLUGINS=../../pkg/cmd/testdata/helmhome/helm/plugins",
"HELM_REPOSITORY_CONFIG=testdata/helmhome/helm/repositories.yaml",
"HELM_REPOSITORY_CACHE=testdata/helmhome/helm/repository",
) )
stdout := &bytes.Buffer{} stdout := &bytes.Buffer{}
stderr := &bytes.Buffer{} stderr := &bytes.Buffer{}

@ -11,7 +11,7 @@ require (
github.com/Masterminds/squirrel v1.5.4 github.com/Masterminds/squirrel v1.5.4
github.com/Masterminds/vcs v1.13.3 github.com/Masterminds/vcs v1.13.3
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.26
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.3 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
@ -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.33.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/yaml v1.4.0 sigs.k8s.io/yaml v1.4.0
) )
@ -63,7 +64,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
@ -173,8 +174,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

@ -48,8 +48,8 @@ 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/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 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNSjIRk=
github.com/chai2010/gettext-go v1.0.2/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA= 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.26 h1:3cs8K2RHlMQaPifLqgRyI4VBkoldNdEw62cb7qQga7k=
github.com/containerd/containerd v1.7.25/go.mod h1:tWfHzVI0azhw4CT2vaIjsb2CoV4LJ9PrMPaULAr21Ok= github.com/containerd/containerd v1.7.26/go.mod h1:m4JU0E+h0ebbo9yXD7Hyt+sWnc8tChm7MudCjj4jRvQ=
github.com/containerd/errdefs v0.3.0 h1:FSZgGOeK4yuT/+DnF07/Olde/q4KBoMsaamhXxIMDp4= 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/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 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
@ -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=
@ -313,9 +313,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=
@ -401,8 +400,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.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= 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=
@ -519,26 +518,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=

@ -25,7 +25,7 @@ import (
"time" "time"
"helm.sh/helm/v4/internal/version" "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. // SearchPath is the url path to the search API in monocular.

@ -27,8 +27,8 @@ import (
"github.com/Masterminds/semver/v3" "github.com/Masterminds/semver/v3"
"github.com/pkg/errors" "github.com/pkg/errors"
"helm.sh/helm/v4/pkg/chart" chart "helm.sh/helm/v4/pkg/chart/v2"
"helm.sh/helm/v4/pkg/chart/loader" "helm.sh/helm/v4/pkg/chart/v2/loader"
"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/registry" "helm.sh/helm/v4/pkg/registry"

@ -19,7 +19,7 @@ import (
"runtime" "runtime"
"testing" "testing"
"helm.sh/helm/v4/pkg/chart" chart "helm.sh/helm/v4/pkg/chart/v2"
"helm.sh/helm/v4/pkg/registry" "helm.sh/helm/v4/pkg/registry"
) )

@ -19,6 +19,7 @@ package action
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"io"
"os" "os"
"path" "path"
"path/filepath" "path/filepath"
@ -32,14 +33,14 @@ import (
"k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest" "k8s.io/client-go/rest"
"helm.sh/helm/v4/pkg/chart" chart "helm.sh/helm/v4/pkg/chart/v2"
"helm.sh/helm/v4/pkg/chartutil" chartutil "helm.sh/helm/v4/pkg/chart/v2/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" releaseutil "helm.sh/helm/v4/pkg/release/util"
"helm.sh/helm/v4/pkg/releaseutil" release "helm.sh/helm/v4/pkg/release/v1"
"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
@ -422,6 +426,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
}

@ -24,11 +24,11 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
fakeclientset "k8s.io/client-go/kubernetes/fake" fakeclientset "k8s.io/client-go/kubernetes/fake"
"helm.sh/helm/v4/pkg/chart" chart "helm.sh/helm/v4/pkg/chart/v2"
"helm.sh/helm/v4/pkg/chartutil" chartutil "helm.sh/helm/v4/pkg/chart/v2/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" release "helm.sh/helm/v4/pkg/release/v1"
"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"
@ -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,8 +26,8 @@ import (
"github.com/Masterminds/semver/v3" "github.com/Masterminds/semver/v3"
"github.com/gosuri/uitable" "github.com/gosuri/uitable"
"helm.sh/helm/v4/pkg/chart" chart "helm.sh/helm/v4/pkg/chart/v2"
"helm.sh/helm/v4/pkg/chart/loader" "helm.sh/helm/v4/pkg/chart/v2/loader"
) )
// Dependency is the action for building a given chart's dependency tree. // Dependency is the action for building a given chart's dependency tree.

@ -25,8 +25,8 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"helm.sh/helm/v4/internal/test" "helm.sh/helm/v4/internal/test"
"helm.sh/helm/v4/pkg/chart" chart "helm.sh/helm/v4/pkg/chart/v2"
"helm.sh/helm/v4/pkg/chartutil" chartutil "helm.sh/helm/v4/pkg/chart/v2/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/release" release "helm.sh/helm/v4/pkg/release/v1"
) )
// Get is the action for checking a given release's information. // Get is the action for checking a given release's information.

@ -21,7 +21,7 @@ import (
"strings" "strings"
"time" "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. // GetMetadata is the action for checking a given release's metadata.

@ -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/v2/util"
) )
// GetValues is the action for checking a given release's values. // GetValues is the action for checking a given release's values.

@ -19,8 +19,8 @@ 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/v2/util"
"helm.sh/helm/v4/pkg/release" release "helm.sh/helm/v4/pkg/release/v1"
) )
// History is the action for checking the release's ledger. // History is the action for checking the release's ledger.

@ -17,13 +17,20 @@ 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/kube" release "helm.sh/helm/v4/pkg/release/v1"
"helm.sh/helm/v4/pkg/release"
helmtime "helm.sh/helm/v4/pkg/time" helmtime "helm.sh/helm/v4/pkg/time"
) )
@ -87,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
} }
@ -98,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
} }
@ -158,3 +175,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"
chart "helm.sh/helm/v4/pkg/chart/v2"
kubefake "helm.sh/helm/v4/pkg/kube/fake"
release "helm.sh/helm/v4/pkg/release/v1"
)
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)
}

@ -39,8 +39,8 @@ import (
"k8s.io/cli-runtime/pkg/resource" "k8s.io/cli-runtime/pkg/resource"
"sigs.k8s.io/yaml" "sigs.k8s.io/yaml"
"helm.sh/helm/v4/pkg/chart" chart "helm.sh/helm/v4/pkg/chart/v2"
"helm.sh/helm/v4/pkg/chartutil" chartutil "helm.sh/helm/v4/pkg/chart/v2/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"
@ -48,8 +48,8 @@ import (
kubefake "helm.sh/helm/v4/pkg/kube/fake" kubefake "helm.sh/helm/v4/pkg/kube/fake"
"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" releaseutil "helm.sh/helm/v4/pkg/release/util"
"helm.sh/helm/v4/pkg/releaseutil" release "helm.sh/helm/v4/pkg/release/v1"
"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"
@ -32,10 +33,10 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"helm.sh/helm/v4/internal/test" "helm.sh/helm/v4/internal/test"
"helm.sh/helm/v4/pkg/chart" chart "helm.sh/helm/v4/pkg/chart/v2"
"helm.sh/helm/v4/pkg/chartutil" chartutil "helm.sh/helm/v4/pkg/chart/v2/util"
kubefake "helm.sh/helm/v4/pkg/kube/fake" 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" "helm.sh/helm/v4/pkg/storage/driver"
helmtime "helm.sh/helm/v4/pkg/time" helmtime "helm.sh/helm/v4/pkg/time"
) )
@ -354,11 +355,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/v2/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"
) )

@ -22,8 +22,8 @@ import (
"k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/labels"
"helm.sh/helm/v4/pkg/release" releaseutil "helm.sh/helm/v4/pkg/release/util"
"helm.sh/helm/v4/pkg/releaseutil" release "helm.sh/helm/v4/pkg/release/v1"
) )
// 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

@ -21,7 +21,7 @@ import (
"github.com/stretchr/testify/assert" "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" "helm.sh/helm/v4/pkg/storage"
) )

@ -26,8 +26,8 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"golang.org/x/term" "golang.org/x/term"
"helm.sh/helm/v4/pkg/chart/loader" "helm.sh/helm/v4/pkg/chart/v2/loader"
"helm.sh/helm/v4/pkg/chartutil" chartutil "helm.sh/helm/v4/pkg/chart/v2/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/v2/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,14 +20,15 @@ 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/v2/util"
"helm.sh/helm/v4/pkg/release" release "helm.sh/helm/v4/pkg/release/v1"
) )
const ( const (
@ -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,8 +24,8 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"helm.sh/helm/v4/pkg/chartutil" chartutil "helm.sh/helm/v4/pkg/chart/v2/util"
"helm.sh/helm/v4/pkg/release" release "helm.sh/helm/v4/pkg/release/v1"
helmtime "helm.sh/helm/v4/pkg/time" helmtime "helm.sh/helm/v4/pkg/time"
) )

@ -25,9 +25,9 @@ import (
"k8s.io/cli-runtime/pkg/printers" "k8s.io/cli-runtime/pkg/printers"
"sigs.k8s.io/yaml" "sigs.k8s.io/yaml"
"helm.sh/helm/v4/pkg/chart" chart "helm.sh/helm/v4/pkg/chart/v2"
"helm.sh/helm/v4/pkg/chart/loader" "helm.sh/helm/v4/pkg/chart/v2/loader"
"helm.sh/helm/v4/pkg/chartutil" chartutil "helm.sh/helm/v4/pkg/chart/v2/util"
"helm.sh/helm/v4/pkg/registry" "helm.sh/helm/v4/pkg/registry"
) )

@ -19,7 +19,7 @@ package action
import ( import (
"testing" "testing"
"helm.sh/helm/v4/pkg/chart" chart "helm.sh/helm/v4/pkg/chart/v2"
) )
func TestShow(t *testing.T) { func TestShow(t *testing.T) {

@ -21,7 +21,7 @@ import (
"errors" "errors"
"helm.sh/helm/v4/pkg/kube" "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. // Status is the action for checking the deployment status of releases.

@ -24,10 +24,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/v2/util"
"helm.sh/helm/v4/pkg/kube" "helm.sh/helm/v4/pkg/kube"
"helm.sh/helm/v4/pkg/release" releaseutil "helm.sh/helm/v4/pkg/release/util"
"helm.sh/helm/v4/pkg/releaseutil" release "helm.sh/helm/v4/pkg/release/v1"
helmtime "helm.sh/helm/v4/pkg/time" helmtime "helm.sh/helm/v4/pkg/time"
) )

@ -23,7 +23,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
kubefake "helm.sh/helm/v4/pkg/kube/fake" 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 { func uninstallAction(t *testing.T) *Uninstall {

@ -28,13 +28,13 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/cli-runtime/pkg/resource" "k8s.io/cli-runtime/pkg/resource"
"helm.sh/helm/v4/pkg/chart" chart "helm.sh/helm/v4/pkg/chart/v2"
"helm.sh/helm/v4/pkg/chartutil" chartutil "helm.sh/helm/v4/pkg/chart/v2/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" releaseutil "helm.sh/helm/v4/pkg/release/util"
"helm.sh/helm/v4/pkg/releaseutil" release "helm.sh/helm/v4/pkg/release/v1"
"helm.sh/helm/v4/pkg/storage/driver" "helm.sh/helm/v4/pkg/storage/driver"
) )

@ -23,14 +23,14 @@ import (
"testing" "testing"
"time" "time"
"helm.sh/helm/v4/pkg/chart" chart "helm.sh/helm/v4/pkg/chart/v2"
"helm.sh/helm/v4/pkg/storage/driver" "helm.sh/helm/v4/pkg/storage/driver"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
kubefake "helm.sh/helm/v4/pkg/kube/fake" 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" helmtime "helm.sh/helm/v4/pkg/time"
) )

@ -13,7 +13,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package chart package v2
import ( import (
"path/filepath" "path/filepath"

@ -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 See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package chart package v2
import ( import (
"encoding/json" "encoding/json"

@ -13,7 +13,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package chart package v2
import "time" import "time"

@ -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 See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package chart package v2
import ( import (
"testing" "testing"

@ -1,6 +1,5 @@
/* /*
Copyright The Helm Authors. Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
You may obtain a copy of the License at You may obtain a copy of the License at
@ -14,17 +13,11 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package kube // import "helm.sh/helm/v4/pkg/kube" /*
Package v2 provides chart handling for apiVersion v1 and v2 charts
import "k8s.io/cli-runtime/pkg/genericclioptions"
// GetConfig returns a Kubernetes client config. 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
// Deprecated switches based on characteristics.
func GetConfig(kubeconfig, context, namespace string) *genericclioptions.ConfigFlags { */
cf := genericclioptions.NewConfigFlags(true) package v2
cf.Namespace = &namespace
cf.Context = &context
cf.KubeConfig = &kubeconfig
return cf
}

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

@ -13,7 +13,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package chart package v2
// File represents a file as a name/value pair. // File represents a file as a name/value pair.
// //

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

@ -30,7 +30,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"helm.sh/helm/v4/pkg/chart" chart "helm.sh/helm/v4/pkg/chart/v2"
) )
var drivePathPattern = regexp.MustCompile(`^[a-zA-Z]:/`) var drivePathPattern = regexp.MustCompile(`^[a-zA-Z]:/`)

@ -26,7 +26,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"helm.sh/helm/v4/internal/sympath" "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" "helm.sh/helm/v4/pkg/ignore"
) )

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

Before

Width:  |  Height:  |  Size: 374 B

After

Width:  |  Height:  |  Size: 374 B

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