Merge branch 'master' into feat-add-hide-secrets-flag

pull/9130/head
Szymon Gibała 4 years ago
commit a59ff566f9

@ -1,7 +1,6 @@
maintainers: maintainers:
- adamreese - adamreese
- bacongobbler - bacongobbler
- fibonacci1729
- hickeyma - hickeyma
- jdolitsky - jdolitsky
- marckhouzam - marckhouzam
@ -9,8 +8,8 @@ maintainers:
- prydonius - prydonius
- SlickNik - SlickNik
- technosophos - technosophos
- viglesiasce
emeritus: emeritus:
- fibonacci1729
- jascott1 - jascott1
- michelleN - michelleN
- migmartri - migmartri
@ -19,3 +18,4 @@ emeritus:
- seh - seh
- thomastaylor312 - thomastaylor312
- vaikas-google - vaikas-google
- viglesiasce

@ -31,14 +31,18 @@ Charts are sorted by ref name, alphabetically.
` `
func newChartListCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { func newChartListCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
return &cobra.Command{ chartList := action.NewChartList(cfg)
cmd := &cobra.Command{
Use: "list", Use: "list",
Aliases: []string{"ls"}, Aliases: []string{"ls"},
Short: "list all saved charts", Short: "list all saved charts",
Long: chartListDesc, Long: chartListDesc,
Hidden: !FeatureGateOCI.IsEnabled(), Hidden: !FeatureGateOCI.IsEnabled(),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
return action.NewChartList(cfg).Run(out) return chartList.Run(out)
}, },
} }
f := cmd.Flags()
f.UintVar(&chartList.ColumnWidth, "max-col-width", 60, "maximum column width for output table")
return cmd
} }

@ -33,33 +33,41 @@ const bashCompDesc = `
Generate the autocompletion script for Helm for the bash shell. Generate the autocompletion script for Helm for the bash shell.
To load completions in your current shell session: To load completions in your current shell session:
$ source <(helm completion bash)
source <(helm completion bash)
To load completions for every new session, execute once: To load completions for every new session, execute once:
Linux: - Linux:
$ helm completion bash > /etc/bash_completion.d/helm
MacOS: helm completion bash > /etc/bash_completion.d/helm
$ helm completion bash > /usr/local/etc/bash_completion.d/helm
- MacOS:
helm completion bash > /usr/local/etc/bash_completion.d/helm
` `
const zshCompDesc = ` const zshCompDesc = `
Generate the autocompletion script for Helm for the zsh shell. Generate the autocompletion script for Helm for the zsh shell.
To load completions in your current shell session: To load completions in your current shell session:
$ source <(helm completion zsh)
source <(helm completion zsh)
To load completions for every new session, execute once: To load completions for every new session, execute once:
$ helm completion zsh > "${fpath[1]}/_helm"
helm completion zsh > "${fpath[1]}/_helm"
` `
const fishCompDesc = ` const fishCompDesc = `
Generate the autocompletion script for Helm for the fish shell. Generate the autocompletion script for Helm for the fish shell.
To load completions in your current shell session: To load completions in your current shell session:
$ helm completion fish | source
helm completion fish | source
To load completions for every new session, execute once: To load completions for every new session, execute once:
$ helm completion fish > ~/.config/fish/completions/helm.fish
helm completion fish > ~/.config/fish/completions/helm.fish
You will need to start a new shell for this setup to take effect. You will need to start a new shell for this setup to take effect.
` `

@ -100,7 +100,6 @@ func newDependencyCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
func newDependencyListCmd(out io.Writer) *cobra.Command { func newDependencyListCmd(out io.Writer) *cobra.Command {
client := action.NewDependency() client := action.NewDependency()
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "list CHART", Use: "list CHART",
Aliases: []string{"ls"}, Aliases: []string{"ls"},
@ -115,5 +114,9 @@ func newDependencyListCmd(out io.Writer) *cobra.Command {
return client.List(chartpath, out) return client.List(chartpath, out)
}, },
} }
f := cmd.Flags()
f.UintVar(&client.ColumnWidth, "max-col-width", 80, "maximum column width for output table")
return cmd return cmd
} }

@ -57,6 +57,7 @@ func addChartPathOptionsFlags(f *pflag.FlagSet, c *action.ChartPathOptions) {
f.StringVar(&c.KeyFile, "key-file", "", "identify HTTPS client using this SSL key 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.InsecureSkipTLSverify, "insecure-skip-tls-verify", false, "skip tls certificate checks for the chart download")
f.StringVar(&c.CaFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle") 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")
} }
// bindOutputFlag will add the output flag to the given command and bind the // bindOutputFlag will add the output flag to the given command and bind the

@ -145,7 +145,7 @@ func addInstallFlags(cmd *cobra.Command, f *pflag.FlagSet, client *action.Instal
f.StringVar(&client.NameTemplate, "name-template", "", "specify template used to name the release") f.StringVar(&client.NameTemplate, "name-template", "", "specify template used to name the release")
f.StringVar(&client.Description, "description", "", "add a custom description") f.StringVar(&client.Description, "description", "", "add a custom description")
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.Devel, "devel", false, "use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored")
f.BoolVar(&client.DependencyUpdate, "dependency-update", false, "run helm dependency update before installing the chart") f.BoolVar(&client.DependencyUpdate, "dependency-update", false, "update dependencies if they are missing before installing the chart")
f.BoolVar(&client.DisableOpenAPIValidation, "disable-openapi-validation", false, "if set, the installation process will not validate rendered templates against the Kubernetes OpenAPI Schema") f.BoolVar(&client.DisableOpenAPIValidation, "disable-openapi-validation", false, "if set, the installation process will not validate rendered templates against the Kubernetes OpenAPI Schema")
f.BoolVar(&client.Atomic, "atomic", false, "if set, the installation process deletes the installation on failure. The --wait flag will be set automatically if --atomic is used") f.BoolVar(&client.Atomic, "atomic", false, "if set, the installation process deletes the installation on failure. The --wait flag will be set automatically if --atomic is used")
f.BoolVar(&client.SkipCRDs, "skip-crds", false, "if set, no CRDs will be installed. By default, CRDs are installed if not already present") f.BoolVar(&client.SkipCRDs, "skip-crds", false, "if set, no CRDs will be installed. By default, CRDs are installed if not already present")

@ -18,10 +18,36 @@ package main
import ( import (
"fmt" "fmt"
"net/http"
"net/http/httptest"
"testing" "testing"
"helm.sh/helm/v3/pkg/repo/repotest"
) )
func TestInstall(t *testing.T) { func TestInstall(t *testing.T) {
srv, err := repotest.NewTempServerWithCleanup(t, "testdata/testcharts/*.tgz*")
if err != nil {
t.Fatal(err)
}
defer srv.Stop()
srv.WithMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
username, password, ok := r.BasicAuth()
if !ok || username != "username" || password != "password" {
t.Errorf("Expected request to use basic auth and for username == 'username' and password == 'password', got '%v', '%s', '%s'", ok, username, password)
}
}))
srv2 := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.FileServer(http.Dir(srv.Root())).ServeHTTP(w, r)
}))
defer srv2.Close()
if err := srv.LinkIndices(); err != nil {
t.Fatal(err)
}
tests := []cmdTestCase{ tests := []cmdTestCase{
// Install, base case // Install, base case
{ {
@ -207,6 +233,17 @@ func TestInstall(t *testing.T) {
name: "install chart with only crds", name: "install chart with only crds",
cmd: "install crd-test testdata/testcharts/chart-with-only-crds --namespace default", cmd: "install crd-test testdata/testcharts/chart-with-only-crds --namespace default",
}, },
// Verify the user/pass works
{
name: "basic install with credentials",
cmd: "install aeneas reqtest --namespace default --repo " + srv.URL() + " --username username --password password",
golden: "output/install.txt",
},
{
name: "basic install with credentials",
cmd: "install aeneas reqtest --namespace default --repo " + srv2.URL + " --username username --password password --pass-credentials",
golden: "output/install.txt",
},
// Install hiding secret values // Install hiding secret values
{ {
name: "install chart hiding secret values", name: "install chart hiding secret values",

@ -18,6 +18,8 @@ package main
import ( import (
"fmt" "fmt"
"net/http"
"net/http/httptest"
"os" "os"
"path/filepath" "path/filepath"
"testing" "testing"
@ -250,6 +252,115 @@ func TestPullCmd(t *testing.T) {
} }
} }
func TestPullWithCredentialsCmd(t *testing.T) {
srv, err := repotest.NewTempServerWithCleanup(t, "testdata/testcharts/*.tgz*")
if err != nil {
t.Fatal(err)
}
defer srv.Stop()
srv.WithMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
username, password, ok := r.BasicAuth()
if !ok || username != "username" || password != "password" {
t.Errorf("Expected request to use basic auth and for username == 'username' and password == 'password', got '%v', '%s', '%s'", ok, username, password)
}
}))
srv2 := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.FileServer(http.Dir(srv.Root())).ServeHTTP(w, r)
}))
defer srv2.Close()
if err := srv.LinkIndices(); err != nil {
t.Fatal(err)
}
// all flags will get "-d outdir" appended.
tests := []struct {
name string
args string
existFile string
existDir string
wantError bool
wantErrorMsg string
expectFile string
expectDir bool
}{
{
name: "Chart fetch using repo URL",
expectFile: "./signtest-0.1.0.tgz",
args: "signtest --repo " + srv.URL() + " --username username --password password",
},
{
name: "Fail fetching non-existent chart on repo URL",
args: "someChart --repo " + srv.URL() + " --username username --password password",
wantError: true,
},
{
name: "Specific version chart fetch using repo URL",
expectFile: "./signtest-0.1.0.tgz",
args: "signtest --version=0.1.0 --repo " + srv.URL() + " --username username --password password",
},
{
name: "Specific version chart fetch using repo URL",
args: "signtest --version=0.2.0 --repo " + srv.URL() + " --username username --password password",
wantError: true,
},
{
name: "Chart located on different domain with credentials passed",
args: "reqtest --repo " + srv2.URL + " --username username --password password --pass-credentials",
expectFile: "./reqtest-0.1.0.tgz",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
outdir := srv.Root()
cmd := fmt.Sprintf("pull %s -d '%s' --repository-config %s --repository-cache %s --registry-config %s",
tt.args,
outdir,
filepath.Join(outdir, "repositories.yaml"),
outdir,
filepath.Join(outdir, "config.json"),
)
// Create file or Dir before helm pull --untar, see: https://github.com/helm/helm/issues/7182
if tt.existFile != "" {
file := filepath.Join(outdir, tt.existFile)
_, err := os.Create(file)
if err != nil {
t.Fatal(err)
}
}
if tt.existDir != "" {
file := filepath.Join(outdir, tt.existDir)
err := os.Mkdir(file, 0755)
if err != nil {
t.Fatal(err)
}
}
_, _, err := executeActionCommand(cmd)
if err != nil {
if tt.wantError {
if tt.wantErrorMsg != "" && tt.wantErrorMsg == err.Error() {
t.Fatalf("Actual error %s, not equal to expected error %s", err, tt.wantErrorMsg)
}
return
}
t.Fatalf("%q reported error: %s", tt.name, err)
}
ef := filepath.Join(outdir, tt.expectFile)
fi, err := os.Stat(ef)
if err != nil {
t.Errorf("%q: expected a file at %s. %s", tt.name, ef, err)
}
if fi.IsDir() != tt.expectDir {
t.Errorf("%q: expected directory=%t, but it's not.", tt.name, tt.expectDir)
}
})
}
}
func TestPullVersionCompletion(t *testing.T) { func TestPullVersionCompletion(t *testing.T) {
repoFile := "testdata/helmhome/helm/repositories.yaml" repoFile := "testdata/helmhome/helm/repositories.yaml"
repoCache := "testdata/helmhome/helm/repository" repoCache := "testdata/helmhome/helm/repository"

@ -48,6 +48,7 @@ type repoAddOptions struct {
url string url string
username string username string
password string password string
passCredentialsAll bool
forceUpdate bool forceUpdate bool
allowDeprecatedRepos bool allowDeprecatedRepos bool
@ -91,6 +92,7 @@ func newRepoAddCmd(out io.Writer) *cobra.Command {
f.StringVar(&o.caFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle") 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 repository") f.BoolVar(&o.insecureSkipTLSverify, "insecure-skip-tls-verify", false, "skip tls certificate checks for the repository")
f.BoolVar(&o.allowDeprecatedRepos, "allow-deprecated-repos", false, "by default, this command will not allow adding official repos that have been permanently deleted. This disables that behavior") f.BoolVar(&o.allowDeprecatedRepos, "allow-deprecated-repos", false, "by default, this command will not allow adding official repos that have been permanently deleted. This disables that behavior")
f.BoolVar(&o.passCredentialsAll, "pass-credentials", false, "pass credentials to all domains")
return cmd return cmd
} }
@ -112,7 +114,14 @@ func (o *repoAddOptions) run(out io.Writer) error {
} }
// Acquire a file lock for process synchronization // Acquire a file lock for process synchronization
fileLock := flock.New(strings.Replace(o.repoFile, filepath.Ext(o.repoFile), ".lock", 1)) repoFileExt := filepath.Ext(o.repoFile)
var lockPath string
if len(repoFileExt) > 0 && len(repoFileExt) < len(o.repoFile) {
lockPath = strings.Replace(o.repoFile, repoFileExt, ".lock", 1)
} else {
lockPath = o.repoFile + ".lock"
}
fileLock := flock.New(lockPath)
lockCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second) lockCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel() defer cancel()
locked, err := fileLock.TryLockContext(lockCtx, time.Second) locked, err := fileLock.TryLockContext(lockCtx, time.Second)
@ -149,6 +158,7 @@ func (o *repoAddOptions) run(out io.Writer) error {
URL: o.url, URL: o.url,
Username: o.username, Username: o.username,
Password: o.password, Password: o.password,
PassCredentialsAll: o.passCredentialsAll,
CertFile: o.certFile, CertFile: o.certFile,
KeyFile: o.keyFile, KeyFile: o.keyFile,
CAFile: o.caFile, CAFile: o.caFile,

@ -142,6 +142,18 @@ func TestRepoAddConcurrentDirNotExist(t *testing.T) {
repoAddConcurrent(t, testName, repoFile) repoAddConcurrent(t, testName, repoFile)
} }
func TestRepoAddConcurrentNoFileExtension(t *testing.T) {
const testName = "test-name-3"
repoFile := filepath.Join(ensure.TempDir(t), "repositories")
repoAddConcurrent(t, testName, repoFile)
}
func TestRepoAddConcurrentHiddenFile(t *testing.T) {
const testName = "test-name-4"
repoFile := filepath.Join(ensure.TempDir(t), ".repositories")
repoAddConcurrent(t, testName, repoFile)
}
func repoAddConcurrent(t *testing.T, testName, repoFile string) { func repoAddConcurrent(t *testing.T, testName, repoFile string) {
ts, err := repotest.NewTempServerWithCleanup(t, "testdata/testserver/*.*") ts, err := repotest.NewTempServerWithCleanup(t, "testdata/testserver/*.*")
if err != nil { if err != nil {

@ -0,0 +1,9 @@
Release "funny-bunny" has been upgraded. Happy Helming!
NAME: funny-bunny
LAST DEPLOYED: Fri Sep 2 22:04:05 1977
NAMESPACE: default
STATUS: deployed
REVISION: 3
TEST SUITE: None
NOTES:
PARENT NOTES

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

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

@ -1 +1 @@
Version: v3.5 Version: v3.6

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

@ -0,0 +1,6 @@
dependencies:
- name: subchart-with-notes
repository: file://../chart-with-subchart-notes/charts/subchart-with-notes
version: 0.0.1
digest: sha256:8ca45f73ae3f6170a09b64a967006e98e13cd91eb51e5ab0599bb87296c7df0a
generated: "2021-05-02T15:07:22.1099921+02:00"

@ -30,6 +30,7 @@ import (
"helm.sh/helm/v3/pkg/chart/loader" "helm.sh/helm/v3/pkg/chart/loader"
"helm.sh/helm/v3/pkg/cli/output" "helm.sh/helm/v3/pkg/cli/output"
"helm.sh/helm/v3/pkg/cli/values" "helm.sh/helm/v3/pkg/cli/values"
"helm.sh/helm/v3/pkg/downloader"
"helm.sh/helm/v3/pkg/getter" "helm.sh/helm/v3/pkg/getter"
"helm.sh/helm/v3/pkg/storage/driver" "helm.sh/helm/v3/pkg/storage/driver"
) )
@ -132,7 +133,8 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
return err return err
} }
vals, err := valueOpts.MergeValues(getter.All(settings)) p := getter.All(settings)
vals, err := valueOpts.MergeValues(p)
if err != nil { if err != nil {
return err return err
} }
@ -144,8 +146,28 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
} }
if req := ch.Metadata.Dependencies; req != nil { if req := ch.Metadata.Dependencies; req != nil {
if err := action.CheckDependencies(ch, req); err != nil { if err := action.CheckDependencies(ch, req); err != nil {
if client.DependencyUpdate {
man := &downloader.Manager{
Out: out,
ChartPath: chartPath,
Keyring: client.ChartPathOptions.Keyring,
SkipUpdate: false,
Getters: p,
RepositoryConfig: settings.RepositoryConfig,
RepositoryCache: settings.RepositoryCache,
Debug: settings.Debug,
}
if err := man.Update(); err != nil {
return err return err
} }
// Reload the chart with the updated Chart.lock file.
if ch, err = loader.Load(chartPath); err != nil {
return errors.Wrap(err, "failed reloading chart after repo update")
}
} else {
return err
}
}
} }
if ch.Metadata.Deprecated { if ch.Metadata.Deprecated {
@ -186,6 +208,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
f.BoolVar(&client.CleanupOnFail, "cleanup-on-fail", false, "allow deletion of new resources created in this upgrade when upgrade fails") f.BoolVar(&client.CleanupOnFail, "cleanup-on-fail", false, "allow deletion of new resources created in this upgrade when upgrade fails")
f.BoolVar(&client.SubNotes, "render-subchart-notes", false, "if set, render subchart notes along with the parent") f.BoolVar(&client.SubNotes, "render-subchart-notes", false, "if set, render subchart notes along with the parent")
f.StringVar(&client.Description, "description", "", "add a custom description") f.StringVar(&client.Description, "description", "", "add a custom description")
f.BoolVar(&client.DependencyUpdate, "dependency-update", false, "update dependencies if they are missing before installing the chart")
addChartPathOptionsFlags(f, &client.ChartPathOptions) addChartPathOptionsFlags(f, &client.ChartPathOptions)
addValueOptionsFlags(f, valueOpts) addValueOptionsFlags(f, valueOpts)
bindOutputFlag(cmd, &outfmt) bindOutputFlag(cmd, &outfmt)

@ -32,6 +32,7 @@ import (
) )
func TestUpgradeCmd(t *testing.T) { func TestUpgradeCmd(t *testing.T) {
tmpChart := ensure.TempDir(t) tmpChart := ensure.TempDir(t)
cfile := &chart.Chart{ cfile := &chart.Chart{
Metadata: &chart.Metadata{ Metadata: &chart.Metadata{
@ -79,6 +80,7 @@ func TestUpgradeCmd(t *testing.T) {
missingDepsPath := "testdata/testcharts/chart-missing-deps" missingDepsPath := "testdata/testcharts/chart-missing-deps"
badDepsPath := "testdata/testcharts/chart-bad-requirements" badDepsPath := "testdata/testcharts/chart-bad-requirements"
presentDepsPath := "testdata/testcharts/chart-with-subchart-update"
relWithStatusMock := func(n string, v int, ch *chart.Chart, status release.Status) *release.Release { relWithStatusMock := func(n string, v int, ch *chart.Chart, status release.Status) *release.Release {
return release.Mock(&release.MockReleaseOptions{Name: n, Version: v, Chart: ch, Status: status}) return release.Mock(&release.MockReleaseOptions{Name: n, Version: v, Chart: ch, Status: status})
@ -149,6 +151,12 @@ func TestUpgradeCmd(t *testing.T) {
golden: "output/upgrade-with-bad-dependencies.txt", golden: "output/upgrade-with-bad-dependencies.txt",
wantError: true, wantError: true,
}, },
{
name: "upgrade a release with resolving missing dependencies",
cmd: fmt.Sprintf("upgrade --dependency-update funny-bunny %s", presentDepsPath),
golden: "output/upgrade-with-dependency-update.txt",
rels: []*release.Release{relMock("funny-bunny", 2, ch2)},
},
{ {
name: "upgrade a non-existent release", name: "upgrade a non-existent release",
cmd: fmt.Sprintf("upgrade funny-bunny '%s'", chartPath), cmd: fmt.Sprintf("upgrade funny-bunny '%s'", chartPath),

@ -51,6 +51,7 @@ type (
authorizer *Authorizer authorizer *Authorizer
resolver *Resolver resolver *Resolver
cache *Cache cache *Cache
columnWidth uint
} }
) )
@ -95,6 +96,10 @@ func NewClient(opts ...ClientOption) (*Client, error) {
} }
client.cache = cache client.cache = cache
} }
if client.columnWidth == 0 {
client.columnWidth = 60
}
return client, nil return client, nil
} }
@ -279,7 +284,7 @@ func (c *Client) RemoveChart(ref *Reference) error {
// PrintChartTable prints a list of locally stored charts // PrintChartTable prints a list of locally stored charts
func (c *Client) PrintChartTable() error { func (c *Client) PrintChartTable() error {
table := uitable.New() table := uitable.New()
table.MaxColWidth = 60 table.MaxColWidth = c.columnWidth
table.AddRow("REF", "NAME", "VERSION", "DIGEST", "SIZE", "CREATED") table.AddRow("REF", "NAME", "VERSION", "DIGEST", "SIZE", "CREATED")
rows, err := c.getChartTableRows() rows, err := c.getChartTableRows()
if err != nil { if err != nil {

@ -67,3 +67,10 @@ func ClientOptCredentialsFile(credentialsFile string) ClientOption {
client.credentialsFile = credentialsFile client.credentialsFile = credentialsFile
} }
} }
// ClientOptColumnWidth returns a function that sets the column width on a client options set
func ClientOptColumnWidth(columnWidth uint) ClientOption {
return func(client *Client) {
client.columnWidth = columnWidth
}
}

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

@ -102,11 +102,11 @@ type Configuration struct {
// TODO: This function is badly in need of a refactor. // TODO: This function is badly in need of a refactor.
// TODO: As part of the refactor the duplicate code in cmd/helm/template.go should be removed // 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. // This code has to do with writing files to disk.
func (c *Configuration) renderResources(ch *chart.Chart, values chartutil.Values, releaseName, outputDir string, subNotes, useReleaseName, includeCrds bool, pr postrender.PostRenderer, dryRun 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, dryRun bool) ([]*release.Hook, *bytes.Buffer, string, error) {
hs := []*release.Hook{} hs := []*release.Hook{}
b := bytes.NewBuffer(nil) b := bytes.NewBuffer(nil)
caps, err := c.getCapabilities() caps, err := cfg.getCapabilities()
if err != nil { if err != nil {
return hs, b, "", err return hs, b, "", err
} }
@ -125,12 +125,12 @@ func (c *Configuration) renderResources(ch *chart.Chart, values chartutil.Values
// is mocked. It is not up to the template author to decide when the user wants to // 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 // connect to the cluster. So when the user says to dry run, respect the user's
// wishes and do not connect to the cluster. // wishes and do not connect to the cluster.
if !dryRun && c.RESTClientGetter != nil { if !dryRun && cfg.RESTClientGetter != nil {
rest, err := c.RESTClientGetter.ToRESTConfig() restConfig, err := cfg.RESTClientGetter.ToRESTConfig()
if err != nil { if err != nil {
return hs, b, "", err return hs, b, "", err
} }
files, err2 = engine.RenderWithClient(ch, values, rest) files, err2 = engine.RenderWithClient(ch, values, restConfig)
} else { } else {
files, err2 = engine.Render(ch, values) files, err2 = engine.Render(ch, values)
} }
@ -236,11 +236,11 @@ type RESTClientGetter interface {
type DebugLog func(format string, v ...interface{}) type DebugLog func(format string, v ...interface{})
// capabilities builds a Capabilities from discovery information. // capabilities builds a Capabilities from discovery information.
func (c *Configuration) getCapabilities() (*chartutil.Capabilities, error) { func (cfg *Configuration) getCapabilities() (*chartutil.Capabilities, error) {
if c.Capabilities != nil { if cfg.Capabilities != nil {
return c.Capabilities, nil return cfg.Capabilities, nil
} }
dc, err := c.RESTClientGetter.ToDiscoveryClient() dc, err := cfg.RESTClientGetter.ToDiscoveryClient()
if err != nil { if err != nil {
return nil, errors.Wrap(err, "could not get Kubernetes discovery client") return nil, errors.Wrap(err, "could not get Kubernetes discovery client")
} }
@ -258,14 +258,14 @@ func (c *Configuration) getCapabilities() (*chartutil.Capabilities, error) {
apiVersions, err := GetVersionSet(dc) apiVersions, err := GetVersionSet(dc)
if err != nil { if err != nil {
if discovery.IsGroupDiscoveryFailedError(err) { if discovery.IsGroupDiscoveryFailedError(err) {
c.Log("WARNING: The Kubernetes server has an orphaned API service. Server reports: %s", err) cfg.Log("WARNING: The Kubernetes server has an orphaned API service. Server reports: %s", err)
c.Log("WARNING: To fix this, kubectl delete apiservice <service-name>") cfg.Log("WARNING: To fix this, kubectl delete apiservice <service-name>")
} else { } else {
return nil, errors.Wrap(err, "could not get apiVersions from Kubernetes") return nil, errors.Wrap(err, "could not get apiVersions from Kubernetes")
} }
} }
c.Capabilities = &chartutil.Capabilities{ cfg.Capabilities = &chartutil.Capabilities{
APIVersions: apiVersions, APIVersions: apiVersions,
KubeVersion: chartutil.KubeVersion{ KubeVersion: chartutil.KubeVersion{
Version: kubeVersion.GitVersion, Version: kubeVersion.GitVersion,
@ -273,12 +273,12 @@ func (c *Configuration) getCapabilities() (*chartutil.Capabilities, error) {
Minor: kubeVersion.Minor, Minor: kubeVersion.Minor,
}, },
} }
return c.Capabilities, nil return cfg.Capabilities, nil
} }
// KubernetesClientSet creates a new kubernetes ClientSet based on the configuration // KubernetesClientSet creates a new kubernetes ClientSet based on the configuration
func (c *Configuration) KubernetesClientSet() (kubernetes.Interface, error) { func (cfg *Configuration) KubernetesClientSet() (kubernetes.Interface, error) {
conf, err := c.RESTClientGetter.ToRESTConfig() conf, err := cfg.RESTClientGetter.ToRESTConfig()
if err != nil { if err != nil {
return nil, errors.Wrap(err, "unable to generate config for kubernetes client") return nil, errors.Wrap(err, "unable to generate config for kubernetes client")
} }
@ -290,20 +290,20 @@ func (c *Configuration) KubernetesClientSet() (kubernetes.Interface, error) {
// //
// If the configuration has a Timestamper on it, that will be used. // If the configuration has a Timestamper on it, that will be used.
// Otherwise, this will use time.Now(). // Otherwise, this will use time.Now().
func (c *Configuration) Now() time.Time { func (cfg *Configuration) Now() time.Time {
return Timestamper() return Timestamper()
} }
func (c *Configuration) releaseContent(name string, version int) (*release.Release, error) { func (cfg *Configuration) releaseContent(name string, version int) (*release.Release, error) {
if err := chartutil.ValidateReleaseName(name); err != nil { if err := chartutil.ValidateReleaseName(name); err != nil {
return nil, errors.Errorf("releaseContent: Release name is invalid: %s", name) return nil, errors.Errorf("releaseContent: Release name is invalid: %s", name)
} }
if version <= 0 { if version <= 0 {
return c.Releases.Last(name) return cfg.Releases.Last(name)
} }
return c.Releases.Get(name, version) return cfg.Releases.Get(name, version)
} }
// GetVersionSet retrieves a set of available k8s API versions // GetVersionSet retrieves a set of available k8s API versions
@ -355,14 +355,14 @@ func GetVersionSet(client discovery.ServerResourcesInterface) (chartutil.Version
} }
// recordRelease with an update operation in case reuse has been set. // recordRelease with an update operation in case reuse has been set.
func (c *Configuration) recordRelease(r *release.Release) { func (cfg *Configuration) recordRelease(r *release.Release) {
if err := c.Releases.Update(r); err != nil { if err := cfg.Releases.Update(r); err != nil {
c.Log("warning: Failed to update release %s: %s", r.Name, err) cfg.Log("warning: Failed to update release %s: %s", r.Name, err)
} }
} }
// Init initializes the action configuration // Init initializes the action configuration
func (c *Configuration) Init(getter genericclioptions.RESTClientGetter, namespace, helmDriver string, log DebugLog) error { func (cfg *Configuration) Init(getter genericclioptions.RESTClientGetter, namespace, helmDriver string, log DebugLog) error {
kc := kube.New(getter) kc := kube.New(getter)
kc.Log = log kc.Log = log
@ -383,8 +383,8 @@ func (c *Configuration) Init(getter genericclioptions.RESTClientGetter, namespac
store = storage.Init(d) store = storage.Init(d)
case "memory": case "memory":
var d *driver.Memory var d *driver.Memory
if c.Releases != nil { if cfg.Releases != nil {
if mem, ok := c.Releases.Driver.(*driver.Memory); ok { if mem, ok := cfg.Releases.Driver.(*driver.Memory); ok {
// This function can be called more than once (e.g., helm list --all-namespaces). // This function can be called more than once (e.g., helm list --all-namespaces).
// If a memory driver was already initialized, re-use it but set the possibly new namespace. // If a memory driver was already initialized, re-use it but set the possibly new namespace.
// We re-use it in case some releases where already created in the existing memory driver. // We re-use it in case some releases where already created in the existing memory driver.
@ -411,10 +411,10 @@ func (c *Configuration) Init(getter genericclioptions.RESTClientGetter, namespac
panic("Unknown driver in HELM_DRIVER: " + helmDriver) panic("Unknown driver in HELM_DRIVER: " + helmDriver)
} }
c.RESTClientGetter = getter cfg.RESTClientGetter = getter
c.KubeClient = kc cfg.KubeClient = kc
c.Releases = store cfg.Releases = store
c.Log = log cfg.Log = log
return nil return nil
} }

@ -18,11 +18,14 @@ package action
import ( import (
"io" "io"
"helm.sh/helm/v3/internal/experimental/registry"
) )
// ChartList performs a chart list operation. // ChartList performs a chart list operation.
type ChartList struct { type ChartList struct {
cfg *Configuration cfg *Configuration
ColumnWidth uint
} }
// NewChartList creates a new ChartList object with the given configuration. // NewChartList creates a new ChartList object with the given configuration.
@ -34,5 +37,8 @@ func NewChartList(cfg *Configuration) *ChartList {
// Run executes the chart list operation // Run executes the chart list operation
func (a *ChartList) Run(out io.Writer) error { func (a *ChartList) Run(out io.Writer) error {
return a.cfg.RegistryClient.PrintChartTable() client := a.cfg.RegistryClient
opt := registry.ClientOptColumnWidth(a.ColumnWidth)
opt(client)
return client.PrintChartTable()
} }

@ -37,11 +37,14 @@ type Dependency struct {
Verify bool Verify bool
Keyring string Keyring string
SkipRefresh bool SkipRefresh bool
ColumnWidth uint
} }
// NewDependency creates a new Dependency object with the given configuration. // NewDependency creates a new Dependency object with the given configuration.
func NewDependency() *Dependency { func NewDependency() *Dependency {
return &Dependency{} return &Dependency{
ColumnWidth: 80,
}
} }
// List executes 'helm dependency list'. // List executes 'helm dependency list'.
@ -181,7 +184,7 @@ func statArchiveForStatus(archive string, dep *chart.Dependency) string {
// printDependencies prints all of the dependencies in the yaml file. // printDependencies prints all of the dependencies in the yaml file.
func (d *Dependency) printDependencies(chartpath string, out io.Writer, c *chart.Chart) { func (d *Dependency) printDependencies(chartpath string, out io.Writer, c *chart.Chart) {
table := uitable.New() table := uitable.New()
table.MaxColWidth = 80 table.MaxColWidth = d.ColumnWidth
table.AddRow("NAME", "VERSION", "REPOSITORY", "STATUS") table.AddRow("NAME", "VERSION", "REPOSITORY", "STATUS")
for _, row := range c.Metadata.Dependencies { for _, row := range c.Metadata.Dependencies {
table.AddRow(row.Name, row.Version, row.Repository, d.dependencyStatus(chartpath, row, c)) table.AddRow(row.Name, row.Version, row.Repository, d.dependencyStatus(chartpath, row, c))

@ -20,6 +20,7 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"net/url"
"os" "os"
"path" "path"
"path/filepath" "path/filepath"
@ -113,6 +114,7 @@ type ChartPathOptions struct {
InsecureSkipTLSverify bool // --insecure-skip-verify InsecureSkipTLSverify bool // --insecure-skip-verify
Keyring string // --keyring Keyring string // --keyring
Password string // --password Password string // --password
PassCredentialsAll bool // --pass-credentials
RepoURL string // --repo RepoURL string // --repo
Username string // --username Username string // --username
Verify bool // --verify Verify bool // --verify
@ -225,7 +227,7 @@ func (i *Install) Run(chrt *chart.Chart, vals map[string]interface{}) (*release.
return nil, err return nil, err
} }
//special case for helm template --is-upgrade // special case for helm template --is-upgrade
isUpgrade := i.IsUpgrade && i.DryRun isUpgrade := i.IsUpgrade && i.DryRun
options := chartutil.ReleaseOptions{ options := chartutil.ReleaseOptions{
Name: i.ReleaseName, Name: i.ReleaseName,
@ -654,7 +656,7 @@ func (c *ChartPathOptions) LocateChart(name string, settings *cli.EnvSettings) (
Keyring: c.Keyring, Keyring: c.Keyring,
Getters: getter.All(settings), Getters: getter.All(settings),
Options: []getter.Option{ Options: []getter.Option{
getter.WithBasicAuth(c.Username, c.Password), getter.WithPassCredentialsAll(c.PassCredentialsAll),
getter.WithTLSClientConfig(c.CertFile, c.KeyFile, c.CaFile), getter.WithTLSClientConfig(c.CertFile, c.KeyFile, c.CaFile),
getter.WithInsecureSkipVerifyTLS(c.InsecureSkipTLSverify), getter.WithInsecureSkipVerifyTLS(c.InsecureSkipTLSverify),
}, },
@ -665,12 +667,34 @@ func (c *ChartPathOptions) LocateChart(name string, settings *cli.EnvSettings) (
dl.Verify = downloader.VerifyAlways dl.Verify = downloader.VerifyAlways
} }
if c.RepoURL != "" { if c.RepoURL != "" {
chartURL, err := repo.FindChartInAuthAndTLSRepoURL(c.RepoURL, c.Username, c.Password, name, version, chartURL, err := repo.FindChartInAuthAndTLSAndPassRepoURL(c.RepoURL, c.Username, c.Password, name, version,
c.CertFile, c.KeyFile, c.CaFile, c.InsecureSkipTLSverify, getter.All(settings)) c.CertFile, c.KeyFile, c.CaFile, c.InsecureSkipTLSverify, c.PassCredentialsAll, getter.All(settings))
if err != nil { if err != nil {
return "", err return "", err
} }
name = chartURL name = chartURL
// Only pass the user/pass on when the user has said to or when the
// location of the chart repo and the chart are the same domain.
u1, err := url.Parse(c.RepoURL)
if err != nil {
return "", err
}
u2, err := url.Parse(chartURL)
if err != nil {
return "", err
}
// Host on URL (returned from url.Parse) contains the port if present.
// This check ensures credentials are not passed between different
// services on different ports.
if c.PassCredentialsAll || (u1.Scheme == u2.Scheme && u1.Host == u2.Host) {
dl.Options = append(dl.Options, getter.WithBasicAuth(c.Username, c.Password))
} else {
dl.Options = append(dl.Options, getter.WithBasicAuth("", ""))
}
} else {
dl.Options = append(dl.Options, getter.WithBasicAuth(c.Username, c.Password))
} }
if err := os.MkdirAll(settings.RepositoryCache, 0755); err != nil { if err := os.MkdirAll(settings.RepositoryCache, 0755); err != nil {

@ -82,6 +82,7 @@ func (p *Pull) Run(chartRef string) (string, error) {
Getters: getter.All(p.Settings), Getters: getter.All(p.Settings),
Options: []getter.Option{ Options: []getter.Option{
getter.WithBasicAuth(p.Username, p.Password), getter.WithBasicAuth(p.Username, p.Password),
getter.WithPassCredentialsAll(p.PassCredentialsAll),
getter.WithTLSClientConfig(p.CertFile, p.KeyFile, p.CaFile), getter.WithTLSClientConfig(p.CertFile, p.KeyFile, p.CaFile),
getter.WithInsecureSkipVerifyTLS(p.InsecureSkipTLSverify), getter.WithInsecureSkipVerifyTLS(p.InsecureSkipTLSverify),
}, },
@ -118,7 +119,7 @@ func (p *Pull) Run(chartRef string) (string, error) {
} }
if p.RepoURL != "" { if p.RepoURL != "" {
chartURL, err := repo.FindChartInAuthAndTLSRepoURL(p.RepoURL, p.Username, p.Password, chartRef, p.Version, p.CertFile, p.KeyFile, p.CaFile, p.InsecureSkipTLSverify, getter.All(p.Settings)) chartURL, err := repo.FindChartInAuthAndTLSAndPassRepoURL(p.RepoURL, p.Username, p.Password, chartRef, p.Version, p.CertFile, p.KeyFile, p.CaFile, p.InsecureSkipTLSverify, p.PassCredentialsAll, getter.All(p.Settings))
if err != nil { if err != nil {
return out.String(), err return out.String(), err
} }

@ -98,6 +98,8 @@ type Upgrade struct {
PostRenderer postrender.PostRenderer PostRenderer postrender.PostRenderer
// DisableOpenAPIValidation controls whether OpenAPI validation is enforced. // DisableOpenAPIValidation controls whether OpenAPI validation is enforced.
DisableOpenAPIValidation bool DisableOpenAPIValidation bool
// Get missing dependencies
DependencyUpdate bool
} }
// NewUpgrade creates a new Upgrade object with the given configuration. // NewUpgrade creates a new Upgrade object with the given configuration.

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

@ -127,10 +127,11 @@ func coalesceGlobals(dest, src map[string]interface{}) {
// It's not clear if this condition can actually ever trigger. // It's not clear if this condition can actually ever trigger.
log.Printf("key %s is table. Skipping", key) log.Printf("key %s is table. Skipping", key)
continue continue
} } else {
// TODO: Do we need to do any additional checking on the value? // TODO: Do we need to do any additional checking on the value?
dg[key] = val dg[key] = val
} }
}
dest[GlobalKey] = dg dest[GlobalKey] = dg
} }

@ -87,6 +87,9 @@ func TestCoalesceValues(t *testing.T) {
&chart.Chart{ &chart.Chart{
Metadata: &chart.Metadata{Name: "ahab"}, Metadata: &chart.Metadata{Name: "ahab"},
Values: map[string]interface{}{ Values: map[string]interface{}{
"global": map[string]interface{}{
"nested": map[string]interface{}{"foo": "bar"},
},
"scope": "ahab", "scope": "ahab",
"name": "ahab", "name": "ahab",
"boat": true, "boat": true,
@ -135,9 +138,11 @@ func TestCoalesceValues(t *testing.T) {
{"{{.pequod.ahab.scope}}", "whale"}, {"{{.pequod.ahab.scope}}", "whale"},
{"{{.pequod.ahab.nested.foo}}", "true"}, {"{{.pequod.ahab.nested.foo}}", "true"},
{"{{.pequod.ahab.global.name}}", "Ishmael"}, {"{{.pequod.ahab.global.name}}", "Ishmael"},
{"{{.pequod.ahab.global.nested.foo}}", "bar"},
{"{{.pequod.ahab.global.subject}}", "Queequeg"}, {"{{.pequod.ahab.global.subject}}", "Queequeg"},
{"{{.pequod.ahab.global.harpooner}}", "Tashtego"}, {"{{.pequod.ahab.global.harpooner}}", "Tashtego"},
{"{{.pequod.global.name}}", "Ishmael"}, {"{{.pequod.global.name}}", "Ishmael"},
{"{{.pequod.global.nested.foo}}", "<no value>"},
{"{{.pequod.global.subject}}", "Queequeg"}, {"{{.pequod.global.subject}}", "Queequeg"},
{"{{.spouter.global.name}}", "Ishmael"}, {"{{.spouter.global.name}}", "Ishmael"},
{"{{.spouter.global.harpooner}}", "<no value>"}, {"{{.spouter.global.harpooner}}", "<no value>"},

@ -195,6 +195,7 @@ func (c *ChartDownloader) ResolveChartVersion(ref, version string) (*url.URL, er
c.Options = append( c.Options = append(
c.Options, c.Options,
getter.WithBasicAuth(rc.Username, rc.Password), getter.WithBasicAuth(rc.Username, rc.Password),
getter.WithPassCredentialsAll(rc.PassCredentialsAll),
) )
} }
return u, nil return u, nil
@ -224,7 +225,10 @@ func (c *ChartDownloader) ResolveChartVersion(ref, version string) (*url.URL, er
c.Options = append(c.Options, getter.WithTLSClientConfig(r.Config.CertFile, r.Config.KeyFile, r.Config.CAFile)) c.Options = append(c.Options, getter.WithTLSClientConfig(r.Config.CertFile, r.Config.KeyFile, r.Config.CAFile))
} }
if r.Config.Username != "" && r.Config.Password != "" { if r.Config.Username != "" && r.Config.Password != "" {
c.Options = append(c.Options, getter.WithBasicAuth(r.Config.Username, r.Config.Password)) c.Options = append(c.Options,
getter.WithBasicAuth(r.Config.Username, r.Config.Password),
getter.WithPassCredentialsAll(r.Config.PassCredentialsAll),
)
} }
} }

@ -205,6 +205,7 @@ func TestDownloadTo(t *testing.T) {
}), }),
Options: []getter.Option{ Options: []getter.Option{
getter.WithBasicAuth("username", "password"), getter.WithBasicAuth("username", "password"),
getter.WithPassCredentialsAll(false),
}, },
} }
cname := "/signtest-0.1.0.tgz" cname := "/signtest-0.1.0.tgz"

@ -276,21 +276,21 @@ func (m *Manager) downloadAll(deps []*chart.Dependency) error {
chartPath := filepath.Join(tmpPath, dep.Name) chartPath := filepath.Join(tmpPath, dep.Name)
ch, err := loader.LoadDir(chartPath) ch, err := loader.LoadDir(chartPath)
if err != nil { if err != nil {
return fmt.Errorf("Unable to load chart: %v", err) return fmt.Errorf("unable to load chart: %v", err)
} }
constraint, err := semver.NewConstraint(dep.Version) constraint, err := semver.NewConstraint(dep.Version)
if err != nil { if err != nil {
return fmt.Errorf("Dependency %s has an invalid version/constraint format: %s", dep.Name, err) return fmt.Errorf("dependency %s has an invalid version/constraint format: %s", dep.Name, err)
} }
v, err := semver.NewVersion(ch.Metadata.Version) v, err := semver.NewVersion(ch.Metadata.Version)
if err != nil { if err != nil {
return fmt.Errorf("Invalid version %s for dependency %s: %s", dep.Version, dep.Name, err) return fmt.Errorf("invalid version %s for dependency %s: %s", dep.Version, dep.Name, err)
} }
if !constraint.Check(v) { if !constraint.Check(v) {
saveError = fmt.Errorf("Dependency %s at version %s does not satisfy the constraint %s", dep.Name, ch.Metadata.Version, dep.Version) saveError = fmt.Errorf("dependency %s at version %s does not satisfy the constraint %s", dep.Name, ch.Metadata.Version, dep.Version)
break break
} }
continue continue
@ -310,7 +310,7 @@ func (m *Manager) downloadAll(deps []*chart.Dependency) error {
// Any failure to resolve/download a chart should fail: // Any failure to resolve/download a chart should fail:
// https://github.com/helm/helm/issues/1439 // https://github.com/helm/helm/issues/1439
churl, username, password, err := m.findChartURL(dep.Name, dep.Version, dep.Repository, repos) churl, username, password, insecureskiptlsverify, passcredentialsall, err := m.findChartURL(dep.Name, dep.Version, dep.Repository, repos)
if err != nil { if err != nil {
saveError = errors.Wrapf(err, "could not find %s", churl) saveError = errors.Wrapf(err, "could not find %s", churl)
break break
@ -332,6 +332,8 @@ func (m *Manager) downloadAll(deps []*chart.Dependency) error {
Getters: m.Getters, Getters: m.Getters,
Options: []getter.Option{ Options: []getter.Option{
getter.WithBasicAuth(username, password), getter.WithBasicAuth(username, password),
getter.WithPassCredentialsAll(passcredentialsall),
getter.WithInsecureSkipVerifyTLS(insecureskiptlsverify),
}, },
} }
@ -685,9 +687,9 @@ func (m *Manager) parallelRepoUpdate(repos []*repo.Entry) error {
// repoURL is the repository to search // repoURL is the repository to search
// //
// If it finds a URL that is "relative", it will prepend the repoURL. // If it finds a URL that is "relative", it will prepend the repoURL.
func (m *Manager) findChartURL(name, version, repoURL string, repos map[string]*repo.ChartRepository) (url, username, password string, err error) { func (m *Manager) findChartURL(name, version, repoURL string, repos map[string]*repo.ChartRepository) (url, username, password string, insecureskiptlsverify, passcredentialsall bool, err error) {
if strings.HasPrefix(repoURL, "oci://") { if strings.HasPrefix(repoURL, "oci://") {
return fmt.Sprintf("%s/%s:%s", repoURL, name, version), "", "", nil return fmt.Sprintf("%s/%s:%s", repoURL, name, version), "", "", false, false, nil
} }
for _, cr := range repos { for _, cr := range repos {
@ -709,15 +711,17 @@ func (m *Manager) findChartURL(name, version, repoURL string, repos map[string]*
} }
username = cr.Config.Username username = cr.Config.Username
password = cr.Config.Password password = cr.Config.Password
passcredentialsall = cr.Config.PassCredentialsAll
insecureskiptlsverify = cr.Config.InsecureSkipTLSverify
return return
} }
} }
url, err = repo.FindChartInRepoURL(repoURL, name, version, "", "", "", m.Getters) url, err = repo.FindChartInRepoURL(repoURL, name, version, "", "", "", m.Getters)
if err == nil { if err == nil {
return url, username, password, err return url, username, password, false, false, err
} }
err = errors.Errorf("chart %s not found in %s: %s", name, repoURL, err) err = errors.Errorf("chart %s not found in %s: %s", name, repoURL, err)
return url, username, password, err return url, username, password, false, false, err
} }
// findEntryByName finds an entry in the chart repository whose name matches the given name. // findEntryByName finds an entry in the chart repository whose name matches the given name.

@ -81,10 +81,11 @@ func TestFindChartURL(t *testing.T) {
version := "0.1.0" version := "0.1.0"
repoURL := "http://example.com/charts" repoURL := "http://example.com/charts"
churl, username, password, err := m.findChartURL(name, version, repoURL, repos) churl, username, password, insecureSkipTLSVerify, passcredentialsall, err := m.findChartURL(name, version, repoURL, repos)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if churl != "https://charts.helm.sh/stable/alpine-0.1.0.tgz" { if churl != "https://charts.helm.sh/stable/alpine-0.1.0.tgz" {
t.Errorf("Unexpected URL %q", churl) t.Errorf("Unexpected URL %q", churl)
} }
@ -94,6 +95,37 @@ func TestFindChartURL(t *testing.T) {
if password != "" { if password != "" {
t.Errorf("Unexpected password %q", password) t.Errorf("Unexpected password %q", password)
} }
if passcredentialsall != false {
t.Errorf("Unexpected passcredentialsall %t", passcredentialsall)
}
if insecureSkipTLSVerify {
t.Errorf("Unexpected insecureSkipTLSVerify %t", insecureSkipTLSVerify)
}
name = "tlsfoo"
version = "1.2.3"
repoURL = "https://example-https-insecureskiptlsverify.com"
churl, username, password, insecureSkipTLSVerify, passcredentialsall, err = m.findChartURL(name, version, repoURL, repos)
if err != nil {
t.Fatal(err)
}
if !insecureSkipTLSVerify {
t.Errorf("Unexpected insecureSkipTLSVerify %t", insecureSkipTLSVerify)
}
if churl != "https://example.com/tlsfoo-1.2.3.tgz" {
t.Errorf("Unexpected URL %q", churl)
}
if username != "" {
t.Errorf("Unexpected username %q", username)
}
if password != "" {
t.Errorf("Unexpected password %q", password)
}
if passcredentialsall != false {
t.Errorf("Unexpected passcredentialsall %t", passcredentialsall)
}
} }
func TestGetRepoNames(t *testing.T) { func TestGetRepoNames(t *testing.T) {

@ -21,3 +21,6 @@ repositories:
certFile: "cert" certFile: "cert"
keyFile: "key" keyFile: "key"
caFile: "ca" caFile: "ca"
- name: testing-https-insecureskip-tls-verify
url: "https://example-https-insecureskiptlsverify.com"
insecure_skip_tls_verify: true

@ -0,0 +1,14 @@
apiVersion: v1
entries:
tlsfoo:
- name: tlsfoo
description: TLS FOO Chart
home: https://helm.sh/helm
keywords: []
maintainers: []
sources:
- https://github.com/helm/charts
urls:
- https://example.com/tlsfoo-1.2.3.tgz
version: 1.2.3
checksum: 0e6661f193211d7a5206918d42f5c2a9470b7373

@ -173,6 +173,16 @@ func (e Engine) initFunMap(t *template.Template, referenceTpls map[string]render
return val, nil return val, nil
} }
// Override sprig fail function for linting and wrapping message
funcMap["fail"] = func(msg string) (string, error) {
if e.LintMode {
// Don't fail when linting
log.Printf("[INFO] Fail: %s", msg)
return "", nil
}
return "", errors.New(warnWrap(msg))
}
// If we are not linting and have a cluster connection, provide a Kubernetes-backed // If we are not linting and have a cluster connection, provide a Kubernetes-backed
// implementation. // implementation.
if !e.LintMode && e.config != nil { if !e.LintMode && e.config != nil {

@ -286,6 +286,35 @@ func TestExecErrors(t *testing.T) {
} }
} }
func TestFailErrors(t *testing.T) {
vals := chartutil.Values{"Values": map[string]interface{}{}}
failtpl := `All your base are belong to us{{ fail "This is an error" }}`
tplsFailed := map[string]renderable{
"failtpl": {tpl: failtpl, vals: vals},
}
_, err := new(Engine).render(tplsFailed)
if err == nil {
t.Fatalf("Expected failures while rendering: %s", err)
}
expected := `execution error at (failtpl:1:33): This is an error`
if err.Error() != expected {
t.Errorf("Expected '%s', got %q", expected, err.Error())
}
var e Engine
e.LintMode = true
out, err := e.render(tplsFailed)
if err != nil {
t.Fatal(err)
}
expectStr := "All your base are belong to us"
if gotStr := out["failtpl"]; gotStr != expectStr {
t.Errorf("Expected %q, got %q (%v)", expectStr, gotStr, out)
}
}
func TestAllTemplates(t *testing.T) { func TestAllTemplates(t *testing.T) {
ch1 := &chart.Chart{ ch1 := &chart.Chart{
Metadata: &chart.Metadata{Name: "ch1"}, Metadata: &chart.Metadata{Name: "ch1"},

@ -63,7 +63,7 @@ func NewLookupFunction(config *rest.Config) lookupFunc {
} }
return obj.UnstructuredContent(), nil return obj.UnstructuredContent(), nil
} }
//this will return a list // this will return a list
obj, err := client.List(context.Background(), metav1.ListOptions{}) obj, err := client.List(context.Background(), metav1.ListOptions{})
if err != nil { if err != nil {
if apierrors.IsNotFound(err) { if apierrors.IsNotFound(err) {
@ -112,7 +112,7 @@ func getAPIResourceForGVK(gvk schema.GroupVersionKind, config *rest.Config) (met
return res, err return res, err
} }
for _, resource := range resList.APIResources { for _, resource := range resList.APIResources {
//if a resource contains a "/" it's referencing a subresource. we don't support suberesource for now. // if a resource contains a "/" it's referencing a subresource. we don't support suberesource for now.
if resource.Kind == gvk.Kind && !strings.Contains(resource.Name, "/") { if resource.Kind == gvk.Kind && !strings.Contains(resource.Name, "/") {
res = resource res = resource
res.Group = gvk.Group res.Group = gvk.Group

@ -38,6 +38,7 @@ type options struct {
insecureSkipVerifyTLS bool insecureSkipVerifyTLS bool
username string username string
password string password string
passCredentialsAll bool
userAgent string userAgent string
version string version string
registryClient *registry.Client registryClient *registry.Client
@ -64,6 +65,12 @@ func WithBasicAuth(username, password string) Option {
} }
} }
func WithPassCredentialsAll(pass bool) Option {
return func(opts *options) {
opts.passCredentialsAll = pass
}
}
// WithUserAgent sets the request's User-Agent header to use the provided agent name. // WithUserAgent sets the request's User-Agent header to use the provided agent name.
func WithUserAgent(userAgent string) Option { func WithUserAgent(userAgent string) Option {
return func(opts *options) { return func(opts *options) {

@ -20,6 +20,7 @@ import (
"crypto/tls" "crypto/tls"
"io" "io"
"net/http" "net/http"
"net/url"
"github.com/pkg/errors" "github.com/pkg/errors"
@ -33,7 +34,7 @@ type HTTPGetter struct {
opts options opts options
} }
//Get performs a Get from repo.Getter and returns the body. // Get performs a Get from repo.Getter and returns the body.
func (g *HTTPGetter) Get(href string, options ...Option) (*bytes.Buffer, error) { func (g *HTTPGetter) Get(href string, options ...Option) (*bytes.Buffer, error) {
for _, opt := range options { for _, opt := range options {
opt(&g.opts) opt(&g.opts)
@ -56,9 +57,25 @@ func (g *HTTPGetter) get(href string) (*bytes.Buffer, error) {
req.Header.Set("User-Agent", g.opts.userAgent) req.Header.Set("User-Agent", g.opts.userAgent)
} }
// Before setting the basic auth credentials, make sure the URL associated
// with the basic auth is the one being fetched.
u1, err := url.Parse(g.opts.url)
if err != nil {
return buf, errors.Wrap(err, "Unable to parse getter URL")
}
u2, err := url.Parse(href)
if err != nil {
return buf, errors.Wrap(err, "Unable to parse URL getting from")
}
// Host on URL (returned from url.Parse) contains the port if present.
// This check ensures credentials are not passed between different
// services on different ports.
if g.opts.passCredentialsAll || (u1.Scheme == u2.Scheme && u1.Host == u2.Host) {
if g.opts.username != "" && g.opts.password != "" { if g.opts.username != "" && g.opts.password != "" {
req.SetBasicAuth(g.opts.username, g.opts.password) req.SetBasicAuth(g.opts.username, g.opts.password)
} }
}
client, err := g.httpClient() client, err := g.httpClient()
if err != nil { if err != nil {

@ -54,6 +54,7 @@ func TestHTTPGetter(t *testing.T) {
// Test with options // Test with options
g, err = NewHTTPGetter( g, err = NewHTTPGetter(
WithBasicAuth("I", "Am"), WithBasicAuth("I", "Am"),
WithPassCredentialsAll(false),
WithUserAgent("Groot"), WithUserAgent("Groot"),
WithTLSClientConfig(pub, priv, ca), WithTLSClientConfig(pub, priv, ca),
WithInsecureSkipVerifyTLS(insecure), WithInsecureSkipVerifyTLS(insecure),
@ -76,6 +77,10 @@ func TestHTTPGetter(t *testing.T) {
t.Errorf("Expected NewHTTPGetter to contain %q as the password, got %q", "Am", hg.opts.password) t.Errorf("Expected NewHTTPGetter to contain %q as the password, got %q", "Am", hg.opts.password)
} }
if hg.opts.passCredentialsAll != false {
t.Errorf("Expected NewHTTPGetter to contain %t as PassCredentialsAll, got %t", false, hg.opts.passCredentialsAll)
}
if hg.opts.userAgent != "Groot" { if hg.opts.userAgent != "Groot" {
t.Errorf("Expected NewHTTPGetter to contain %q as the user agent, got %q", "Groot", hg.opts.userAgent) t.Errorf("Expected NewHTTPGetter to contain %q as the user agent, got %q", "Groot", hg.opts.userAgent)
} }
@ -118,6 +123,28 @@ func TestHTTPGetter(t *testing.T) {
if hg.opts.insecureSkipVerifyTLS != insecure { if hg.opts.insecureSkipVerifyTLS != insecure {
t.Errorf("Expected NewHTTPGetter to contain %t as InsecureSkipVerifyTLs flag, got %t", insecure, hg.opts.insecureSkipVerifyTLS) t.Errorf("Expected NewHTTPGetter to contain %t as InsecureSkipVerifyTLs flag, got %t", insecure, hg.opts.insecureSkipVerifyTLS)
} }
// Checking false by default
if hg.opts.passCredentialsAll != false {
t.Errorf("Expected NewHTTPGetter to contain %t as PassCredentialsAll, got %t", false, hg.opts.passCredentialsAll)
}
// Test setting PassCredentialsAll
g, err = NewHTTPGetter(
WithBasicAuth("I", "Am"),
WithPassCredentialsAll(true),
)
if err != nil {
t.Fatal(err)
}
hg, ok = g.(*HTTPGetter)
if !ok {
t.Fatal("expected NewHTTPGetter to produce an *HTTPGetter")
}
if hg.opts.passCredentialsAll != true {
t.Errorf("Expected NewHTTPGetter to contain %t as PassCredentialsAll, got %t", true, hg.opts.passCredentialsAll)
}
} }
func TestDownload(t *testing.T) { func TestDownload(t *testing.T) {
@ -163,6 +190,7 @@ func TestDownload(t *testing.T) {
httpgetter, err := NewHTTPGetter( httpgetter, err := NewHTTPGetter(
WithURL(u.String()), WithURL(u.String()),
WithBasicAuth("username", "password"), WithBasicAuth("username", "password"),
WithPassCredentialsAll(false),
WithUserAgent(expectedUserAgent), WithUserAgent(expectedUserAgent),
) )
if err != nil { if err != nil {
@ -176,6 +204,76 @@ func TestDownload(t *testing.T) {
if got.String() != expect { if got.String() != expect {
t.Errorf("Expected %q, got %q", expect, got.String()) t.Errorf("Expected %q, got %q", expect, got.String())
} }
// test with Get URL differing from withURL
crossAuthSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
username, password, ok := r.BasicAuth()
if ok || username == "username" || password == "password" {
t.Errorf("Expected request to not include but got '%v', '%s', '%s'", ok, username, password)
}
fmt.Fprint(w, expect)
}))
defer crossAuthSrv.Close()
u, _ = url.ParseRequestURI(crossAuthSrv.URL)
// A different host is provided for the WithURL from the one used for Get
u2, _ := url.ParseRequestURI(crossAuthSrv.URL)
host := strings.Split(u2.Host, ":")
host[0] = host[0] + "a"
u2.Host = strings.Join(host, ":")
httpgetter, err = NewHTTPGetter(
WithURL(u2.String()),
WithBasicAuth("username", "password"),
WithPassCredentialsAll(false),
)
if err != nil {
t.Fatal(err)
}
got, err = httpgetter.Get(u.String())
if err != nil {
t.Fatal(err)
}
if got.String() != expect {
t.Errorf("Expected %q, got %q", expect, got.String())
}
// test with Get URL differing from withURL and should pass creds
crossAuthSrv = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
username, password, ok := r.BasicAuth()
if !ok || username != "username" || password != "password" {
t.Errorf("Expected request to use basic auth and for username == 'username' and password == 'password', got '%v', '%s', '%s'", ok, username, password)
}
fmt.Fprint(w, expect)
}))
defer crossAuthSrv.Close()
u, _ = url.ParseRequestURI(crossAuthSrv.URL)
// A different host is provided for the WithURL from the one used for Get
u2, _ = url.ParseRequestURI(crossAuthSrv.URL)
host = strings.Split(u2.Host, ":")
host[0] = host[0] + "a"
u2.Host = strings.Join(host, ":")
httpgetter, err = NewHTTPGetter(
WithURL(u2.String()),
WithBasicAuth("username", "password"),
WithPassCredentialsAll(true),
)
if err != nil {
t.Fatal(err)
}
got, err = httpgetter.Get(u.String())
if err != nil {
t.Fatal(err)
}
if got.String() != expect {
t.Errorf("Expected %q, got %q", expect, got.String())
}
} }
func TestDownloadTLS(t *testing.T) { func TestDownloadTLS(t *testing.T) {

@ -113,7 +113,7 @@ func Templates(linter *support.Linter, values map[string]interface{}, namespace
// NOTE: disabled for now, Refs https://github.com/helm/helm/issues/1463 // NOTE: disabled for now, Refs https://github.com/helm/helm/issues/1463
// Check that all the templates have a matching value // Check that all the templates have a matching value
//linter.RunLinterRule(support.WarningSev, fpath, validateNoMissingValues(templatesPath, valuesToRender, preExecutedTemplate)) // linter.RunLinterRule(support.WarningSev, fpath, validateNoMissingValues(templatesPath, valuesToRender, preExecutedTemplate))
// NOTE: disabled for now, Refs https://github.com/helm/helm/issues/1037 // NOTE: disabled for now, Refs https://github.com/helm/helm/issues/1037
// linter.RunLinterRule(support.WarningSev, fpath, validateQuotes(string(preExecutedTemplate))) // linter.RunLinterRule(support.WarningSev, fpath, validateQuotes(string(preExecutedTemplate)))

@ -152,7 +152,7 @@ func (p *Plugin) PrepareCommand(extraArgs []string) (string, []string, error) {
parts = strings.Split(os.ExpandEnv(p.Metadata.Command), " ") parts = strings.Split(os.ExpandEnv(p.Metadata.Command), " ")
} }
if len(parts) == 0 || parts[0] == "" { if len(parts) == 0 || parts[0] == "" {
return "", nil, fmt.Errorf("No plugin command is applicable") return "", nil, fmt.Errorf("no plugin command is applicable")
} }
main := parts[0] main := parts[0]

@ -48,6 +48,7 @@ type Entry struct {
KeyFile string `json:"keyFile"` KeyFile string `json:"keyFile"`
CAFile string `json:"caFile"` CAFile string `json:"caFile"`
InsecureSkipTLSverify bool `json:"insecure_skip_tls_verify"` InsecureSkipTLSverify bool `json:"insecure_skip_tls_verify"`
PassCredentialsAll bool `json:"pass_credentials_all"`
} }
// ChartRepository represents a chart repository // ChartRepository represents a chart repository
@ -129,6 +130,7 @@ func (r *ChartRepository) DownloadIndexFile() (string, error) {
getter.WithInsecureSkipVerifyTLS(r.Config.InsecureSkipTLSverify), getter.WithInsecureSkipVerifyTLS(r.Config.InsecureSkipTLSverify),
getter.WithTLSClientConfig(r.Config.CertFile, r.Config.KeyFile, r.Config.CAFile), getter.WithTLSClientConfig(r.Config.CertFile, r.Config.KeyFile, r.Config.CAFile),
getter.WithBasicAuth(r.Config.Username, r.Config.Password), getter.WithBasicAuth(r.Config.Username, r.Config.Password),
getter.WithPassCredentialsAll(r.Config.PassCredentialsAll),
) )
if err != nil { if err != nil {
return "", err return "", err
@ -217,6 +219,15 @@ func FindChartInAuthRepoURL(repoURL, username, password, chartName, chartVersion
// but it also receives credentials and TLS verify flag for the chart repository. // but it also receives credentials and TLS verify flag for the chart repository.
// TODO Helm 4, FindChartInAuthAndTLSRepoURL should be integrated into FindChartInAuthRepoURL. // TODO Helm 4, FindChartInAuthAndTLSRepoURL should be integrated into FindChartInAuthRepoURL.
func FindChartInAuthAndTLSRepoURL(repoURL, username, password, chartName, chartVersion, certFile, keyFile, caFile string, insecureSkipTLSverify bool, getters getter.Providers) (string, error) { func FindChartInAuthAndTLSRepoURL(repoURL, username, password, chartName, chartVersion, certFile, keyFile, caFile string, insecureSkipTLSverify bool, getters getter.Providers) (string, error) {
return FindChartInAuthAndTLSAndPassRepoURL(repoURL, username, password, chartName, chartVersion, certFile, keyFile, caFile, false, false, getters)
}
// FindChartInAuthAndTLSAndPassRepoURL finds chart in chart repository pointed by repoURL
// without adding repo to repositories, like FindChartInRepoURL,
// but it also receives credentials, TLS verify flag, and if credentials should
// be passed on to other domains.
// TODO Helm 4, FindChartInAuthAndTLSAndPassRepoURL should be integrated into FindChartInAuthRepoURL.
func FindChartInAuthAndTLSAndPassRepoURL(repoURL, username, password, chartName, chartVersion, certFile, keyFile, caFile string, insecureSkipTLSverify, passCredentialsAll bool, getters getter.Providers) (string, error) {
// Download and write the index file to a temporary location // Download and write the index file to a temporary location
buf := make([]byte, 20) buf := make([]byte, 20)
@ -227,6 +238,7 @@ func FindChartInAuthAndTLSRepoURL(repoURL, username, password, chartName, chartV
URL: repoURL, URL: repoURL,
Username: username, Username: username,
Password: password, Password: password,
PassCredentialsAll: passCredentialsAll,
CertFile: certFile, CertFile: certFile,
KeyFile: keyFile, KeyFile: keyFile,
CAFile: caFile, CAFile: caFile,

@ -292,14 +292,14 @@ func startLocalTLSServerForTests(handler http.Handler) (*httptest.Server, error)
return httptest.NewTLSServer(handler), nil return httptest.NewTLSServer(handler), nil
} }
func TestFindChartInAuthAndTLSRepoURL(t *testing.T) { func TestFindChartInAuthAndTLSAndPassRepoURL(t *testing.T) {
srv, err := startLocalTLSServerForTests(nil) srv, err := startLocalTLSServerForTests(nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer srv.Close() defer srv.Close()
chartURL, err := FindChartInAuthAndTLSRepoURL(srv.URL, "", "", "nginx", "", "", "", "", true, getter.All(&cli.EnvSettings{})) chartURL, err := FindChartInAuthAndTLSAndPassRepoURL(srv.URL, "", "", "nginx", "", "", "", "", true, false, getter.All(&cli.EnvSettings{}))
if err != nil { if err != nil {
t.Fatalf("%v", err) t.Fatalf("%v", err)
} }
@ -308,10 +308,10 @@ func TestFindChartInAuthAndTLSRepoURL(t *testing.T) {
} }
// If the insecureSkipTLsverify is false, it will return an error that contains "x509: certificate signed by unknown authority". // If the insecureSkipTLsverify is false, it will return an error that contains "x509: certificate signed by unknown authority".
_, err = FindChartInAuthAndTLSRepoURL(srv.URL, "", "", "nginx", "0.1.0", "", "", "", false, getter.All(&cli.EnvSettings{})) _, err = FindChartInAuthAndTLSAndPassRepoURL(srv.URL, "", "", "nginx", "0.1.0", "", "", "", false, false, getter.All(&cli.EnvSettings{}))
if !strings.Contains(err.Error(), "x509: certificate signed by unknown authority") { if !strings.Contains(err.Error(), "x509: certificate signed by unknown authority") {
t.Errorf("Expected TLS error for function FindChartInAuthAndTLSRepoURL not found, but got a different error (%v)", err) t.Errorf("Expected TLS error for function FindChartInAuthAndTLSAndPassRepoURL not found, but got a different error (%v)", err)
} }
} }

@ -310,6 +310,10 @@ func (s *SQL) Query(labels map[string]string) ([]*rspb.Release, error) {
return nil, err return nil, err
} }
if len(records) == 0 {
return nil, ErrReleaseNotFound
}
var releases []*rspb.Release var releases []*rspb.Release
for _, record := range records { for _, record := range records {
release, err := decodeRelease(record.Body) release, err := decodeRelease(record.Body)

@ -292,6 +292,11 @@ func TestSqlUpdate(t *testing.T) {
func TestSqlQuery(t *testing.T) { func TestSqlQuery(t *testing.T) {
// Reflect actual use cases in ../storage.go // Reflect actual use cases in ../storage.go
labelSetUnknown := map[string]string{
"name": "smug-pigeon",
"owner": sqlReleaseDefaultOwner,
"status": "unknown",
}
labelSetDeployed := map[string]string{ labelSetDeployed := map[string]string{
"name": "smug-pigeon", "name": "smug-pigeon",
"owner": sqlReleaseDefaultOwner, "owner": sqlReleaseDefaultOwner,
@ -320,6 +325,15 @@ func TestSqlQuery(t *testing.T) {
sqlReleaseTableNamespaceColumn, sqlReleaseTableNamespaceColumn,
) )
mock.
ExpectQuery(regexp.QuoteMeta(query)).
WithArgs("smug-pigeon", sqlReleaseDefaultOwner, "unknown", "default").
WillReturnRows(
mock.NewRows([]string{
sqlReleaseTableBodyColumn,
}),
).RowsWillBeClosed()
mock. mock.
ExpectQuery(regexp.QuoteMeta(query)). ExpectQuery(regexp.QuoteMeta(query)).
WithArgs("smug-pigeon", sqlReleaseDefaultOwner, "deployed", "default"). WithArgs("smug-pigeon", sqlReleaseDefaultOwner, "deployed", "default").
@ -353,6 +367,13 @@ func TestSqlQuery(t *testing.T) {
), ),
).RowsWillBeClosed() ).RowsWillBeClosed()
_, err := sqlDriver.Query(labelSetUnknown)
if err == nil {
t.Errorf("Expected error {%v}, got nil", ErrReleaseNotFound)
} else if err != ErrReleaseNotFound {
t.Fatalf("failed to query for unknown smug-pigeon release: %v", err)
}
results, err := sqlDriver.Query(labelSetDeployed) results, err := sqlDriver.Query(labelSetDeployed)
if err != nil { if err != nil {
t.Fatalf("failed to query for deployed smug-pigeon release: %v", err) t.Fatalf("failed to query for deployed smug-pigeon release: %v", err)

@ -76,14 +76,8 @@ verifySupported() {
# checkDesiredVersion checks if the desired version is available. # checkDesiredVersion checks if the desired version is available.
checkDesiredVersion() { checkDesiredVersion() {
if [ "x$DESIRED_VERSION" == "x" ]; then if [ "x$DESIRED_VERSION" == "x" ]; then
# Get tag from release URL # Pinning tag to v2.17.0 as per https://github.com/helm/helm/issues/9607
local release_url="https://github.com/helm/helm/releases" TAG=v2.17.0
if type "curl" > /dev/null; then
TAG=$(curl -Ls $release_url | grep 'href="/helm/helm/releases/tag/v2.[0-9]*.[0-9]*\"' | grep -v no-underline | head -n 1 | cut -d '"' -f 2 | awk '{n=split($NF,a,"/");print a[n]}' | awk 'a !~ $0{print}; {a=$0}')
elif type "wget" > /dev/null; then
TAG=$(wget $release_url -O - 2>&1 | grep 'href="/helm/helm/releases/tag/v2.[0-9]*.[0-9]*\"' | grep -v no-underline | head -n 1 | cut -d '"' -f 2 | awk '{n=split($NF,a,"/");print a[n]}' | awk 'a !~ $0{print}; {a=$0}')
fi
else else
TAG=$DESIRED_VERSION TAG=$DESIRED_VERSION
fi fi

Loading…
Cancel
Save