Merge remote-tracking branch 'upstream/main' into fix1

pull/11926/head
Joe Julian 11 months ago
commit 4b28d7b08d
No known key found for this signature in database
GPG Key ID: FAB12BE0575D999B

@ -4,4 +4,8 @@ updates:
- package-ecosystem: "gomod"
directory: "/"
schedule:
interval: "daily"
interval: "daily"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "daily"

@ -13,11 +13,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout source code
uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b # pin@v3.2.0
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # pin@v3.5.3
- name: Setup Go
uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568 # pin@3.5.0
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # pin@4.1.0
with:
go-version: '1.18'
go-version: '1.20'
- name: Install golangci-lint
run: |
curl -sSLO https://github.com/golangci/golangci-lint/releases/download/v$GOLANGCI_LINT_VERSION/golangci-lint-$GOLANGCI_LINT_VERSION-linux-amd64.tar.gz
@ -26,11 +26,11 @@ jobs:
sudo mv golangci-lint-$GOLANGCI_LINT_VERSION-linux-amd64/golangci-lint /usr/local/bin/golangci-lint
rm -rf golangci-lint-$GOLANGCI_LINT_VERSION-linux-amd64*
env:
GOLANGCI_LINT_VERSION: '1.46.2'
GOLANGCI_LINT_SHA256: '242cd4f2d6ac0556e315192e8555784d13da5d1874e51304711570769c4f2b9b'
GOLANGCI_LINT_VERSION: '1.51.2'
GOLANGCI_LINT_SHA256: '4de479eb9d9bc29da51aec1834e7c255b333723d38dbd56781c68e5dddc6a90b'
- name: Test style
run: make test-style
- name: Run unit tests
run: make test-coverage
- name: Test build
run: make test build
run: make build

@ -35,11 +35,11 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b # pin@v3.2.0
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # pin@v3.5.3
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@959cbb7472c4d4ad70cdfe6f4976053fe48ab394 # pinv2.1.37
uses: github/codeql-action/init@5b6282e01c62d02e720b81eb8a51204f527c3624 # pinv2.21.3
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@ -50,7 +50,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@959cbb7472c4d4ad70cdfe6f4976053fe48ab394 # pinv2.1.37
uses: github/codeql-action/autobuild@5b6282e01c62d02e720b81eb8a51204f527c3624 # pinv2.21.3
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
@ -64,4 +64,4 @@ jobs:
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@959cbb7472c4d4ad70cdfe6f4976053fe48ab394 # pinv2.1.37
uses: github/codeql-action/analyze@5b6282e01c62d02e720b81eb8a51204f527c3624 # pinv2.21.3

@ -18,12 +18,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout source code
uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b # pin@v3.2.0
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # pin@v3.5.3
- name: Setup Go
uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568 # pin@3.5.0
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # pin@4.1.0
with:
go-version: '1.18'
go-version: '1.20'
- name: Run unit tests
run: make test-coverage
@ -49,12 +49,12 @@ jobs:
if: github.ref == 'refs/heads/main'
steps:
- name: Checkout source code
uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b # pin@v3.2.0
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # pin@v3.5.3
- name: Setup Go
uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568 # pin@3.5.0
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # pin@4.1.0
with:
go-version: '1.18'
go-version: '1.20'
- name: Run unit tests
run: make test-coverage

@ -1,19 +1,19 @@
maintainers:
- adamreese
- bacongobbler
- hickeyma
- joejulian
- jdolitsky
- marckhouzam
- mattfarina
- sabre1041
- scottrigby
- SlickNik
- technosophos
triage:
- joejulian
- yxxhero
- zonggen
- gjenkins8
emeritus:
- adamreese
- bacongobbler
- fibonacci1729
- jascott1
- michelleN
@ -22,6 +22,7 @@ emeritus:
- prydonius
- rimusz
- seh
- SlickNik
- thomastaylor312
- vaikas-google
- viglesiasce

@ -18,7 +18,6 @@ package main
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"testing"
@ -77,7 +76,7 @@ func TestCreateStarterCmd(t *testing.T) {
t.Logf("Created %s", dest)
}
tplpath := filepath.Join(starterchart, "starterchart", "templates", "foo.tpl")
if err := ioutil.WriteFile(tplpath, []byte("test"), 0644); err != nil {
if err := os.WriteFile(tplpath, []byte("test"), 0644); err != nil {
t.Fatalf("Could not write template: %s", err)
}
@ -140,7 +139,7 @@ func TestCreateStarterAbsoluteCmd(t *testing.T) {
t.Logf("Created %s", dest)
}
tplpath := filepath.Join(starterchart, "starterchart", "templates", "foo.tpl")
if err := ioutil.WriteFile(tplpath, []byte("test"), 0644); err != nil {
if err := os.WriteFile(tplpath, []byte("test"), 0644); err != nil {
t.Fatalf("Could not write template: %s", err)
}

@ -48,6 +48,7 @@ func addValueOptionsFlags(f *pflag.FlagSet, v *values.Options) {
f.StringArrayVar(&v.StringValues, "set-string", []string{}, "set STRING values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)")
f.StringArrayVar(&v.FileValues, "set-file", []string{}, "set values from respective files specified via the command line (can specify multiple or separate values with commas: key1=path1,key2=path2)")
f.StringArrayVar(&v.JSONValues, "set-json", []string{}, "set JSON values on the command line (can specify multiple or separate values with commas: key1=jsonval1,key2=jsonval2)")
f.StringArrayVar(&v.LiteralValues, "set-literal", []string{}, "set a literal STRING value on the command line")
}
func addChartPathOptionsFlags(f *pflag.FlagSet, c *action.ChartPathOptions) {
@ -60,6 +61,7 @@ func addChartPathOptionsFlags(f *pflag.FlagSet, c *action.ChartPathOptions) {
f.StringVar(&c.CertFile, "cert-file", "", "identify HTTPS client using this SSL certificate file")
f.StringVar(&c.KeyFile, "key-file", "", "identify HTTPS client using this SSL key file")
f.BoolVar(&c.InsecureSkipTLSverify, "insecure-skip-tls-verify", false, "skip tls certificate checks for the chart download")
f.BoolVar(&c.PlainHTTP, "plain-http", false, "use insecure HTTP connections for the chart download")
f.StringVar(&c.CaFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle")
f.BoolVar(&c.PassCredentialsAll, "pass-credentials", false, "pass credentials to all domains")
}

@ -18,7 +18,7 @@ package main // import "helm.sh/helm/v3/cmd/helm"
import (
"fmt"
"io/ioutil"
"io"
"log"
"os"
"strings"
@ -106,10 +106,10 @@ func loadReleasesInMemory(actionConfig *action.Configuration) {
return
}
actionConfig.KubeClient = &kubefake.PrintingKubeClient{Out: ioutil.Discard}
actionConfig.KubeClient = &kubefake.PrintingKubeClient{Out: io.Discard}
for _, path := range filePaths {
b, err := ioutil.ReadFile(path)
b, err := os.ReadFile(path)
if err != nil {
log.Fatal("Unable to read memory driver data", err)
}

@ -18,7 +18,7 @@ package main
import (
"bytes"
"io/ioutil"
"io"
"os"
"os/exec"
"runtime"
@ -92,7 +92,7 @@ func executeActionCommandStdinC(store *storage.Storage, in *os.File, cmd string)
actionConfig := &action.Configuration{
Releases: store,
KubeClient: &kubefake.PrintingKubeClient{Out: ioutil.Discard},
KubeClient: &kubefake.PrintingKubeClient{Out: io.Discard},
Capabilities: chartutil.DefaultCapabilities,
Log: func(format string, v ...interface{}) {},
}

@ -136,12 +136,19 @@ func newInstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
return compInstall(args, toComplete, client)
},
RunE: func(_ *cobra.Command, args []string) error {
registryClient, err := newRegistryClient(client.CertFile, client.KeyFile, client.CaFile, client.InsecureSkipTLSverify)
registryClient, err := newRegistryClient(client.CertFile, client.KeyFile, client.CaFile,
client.InsecureSkipTLSverify, client.PlainHTTP)
if err != nil {
return fmt.Errorf("missing registry client: %w", err)
}
client.SetRegistryClient(registryClient)
// This is for the case where "" is specifically passed in as a
// value. When there is no value passed in NoOptDefVal will be used
// and it is set to client. See addInstallFlags.
if client.DryRunOption == "" {
client.DryRunOption = "none"
}
rel, err := runInstall(args, client, valueOpts, out)
if err != nil {
return errors.Wrap(err, "INSTALLATION FAILED")
@ -160,7 +167,13 @@ func newInstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
func addInstallFlags(cmd *cobra.Command, f *pflag.FlagSet, client *action.Install, valueOpts *values.Options) {
f.BoolVar(&client.CreateNamespace, "create-namespace", false, "create the release namespace if not present")
f.BoolVar(&client.DryRun, "dry-run", false, "simulate an install")
// --dry-run options with expected outcome:
// - Not set means no dry run and server is contacted.
// - Set with no value, a value of client, or a value of true and the server is not contacted
// - Set with a value of false, none, or false and the server is contacted
// The true/false part is meant to reflect some legacy behavior while none is equal to "".
f.StringVar(&client.DryRunOption, "dry-run", "", "simulate an install. If --dry-run is set with no option being specified or as '--dry-run=client', it will not attempt cluster connections. Setting '--dry-run=server' allows attempting cluster connections.")
f.Lookup("dry-run").NoOptDefVal = "client"
f.BoolVar(&client.Force, "force", false, "force resource updates through a replacement strategy")
f.BoolVar(&client.DisableHooks, "no-hooks", false, "prevent hooks from running during install")
f.BoolVar(&client.Replace, "replace", false, "re-use the given name, only if that name is a deleted release which remains in the history. This is unsafe in production")
@ -252,6 +265,7 @@ func runInstall(args []string, client *action.Install, valueOpts *values.Options
RepositoryConfig: settings.RepositoryConfig,
RepositoryCache: settings.RepositoryCache,
Debug: settings.Debug,
RegistryClient: client.GetRegistryClient(),
}
if err := man.Update(); err != nil {
return nil, err
@ -268,6 +282,11 @@ func runInstall(args []string, client *action.Install, valueOpts *values.Options
client.Namespace = settings.Namespace()
// Validate DryRunOption member is one of the allowed values
if err := validateDryRunOptionFlag(client.DryRunOption); err != nil {
return nil, err
}
// Create context and prepare the handle of SIGTERM
ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)
@ -308,3 +327,19 @@ func compInstall(args []string, toComplete string, client *action.Install) ([]st
}
return nil, cobra.ShellCompDirectiveNoFileComp
}
func validateDryRunOptionFlag(dryRunOptionFlagValue string) error {
// Validate dry-run flag value with a set of allowed value
allowedDryRunValues := []string{"false", "true", "none", "client", "server"}
isAllowed := false
for _, v := range allowedDryRunValues {
if dryRunOptionFlagValue == v {
isAllowed = true
break
}
}
if !isAllowed {
return errors.New("Invalid dry-run flag. Flag must one of the following: false, true, none, client, server")
}
return nil
}

@ -53,6 +53,11 @@ func TestLintCmdWithQuietFlag(t *testing.T) {
name: "lint chart with warning using --quiet flag",
cmd: "lint --quiet testdata/testcharts/chart-with-only-crds",
golden: "output/lint-quiet-with-warning.txt",
}, {
name: "lint non-existent chart using --quiet flag",
cmd: "lint --quiet thischartdoesntexist/",
golden: "",
wantError: true,
}}
runTestCmd(t, tests)

@ -19,7 +19,6 @@ import (
"bytes"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"os/exec"
@ -311,7 +310,7 @@ func addPluginCommands(plugin *plugin.Plugin, baseCmd *cobra.Command, cmds *plug
// loadFile takes a yaml file at the given path, parses it and returns a pluginCommand object
func loadFile(path string) (*pluginCommand, error) {
cmds := new(pluginCommand)
b, err := ioutil.ReadFile(path)
b, err := os.ReadFile(path)
if err != nil {
return cmds, fmt.Errorf("file (%s) not provided by plugin. No plugin auto-completion possible", path)
}

@ -19,7 +19,6 @@ package main
import (
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
@ -87,7 +86,7 @@ func newPackageCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
if client.DependencyUpdate {
downloadManager := &downloader.Manager{
Out: ioutil.Discard,
Out: io.Discard,
ChartPath: path,
Keyring: client.Keyring,
Getters: p,

@ -64,7 +64,8 @@ func newPullCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
client.Version = ">0.0.0-0"
}
registryClient, err := newRegistryClient(client.CertFile, client.KeyFile, client.CaFile, client.InsecureSkipTLSverify)
registryClient, err := newRegistryClient(client.CertFile, client.KeyFile, client.CaFile,
client.InsecureSkipTLSverify, client.PlainHTTP)
if err != nil {
return fmt.Errorf("missing registry client: %w", err)
}

@ -39,6 +39,7 @@ type registryPushOptions struct {
keyFile string
caFile string
insecureSkipTLSverify bool
plainHTTP bool
}
func newPushCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
@ -67,7 +68,7 @@ func newPushCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
return nil, cobra.ShellCompDirectiveNoFileComp
},
RunE: func(cmd *cobra.Command, args []string) error {
registryClient, err := newRegistryClient(o.certFile, o.keyFile, o.caFile, o.insecureSkipTLSverify)
registryClient, err := newRegistryClient(o.certFile, o.keyFile, o.caFile, o.insecureSkipTLSverify, o.plainHTTP)
if err != nil {
return fmt.Errorf("missing registry client: %w", err)
}
@ -77,6 +78,7 @@ func newPushCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
client := action.NewPushWithOpts(action.WithPushConfig(cfg),
action.WithTLSClientConfig(o.certFile, o.keyFile, o.caFile),
action.WithInsecureSkipTLSVerify(o.insecureSkipTLSverify),
action.WithPlainHTTP(o.plainHTTP),
action.WithPushOptWriter(out))
client.Settings = settings
output, err := client.Run(chartRef, remote)
@ -93,6 +95,7 @@ func newPushCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
f.StringVar(&o.keyFile, "key-file", "", "identify registry client using this SSL key file")
f.StringVar(&o.caFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle")
f.BoolVar(&o.insecureSkipTLSverify, "insecure-skip-tls-verify", false, "skip tls certificate checks for the chart upload")
f.BoolVar(&o.plainHTTP, "plain-http", false, "use insecure HTTP connections for the chart upload")
return cmd
}

@ -21,7 +21,6 @@ import (
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"strings"
@ -90,7 +89,7 @@ func getUsernamePassword(usernameOpt string, passwordOpt string, passwordFromStd
password := passwordOpt
if passwordFromStdinOpt {
passwordFromStdin, err := ioutil.ReadAll(os.Stdin)
passwordFromStdin, err := io.ReadAll(os.Stdin)
if err != nil {
return "", "", err
}

@ -20,7 +20,6 @@ import (
"context"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
@ -134,7 +133,7 @@ func (o *repoAddOptions) run(out io.Writer) error {
return err
}
b, err := ioutil.ReadFile(o.repoFile)
b, err := os.ReadFile(o.repoFile)
if err != nil && !os.IsNotExist(err) {
return err
}
@ -213,7 +212,7 @@ func (o *repoAddOptions) run(out io.Writer) error {
f.Update(&c)
if err := f.WriteFile(o.repoFile, 0644); err != nil {
if err := f.WriteFile(o.repoFile, 0600); err != nil {
return err
}
fmt.Fprintf(out, "%q has been added to your repositories\n", o.name)

@ -18,7 +18,7 @@ package main
import (
"fmt"
"io/ioutil"
"io"
"os"
"path/filepath"
"strings"
@ -102,7 +102,7 @@ func TestRepoAdd(t *testing.T) {
}
os.Setenv(xdg.CacheHomeEnvVar, rootDir)
if err := o.run(ioutil.Discard); err != nil {
if err := o.run(io.Discard); err != nil {
t.Error(err)
}
@ -126,11 +126,11 @@ func TestRepoAdd(t *testing.T) {
o.forceUpdate = true
if err := o.run(ioutil.Discard); err != nil {
if err := o.run(io.Discard); err != nil {
t.Errorf("Repository was not updated: %s", err)
}
if err := o.run(ioutil.Discard); err != nil {
if err := o.run(io.Discard); err != nil {
t.Errorf("Duplicate repository name was added")
}
}
@ -159,7 +159,7 @@ func TestRepoAddCheckLegalName(t *testing.T) {
wantErrorMsg := fmt.Sprintf("repository name (%s) contains '/', please specify a different name without '/'", testRepoName)
if err := o.run(ioutil.Discard); err != nil {
if err := o.run(io.Discard); err != nil {
if wantErrorMsg != err.Error() {
t.Fatalf("Actual error %s, not equal to expected error %s", err, wantErrorMsg)
}
@ -211,14 +211,14 @@ func repoAddConcurrent(t *testing.T, testName, repoFile string) {
forceUpdate: false,
repoFile: repoFile,
}
if err := o.run(ioutil.Discard); err != nil {
if err := o.run(io.Discard); err != nil {
t.Error(err)
}
}(fmt.Sprintf("%s-%d", testName, i))
}
wg.Wait()
b, err := ioutil.ReadFile(repoFile)
b, err := os.ReadFile(repoFile)
if err != nil {
t.Error(err)
}

@ -67,7 +67,7 @@ func (o *repoRemoveOptions) run(out io.Writer) error {
if !r.Remove(name) {
return errors.Errorf("no repo named %q found", name)
}
if err := r.WriteFile(o.repoFile, 0644); err != nil {
if err := r.WriteFile(o.repoFile, 0600); err != nil {
return err
}

@ -19,7 +19,6 @@ import (
"bytes"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
@ -119,7 +118,7 @@ func TestUpdateCustomCacheCmd(t *testing.T) {
repoFile: filepath.Join(ts.Root(), "repositories.yaml"),
repoCache: cachePath,
}
b := ioutil.Discard
b := io.Discard
if err := o.run(b); err != nil {
t.Fatal(err)
}

@ -17,7 +17,7 @@ package require
import (
"fmt"
"io/ioutil"
"io"
"strings"
"testing"
@ -71,7 +71,7 @@ func runTestCases(t *testing.T, testCases []testCase) {
Args: tc.validateFunc,
}
cmd.SetArgs(tc.args)
cmd.SetOutput(ioutil.Discard)
cmd.SetOutput(io.Discard)
err := cmd.Execute()
if tc.wantError == "" {

@ -152,7 +152,7 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string
flags.ParseErrorsWhitelist.UnknownFlags = true
flags.Parse(args)
registryClient, err := newDefaultRegistryClient()
registryClient, err := newDefaultRegistryClient(false)
if err != nil {
return nil, err
}
@ -257,7 +257,7 @@ func checkForExpiredRepos(repofile string) {
}
func newRegistryClient(certFile, keyFile, caFile string, insecureSkipTLSverify bool) (*registry.Client, error) {
func newRegistryClient(certFile, keyFile, caFile string, insecureSkipTLSverify, plainHTTP bool) (*registry.Client, error) {
if certFile != "" && keyFile != "" || caFile != "" || insecureSkipTLSverify {
registryClient, err := newRegistryClientWithTLS(certFile, keyFile, caFile, insecureSkipTLSverify)
if err != nil {
@ -265,21 +265,26 @@ func newRegistryClient(certFile, keyFile, caFile string, insecureSkipTLSverify b
}
return registryClient, nil
}
registryClient, err := newDefaultRegistryClient()
registryClient, err := newDefaultRegistryClient(plainHTTP)
if err != nil {
return nil, err
}
return registryClient, nil
}
func newDefaultRegistryClient() (*registry.Client, error) {
// Create a new registry client
registryClient, err := registry.NewClient(
func newDefaultRegistryClient(plainHTTP bool) (*registry.Client, error) {
opts := []registry.ClientOption{
registry.ClientOptDebug(settings.Debug),
registry.ClientOptEnableCache(true),
registry.ClientOptWriter(os.Stderr),
registry.ClientOptCredentialsFile(settings.RegistryConfig),
)
}
if plainHTTP {
opts = append(opts, registry.ClientOptPlainHTTP())
}
// Create a new registry client
registryClient, err := registry.NewClient(opts...)
if err != nil {
return nil, err
}

@ -14,7 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
/*Package search provides client-side repository searching.
/*
Package search provides client-side repository searching.
This supports building an in-memory search index based on the contents of
multiple repositories, and then using string matching or regular expressions
@ -146,11 +147,10 @@ func (i *Index) SearchLiteral(term string, threshold int) []*Result {
term = strings.ToLower(term)
buf := []*Result{}
for k, v := range i.lines {
lk := strings.ToLower(k)
lv := strings.ToLower(v)
res := strings.Index(lv, term)
if score := i.calcScore(res, lv); res != -1 && score < threshold {
parts := strings.Split(lk, verSep) // Remove version, if it is there.
parts := strings.Split(k, verSep) // Remove version, if it is there.
buf = append(buf, &Result{Name: parts[0], Score: score, Chart: i.charts[k]})
}
}

@ -105,11 +105,11 @@ func loadTestIndex(t *testing.T, all bool) *Index {
i := NewIndex()
i.AddRepo("testing", &repo.IndexFile{Entries: indexfileEntries}, all)
i.AddRepo("ztesting", &repo.IndexFile{Entries: map[string]repo.ChartVersions{
"pinta": {
"Pinta": {
{
URLs: []string{"http://example.com/charts/pinta-2.0.0.tgz"},
Metadata: &chart.Metadata{
Name: "pinta",
Name: "Pinta",
Version: "2.0.0",
Description: "Two ship, version two",
},
@ -170,14 +170,14 @@ func TestSearchByName(t *testing.T) {
query: "pinta",
expect: []*Result{
{Name: "testing/pinta"},
{Name: "ztesting/pinta"},
{Name: "ztesting/Pinta"},
},
},
{
name: "repo-specific search for one result",
query: "ztesting/pinta",
expect: []*Result{
{Name: "ztesting/pinta"},
{Name: "ztesting/Pinta"},
},
},
{
@ -199,7 +199,15 @@ func TestSearchByName(t *testing.T) {
query: "two",
expect: []*Result{
{Name: "testing/pinta"},
{Name: "ztesting/pinta"},
{Name: "ztesting/Pinta"},
},
},
{
name: "search mixedCase and result should be mixedCase too",
query: "pinta",
expect: []*Result{
{Name: "testing/pinta"},
{Name: "ztesting/Pinta"},
},
},
{
@ -207,7 +215,7 @@ func TestSearchByName(t *testing.T) {
query: "TWO",
expect: []*Result{
{Name: "testing/pinta"},
{Name: "ztesting/pinta"},
{Name: "ztesting/Pinta"},
},
},
{

@ -21,7 +21,6 @@ import (
"bytes"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
@ -259,7 +258,7 @@ func compListChartsOfRepo(repoName string, prefix string) []string {
var charts []string
path := filepath.Join(settings.RepositoryCache, helmpath.CacheChartsFile(repoName))
content, err := ioutil.ReadFile(path)
content, err := os.ReadFile(path)
if err == nil {
scanner := bufio.NewScanner(bytes.NewReader(content))
for scanner.Scan() {

@ -226,7 +226,8 @@ func runShow(args []string, client *action.Show) (string, error) {
}
func addRegistryClient(client *action.Show) error {
registryClient, err := newRegistryClient(client.CertFile, client.KeyFile, client.CaFile, client.InsecureSkipTLSverify)
registryClient, err := newRegistryClient(client.CertFile, client.KeyFile, client.CaFile,
client.InsecureSkipTLSverify, client.PlainHTTP)
if err != nil {
return fmt.Errorf("missing registry client: %w", err)
}

@ -73,12 +73,19 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
client.KubeVersion = parsedKubeVersion
}
registryClient, err := newRegistryClient(client.CertFile, client.KeyFile, client.CaFile, client.InsecureSkipTLSverify)
registryClient, err := newRegistryClient(client.CertFile, client.KeyFile, client.CaFile,
client.InsecureSkipTLSverify, client.PlainHTTP)
if err != nil {
return fmt.Errorf("missing registry client: %w", err)
}
client.SetRegistryClient(registryClient)
// This is for the case where "" is specifically passed in as a
// value. When there is no value passed in NoOptDefVal will be used
// and it is set to client. See addInstallFlags.
if client.DryRunOption == "" {
client.DryRunOption = "true"
}
client.DryRun = true
client.ReleaseName = "release-name"
client.Replace = true // Skip the name check

@ -25,6 +25,8 @@ import (
var chartPath = "testdata/testcharts/subchart"
func TestTemplateCmd(t *testing.T) {
deletevalchart := "testdata/testcharts/issue-9027"
tests := []cmdTestCase{
{
name: "check name",
@ -131,6 +133,34 @@ func TestTemplateCmd(t *testing.T) {
cmd: fmt.Sprintf(`template '%s' --skip-tests`, chartPath),
golden: "output/template-skip-tests.txt",
},
{
// This test case is to ensure the case where specified dependencies
// in the Chart.yaml and those where the Chart.yaml don't have them
// specified are the same.
name: "ensure nil/null values pass to subcharts delete values",
cmd: fmt.Sprintf("template '%s'", deletevalchart),
golden: "output/issue-9027.txt",
},
{
// Ensure that imported values take precedence over parent chart values
name: "template with imported subchart values ensuring import",
cmd: fmt.Sprintf("template '%s' --set configmap.enabled=true --set subchartb.enabled=true", chartPath),
golden: "output/template-subchart-cm.txt",
},
{
// Ensure that user input values take precedence over imported
// values from sub-charts.
name: "template with imported subchart values set with --set",
cmd: fmt.Sprintf("template '%s' --set configmap.enabled=true --set subchartb.enabled=true --set configmap.value=baz", chartPath),
golden: "output/template-subchart-cm-set.txt",
},
{
// Ensure that user input values take precedence over imported
// values from sub-charts when passed by file
name: "template with imported subchart values set with --set",
cmd: fmt.Sprintf("template '%s' -f %s/extra_values.yaml", chartPath, chartPath),
golden: "output/template-subchart-cm-set-file.txt",
},
}
runTestCmd(t, tests)
}

@ -0,0 +1,32 @@
---
# Source: issue-9027/charts/subchart/templates/values.yaml
global:
hash:
key3: 13
key4: 4
key5: 5
key6: 6
hash:
key3: 13
key4: 4
key5: 5
key6: 6
---
# Source: issue-9027/templates/values.yaml
global:
hash:
key1: null
key2: null
key3: 13
subchart:
global:
hash:
key3: 13
key4: 4
key5: 5
key6: 6
hash:
key3: 13
key4: 4
key5: 5
key6: 6

@ -1,6 +1,6 @@
==> Linting testdata/testcharts/chart-with-bad-subcharts
[INFO] Chart.yaml: icon is recommended
[WARNING] templates/: directory not found
[ERROR] templates/: error unpacking bad-subchart in chart-with-bad-subcharts: validation: chart.metadata.name is required
[ERROR] : unable to load chart
error unpacking bad-subchart in chart-with-bad-subcharts: validation: chart.metadata.name is required
@ -9,12 +9,11 @@
[ERROR] Chart.yaml: apiVersion is required. The value must be either "v1" or "v2"
[ERROR] Chart.yaml: version is required
[INFO] Chart.yaml: icon is recommended
[WARNING] templates/: directory not found
[ERROR] templates/: validation: chart.metadata.name is required
[ERROR] : unable to load chart
validation: chart.metadata.name is required
==> Linting testdata/testcharts/chart-with-bad-subcharts/charts/good-subchart
[INFO] Chart.yaml: icon is recommended
[WARNING] templates/: directory not found
Error: 3 chart(s) linted, 2 chart(s) failed

@ -1,6 +1,6 @@
==> Linting testdata/testcharts/chart-with-bad-subcharts
[INFO] Chart.yaml: icon is recommended
[WARNING] templates/: directory not found
[ERROR] templates/: error unpacking bad-subchart in chart-with-bad-subcharts: validation: chart.metadata.name is required
[ERROR] : unable to load chart
error unpacking bad-subchart in chart-with-bad-subcharts: validation: chart.metadata.name is required

@ -1,7 +1,7 @@
==> Linting testdata/testcharts/chart-bad-requirements
[ERROR] Chart.yaml: unable to parse YAML
error converting YAML to JSON: yaml: line 6: did not find expected '-' indicator
[WARNING] templates/: directory not found
[ERROR] templates/: cannot load Chart.yaml: error converting YAML to JSON: yaml: line 6: did not find expected '-' indicator
[ERROR] : unable to load chart
cannot load Chart.yaml: error converting YAML to JSON: yaml: line 6: did not find expected '-' indicator

@ -1,4 +0,0 @@
==> Linting testdata/testcharts/chart-with-only-crds
[WARNING] templates/: directory not found
1 chart(s) linted, 0 chart(s) failed

@ -0,0 +1,122 @@
---
# Source: subchart/templates/subdir/serviceaccount.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: subchart-sa
---
# Source: subchart/templates/subdir/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: subchart-cm
data:
value: qux
---
# Source: subchart/templates/subdir/role.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: subchart-role
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get","list","watch"]
---
# Source: subchart/templates/subdir/rolebinding.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: subchart-binding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: subchart-role
subjects:
- kind: ServiceAccount
name: subchart-sa
namespace: default
---
# Source: subchart/charts/subcharta/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: subcharta
labels:
helm.sh/chart: "subcharta-0.1.0"
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 80
protocol: TCP
name: apache
selector:
app.kubernetes.io/name: subcharta
---
# Source: subchart/charts/subchartb/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: subchartb
labels:
helm.sh/chart: "subchartb-0.1.0"
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 80
protocol: TCP
name: nginx
selector:
app.kubernetes.io/name: subchartb
---
# Source: subchart/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: subchart
labels:
helm.sh/chart: "subchart-0.1.0"
app.kubernetes.io/instance: "release-name"
kube-version/major: "1"
kube-version/minor: "20"
kube-version/version: "v1.20.0"
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 80
protocol: TCP
name: nginx
selector:
app.kubernetes.io/name: subchart
---
# Source: subchart/templates/tests/test-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: "release-name-testconfig"
annotations:
"helm.sh/hook": test
data:
message: Hello World
---
# Source: subchart/templates/tests/test-nothing.yaml
apiVersion: v1
kind: Pod
metadata:
name: "release-name-test"
annotations:
"helm.sh/hook": test
spec:
containers:
- name: test
image: "alpine:latest"
envFrom:
- configMapRef:
name: "release-name-testconfig"
command:
- echo
- "$message"
restartPolicy: Never

@ -0,0 +1,122 @@
---
# Source: subchart/templates/subdir/serviceaccount.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: subchart-sa
---
# Source: subchart/templates/subdir/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: subchart-cm
data:
value: baz
---
# Source: subchart/templates/subdir/role.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: subchart-role
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get","list","watch"]
---
# Source: subchart/templates/subdir/rolebinding.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: subchart-binding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: subchart-role
subjects:
- kind: ServiceAccount
name: subchart-sa
namespace: default
---
# Source: subchart/charts/subcharta/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: subcharta
labels:
helm.sh/chart: "subcharta-0.1.0"
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 80
protocol: TCP
name: apache
selector:
app.kubernetes.io/name: subcharta
---
# Source: subchart/charts/subchartb/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: subchartb
labels:
helm.sh/chart: "subchartb-0.1.0"
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 80
protocol: TCP
name: nginx
selector:
app.kubernetes.io/name: subchartb
---
# Source: subchart/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: subchart
labels:
helm.sh/chart: "subchart-0.1.0"
app.kubernetes.io/instance: "release-name"
kube-version/major: "1"
kube-version/minor: "20"
kube-version/version: "v1.20.0"
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 80
protocol: TCP
name: nginx
selector:
app.kubernetes.io/name: subchart
---
# Source: subchart/templates/tests/test-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: "release-name-testconfig"
annotations:
"helm.sh/hook": test
data:
message: Hello World
---
# Source: subchart/templates/tests/test-nothing.yaml
apiVersion: v1
kind: Pod
metadata:
name: "release-name-test"
annotations:
"helm.sh/hook": test
spec:
containers:
- name: test
image: "alpine:latest"
envFrom:
- configMapRef:
name: "release-name-testconfig"
command:
- echo
- "$message"
restartPolicy: Never

@ -0,0 +1,122 @@
---
# Source: subchart/templates/subdir/serviceaccount.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: subchart-sa
---
# Source: subchart/templates/subdir/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: subchart-cm
data:
value: bar
---
# Source: subchart/templates/subdir/role.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: subchart-role
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get","list","watch"]
---
# Source: subchart/templates/subdir/rolebinding.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: subchart-binding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: subchart-role
subjects:
- kind: ServiceAccount
name: subchart-sa
namespace: default
---
# Source: subchart/charts/subcharta/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: subcharta
labels:
helm.sh/chart: "subcharta-0.1.0"
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 80
protocol: TCP
name: apache
selector:
app.kubernetes.io/name: subcharta
---
# Source: subchart/charts/subchartb/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: subchartb
labels:
helm.sh/chart: "subchartb-0.1.0"
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 80
protocol: TCP
name: nginx
selector:
app.kubernetes.io/name: subchartb
---
# Source: subchart/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: subchart
labels:
helm.sh/chart: "subchart-0.1.0"
app.kubernetes.io/instance: "release-name"
kube-version/major: "1"
kube-version/minor: "20"
kube-version/version: "v1.20.0"
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 80
protocol: TCP
name: nginx
selector:
app.kubernetes.io/name: subchart
---
# Source: subchart/templates/tests/test-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: "release-name-testconfig"
annotations:
"helm.sh/hook": test
data:
message: Hello World
---
# Source: subchart/templates/tests/test-nothing.yaml
apiVersion: v1
kind: Pod
metadata:
name: "release-name-test"
annotations:
"helm.sh/hook": test
spec:
containers:
- name: test
image: "alpine:latest"
envFrom:
- configMapRef:
name: "release-name-testconfig"
command:
- echo
- "$message"
restartPolicy: Never

@ -1,5 +1,5 @@
---
# Source: crds/crdA.yaml
# Source: subchart/crds/crdA.yaml
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:

@ -1 +1 @@
version.BuildInfo{Version:"v3.11", GitCommit:"", GitTreeState:"", GoVersion:""}
version.BuildInfo{Version:"v3.12", GitCommit:"", GitTreeState:"", GoVersion:""}

@ -1 +1 @@
version.BuildInfo{Version:"v3.11", GitCommit:"", GitTreeState:"", GoVersion:""}
version.BuildInfo{Version:"v3.12", GitCommit:"", GitTreeState:"", GoVersion:""}

@ -1 +1 @@
Version: v3.11
Version: v3.12

@ -1 +1 @@
version.BuildInfo{Version:"v3.11", GitCommit:"", GitTreeState:"", GoVersion:""}
version.BuildInfo{Version:"v3.12", GitCommit:"", GitTreeState:"", GoVersion:""}

@ -0,0 +1,6 @@
apiVersion: v2
name: issue-9027
version: 0.1.0
dependencies:
- name: subchart
version: 0.1.0

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

@ -0,0 +1,17 @@
global:
hash:
key1: 1
key2: 2
key3: 3
key4: 4
key5: 5
key6: 6
hash:
key1: 1
key2: 2
key3: 3
key4: 4
key5: 5
key6: 6

@ -0,0 +1,11 @@
global:
hash:
key1: null
key2: null
key3: 13
subchart:
hash:
key1: null
key2: null
key3: 13

@ -29,6 +29,9 @@ dependencies:
parent: imported-chartA-B
- child: exports.SCBexported2
parent: exports.SCBexported2
# - child: exports.configmap
# parent: configmap
- configmap
- SCBexported1
tags:

@ -20,6 +20,10 @@ exports:
SCBexported2:
SCBexported2A: "blaster"
configmap:
configmap:
value: "bar"
global:
kolla:

@ -0,0 +1,5 @@
# This file is used to test values passed by file at the command line
configmap:
enabled: true
value: "qux"

@ -0,0 +1,8 @@
{{ if .Values.configmap.enabled -}}
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Chart.Name }}-cm
data:
value: {{ .Values.configmap.value }}
{{- end }}

@ -53,3 +53,7 @@ exports:
SC1exported2:
all:
SC1exported3: "SC1expstr"
configmap:
enabled: false
value: "foo"

@ -51,6 +51,10 @@ func newUninstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
return compListReleases(toComplete, args, cfg)
},
RunE: func(cmd *cobra.Command, args []string) error {
validationErr := validateCascadeFlag(client)
if validationErr != nil {
return validationErr
}
for i := 0; i < len(args); i++ {
res, err := client.Run(args[i])
@ -72,8 +76,16 @@ func newUninstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
f.BoolVar(&client.DisableHooks, "no-hooks", false, "prevent hooks from running during uninstallation")
f.BoolVar(&client.KeepHistory, "keep-history", false, "remove all associated resources and mark the release as deleted, but retain the release history")
f.BoolVar(&client.Wait, "wait", false, "if set, will wait until all the resources are deleted before returning. It will wait for as long as --timeout")
f.StringVar(&client.DeletionPropagation, "cascade", "background", "Must be \"background\", \"orphan\", or \"foreground\". Selects the deletion cascading strategy for the dependents. Defaults to background.")
f.DurationVar(&client.Timeout, "timeout", 300*time.Second, "time to wait for any individual Kubernetes operation (like Jobs for hooks)")
f.StringVar(&client.Description, "description", "", "add a custom description")
return cmd
}
func validateCascadeFlag(client *action.Uninstall) error {
if client.DeletionPropagation != "background" && client.DeletionPropagation != "foreground" && client.DeletionPropagation != "orphan" {
return fmt.Errorf("invalid cascade value (%s). Must be \"background\", \"foreground\", or \"orphan\"", client.DeletionPropagation)
}
return nil
}

@ -90,12 +90,19 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
RunE: func(cmd *cobra.Command, args []string) error {
client.Namespace = settings.Namespace()
registryClient, err := newRegistryClient(client.CertFile, client.KeyFile, client.CaFile, client.InsecureSkipTLSverify)
registryClient, err := newRegistryClient(client.CertFile, client.KeyFile, client.CaFile,
client.InsecureSkipTLSverify, client.PlainHTTP)
if err != nil {
return fmt.Errorf("missing registry client: %w", err)
}
client.SetRegistryClient(registryClient)
// This is for the case where "" is specifically passed in as a
// value. When there is no value passed in NoOptDefVal will be used
// and it is set to client. See addInstallFlags.
if client.DryRunOption == "" {
client.DryRunOption = "none"
}
// Fixes #7002 - Support reading values from STDIN for `upgrade` command
// Must load values AFTER determining if we have to call install so that values loaded from stdin are are not read twice
if client.Install {
@ -112,6 +119,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
instClient.ChartPathOptions = client.ChartPathOptions
instClient.Force = client.Force
instClient.DryRun = client.DryRun
instClient.DryRunOption = client.DryRunOption
instClient.DisableHooks = client.DisableHooks
instClient.SkipCRDs = client.SkipCRDs
instClient.Timeout = client.Timeout
@ -146,6 +154,10 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
if err != nil {
return err
}
// Validate dry-run flag value is one of the allowed values
if err := validateDryRunOptionFlag(client.DryRunOption); err != nil {
return err
}
p := getter.All(settings)
vals, err := valueOpts.MergeValues(p)
@ -221,7 +233,8 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
f.BoolVar(&createNamespace, "create-namespace", false, "if --install is set, create the release namespace if not present")
f.BoolVarP(&client.Install, "install", "i", false, "if a release by this name doesn't already exist, run an install")
f.BoolVar(&client.Devel, "devel", false, "use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored")
f.BoolVar(&client.DryRun, "dry-run", false, "simulate an upgrade")
f.StringVar(&client.DryRunOption, "dry-run", "", "simulate an install. If --dry-run is set with no option being specified or as '--dry-run=client', it will not attempt cluster connections. Setting '--dry-run=server' allows attempting cluster connections.")
f.Lookup("dry-run").NoOptDefVal = "client"
f.BoolVar(&client.Recreate, "recreate-pods", false, "performs pods restart for the resource if applicable")
f.MarkDeprecated("recreate-pods", "functionality will no longer be updated. Consult the documentation for other methods to recreate pods")
f.BoolVar(&client.Force, "force", false, "force resource updates through a replacement strategy")

@ -18,7 +18,6 @@ package main
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
@ -359,7 +358,7 @@ func TestUpgradeInstallWithValuesFromStdin(t *testing.T) {
func prepareMockRelease(releaseName string, t *testing.T) (func(n string, v int, ch *chart.Chart) *release.Release, *chart.Chart, string) {
tmpChart := ensure.TempDir(t)
configmapData, err := ioutil.ReadFile("testdata/testcharts/upgradetest/templates/configmap.yaml")
configmapData, err := os.ReadFile("testdata/testcharts/upgradetest/templates/configmap.yaml")
if err != nil {
t.Fatalf("Error loading template yaml %v", err)
}

108
go.mod

@ -1,52 +1,55 @@
module helm.sh/helm/v3
go 1.17
go 1.19
require (
github.com/BurntSushi/toml v1.2.1
github.com/BurntSushi/toml v1.3.2
github.com/DATA-DOG/go-sqlmock v1.5.0
github.com/Masterminds/semver/v3 v3.2.0
github.com/Masterminds/semver/v3 v3.2.1
github.com/Masterminds/sprig/v3 v3.2.3
github.com/Masterminds/squirrel v1.5.3
github.com/Masterminds/squirrel v1.5.4
github.com/Masterminds/vcs v1.13.3
github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535
github.com/containerd/containerd v1.6.15
github.com/containerd/containerd v1.7.0
github.com/cyphar/filepath-securejoin v0.2.3
github.com/distribution/distribution/v3 v3.0.0-20221208165359-362910506bc2
github.com/evanphx/json-patch v5.6.0+incompatible
github.com/foxcpp/go-mockdns v1.0.0
github.com/gobwas/glob v0.2.3
github.com/gofrs/flock v0.8.1
github.com/gosuri/uitable v0.0.4
github.com/hashicorp/go-multierror v1.1.1
github.com/jmoiron/sqlx v1.3.5
github.com/lib/pq v1.10.7
github.com/lib/pq v1.10.9
github.com/mattn/go-shellwords v1.0.12
github.com/mitchellh/copystructure v1.2.0
github.com/moby/term v0.0.0-20221205130635-1aeaba878587
github.com/opencontainers/image-spec v1.1.0-rc2
github.com/opencontainers/image-spec v1.1.0-rc4
github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5
github.com/pkg/errors v0.9.1
github.com/rubenv/sql-migrate v1.3.1
github.com/sirupsen/logrus v1.9.0
github.com/spf13/cobra v1.6.1
github.com/rubenv/sql-migrate v1.5.1
github.com/sirupsen/logrus v1.9.3
github.com/spf13/cobra v1.7.0
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.8.1
github.com/stretchr/testify v1.8.4
github.com/xeipuuv/gojsonschema v1.2.0
golang.org/x/crypto v0.5.0
golang.org/x/term v0.4.0
golang.org/x/text v0.6.0
k8s.io/api v0.26.0
k8s.io/apiextensions-apiserver v0.26.0
k8s.io/apimachinery v0.26.0
k8s.io/apiserver v0.26.0
k8s.io/cli-runtime v0.26.0
k8s.io/client-go v0.26.0
k8s.io/klog/v2 v2.80.1
k8s.io/kubectl v0.26.0
oras.land/oras-go v1.2.2
golang.org/x/crypto v0.11.0
golang.org/x/term v0.10.0
golang.org/x/text v0.11.0
k8s.io/api v0.27.3
k8s.io/apiextensions-apiserver v0.27.3
k8s.io/apimachinery v0.27.3
k8s.io/apiserver v0.27.3
k8s.io/cli-runtime v0.27.3
k8s.io/client-go v0.27.3
k8s.io/klog/v2 v2.100.1
k8s.io/kubectl v0.27.3
oras.land/oras-go v1.2.3
sigs.k8s.io/yaml v1.3.0
)
require (
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230106234847-43070de90fa1 // indirect
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
github.com/MakeNowJust/heredoc v1.0.0 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect
@ -56,32 +59,33 @@ require (
github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd // indirect
github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b // indirect
github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/chai2010/gettext-go v1.0.2 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/docker/cli v20.10.21+incompatible // indirect
github.com/docker/distribution v2.8.1+incompatible // indirect
github.com/docker/docker v20.10.21+incompatible // indirect
github.com/docker/cli v23.0.1+incompatible // indirect
github.com/docker/distribution v2.8.2+incompatible // indirect
github.com/docker/docker v23.0.3+incompatible // indirect
github.com/docker/docker-credential-helpers v0.7.0 // indirect
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect
github.com/docker/go-metrics v0.0.1 // indirect
github.com/docker/go-units v0.4.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1 // indirect
github.com/emicklei/go-restful/v3 v3.9.0 // indirect
github.com/emicklei/go-restful/v3 v3.10.1 // indirect
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect
github.com/fatih/color v1.13.0 // indirect
github.com/felixge/httpsnoop v1.0.3 // indirect
github.com/fvbommel/sortorder v1.0.1 // indirect
github.com/go-errors/errors v1.0.1 // indirect
github.com/go-gorp/gorp/v3 v3.0.5 // indirect
github.com/go-errors/errors v1.4.2 // indirect
github.com/go-gorp/gorp/v3 v3.1.0 // indirect
github.com/go-logr/logr v1.2.3 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.20.0 // indirect
github.com/go-openapi/swag v0.19.14 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/jsonpointer v0.19.6 // indirect
github.com/go-openapi/jsonreference v0.20.1 // indirect
github.com/go-openapi/swag v0.22.3 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/gomodule/redigo v1.8.2 // indirect
github.com/google/btree v1.0.1 // indirect
github.com/google/gnostic v0.5.7-v3refs // indirect
@ -92,21 +96,23 @@ require (
github.com/gorilla/handlers v1.5.1 // indirect
github.com/gorilla/mux v1.8.0 // indirect
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/golang-lru v0.5.4 // indirect
github.com/huandu/xstrings v1.4.0 // indirect
github.com/imdario/mergo v0.3.13 // indirect
github.com/inconshreveable/mousetrap v1.0.1 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.11.13 // indirect
github.com/klauspost/compress v1.16.0 // indirect
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
github.com/mailru/easyjson v0.7.6 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/mattn/go-runewidth v0.0.9 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/miekg/dns v1.1.25 // indirect
github.com/mitchellh/go-wordwrap v1.0.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/moby/locker v1.0.1 // indirect
@ -126,30 +132,32 @@ require (
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/shopspring/decimal v1.3.1 // indirect
github.com/spf13/cast v1.5.0 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xlab/treeprint v1.1.0 // indirect
github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43 // indirect
github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50 // indirect
github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f // indirect
go.opentelemetry.io/otel v1.14.0 // indirect
go.opentelemetry.io/otel/trace v1.14.0 // indirect
go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect
golang.org/x/net v0.5.0 // indirect
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b // indirect
golang.org/x/net v0.10.0 // indirect
golang.org/x/oauth2 v0.4.0 // indirect
golang.org/x/sync v0.1.0 // indirect
golang.org/x/sys v0.4.0 // indirect
golang.org/x/sys v0.10.0 // indirect
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21 // indirect
google.golang.org/grpc v1.49.0 // indirect
google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 // indirect
google.golang.org/grpc v1.53.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/component-base v0.26.0 // indirect
k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 // indirect
k8s.io/utils v0.0.0-20221107191617-1a15be271d1d // indirect
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect
sigs.k8s.io/kustomize/api v0.12.1 // indirect
sigs.k8s.io/kustomize/kyaml v0.13.9 // indirect
k8s.io/component-base v0.27.3 // indirect
k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f // indirect
k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5 // indirect
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
sigs.k8s.io/kustomize/api v0.13.2 // indirect
sigs.k8s.io/kustomize/kyaml v0.14.1 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
)

1344
go.sum

File diff suppressed because it is too large Load Diff

@ -18,7 +18,6 @@ package fileutil
import (
"io"
"io/ioutil"
"os"
"path/filepath"
@ -28,7 +27,7 @@ import (
// AtomicWriteFile atomically (as atomic as os.Rename allows) writes a file to a
// disk.
func AtomicWriteFile(filename string, reader io.Reader, mode os.FileMode) error {
tempFile, err := ioutil.TempFile(filepath.Split(filename))
tempFile, err := os.CreateTemp(filepath.Split(filename))
if err != nil {
return err
}

@ -18,7 +18,6 @@ package fileutil
import (
"bytes"
"io/ioutil"
"os"
"path/filepath"
"testing"
@ -37,7 +36,7 @@ func TestAtomicWriteFile(t *testing.T) {
t.Errorf("AtomicWriteFile error: %s", err)
}
got, err := ioutil.ReadFile(testpath)
got, err := os.ReadFile(testpath)
if err != nil {
t.Fatal(err)
}

@ -14,7 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
/*Package ignore provides tools for writing ignore files (a la .gitignore).
/*
Package ignore provides tools for writing ignore files (a la .gitignore).
This provides both an ignore parser and a file-aware processor.
@ -23,19 +24,19 @@ format for .gitignore files (https://git-scm.com/docs/gitignore).
The formatting rules are as follows:
- Parsing is line-by-line
- Empty lines are ignored
- Lines the begin with # (comments) will be ignored
- Leading and trailing spaces are always ignored
- Inline comments are NOT supported ('foo* # Any foo' does not contain a comment)
- There is no support for multi-line patterns
- Shell glob patterns are supported. See Go's "path/filepath".Match
- If a pattern begins with a leading !, the match will be negated.
- If a pattern begins with a leading /, only paths relatively rooted will match.
- If the pattern ends with a trailing /, only directories will match
- If a pattern contains no slashes, file basenames are tested (not paths)
- The pattern sequence "**", while legal in a glob, will cause an error here
(to indicate incompatibility with .gitignore).
- Parsing is line-by-line
- Empty lines are ignored
- Lines the begin with # (comments) will be ignored
- Leading and trailing spaces are always ignored
- Inline comments are NOT supported ('foo* # Any foo' does not contain a comment)
- There is no support for multi-line patterns
- Shell glob patterns are supported. See Go's "path/filepath".Match
- If a pattern begins with a leading !, the match will be negated.
- If a pattern begins with a leading /, only paths relatively rooted will match.
- If the pattern ends with a trailing /, only directories will match
- If a pattern contains no slashes, file basenames are tested (not paths)
- The pattern sequence "**", while legal in a glob, will cause an error here
(to indicate incompatibility with .gitignore).
Example:
@ -58,10 +59,10 @@ Example:
a[b-d].txt
Notable differences from .gitignore:
- The '**' syntax is not supported.
- The globbing library is Go's 'filepath.Match', not fnmatch(3)
- Trailing spaces are always ignored (there is no supported escape sequence)
- The evaluation of escape sequences has not been tested for compatibility
- There is no support for '\!' as a special leading sequence.
- The '**' syntax is not supported.
- The globbing library is Go's 'filepath.Match', not fnmatch(3)
- Trailing spaces are always ignored (there is no supported escape sequence)
- The evaluation of escape sequences has not been tested for compatibility
- There is no support for '\!' as a special leading sequence.
*/
package ignore // import "helm.sh/helm/v3/internal/ignore"

@ -114,7 +114,7 @@ func (c *Client) Search(term string) ([]SearchResult, error) {
p.RawQuery = "q=" + url.QueryEscape(term)
// Create request
req, err := http.NewRequest("GET", p.String(), nil)
req, err := http.NewRequest(http.MethodGet, p.String(), nil)
if err != nil {
return nil, err
}

@ -17,7 +17,6 @@ limitations under the License.
package ensure
import (
"io/ioutil"
"os"
"path/filepath"
"testing"
@ -44,7 +43,7 @@ func HelmHome(t *testing.T) func() {
// TempDir ensures a scratch test directory for unit testing purposes.
func TempDir(t *testing.T) string {
t.Helper()
d, err := ioutil.TempDir("", "helm")
d, err := os.MkdirTemp("", "helm")
if err != nil {
t.Fatal(err)
}
@ -57,13 +56,13 @@ func TempDir(t *testing.T) string {
//
// You must clean up the directory that is returned.
//
// tempdir := TempFile(t, "foo", []byte("bar"))
// defer os.RemoveAll(tempdir)
// filename := filepath.Join(tempdir, "foo")
// tempdir := TempFile(t, "foo", []byte("bar"))
// defer os.RemoveAll(tempdir)
// filename := filepath.Join(tempdir, "foo")
func TempFile(t *testing.T, name string, data []byte) string {
path := TempDir(t)
filename := filepath.Join(path, name)
if err := ioutil.WriteFile(filename, data, 0755); err != nil {
if err := os.WriteFile(filename, data, 0755); err != nil {
t.Fatal(err)
}
return path

@ -19,7 +19,7 @@ package test
import (
"bytes"
"flag"
"io/ioutil"
"os"
"path/filepath"
"github.com/pkg/errors"
@ -53,7 +53,7 @@ func AssertGoldenString(t TestingT, actual, filename string) {
func AssertGoldenFile(t TestingT, actualFileName string, expectedFilename string) {
t.Helper()
actual, err := ioutil.ReadFile(actualFileName)
actual, err := os.ReadFile(actualFileName)
if err != nil {
t.Fatalf("%v", err)
}
@ -73,7 +73,7 @@ func compare(actual []byte, filename string) error {
return err
}
expected, err := ioutil.ReadFile(filename)
expected, err := os.ReadFile(filename)
if err != nil {
return errors.Wrapf(err, "unable to read testdata %s", filename)
}
@ -88,7 +88,7 @@ func update(filename string, in []byte) error {
if !*updateGolden {
return nil
}
return ioutil.WriteFile(filename, normalize(in), 0666)
return os.WriteFile(filename, normalize(in), 0666)
}
func normalize(in []byte) []byte {

@ -32,7 +32,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package fs
import (
"io/ioutil"
"os"
"os/exec"
"path/filepath"
@ -137,7 +136,7 @@ func TestCopyDir(t *testing.T) {
t.Fatalf("expected %s to be a directory", dn)
}
got, err := ioutil.ReadFile(fn)
got, err := os.ReadFile(fn)
if err != nil {
t.Fatal(err)
}
@ -337,7 +336,7 @@ func TestCopyFile(t *testing.T) {
t.Fatal(err)
}
got, err := ioutil.ReadFile(destf)
got, err := os.ReadFile(destf)
if err != nil {
t.Fatal(err)
}
@ -396,11 +395,11 @@ func TestCopyFileSymlink(t *testing.T) {
// Creating symlinks on Windows require an additional permission
// regular users aren't granted usually. So we copy the file
// content as a fall back instead of creating a real symlink.
srcb, err := ioutil.ReadFile(symlink)
srcb, err := os.ReadFile(symlink)
if err != nil {
t.Fatalf("%+v", err)
}
dstb, err := ioutil.ReadFile(dst)
dstb, err := os.ReadFile(dst)
if err != nil {
t.Fatalf("%+v", err)
}

@ -92,9 +92,9 @@ func FindNewReplicaSet(deployment *apps.Deployment, rsList []*apps.ReplicaSet) *
// EqualIgnoreHash returns true if two given podTemplateSpec are equal, ignoring the diff in value of Labels[pod-template-hash]
// We ignore pod-template-hash because:
// 1. The hash result would be different upon podTemplateSpec API changes
// (e.g. the addition of a new field will cause the hash code to change)
// 2. The deployment template won't have hash labels
// 1. The hash result would be different upon podTemplateSpec API changes
// (e.g. the addition of a new field will cause the hash code to change)
// 2. The deployment template won't have hash labels
func EqualIgnoreHash(template1, template2 *v1.PodTemplateSpec) bool {
t1Copy := template1.DeepCopy()
t2Copy := template2.DeepCopy()

@ -19,7 +19,7 @@ package tlsutil
import (
"crypto/tls"
"crypto/x509"
"io/ioutil"
"os"
"github.com/pkg/errors"
)
@ -54,7 +54,7 @@ func NewClientTLS(certFile, keyFile, caFile string, insecureSkipTLSverify bool)
// Returns an error if the file could not be read, a certificate could not
// be parsed, or if the file does not contain any certificates
func CertPoolFromFile(filename string) (*x509.CertPool, error) {
b, err := ioutil.ReadFile(filename)
b, err := os.ReadFile(filename)
if err != nil {
return nil, errors.Errorf("can't read CA file: %v", filename)
}

@ -29,7 +29,7 @@ var (
//
// Increment major number for new feature additions and behavioral changes.
// Increment minor number for bug fixes and performance enhancements.
version = "v3.11"
version = "v3.12"
// metadata is extra build time data
metadata = ""

@ -103,7 +103,7 @@ type Configuration struct {
// TODO: As part of the refactor the duplicate code in cmd/helm/template.go should be removed
//
// This code has to do with writing files to disk.
func (cfg *Configuration) renderResources(ch *chart.Chart, values chartutil.Values, releaseName, outputDir string, subNotes, useReleaseName, includeCrds bool, pr postrender.PostRenderer, dryRun, enableDNS bool) ([]*release.Hook, *bytes.Buffer, string, error) {
func (cfg *Configuration) renderResources(ch *chart.Chart, values chartutil.Values, releaseName, outputDir string, subNotes, useReleaseName, includeCrds bool, pr postrender.PostRenderer, interactWithRemote, enableDNS bool) ([]*release.Hook, *bytes.Buffer, string, error) {
hs := []*release.Hook{}
b := bytes.NewBuffer(nil)
@ -121,12 +121,10 @@ func (cfg *Configuration) renderResources(ch *chart.Chart, values chartutil.Valu
var files map[string]string
var err2 error
// A `helm template` or `helm install --dry-run` should not talk to the remote cluster.
// It will break in interesting and exotic ways because other data (e.g. discovery)
// is mocked. It is not up to the template author to decide when the user wants to
// connect to the cluster. So when the user says to dry run, respect the user's
// wishes and do not connect to the cluster.
if !dryRun && cfg.RESTClientGetter != nil {
// 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.
// It may break in interesting and exotic ways because other data (e.g. discovery) is mocked.
if interactWithRemote && cfg.RESTClientGetter != nil {
restConfig, err := cfg.RESTClientGetter.ToRESTConfig()
if err != nil {
return hs, b, "", err
@ -189,13 +187,13 @@ func (cfg *Configuration) renderResources(ch *chart.Chart, values chartutil.Valu
if includeCrds {
for _, crd := range ch.CRDObjects() {
if outputDir == "" {
fmt.Fprintf(b, "---\n# Source: %s\n%s\n", crd.Name, string(crd.File.Data[:]))
fmt.Fprintf(b, "---\n# Source: %s\n%s\n", crd.Filename, string(crd.File.Data[:]))
} else {
err = writeToFile(outputDir, crd.Filename, string(crd.File.Data[:]), fileWritten[crd.Name])
err = writeToFile(outputDir, crd.Filename, string(crd.File.Data[:]), fileWritten[crd.Filename])
if err != nil {
return hs, b, "", err
}
fileWritten[crd.Name] = true
fileWritten[crd.Filename] = true
}
}
}

@ -5,7 +5,7 @@ Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
@ -17,7 +17,7 @@ package action
import (
"flag"
"io/ioutil"
"io"
"testing"
fakeclientset "k8s.io/client-go/kubernetes/fake"
@ -44,7 +44,7 @@ func actionConfigFixture(t *testing.T) *Configuration {
return &Configuration{
Releases: storage.Init(driver.NewMemory()),
KubeClient: &kubefake.FailingKubeClient{PrintingKubeClient: kubefake.PrintingKubeClient{Out: ioutil.Discard}},
KubeClient: &kubefake.FailingKubeClient{PrintingKubeClient: kubefake.PrintingKubeClient{Out: io.Discard}},
Capabilities: chartutil.DefaultCapabilities,
RegistryClient: registryClient,
Log: func(format string, v ...interface{}) {

@ -20,7 +20,7 @@ import (
"bytes"
"context"
"fmt"
"io/ioutil"
"io"
"net/url"
"os"
"path"
@ -34,6 +34,7 @@ import (
"github.com/pkg/errors"
v1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/cli-runtime/pkg/resource"
"sigs.k8s.io/yaml"
@ -72,6 +73,7 @@ type Install struct {
Force bool
CreateNamespace bool
DryRun bool
DryRunOption string
DisableHooks bool
Replace bool
Wait bool
@ -113,6 +115,7 @@ type ChartPathOptions struct {
CertFile string // --cert-file
KeyFile string // --key-file
InsecureSkipTLSverify bool // --insecure-skip-verify
PlainHTTP bool // --plain-http
Keyring string // --keyring
Password string // --password
PassCredentialsAll bool // --pass-credentials
@ -141,6 +144,11 @@ func (i *Install) SetRegistryClient(registryClient *registry.Client) {
i.ChartPathOptions.registryClient = registryClient
}
// GetRegistryClient get the registry client.
func (i *Install) GetRegistryClient() *registry.Client {
return i.ChartPathOptions.registryClient
}
func (i *Install) installCRDs(crds []chart.CRD) error {
// We do these one file at a time in the order they were read.
totalItems := []*resource.Info{}
@ -164,22 +172,38 @@ func (i *Install) installCRDs(crds []chart.CRD) error {
totalItems = append(totalItems, res...)
}
if len(totalItems) > 0 {
// Invalidate the local cache, since it will not have the new CRDs
// present.
discoveryClient, err := i.cfg.RESTClientGetter.ToDiscoveryClient()
if err != nil {
return err
}
i.cfg.Log("Clearing discovery cache")
discoveryClient.Invalidate()
// Give time for the CRD to be recognized.
if err := i.cfg.KubeClient.Wait(totalItems, 60*time.Second); err != nil {
return err
}
// Make sure to force a rebuild of the cache.
discoveryClient.ServerGroups()
// If we have already gathered the capabilities, we need to invalidate
// the cache so that the new CRDs are recognized. This should only be
// the case when an action configuration is reused for multiple actions,
// as otherwise it is later loaded by ourselves when getCapabilities
// is called later on in the installation process.
if i.cfg.Capabilities != nil {
discoveryClient, err := i.cfg.RESTClientGetter.ToDiscoveryClient()
if err != nil {
return err
}
i.cfg.Log("Clearing discovery cache")
discoveryClient.Invalidate()
_, _ = discoveryClient.ServerGroups()
}
// Invalidate the REST mapper, since it will not have the new CRDs
// present.
restMapper, err := i.cfg.RESTClientGetter.ToRESTMapper()
if err != nil {
return err
}
if resettable, ok := restMapper.(meta.ResettableRESTMapper); ok {
i.cfg.Log("Clearing REST mapper cache")
resettable.Reset()
}
}
return nil
}
@ -206,15 +230,20 @@ func (i *Install) RunWithContext(ctx context.Context, chrt *chart.Chart, vals ma
return nil, err
}
if err := chartutil.ProcessDependencies(chrt, vals); err != nil {
if err := chartutil.ProcessDependenciesWithMerge(chrt, vals); err != nil {
return nil, err
}
var interactWithRemote bool
if !i.isDryRun() || i.DryRunOption == "server" || i.DryRunOption == "none" || i.DryRunOption == "false" {
interactWithRemote = true
}
// Pre-install anything in the crd/ directory. We do this before Helm
// contacts the upstream server and builds the capabilities object.
if crds := chrt.CRDObjects(); !i.ClientOnly && !i.SkipCRDs && len(crds) > 0 {
// On dry run, bail here
if i.DryRun {
if i.isDryRun() {
i.cfg.Log("WARNING: This chart or one of its subcharts contains CRDs. Rendering may fail or contain inaccuracies.")
} else if err := i.installCRDs(crds); err != nil {
return nil, err
@ -229,7 +258,7 @@ func (i *Install) RunWithContext(ctx context.Context, chrt *chart.Chart, vals ma
i.cfg.Capabilities.KubeVersion = *i.KubeVersion
}
i.cfg.Capabilities.APIVersions = append(i.cfg.Capabilities.APIVersions, i.APIVersions...)
i.cfg.KubeClient = &kubefake.PrintingKubeClient{Out: ioutil.Discard}
i.cfg.KubeClient = &kubefake.PrintingKubeClient{Out: io.Discard}
mem := driver.NewMemory()
mem.SetNamespace(i.Namespace)
@ -248,7 +277,7 @@ func (i *Install) RunWithContext(ctx context.Context, chrt *chart.Chart, vals ma
}
// special case for helm template --is-upgrade
isUpgrade := i.IsUpgrade && i.DryRun
isUpgrade := i.IsUpgrade && i.isDryRun()
options := chartutil.ReleaseOptions{
Name: i.ReleaseName,
Namespace: i.Namespace,
@ -264,7 +293,7 @@ func (i *Install) RunWithContext(ctx context.Context, chrt *chart.Chart, vals ma
rel := i.createRelease(chrt, vals)
var manifestDoc *bytes.Buffer
rel.Hooks, manifestDoc, rel.Info.Notes, err = i.cfg.renderResources(chrt, valuesToRender, i.ReleaseName, i.OutputDir, i.SubNotes, i.UseReleaseName, i.IncludeCRDs, i.PostRenderer, i.DryRun, i.EnableDNS)
rel.Hooks, manifestDoc, rel.Info.Notes, err = i.cfg.renderResources(chrt, valuesToRender, i.ReleaseName, i.OutputDir, i.SubNotes, i.UseReleaseName, i.IncludeCRDs, i.PostRenderer, interactWithRemote, i.EnableDNS)
// Even for errors, attach this if available
if manifestDoc != nil {
rel.Manifest = manifestDoc.String()
@ -300,12 +329,12 @@ func (i *Install) RunWithContext(ctx context.Context, chrt *chart.Chart, vals ma
if !i.ClientOnly && !isUpgrade && len(resources) > 0 {
toBeAdopted, err = existingResourceConflict(resources, rel.Name, rel.Namespace)
if err != nil {
return nil, errors.Wrap(err, "rendered manifests contain a resource that already exists. Unable to continue with install")
return nil, errors.Wrap(err, "Unable to continue with install")
}
}
// Bail out here if it is a dry run
if i.DryRun {
if i.isDryRun() {
rel.Info.Description = "Dry run complete"
return rel, nil
}
@ -352,13 +381,25 @@ func (i *Install) RunWithContext(ctx context.Context, chrt *chart.Chart, vals ma
return rel, err
}
rChan := make(chan resultMessage)
ctxChan := make(chan resultMessage)
doneChan := make(chan struct{})
defer close(doneChan)
go i.performInstall(rChan, rel, toBeAdopted, resources)
go i.handleContext(ctx, rChan, doneChan, rel)
result := <-rChan
//start preformInstall go routine
return result.r, result.e
go i.handleContext(ctx, ctxChan, doneChan, rel)
select {
case result := <-rChan:
return result.r, result.e
case result := <-ctxChan:
return result.r, result.e
}
}
// isDryRun returns true if Upgrade is set to run as a DryRun
func (i *Install) isDryRun() bool {
if i.DryRun || i.DryRunOption == "client" || i.DryRunOption == "server" || i.DryRunOption == "true" {
return true
}
return false
}
func (i *Install) performInstall(c chan<- resultMessage, rel *release.Release, toBeAdopted kube.ResourceList, resources kube.ResourceList) {
@ -474,7 +515,8 @@ func (i *Install) availableName() error {
if err := chartutil.ValidateReleaseName(start); err != nil {
return errors.Wrapf(err, "release name %q", start)
}
if i.DryRun {
// On dry run, bail here
if i.isDryRun() {
return nil
}
@ -712,6 +754,7 @@ func (c *ChartPathOptions) LocateChart(name string, settings *cli.EnvSettings) (
getter.WithPassCredentialsAll(c.PassCredentialsAll),
getter.WithTLSClientConfig(c.CertFile, c.KeyFile, c.CaFile),
getter.WithInsecureSkipVerifyTLS(c.InsecureSkipTLSverify),
getter.WithPlainHTTP(c.PlainHTTP),
},
RepositoryConfig: settings.RepositoryConfig,
RepositoryCache: settings.RepositoryCache,

@ -19,7 +19,7 @@ package action
import (
"context"
"fmt"
"io/ioutil"
"io"
"os"
"path/filepath"
"regexp"
@ -132,7 +132,7 @@ func TestInstallReleaseClientOnly(t *testing.T) {
instAction.Run(buildChart(), nil) // disregard output
is.Equal(instAction.cfg.Capabilities, chartutil.DefaultCapabilities)
is.Equal(instAction.cfg.KubeClient, &kubefake.PrintingKubeClient{Out: ioutil.Discard})
is.Equal(instAction.cfg.KubeClient, &kubefake.PrintingKubeClient{Out: io.Discard})
}
func TestInstallRelease_NoName(t *testing.T) {
@ -254,7 +254,7 @@ func TestInstallRelease_DryRun(t *testing.T) {
is.Equal(res.Info.Description, "Dry run complete")
}
// Regression test for #7955: Lookup must not connect to Kubernetes on a dry-run.
// Regression test for #7955
func TestInstallRelease_DryRun_Lookup(t *testing.T) {
is := assert.New(t)
instAction := installAction(t)

@ -17,7 +17,6 @@ limitations under the License.
package action
import (
"io/ioutil"
"os"
"path/filepath"
"strings"
@ -83,7 +82,7 @@ func HasWarningsOrErrors(result *LintResult) bool {
return true
}
}
return false
return len(result.Errors) > 0
}
func lintChart(path string, vals map[string]interface{}, namespace string, strict bool) (support.Linter, error) {
@ -91,7 +90,7 @@ func lintChart(path string, vals map[string]interface{}, namespace string, stric
linter := support.Linter{}
if strings.HasSuffix(path, ".tgz") || strings.HasSuffix(path, ".tar.gz") {
tempDir, err := ioutil.TempDir("", "helm-lint")
tempDir, err := os.MkdirTemp("", "helm-lint")
if err != nil {
return linter, errors.Wrap(err, "unable to create temp dir to extract tarball")
}

@ -149,12 +149,12 @@ func TestLint_ChartWithWarnings(t *testing.T) {
}
})
t.Run("should fail with errors when strict", func(t *testing.T) {
t.Run("should pass with no errors when strict", func(t *testing.T) {
testCharts := []string{chartWithNoTemplatesDir}
testLint := NewLint()
testLint.Strict = true
if result := testLint.Run(testCharts, values); len(result.Errors) != 1 {
t.Error("expected one error, but got", len(result.Errors))
if result := testLint.Run(testCharts, values); len(result.Errors) != 0 {
t.Error("expected no errors, but got", len(result.Errors))
}
})
}

@ -19,7 +19,6 @@ package action
import (
"bufio"
"fmt"
"io/ioutil"
"os"
"syscall"
@ -137,7 +136,7 @@ func (p *Package) Clearsign(filename string) error {
return err
}
return ioutil.WriteFile(filename+".prov", []byte(sig), 0644)
return os.WriteFile(filename+".prov", []byte(sig), 0644)
}
// promptUser implements provenance.PassphraseFetcher

@ -17,7 +17,6 @@ limitations under the License.
package action
import (
"io/ioutil"
"os"
"path"
"testing"
@ -71,7 +70,7 @@ func TestPassphraseFileFetcher_WithInvalidStdin(t *testing.T) {
directory := ensure.TempDir(t)
defer os.RemoveAll(directory)
stdin, err := ioutil.TempFile(directory, "non-existing")
stdin, err := os.CreateTemp(directory, "non-existing")
if err != nil {
t.Fatal("Unable to create test file", err)
}

@ -18,7 +18,6 @@ package action
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
@ -91,6 +90,7 @@ func (p *Pull) Run(chartRef string) (string, error) {
getter.WithPassCredentialsAll(p.PassCredentialsAll),
getter.WithTLSClientConfig(p.CertFile, p.KeyFile, p.CaFile),
getter.WithInsecureSkipVerifyTLS(p.InsecureSkipTLSverify),
getter.WithPlainHTTP(p.PlainHTTP),
},
RegistryClient: p.cfg.RegistryClient,
RepositoryConfig: p.Settings.RepositoryConfig,
@ -114,7 +114,7 @@ func (p *Pull) Run(chartRef string) (string, error) {
dest := p.DestDir
if p.Untar {
var err error
dest, err = ioutil.TempDir("", "helm-")
dest, err = os.MkdirTemp("", "helm-")
if err != nil {
return out.String(), errors.Wrap(err, "failed to untar")
}

@ -36,6 +36,7 @@ type Push struct {
keyFile string
caFile string
insecureSkipTLSverify bool
plainHTTP bool
out io.Writer
}
@ -65,6 +66,13 @@ func WithInsecureSkipTLSVerify(insecureSkipTLSVerify bool) PushOpt {
}
}
// WithPlainHTTP configures the use of plain HTTP connections.
func WithPlainHTTP(plainHTTP bool) PushOpt {
return func(p *Push) {
p.plainHTTP = plainHTTP
}
}
// WithOptWriter sets the registryOut field on the push configuration object.
func WithPushOptWriter(out io.Writer) PushOpt {
return func(p *Push) {
@ -91,6 +99,7 @@ func (p *Push) Run(chartRef string, remote string) (string, error) {
Options: []pusher.Option{
pusher.WithTLSClientConfig(p.certFile, p.keyFile, p.caFile),
pusher.WithInsecureSkipTLSVerify(p.insecureSkipTLSverify),
pusher.WithPlainHTTP(p.plainHTTP),
},
}

@ -21,6 +21,7 @@ import (
"time"
"github.com/pkg/errors"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"helm.sh/helm/v3/pkg/chartutil"
"helm.sh/helm/v3/pkg/kube"
@ -35,12 +36,13 @@ import (
type Uninstall struct {
cfg *Configuration
DisableHooks bool
DryRun bool
KeepHistory bool
Wait bool
Timeout time.Duration
Description string
DisableHooks bool
DryRun bool
KeepHistory bool
Wait bool
DeletionPropagation string
Timeout time.Duration
Description string
}
// NewUninstall creates a new Uninstall object with the given configuration.
@ -220,7 +222,25 @@ func (u *Uninstall) deleteRelease(rel *release.Release) (kube.ResourceList, stri
return nil, "", []error{errors.Wrap(err, "unable to build kubernetes objects for delete")}
}
if len(resources) > 0 {
if kubeClient, ok := u.cfg.KubeClient.(kube.InterfaceDeletionPropagation); ok {
_, errs = kubeClient.DeleteWithPropagationPolicy(resources, parseCascadingFlag(u.cfg, u.DeletionPropagation))
return resources, kept, errs
}
_, errs = u.cfg.KubeClient.Delete(resources)
}
return resources, kept, errs
}
func parseCascadingFlag(cfg *Configuration, cascadingFlag string) v1.DeletionPropagation {
switch cascadingFlag {
case "orphan":
return v1.DeletePropagationOrphan
case "foreground":
return v1.DeletePropagationForeground
case "background":
return v1.DeletePropagationBackground
default:
cfg.Log("uninstall: given cascade value: %s, defaulting to delete propagation background", cascadingFlag)
return v1.DeletePropagationBackground
}
}

@ -95,3 +95,35 @@ func TestUninstallRelease_Wait(t *testing.T) {
is.Contains(err.Error(), "U timed out")
is.Equal(res.Release.Info.Status, release.StatusUninstalled)
}
func TestUninstallRelease_Cascade(t *testing.T) {
is := assert.New(t)
unAction := uninstallAction(t)
unAction.DisableHooks = true
unAction.DryRun = false
unAction.Wait = false
unAction.DeletionPropagation = "foreground"
rel := releaseStub()
rel.Name = "come-fail-away"
rel.Manifest = `{
"apiVersion": "v1",
"kind": "Secret",
"metadata": {
"name": "secret"
},
"type": "Opaque",
"data": {
"password": "password"
}
}`
unAction.cfg.Releases.Create(rel)
failer := unAction.cfg.KubeClient.(*kubefake.FailingKubeClient)
failer.DeleteWithPropagationError = fmt.Errorf("Uninstall with cascade failed")
failer.BuildDummy = true
unAction.cfg.KubeClient = failer
_, err := unAction.Run(rel.Name)
is.Error(err)
is.Contains(err.Error(), "failed to delete release: come-fail-away")
}

@ -71,8 +71,9 @@ type Upgrade struct {
// DisableHooks disables hook processing if set to true.
DisableHooks bool
// DryRun controls whether the operation is prepared, but not executed.
// If `true`, the upgrade is prepared but not performed.
DryRun bool
// DryRunOption controls whether the operation is prepared, but not executed with options on whether or not to interact with the remote cluster.
DryRunOption string
// Force will, if set to `true`, ignore certain warnings and perform the upgrade anyway.
//
// This should be used with caution.
@ -147,6 +148,7 @@ func (u *Upgrade) RunWithContext(ctx context.Context, name string, chart *chart.
if err := chartutil.ValidateReleaseName(name); err != nil {
return nil, errors.Errorf("release name is invalid: %s", name)
}
u.cfg.Log("preparing upgrade for %s", name)
currentRelease, upgradedRelease, err := u.prepareUpgrade(name, chart, vals)
if err != nil {
@ -161,7 +163,8 @@ func (u *Upgrade) RunWithContext(ctx context.Context, name string, chart *chart.
return res, err
}
if !u.DryRun {
// Do not update for dry runs
if !u.isDryRun() {
u.cfg.Log("updating status for upgraded release for %s", name)
if err := u.cfg.Releases.Update(upgradedRelease); err != nil {
return res, err
@ -171,6 +174,14 @@ func (u *Upgrade) RunWithContext(ctx context.Context, name string, chart *chart.
return res, nil
}
// isDryRun returns true if Upgrade is set to run as a DryRun
func (u *Upgrade) isDryRun() bool {
if u.DryRun || u.DryRunOption == "client" || u.DryRunOption == "server" || u.DryRunOption == "true" {
return true
}
return false
}
// prepareUpgrade builds an upgraded release for an upgrade operation.
func (u *Upgrade) prepareUpgrade(name string, chart *chart.Chart, vals map[string]interface{}) (*release.Release, *release.Release, error) {
if chart == nil {
@ -215,7 +226,7 @@ func (u *Upgrade) prepareUpgrade(name string, chart *chart.Chart, vals map[strin
return nil, nil, err
}
if err := chartutil.ProcessDependencies(chart, vals); err != nil {
if err := chartutil.ProcessDependenciesWithMerge(chart, vals); err != nil {
return nil, nil, err
}
@ -239,7 +250,13 @@ func (u *Upgrade) prepareUpgrade(name string, chart *chart.Chart, vals map[strin
return nil, nil, err
}
hooks, manifestDoc, notesTxt, err := u.cfg.renderResources(chart, valuesToRender, "", "", u.SubNotes, false, false, u.PostRenderer, u.DryRun, u.EnableDNS)
// Determine whether or not to interact with remote
var interactWithRemote bool
if !u.isDryRun() || u.DryRunOption == "server" || u.DryRunOption == "none" || u.DryRunOption == "false" {
interactWithRemote = true
}
hooks, manifestDoc, notesTxt, err := u.cfg.renderResources(chart, valuesToRender, "", "", u.SubNotes, false, false, u.PostRenderer, interactWithRemote, u.EnableDNS)
if err != nil {
return nil, nil, err
}
@ -306,7 +323,7 @@ func (u *Upgrade) performUpgrade(ctx context.Context, originalRelease, upgradedR
toBeUpdated, err := existingResourceConflict(toBeCreated, upgradedRelease.Name, upgradedRelease.Namespace)
if err != nil {
return nil, errors.Wrap(err, "rendered manifests contain a resource that already exists. Unable to continue with update")
return nil, errors.Wrap(err, "Unable to continue with update")
}
toBeUpdated.Visit(func(r *resource.Info, err error) error {
@ -317,7 +334,8 @@ func (u *Upgrade) performUpgrade(ctx context.Context, originalRelease, upgradedR
return nil
})
if u.DryRun {
// Run if it is a dry run
if u.isDryRun() {
u.cfg.Log("dry run for %s", upgradedRelease.Name)
if len(u.Description) > 0 {
upgradedRelease.Info.Description = u.Description

@ -5,7 +5,7 @@ Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,

@ -5,7 +5,7 @@ Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,

@ -19,7 +19,6 @@ package loader
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
@ -102,7 +101,7 @@ func LoadDir(dir string) (*chart.Chart, error) {
return fmt.Errorf("cannot load irregular file %s as it has file mode type bits set", name)
}
data, err := ioutil.ReadFile(name)
data, err := os.ReadFile(name)
if err != nil {
return errors.Wrapf(err, "error reading %s", n)
}

@ -21,7 +21,6 @@ import (
"bytes"
"compress/gzip"
"io"
"io/ioutil"
"log"
"os"
"path/filepath"
@ -90,13 +89,13 @@ func TestLoadDirWithSymlink(t *testing.T) {
func TestBomTestData(t *testing.T) {
testFiles := []string{"frobnitz_with_bom/.helmignore", "frobnitz_with_bom/templates/template.tpl", "frobnitz_with_bom/Chart.yaml"}
for _, file := range testFiles {
data, err := ioutil.ReadFile("testdata/" + file)
data, err := os.ReadFile("testdata/" + file)
if err != nil || !bytes.HasPrefix(data, utf8bom) {
t.Errorf("Test file has no BOM or is invalid: testdata/%s", file)
}
}
archive, err := ioutil.ReadFile("testdata/frobnitz_with_bom.tgz")
archive, err := os.ReadFile("testdata/frobnitz_with_bom.tgz")
if err != nil {
t.Fatalf("Error reading archive frobnitz_with_bom.tgz: %s", err)
}

@ -5,7 +5,7 @@ Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,

@ -62,8 +62,8 @@ func TestDefaultCapabilities(t *testing.T) {
func TestDefaultCapabilitiesHelmVersion(t *testing.T) {
hv := DefaultCapabilities.HelmVersion
if hv.Version != "v3.11" {
t.Errorf("Expected default HelmVersion to be v3.11, got %q", hv.Version)
if hv.Version != "v3.12" {
t.Errorf("Expected default HelmVersion to be v3.12, got %q", hv.Version)
}
}

@ -17,7 +17,6 @@ limitations under the License.
package chartutil
import (
"io/ioutil"
"os"
"path/filepath"
@ -29,7 +28,7 @@ import (
// LoadChartfile loads a Chart.yaml file into a *chart.Metadata.
func LoadChartfile(filename string) (*chart.Metadata, error) {
b, err := ioutil.ReadFile(filename)
b, err := os.ReadFile(filename)
if err != nil {
return nil, err
}
@ -55,7 +54,7 @@ func SaveChartfile(filename string, cf *chart.Metadata) error {
if err != nil {
return err
}
return ioutil.WriteFile(filename, out, 0644)
return os.WriteFile(filename, out, 0644)
}
// IsChartDir validate a chart directory.
@ -73,7 +72,7 @@ func IsChartDir(dirName string) (bool, error) {
return false, errors.Errorf("no %s exists in directory %q", ChartfileName, dirName)
}
chartYamlContent, err := ioutil.ReadFile(chartYaml)
chartYamlContent, err := os.ReadFile(chartYaml)
if err != nil {
return false, errors.Errorf("cannot read %s in directory %q", ChartfileName, dirName)
}

@ -35,11 +35,11 @@ func TestLoadChartfile(t *testing.T) {
func verifyChartfile(t *testing.T, f *chart.Metadata, name string) {
if f == nil {
if f == nil { //nolint:staticcheck
t.Fatal("Failed verifyChartfile because f is nil")
}
if f.APIVersion != chart.APIVersionV1 {
if f.APIVersion != chart.APIVersionV1 { //nolint:staticcheck
t.Errorf("Expected API Version %q, got %q", chart.APIVersionV1, f.APIVersion)
}

@ -37,12 +37,42 @@ func concatPrefix(a, b string) string {
//
// Values are coalesced together using the following rules:
//
// - Values in a higher level chart always override values in a lower-level
// dependency chart
// - Scalar values and arrays are replaced, maps are merged
// - A chart has access to all of the variables for it, as well as all of
// the values destined for its dependencies.
// - Values in a higher level chart always override values in a lower-level
// dependency chart
// - Scalar values and arrays are replaced, maps are merged
// - A chart has access to all of the variables for it, as well as all of
// the values destined for its dependencies.
func CoalesceValues(chrt *chart.Chart, vals map[string]interface{}) (Values, error) {
valsCopy, err := copyValues(vals)
if err != nil {
return vals, err
}
return coalesce(log.Printf, chrt, valsCopy, "", false)
}
// MergeValues is used to merge the values in a chart and its subcharts. This
// is different from Coalescing as nil/null values are preserved.
//
// Values are coalesced together using the following rules:
//
// - Values in a higher level chart always override values in a lower-level
// dependency chart
// - Scalar values and arrays are replaced, maps are merged
// - A chart has access to all of the variables for it, as well as all of
// the values destined for its dependencies.
//
// Retaining Nils is useful when processes early in a Helm action or business
// logic need to retain them for when Coalescing will happen again later in the
// business logic.
func MergeValues(chrt *chart.Chart, vals map[string]interface{}) (Values, error) {
valsCopy, err := copyValues(vals)
if err != nil {
return vals, err
}
return coalesce(log.Printf, chrt, valsCopy, "", true)
}
func copyValues(vals map[string]interface{}) (Values, error) {
v, err := copystructure.Copy(vals)
if err != nil {
return vals, err
@ -53,21 +83,26 @@ func CoalesceValues(chrt *chart.Chart, vals map[string]interface{}) (Values, err
if valsCopy == nil {
valsCopy = make(map[string]interface{})
}
return coalesce(log.Printf, chrt, valsCopy, "")
return valsCopy, nil
}
type printFn func(format string, v ...interface{})
// coalesce coalesces the dest values and the chart values, giving priority to the dest values.
//
// This is a helper function for CoalesceValues.
func coalesce(printf printFn, ch *chart.Chart, dest map[string]interface{}, prefix string) (map[string]interface{}, error) {
coalesceValues(printf, ch, dest, prefix)
return coalesceDeps(printf, ch, dest, prefix)
// This is a helper function for CoalesceValues and MergeValues.
//
// Note, the merge argument specifies whether this is being used by MergeValues
// or CoalesceValues. Coalescing removes null values and their keys in some
// situations while merging keeps the null values.
func coalesce(printf printFn, ch *chart.Chart, dest map[string]interface{}, prefix string, merge bool) (map[string]interface{}, error) {
coalesceValues(printf, ch, dest, prefix, merge)
return coalesceDeps(printf, ch, dest, prefix, merge)
}
// coalesceDeps coalesces the dependencies of the given chart.
func coalesceDeps(printf printFn, chrt *chart.Chart, dest map[string]interface{}, prefix string) (map[string]interface{}, error) {
func coalesceDeps(printf printFn, chrt *chart.Chart, dest map[string]interface{}, prefix string, merge bool) (map[string]interface{}, error) {
for _, subchart := range chrt.Dependencies() {
if c, ok := dest[subchart.Name()]; !ok {
// If dest doesn't already have the key, create it.
@ -78,13 +113,11 @@ func coalesceDeps(printf printFn, chrt *chart.Chart, dest map[string]interface{}
if dv, ok := dest[subchart.Name()]; ok {
dvmap := dv.(map[string]interface{})
subPrefix := concatPrefix(prefix, chrt.Metadata.Name)
// Get globals out of dest and merge them into dvmap.
coalesceGlobals(printf, dvmap, dest, subPrefix)
coalesceGlobals(printf, dvmap, dest, subPrefix, merge)
// Now coalesce the rest of the values.
var err error
dest[subchart.Name()], err = coalesce(printf, subchart, dvmap, subPrefix)
dest[subchart.Name()], err = coalesce(printf, subchart, dvmap, subPrefix, merge)
if err != nil {
return dest, err
}
@ -96,7 +129,7 @@ func coalesceDeps(printf printFn, chrt *chart.Chart, dest map[string]interface{}
// coalesceGlobals copies the globals out of src and merges them into dest.
//
// For convenience, returns dest.
func coalesceGlobals(printf printFn, dest, src map[string]interface{}, prefix string) {
func coalesceGlobals(printf printFn, dest, src map[string]interface{}, prefix string, merge bool) {
var dg, sg map[string]interface{}
if destglob, ok := dest[GlobalKey]; !ok {
@ -130,7 +163,10 @@ func coalesceGlobals(printf printFn, dest, src map[string]interface{}, prefix st
// Basically, we reverse order of coalesce here to merge
// top-down.
subPrefix := concatPrefix(prefix, key)
coalesceTablesFullKey(printf, vv, destvmap, subPrefix)
// In this location coalesceTablesFullKey should always have
// merge set to true. The output of coalesceGlobals is run
// through coalesce where any nils will be removed.
coalesceTablesFullKey(printf, vv, destvmap, subPrefix, true)
dg[key] = vv
}
}
@ -156,12 +192,38 @@ func copyMap(src map[string]interface{}) map[string]interface{} {
// coalesceValues builds up a values map for a particular chart.
//
// Values in v will override the values in the chart.
func coalesceValues(printf printFn, c *chart.Chart, v map[string]interface{}, prefix string) {
func coalesceValues(printf printFn, c *chart.Chart, v map[string]interface{}, prefix string, merge bool) {
subPrefix := concatPrefix(prefix, c.Metadata.Name)
for key, val := range c.Values {
// Using c.Values directly when coalescing a table can cause problems where
// the original c.Values is altered. Creating a deep copy stops the problem.
// This section is fault-tolerant as there is no ability to return an error.
valuesCopy, err := copystructure.Copy(c.Values)
var vc map[string]interface{}
var ok bool
if err != nil {
// If there is an error something is wrong with copying c.Values it
// means there is a problem in the deep copying package or something
// wrong with c.Values. In this case we will use c.Values and report
// an error.
printf("warning: unable to copy values, err: %s", err)
vc = c.Values
} else {
vc, ok = valuesCopy.(map[string]interface{})
if !ok {
// c.Values has a map[string]interface{} structure. If the copy of
// it cannot be treated as map[string]interface{} there is something
// strangely wrong. Log it and use c.Values
printf("warning: unable to convert values copy to values type")
vc = c.Values
}
}
for key, val := range vc {
if value, ok := v[key]; ok {
if value == nil {
// When the YAML value is null, we remove the value's key.
if value == nil && !merge {
// When the YAML value is null and we are coalescing instead of
// merging, we remove the value's key.
// This allows Helm's various sources of values (value files or --set) to
// remove incompatible keys from any previous chart, file, or set values.
delete(v, key)
@ -177,7 +239,7 @@ func coalesceValues(printf printFn, c *chart.Chart, v map[string]interface{}, pr
} else {
// Because v has higher precedence than nv, dest values override src
// values.
coalesceTablesFullKey(printf, dest, src, concatPrefix(subPrefix, key))
coalesceTablesFullKey(printf, dest, src, concatPrefix(subPrefix, key), merge)
}
}
} else {
@ -191,13 +253,17 @@ func coalesceValues(printf printFn, c *chart.Chart, v map[string]interface{}, pr
//
// dest is considered authoritative.
func CoalesceTables(dst, src map[string]interface{}) map[string]interface{} {
return coalesceTablesFullKey(log.Printf, dst, src, "")
return coalesceTablesFullKey(log.Printf, dst, src, "", false)
}
func MergeTables(dst, src map[string]interface{}) map[string]interface{} {
return coalesceTablesFullKey(log.Printf, dst, src, "", true)
}
// coalesceTablesFullKey merges a source map into a destination map.
//
// dest is considered authoritative.
func coalesceTablesFullKey(printf printFn, dst, src map[string]interface{}, prefix string) map[string]interface{} {
func coalesceTablesFullKey(printf printFn, dst, src map[string]interface{}, prefix string, merge bool) map[string]interface{} {
// When --reuse-values is set but there are no modifications yet, return new values
if src == nil {
return dst
@ -209,13 +275,13 @@ func coalesceTablesFullKey(printf printFn, dst, src map[string]interface{}, pref
// values.
for key, val := range src {
fullkey := concatPrefix(prefix, key)
if dv, ok := dst[key]; ok && dv == nil {
if dv, ok := dst[key]; ok && !merge && dv == nil {
delete(dst, key)
} else if !ok {
dst[key] = val
} else if istable(val) {
if istable(dv) {
coalesceTablesFullKey(printf, dv.(map[string]interface{}), val.(map[string]interface{}), fullkey)
coalesceTablesFullKey(printf, dv.(map[string]interface{}), val.(map[string]interface{}), fullkey, merge)
} else {
printf("warning: cannot overwrite table with non table for %s (%v)", fullkey, val)
}

@ -213,6 +213,160 @@ func TestCoalesceValues(t *testing.T) {
is.Equal(valsCopy, vals)
}
func TestMergeValues(t *testing.T) {
is := assert.New(t)
c := withDeps(&chart.Chart{
Metadata: &chart.Metadata{Name: "moby"},
Values: map[string]interface{}{
"back": "exists",
"bottom": "exists",
"front": "exists",
"left": "exists",
"name": "moby",
"nested": map[string]interface{}{"boat": true},
"override": "bad",
"right": "exists",
"scope": "moby",
"top": "nope",
"global": map[string]interface{}{
"nested2": map[string]interface{}{"l0": "moby"},
},
},
},
withDeps(&chart.Chart{
Metadata: &chart.Metadata{Name: "pequod"},
Values: map[string]interface{}{
"name": "pequod",
"scope": "pequod",
"global": map[string]interface{}{
"nested2": map[string]interface{}{"l1": "pequod"},
},
},
},
&chart.Chart{
Metadata: &chart.Metadata{Name: "ahab"},
Values: map[string]interface{}{
"global": map[string]interface{}{
"nested": map[string]interface{}{"foo": "bar"},
"nested2": map[string]interface{}{"l2": "ahab"},
},
"scope": "ahab",
"name": "ahab",
"boat": true,
"nested": map[string]interface{}{"foo": false, "bar": true},
},
},
),
&chart.Chart{
Metadata: &chart.Metadata{Name: "spouter"},
Values: map[string]interface{}{
"scope": "spouter",
"global": map[string]interface{}{
"nested2": map[string]interface{}{"l1": "spouter"},
},
},
},
)
vals, err := ReadValues(testCoalesceValuesYaml)
if err != nil {
t.Fatal(err)
}
// taking a copy of the values before passing it
// to MergeValues as argument, so that we can
// use it for asserting later
valsCopy := make(Values, len(vals))
for key, value := range vals {
valsCopy[key] = value
}
v, err := MergeValues(c, vals)
if err != nil {
t.Fatal(err)
}
j, _ := json.MarshalIndent(v, "", " ")
t.Logf("Coalesced Values: %s", string(j))
tests := []struct {
tpl string
expect string
}{
{"{{.top}}", "yup"},
{"{{.back}}", ""},
{"{{.name}}", "moby"},
{"{{.global.name}}", "Ishmael"},
{"{{.global.subject}}", "Queequeg"},
{"{{.global.harpooner}}", "<no value>"},
{"{{.pequod.name}}", "pequod"},
{"{{.pequod.ahab.name}}", "ahab"},
{"{{.pequod.ahab.scope}}", "whale"},
{"{{.pequod.ahab.nested.foo}}", "true"},
{"{{.pequod.ahab.global.name}}", "Ishmael"},
{"{{.pequod.ahab.global.nested.foo}}", "bar"},
{"{{.pequod.ahab.global.subject}}", "Queequeg"},
{"{{.pequod.ahab.global.harpooner}}", "Tashtego"},
{"{{.pequod.global.name}}", "Ishmael"},
{"{{.pequod.global.nested.foo}}", "<no value>"},
{"{{.pequod.global.subject}}", "Queequeg"},
{"{{.spouter.global.name}}", "Ishmael"},
{"{{.spouter.global.harpooner}}", "<no value>"},
{"{{.global.nested.boat}}", "true"},
{"{{.pequod.global.nested.boat}}", "true"},
{"{{.spouter.global.nested.boat}}", "true"},
{"{{.pequod.global.nested.sail}}", "true"},
{"{{.spouter.global.nested.sail}}", "<no value>"},
{"{{.global.nested2.l0}}", "moby"},
{"{{.global.nested2.l1}}", "<no value>"},
{"{{.global.nested2.l2}}", "<no value>"},
{"{{.pequod.global.nested2.l0}}", "moby"},
{"{{.pequod.global.nested2.l1}}", "pequod"},
{"{{.pequod.global.nested2.l2}}", "<no value>"},
{"{{.pequod.ahab.global.nested2.l0}}", "moby"},
{"{{.pequod.ahab.global.nested2.l1}}", "pequod"},
{"{{.pequod.ahab.global.nested2.l2}}", "ahab"},
{"{{.spouter.global.nested2.l0}}", "moby"},
{"{{.spouter.global.nested2.l1}}", "spouter"},
{"{{.spouter.global.nested2.l2}}", "<no value>"},
}
for _, tt := range tests {
if o, err := ttpl(tt.tpl, v); err != nil || o != tt.expect {
t.Errorf("Expected %q to expand to %q, got %q", tt.tpl, tt.expect, o)
}
}
// nullKeys is different from coalescing. Here the null/nil values are not
// removed.
nullKeys := []string{"bottom", "right", "left", "front"}
for _, nullKey := range nullKeys {
if vv, ok := v[nullKey]; !ok {
t.Errorf("Expected key %q to be present but it was removed", nullKey)
} else if vv != nil {
t.Errorf("Expected key %q to be null but it has a value of %v", nullKey, vv)
}
}
if _, ok := v["nested"].(map[string]interface{})["boat"]; !ok {
t.Error("Expected nested boat key to be present but it was removed")
}
subchart := v["pequod"].(map[string]interface{})["ahab"].(map[string]interface{})
if _, ok := subchart["boat"]; !ok {
t.Error("Expected subchart boat key to be present but it was removed")
}
if _, ok := subchart["nested"].(map[string]interface{})["bar"]; !ok {
t.Error("Expected subchart nested bar key to be present but it was removed")
}
// CoalesceValues should not mutate the passed arguments
is.Equal(valsCopy, vals)
}
func TestCoalesceTables(t *testing.T) {
dst := map[string]interface{}{
"name": "Ishmael",
@ -341,6 +495,143 @@ func TestCoalesceTables(t *testing.T) {
}
}
func TestMergeTables(t *testing.T) {
dst := map[string]interface{}{
"name": "Ishmael",
"address": map[string]interface{}{
"street": "123 Spouter Inn Ct.",
"city": "Nantucket",
"country": nil,
},
"details": map[string]interface{}{
"friends": []string{"Tashtego"},
},
"boat": "pequod",
"hole": nil,
}
src := map[string]interface{}{
"occupation": "whaler",
"address": map[string]interface{}{
"state": "MA",
"street": "234 Spouter Inn Ct.",
"country": "US",
},
"details": "empty",
"boat": map[string]interface{}{
"mast": true,
},
"hole": "black",
}
// What we expect is that anything in dst overrides anything in src, but that
// otherwise the values are coalesced.
MergeTables(dst, src)
if dst["name"] != "Ishmael" {
t.Errorf("Unexpected name: %s", dst["name"])
}
if dst["occupation"] != "whaler" {
t.Errorf("Unexpected occupation: %s", dst["occupation"])
}
addr, ok := dst["address"].(map[string]interface{})
if !ok {
t.Fatal("Address went away.")
}
if addr["street"].(string) != "123 Spouter Inn Ct." {
t.Errorf("Unexpected address: %v", addr["street"])
}
if addr["city"].(string) != "Nantucket" {
t.Errorf("Unexpected city: %v", addr["city"])
}
if addr["state"].(string) != "MA" {
t.Errorf("Unexpected state: %v", addr["state"])
}
// This is one test that is different from CoalesceTables. Because country
// is a nil value and it's not removed it's still present.
if _, ok = addr["country"]; !ok {
t.Error("The country is left out.")
}
if det, ok := dst["details"].(map[string]interface{}); !ok {
t.Fatalf("Details is the wrong type: %v", dst["details"])
} else if _, ok := det["friends"]; !ok {
t.Error("Could not find your friends. Maybe you don't have any. :-(")
}
if dst["boat"].(string) != "pequod" {
t.Errorf("Expected boat string, got %v", dst["boat"])
}
// This is one test that is different from CoalesceTables. Because hole
// is a nil value and it's not removed it's still present.
if _, ok = dst["hole"]; !ok {
t.Error("The hole no longer exists.")
}
dst2 := map[string]interface{}{
"name": "Ishmael",
"address": map[string]interface{}{
"street": "123 Spouter Inn Ct.",
"city": "Nantucket",
"country": "US",
},
"details": map[string]interface{}{
"friends": []string{"Tashtego"},
},
"boat": "pequod",
"hole": "black",
"nilval": nil,
}
// What we expect is that anything in dst should have all values set,
// this happens when the --reuse-values flag is set but the chart has no modifications yet
MergeTables(dst2, nil)
if dst2["name"] != "Ishmael" {
t.Errorf("Unexpected name: %s", dst2["name"])
}
addr2, ok := dst2["address"].(map[string]interface{})
if !ok {
t.Fatal("Address went away.")
}
if addr2["street"].(string) != "123 Spouter Inn Ct." {
t.Errorf("Unexpected address: %v", addr2["street"])
}
if addr2["city"].(string) != "Nantucket" {
t.Errorf("Unexpected city: %v", addr2["city"])
}
if addr2["country"].(string) != "US" {
t.Errorf("Unexpected Country: %v", addr2["country"])
}
if det2, ok := dst2["details"].(map[string]interface{}); !ok {
t.Fatalf("Details is the wrong type: %v", dst2["details"])
} else if _, ok := det2["friends"]; !ok {
t.Error("Could not find your friends. Maybe you don't have any. :-(")
}
if dst2["boat"].(string) != "pequod" {
t.Errorf("Expected boat string, got %v", dst2["boat"])
}
if dst2["hole"].(string) != "black" {
t.Errorf("Expected hole string, got %v", dst2["boat"])
}
if dst2["nilval"] != nil {
t.Error("Expected nilvalue to have nil value but it does not")
}
}
func TestCoalesceValuesWarnings(t *testing.T) {
c := withDeps(&chart.Chart{
@ -391,7 +682,7 @@ func TestCoalesceValuesWarnings(t *testing.T) {
warnings = append(warnings, fmt.Sprintf(format, v...))
}
_, err := coalesce(printf, c, vals, "")
_, err := coalesce(printf, c, vals, "", false)
if err != nil {
t.Fatal(err)
}

@ -19,7 +19,6 @@ package chartutil
import (
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"regexp"
@ -180,6 +179,19 @@ autoscaling:
targetCPUUtilizationPercentage: 80
# targetMemoryUtilizationPercentage: 80
# Additional volumes on the output Deployment definition.
volumes: []
# - name: foo
# secret:
# secretName: mysecret
# optional: false
# Additional volumeMounts on the output Deployment definition.
volumeMounts: []
# - name: foo
# mountPath: "/etc/foo"
# readOnly: true
nodeSelector: {}
tolerations: []
@ -324,6 +336,14 @@ spec:
port: http
resources:
{{- toYaml .Values.resources | nindent 12 }}
{{- with .Values.volumeMounts }}
volumeMounts:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.volumes }}
volumes:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
@ -370,7 +390,7 @@ metadata:
`
const defaultHorizontalPodAutoscaler = `{{- if .Values.autoscaling.enabled }}
apiVersion: autoscaling/v2beta1
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: {{ include "<CHARTNAME>.fullname" . }}
@ -388,13 +408,17 @@ spec:
- type: Resource
resource:
name: cpu
targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
target:
type: Utilization
averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
{{- end }}
{{- if .Values.autoscaling.targetMemoryUtilizationPercentage }}
- type: Resource
resource:
name: memory
targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
target:
type: Utilization
averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
{{- end }}
{{- end }}
`
@ -673,7 +697,7 @@ func writeFile(name string, content []byte) error {
if err := os.MkdirAll(filepath.Dir(name), 0755); err != nil {
return err
}
return ioutil.WriteFile(name, content, 0644)
return os.WriteFile(name, content, 0644)
}
func validateChartName(name string) error {

@ -18,7 +18,6 @@ package chartutil
import (
"bytes"
"io/ioutil"
"os"
"path/filepath"
"testing"
@ -100,7 +99,7 @@ func TestCreateFrom(t *testing.T) {
}
// Check each file to make sure <CHARTNAME> has been replaced
b, err := ioutil.ReadFile(filepath.Join(dir, f))
b, err := os.ReadFile(filepath.Join(dir, f))
if err != nil {
t.Errorf("Unable to read file %s: %s", f, err)
}
@ -131,7 +130,7 @@ func TestCreate_Overwrite(t *testing.T) {
t.Fatal(err)
}
data, err := ioutil.ReadFile(tplname)
data, err := os.ReadFile(tplname)
if err != nil {
t.Fatal(err)
}

@ -19,15 +19,29 @@ import (
"log"
"strings"
"github.com/mitchellh/copystructure"
"helm.sh/helm/v3/pkg/chart"
)
// ProcessDependencies checks through this chart's dependencies, processing accordingly.
//
// TODO: For Helm v4 this can be combined with or turned into ProcessDependenciesWithMerge
func ProcessDependencies(c *chart.Chart, v Values) error {
if err := processDependencyEnabled(c, v, ""); err != nil {
return err
}
return processDependencyImportValues(c)
return processDependencyImportValues(c, false)
}
// ProcessDependenciesWithMerge checks through this chart's dependencies, processing accordingly.
// It is similar to ProcessDependencies but it does not remove nil values during
// the import/export handling process.
func ProcessDependenciesWithMerge(c *chart.Chart, v Values) error {
if err := processDependencyEnabled(c, v, ""); err != nil {
return err
}
return processDependencyImportValues(c, true)
}
// processDependencyConditions disables charts based on condition path value in values
@ -217,12 +231,18 @@ func set(path []string, data map[string]interface{}) map[string]interface{} {
}
// processImportValues merges values from child to parent based on the chart's dependencies' ImportValues field.
func processImportValues(c *chart.Chart) error {
func processImportValues(c *chart.Chart, merge bool) error {
if c.Metadata.Dependencies == nil {
return nil
}
// combine chart values and empty config to get Values
cvals, err := CoalesceValues(c, nil)
var cvals Values
var err error
if merge {
cvals, err = MergeValues(c, nil)
} else {
cvals, err = CoalesceValues(c, nil)
}
if err != nil {
return err
}
@ -248,7 +268,11 @@ func processImportValues(c *chart.Chart) error {
continue
}
// create value map from child to be merged into parent
b = CoalesceTables(cvals, pathToMap(parent, vv.AsMap()))
if merge {
b = MergeTables(b, pathToMap(parent, vv.AsMap()))
} else {
b = CoalesceTables(b, pathToMap(parent, vv.AsMap()))
}
case string:
child := "exports." + iv
outiv = append(outiv, map[string]string{
@ -260,26 +284,71 @@ func processImportValues(c *chart.Chart) error {
log.Printf("Warning: ImportValues missing table: %v", err)
continue
}
b = CoalesceTables(b, vm.AsMap())
if merge {
b = MergeTables(b, vm.AsMap())
} else {
b = CoalesceTables(b, vm.AsMap())
}
}
}
// set our formatted import values
r.ImportValues = outiv
}
// set the new values
c.Values = CoalesceTables(cvals, b)
// Imported values from a child to a parent chart have a higher priority than
// values specified in the parent chart.
if merge {
// deep copying the cvals as there are cases where pointers can end
// up in the cvals when they are copied onto b in ways that break things.
cvals = deepCopyMap(cvals)
c.Values = MergeTables(b, cvals)
} else {
// Trimming the nil values from cvals is needed for backwards compatibility.
// Previously, the b value had been populated with cvals along with some
// overrides. This caused the coalescing functionality to remove the
// nil/null values. This trimming is for backwards compat.
cvals = trimNilValues(cvals)
c.Values = CoalesceTables(b, cvals)
}
return nil
}
func deepCopyMap(vals map[string]interface{}) map[string]interface{} {
valsCopy, err := copystructure.Copy(vals)
if err != nil {
return vals
}
return valsCopy.(map[string]interface{})
}
func trimNilValues(vals map[string]interface{}) map[string]interface{} {
valsCopy, err := copystructure.Copy(vals)
if err != nil {
return vals
}
valsCopyMap := valsCopy.(map[string]interface{})
for key, val := range valsCopyMap {
if val == nil {
log.Printf("trim deleting %q", key)
// Iterate over the values and remove nil keys
delete(valsCopyMap, key)
} else if istable(val) {
log.Printf("trim copying %q", key)
// Recursively call into ourselves to remove keys from inner tables
valsCopyMap[key] = trimNilValues(val.(map[string]interface{}))
}
}
return valsCopyMap
}
// processDependencyImportValues imports specified chart values from child to parent.
func processDependencyImportValues(c *chart.Chart) error {
func processDependencyImportValues(c *chart.Chart, merge bool) error {
for _, d := range c.Dependencies() {
// recurse
if err := processDependencyImportValues(d); err != nil {
if err := processDependencyImportValues(d, merge); err != nil {
return err
}
}
return processImportValues(c)
return processImportValues(c, merge)
}

@ -181,10 +181,13 @@ func TestProcessDependencyImportValues(t *testing.T) {
e["imported-chartA-B.SPextra5"] = "k8s"
e["imported-chartA-B.SC1extra5"] = "tiller"
e["overridden-chart1.SC1bool"] = "false"
e["overridden-chart1.SC1float"] = "3.141592"
e["overridden-chart1.SC1int"] = "99"
e["overridden-chart1.SC1string"] = "pollywog"
// These values are imported from the child chart to the parent. Imported
// values take precedence over those in the parent so these should be the
// values from the child chart.
e["overridden-chart1.SC1bool"] = "true"
e["overridden-chart1.SC1float"] = "3.14"
e["overridden-chart1.SC1int"] = "100"
e["overridden-chart1.SC1string"] = "dollywood"
e["overridden-chart1.SPextra2"] = "42"
e["overridden-chartA.SCAbool"] = "true"
@ -193,14 +196,17 @@ func TestProcessDependencyImportValues(t *testing.T) {
e["overridden-chartA.SCAstring"] = "jabberwocky"
e["overridden-chartA.SPextra4"] = "true"
// These values are imported from the child chart to the parent. Imported
// values take precedence over those in the parent so these should be the
// values from the child chart.
e["overridden-chartA-B.SCAbool"] = "true"
e["overridden-chartA-B.SCAfloat"] = "41.3"
e["overridden-chartA-B.SCAint"] = "808"
e["overridden-chartA-B.SCAstring"] = "jabberwocky"
e["overridden-chartA-B.SCBbool"] = "false"
e["overridden-chartA-B.SCBfloat"] = "1.99"
e["overridden-chartA-B.SCBint"] = "77"
e["overridden-chartA-B.SCBstring"] = "jango"
e["overridden-chartA-B.SCAfloat"] = "3.33"
e["overridden-chartA-B.SCAint"] = "555"
e["overridden-chartA-B.SCAstring"] = "wormwood"
e["overridden-chartA-B.SCBbool"] = "true"
e["overridden-chartA-B.SCBfloat"] = "0.25"
e["overridden-chartA-B.SCBint"] = "98"
e["overridden-chartA-B.SCBstring"] = "murkwood"
e["overridden-chartA-B.SPextra6"] = "111"
e["overridden-chartA-B.SCAextra1"] = "23"
e["overridden-chartA-B.SCBextra1"] = "13"
@ -212,7 +218,7 @@ func TestProcessDependencyImportValues(t *testing.T) {
e["SCBexported2A"] = "blaster"
e["global.SC1exported2.all.SC1exported3"] = "SC1expstr"
if err := processDependencyImportValues(c); err != nil {
if err := processDependencyImportValues(c, false); err != nil {
t.Fatalf("processing import values dependencies %v", err)
}
cc := Values(c.Values)
@ -225,18 +231,44 @@ func TestProcessDependencyImportValues(t *testing.T) {
switch pv := pv.(type) {
case float64:
if s := strconv.FormatFloat(pv, 'f', -1, 64); s != vv {
t.Errorf("failed to match imported float value %v with expected %v", s, vv)
t.Errorf("failed to match imported float value %v with expected %v for key %q", s, vv, kk)
}
case bool:
if b := strconv.FormatBool(pv); b != vv {
t.Errorf("failed to match imported bool value %v with expected %v", b, vv)
t.Errorf("failed to match imported bool value %v with expected %v for key %q", b, vv, kk)
}
default:
if pv != vv {
t.Errorf("failed to match imported string value %q with expected %q", pv, vv)
t.Errorf("failed to match imported string value %q with expected %q for key %q", pv, vv, kk)
}
}
}
// Since this was processed with coalescing there should be no null values.
// Here we verify that.
_, err := cc.PathValue("ensurenull")
if err == nil {
t.Error("expect nil value not found but found it")
}
switch xerr := err.(type) {
case ErrNoValue:
// We found what we expected
default:
t.Errorf("expected an ErrNoValue but got %q instead", xerr)
}
c = loadChart(t, "testdata/subpop")
if err := processDependencyImportValues(c, true); err != nil {
t.Fatalf("processing import values dependencies %v", err)
}
cc = Values(c.Values)
val, err := cc.PathValue("ensurenull")
if err != nil {
t.Error("expect value but ensurenull was not found")
}
if val != nil {
t.Errorf("expect nil value but got %q instead", val)
}
}
func TestProcessDependencyImportValuesMultiLevelPrecedence(t *testing.T) {
@ -244,10 +276,25 @@ func TestProcessDependencyImportValuesMultiLevelPrecedence(t *testing.T) {
e := make(map[string]string)
// The order of precedence should be:
// 1. User specified values (e.g CLI)
// 2. Imported values
// 3. Parent chart values
// 4. Sub-chart values
// The 4 app charts here deal with things differently:
// - app1 has a port value set in the umbrella chart. It does not import any
// values so the value from the umbrella chart should be used.
// - app2 has a value in the app chart and imports from the library. The
// library chart value should take precedence.
// - app3 has no value in the app chart and imports the value from the library
// chart. The library chart value should be used.
// - app4 has a value in the app chart and does not import the value from the
// library chart. The app charts value should be used.
e["app1.service.port"] = "3456"
e["app2.service.port"] = "8080"
if err := processDependencyImportValues(c); err != nil {
e["app2.service.port"] = "9090"
e["app3.service.port"] = "9090"
e["app4.service.port"] = "1234"
if err := processDependencyImportValues(c, true); err != nil {
t.Fatalf("processing import values dependencies %v", err)
}
cc := Values(c.Values)
@ -274,7 +321,7 @@ func TestProcessDependencyImportValuesForEnabledCharts(t *testing.T) {
c := loadChart(t, "testdata/import-values-from-enabled-subchart/parent-chart")
nameOverride := "parent-chart-prod"
if err := processDependencyImportValues(c); err != nil {
if err := processDependencyImportValues(c, true); err != nil {
t.Fatalf("processing import values dependencies %v", err)
}

@ -14,16 +14,17 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
/*Package chartutil contains tools for working with charts.
/*
Package chartutil contains tools for working with charts.
Charts are described in the chart package (pkg/chart).
This package provides utilities for serializing and deserializing charts.
A chart can be represented on the file system in one of two ways:
- As a directory that contains a Chart.yaml file and other chart things.
- As a tarred gzipped file containing a directory that then contains a
Chart.yaml file.
- As a directory that contains a Chart.yaml file and other chart things.
- As a tarred gzipped file containing a directory that then contains a
Chart.yaml file.
This package provides utilities for working with those file formats.

@ -18,7 +18,6 @@ package chartutil
import (
"io"
"io/ioutil"
"os"
"path/filepath"
@ -72,7 +71,7 @@ func Expand(dir string, r io.Reader) error {
return err
}
if err := ioutil.WriteFile(outpath, file.Data, 0644); err != nil {
if err := os.WriteFile(outpath, file.Data, 0644); err != nil {
return err
}
}

@ -17,7 +17,7 @@ limitations under the License.
package chartutil
import (
"io/ioutil"
"os"
"testing"
"helm.sh/helm/v3/pkg/chart"
@ -28,7 +28,7 @@ func TestValidateAgainstSingleSchema(t *testing.T) {
if err != nil {
t.Fatalf("Error reading YAML file: %s", err)
}
schema, err := ioutil.ReadFile("./testdata/test-values.schema.json")
schema, err := os.ReadFile("./testdata/test-values.schema.json")
if err != nil {
t.Fatalf("Error reading YAML file: %s", err)
}
@ -43,7 +43,7 @@ func TestValidateAgainstInvalidSingleSchema(t *testing.T) {
if err != nil {
t.Fatalf("Error reading YAML file: %s", err)
}
schema, err := ioutil.ReadFile("./testdata/test-values-invalid.schema.json")
schema, err := os.ReadFile("./testdata/test-values-invalid.schema.json")
if err != nil {
t.Fatalf("Error reading YAML file: %s", err)
}
@ -67,7 +67,7 @@ func TestValidateAgainstSingleSchemaNegative(t *testing.T) {
if err != nil {
t.Fatalf("Error reading YAML file: %s", err)
}
schema, err := ioutil.ReadFile("./testdata/test-values.schema.json")
schema, err := os.ReadFile("./testdata/test-values.schema.json")
if err != nil {
t.Fatalf("Error reading YAML file: %s", err)
}

@ -38,7 +38,7 @@ func TestSave(t *testing.T) {
tmp := ensure.TempDir(t)
defer os.RemoveAll(tmp)
for _, dest := range []string{tmp, path.Join(tmp, "newdir")} {
for _, dest := range []string{tmp, filepath.Join(tmp, "newdir")} {
t.Run("outDir="+dest, func(t *testing.T) {
c := &chart.Chart{
Metadata: &chart.Metadata{
@ -210,7 +210,7 @@ func TestSaveDir(t *testing.T) {
{Name: "scheherazade/shahryar.txt", Data: []byte("1,001 Nights")},
},
Templates: []*chart.File{
{Name: filepath.Join(TemplatesDir, "nested", "dir", "thing.yaml"), Data: []byte("abc: {{ .Values.abc }}")},
{Name: path.Join(TemplatesDir, "nested", "dir", "thing.yaml"), Data: []byte("abc: {{ .Values.abc }}")},
},
}
@ -227,11 +227,11 @@ func TestSaveDir(t *testing.T) {
t.Fatalf("Expected chart archive to have %q, got %q", c.Name(), c2.Name())
}
if len(c2.Templates) != 1 || c2.Templates[0].Name != filepath.Join(TemplatesDir, "nested", "dir", "thing.yaml") {
if len(c2.Templates) != 1 || c2.Templates[0].Name != c.Templates[0].Name {
t.Fatal("Templates data did not match")
}
if len(c2.Files) != 1 || c2.Files[0].Name != "scheherazade/shahryar.txt" {
if len(c2.Files) != 1 || c2.Files[0].Name != c.Files[0].Name {
t.Fatal("Files data did not match")
}
}

@ -41,3 +41,5 @@ tags:
subchart2alias:
enabled: false
ensurenull: null

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

Loading…
Cancel
Save