Repository: helm/helm
Pull request #7645 (2020-02-20 18:36:59)

fix(ADOPTERS): alphabetize org list
addressing a comment made in #7554

Signed-off-by: Matthew Fisher <matt.fisher@microsoft.com>


Patch:
diff --git a/ADOPTERS.md b/ADOPTERS.md
index a72f51e09..46b42b8a0 100644
--- a/ADOPTERS.md
+++ b/ADOPTERS.md
@@ -6,8 +6,8 @@
 # Organizations Using Helm
 
 - [Blood Orange](https://bloodorange.io)
-- [Microsoft](https://microsoft.com)
 - [IBM](https://www.ibm.com)
+- [Microsoft](https://microsoft.com)
 - [Qovery](https://www.qovery.com/)
 - [Samsung SDS](https://www.samsungsds.com/)
 - [Ville de Montreal](https://montreal.ca)
diff --git a/Makefile b/Makefile
index d7954de50..27404bca8 100644
--- a/Makefile
+++ b/Makefile
@@ -24,7 +24,7 @@ GOFLAGS    :=
 SRC        := $(shell find . -type f -name '*.go' -print)
 
 # Required for globs to work correctly
-SHELL      = /bin/bash
+SHELL      = /usr/bin/env bash
 
 GIT_COMMIT = $(shell git rev-parse HEAD)
 GIT_SHA    = $(shell git rev-parse --short HEAD)
diff --git a/cmd/helm/helm.go b/cmd/helm/helm.go
index 112d5123f..257387547 100644
--- a/cmd/helm/helm.go
+++ b/cmd/helm/helm.go
@@ -19,6 +19,7 @@ package main // import "helm.sh/helm/v3/cmd/helm"
 import (
 	"flag"
 	"fmt"
+	"io/ioutil"
 	"log"
 	"os"
 	"strings"
@@ -26,13 +27,18 @@ import (
 	"github.com/spf13/cobra"
 	"github.com/spf13/pflag"
 	"k8s.io/klog"
+	"sigs.k8s.io/yaml"
 
 	// Import to initialize client auth plugins.
 	_ "k8s.io/client-go/plugin/pkg/client/auth"
 
+	"helm.sh/helm/v3/internal/completion"
 	"helm.sh/helm/v3/pkg/action"
 	"helm.sh/helm/v3/pkg/cli"
 	"helm.sh/helm/v3/pkg/gates"
+	kubefake "helm.sh/helm/v3/pkg/kube/fake"
+	"helm.sh/helm/v3/pkg/release"
+	"helm.sh/helm/v3/pkg/storage/driver"
 )
 
 // FeatureGateOCI is the feature gate for checking if `helm chart` and `helm registry` commands should work
@@ -67,9 +73,23 @@ func main() {
 	actionConfig := new(action.Configuration)
 	cmd := newRootCmd(actionConfig, os.Stdout, os.Args[1:])
 
-	if err := actionConfig.Init(settings.RESTClientGetter(), settings.Namespace(), os.Getenv("HELM_DRIVER"), debug); err != nil {
+	if calledCmd, _, err := cmd.Find(os.Args[1:]); err == nil && calledCmd.Name() == completion.CompRequestCmd {
+		// If completion is being called, we have to check if the completion is for the "--kube-context"
+		// value; if it is, we cannot call the action.Init() method with an incomplete kube-context value
+		// or else it will fail immediately.  So, we simply unset the invalid kube-context value.
+		if args := os.Args[1:]; len(args) > 2 && args[len(args)-2] == "--kube-context" {
+			// We are completing the kube-context value!  Reset it as the current value is not valid.
+			settings.KubeContext = ""
+		}
+	}
+
+	helmDriver := os.Getenv("HELM_DRIVER")
+	if err := actionConfig.Init(settings.RESTClientGetter(), settings.Namespace(), helmDriver, debug); err != nil {
 		log.Fatal(err)
 	}
+	if helmDriver == "memory" {
+		loadReleasesInMemory(actionConfig)
+	}
 
 	if err := cmd.Execute(); err != nil {
 		debug("%+v", err)
@@ -95,3 +115,41 @@ func checkOCIFeatureGate() func(_ *cobra.Command, _ []string) error {
 		return nil
 	}
 }
+
+// This function loads releases into the memory storage if the
+// environment variable is properly set.
+func loadReleasesInMemory(actionConfig *action.Configuration) {
+	filePaths := strings.Split(os.Getenv("HELM_MEMORY_DRIVER_DATA"), ":")
+	if len(filePaths) == 0 {
+		return
+	}
+
+	store := actionConfig.Releases
+	mem, ok := store.Driver.(*driver.Memory)
+	if !ok {
+		// For an unexpected reason we are not dealing with the memory storage driver.
+		return
+	}
+
+	actionConfig.KubeClient = &kubefake.PrintingKubeClient{Out: ioutil.Discard}
+
+	for _, path := range filePaths {
+		b, err := ioutil.ReadFile(path)
+		if err != nil {
+			log.Fatal("Unable to read memory driver data", err)
+		}
+
+		releases := []*release.Release{}
+		if err := yaml.Unmarshal(b, &releases); err != nil {
+			log.Fatal("Unable to unmarshal memory driver data: ", err)
+		}
+
+		for _, rel := range releases {
+			if err := store.Create(rel); err != nil {
+				log.Fatal(err)
+			}
+		}
+	}
+	// Must reset namespace to the proper one
+	mem.SetNamespace(settings.Namespace())
+}
diff --git a/cmd/helm/install.go b/cmd/helm/install.go
index ec2c75a12..719dc9014 100644
--- a/cmd/helm/install.go
+++ b/cmd/helm/install.go
@@ -136,6 +136,7 @@ func newInstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
 }
 
 func addInstallFlags(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")
 	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")
diff --git a/cmd/helm/load_plugins.go b/cmd/helm/load_plugins.go
index f2fb5c01d..358679302 100644
--- a/cmd/helm/load_plugins.go
+++ b/cmd/helm/load_plugins.go
@@ -16,20 +16,31 @@ limitations under the License.
 package main
 
 import (
+	"bytes"
 	"fmt"
 	"io"
+	"io/ioutil"
+	"log"
 	"os"
 	"os/exec"
 	"path/filepath"
+	"strconv"
 	"strings"
 	"syscall"
 
 	"github.com/pkg/errors"
 	"github.com/spf13/cobra"
+	"sigs.k8s.io/yaml"
 
+	"helm.sh/helm/v3/internal/completion"
 	"helm.sh/helm/v3/pkg/plugin"
 )
 
+const (
+	pluginStaticCompletionFile        = "completion.yaml"
+	pluginDynamicCompletionExecutable = "plugin.complete"
+)
+
 type pluginError struct {
 	error
 	code int
@@ -61,6 +72,13 @@ func loadPlugins(baseCmd *cobra.Command, out io.Writer) {
 		return u, nil
 	}
 
+	// If we are dealing with the completion command, we try to load more details about the plugins
+	// if available, so as to allow for command and flag completion
+	if subCmd, _, err := baseCmd.Find(os.Args[1:]); err == nil && subCmd.Name() == "completion" {
+		loadPluginsForCompletion(baseCmd, found)
+		return
+	}
+
 	// Now we create commands for all of these.
 	for _, plug := range found {
 		plug := plug
@@ -69,6 +87,33 @@ func loadPlugins(baseCmd *cobra.Command, out io.Writer) {
 			md.Usage = fmt.Sprintf("the %q plugin", md.Name)
 		}
 
+		// This function is used to setup the environment for the plugin and then
+		// call the executable specified by the parameter 'main'
+		callPluginExecutable := func(cmd *cobra.Command, main string, argv []string, out io.Writer) error {
+			env := os.Environ()
+			for k, v := range settings.EnvVars() {
+				env = append(env, fmt.Sprintf("%s=%s", k, v))
+			}
+
+			prog := exec.Command(main, argv...)
+			prog.Env = env
+			prog.Stdin = os.Stdin
+			prog.Stdout = out
+			prog.Stderr = os.Stderr
+			if err := prog.Run(); err != nil {
+				if eerr, ok := err.(*exec.ExitError); ok {
+					os.Stderr.Write(eerr.Stderr)
+					status := eerr.Sys().(syscall.WaitStatus)
+					return pluginError{
+						error: errors.Errorf("plugin %q exited with error", md.Name),
+						code:  status.ExitStatus(),
+					}
+				}
+				return err
+			}
+			return nil
+		}
+
 		c := &cobra.Command{
 			Use:   md.Name,
 			Short: md.Usage,
@@ -89,33 +134,59 @@ func loadPlugins(baseCmd *cobra.Command, out io.Writer) {
 					return errors.Errorf("plugin %q exited with error", md.Name)
 				}
 
-				env := os.Environ()
-				for k, v := range settings.EnvVars() {
-					env = append(env, fmt.Sprintf("%s=%s", k, v))
-				}
-
-				prog := exec.Command(main, argv...)
-				prog.Env = env
-				prog.Stdin = os.Stdin
-				prog.Stdout = out
-				prog.Stderr = os.Stderr
-				if err := prog.Run(); err != nil {
-					if eerr, ok := err.(*exec.ExitError); ok {
-						os.Stderr.Write(eerr.Stderr)
-						status := eerr.Sys().(syscall.WaitStatus)
-						return pluginError{
-							error: errors.Errorf("plugin %q exited with error", md.Name),
-							code:  status.ExitStatus(),
-						}
-					}
-					return err
-				}
-				return nil
+				return callPluginExecutable(cmd, main, argv, out)
 			},
 			// This passes all the flags to the subcommand.
 			DisableFlagParsing: true,
 		}
 
+		// Setup dynamic completion for the plugin
+		completion.RegisterValidArgsFunc(c, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
+			u, err := processParent(cmd, args)
+			if err != nil {
+				return nil, completion.BashCompDirectiveError
+			}
+
+			// We will call the dynamic completion script of the plugin
+			main := strings.Join([]string{plug.Dir, pluginDynamicCompletionExecutable}, string(filepath.Separator))
+
+			argv := []string{}
+			if !md.IgnoreFlags {
+				argv = append(argv, u...)
+				argv = append(argv, toComplete)
+			}
+			plugin.SetupPluginEnv(settings, md.Name, plug.Dir)
+
+			completion.CompDebugln(fmt.Sprintf("calling %s with args %v", main, argv))
+			buf := new(bytes.Buffer)
+			if err := callPluginExecutable(cmd, main, argv, buf); err != nil {
+				return nil, completion.BashCompDirectiveError
+			}
+
+			var completions []string
+			for _, comp := range strings.Split(buf.String(), "\n") {
+				// Remove any empty lines
+				if len(comp) > 0 {
+					completions = append(completions, comp)
+				}
+			}
+
+			// Check if the last line of output is of the form :<integer>, which
+			// indicates the BashCompletionDirective.
+			directive := completion.BashCompDirectiveDefault
+			if len(completions) > 0 {
+				lastLine := completions[len(completions)-1]
+				if len(lastLine) > 1 && lastLine[0] == ':' {
+					if strInt, err := strconv.Atoi(lastLine[1:]); err == nil {
+						directive = completion.BashCompDirective(strInt)
+						completions = completions[:len(completions)-1]
+					}
+				}
+			}
+
+			return completions, directive
+		})
+
 		// TODO: Make sure a command with this name does not already exist.
 		baseCmd.AddCommand(c)
 	}
@@ -180,3 +251,119 @@ func findPlugins(plugdirs string) ([]*plugin.Plugin, error) {
 	}
 	return found, nil
 }
+
+// pluginCommand represents the optional completion.yaml file of a plugin
+type pluginCommand struct {
+	Name      string          `json:"name"`
+	ValidArgs []string        `json:"validArgs"`
+	Flags     []string        `json:"flags"`
+	Commands  []pluginCommand `json:"commands"`
+}
+
+// loadPluginsForCompletion will load and parse any completion.yaml provided by the plugins
+func loadPluginsForCompletion(baseCmd *cobra.Command, plugins []*plugin.Plugin) {
+	for _, plug := range plugins {
+		// Parse the yaml file providing the plugin's subcmds and flags
+		cmds, err := loadFile(strings.Join(
+			[]string{plug.Dir, pluginStaticCompletionFile}, string(filepath.Separator)))
+
+		if err != nil {
+			// The file could be missing or invalid.  Either way, we at least create the command
+			// for the plugin name.
+			if settings.Debug {
+				log.Output(2, fmt.Sprintf("[info] %s\n", err.Error()))
+			}
+			cmds = &pluginCommand{Name: plug.Metadata.Name}
+		}
+
+		// We know what the plugin name must be.
+		// Let's set it in case the Name field was not specified correctly in the file.
+		// This insures that we will at least get the plugin name to complete, even if
+		// there is a problem with the completion.yaml file
+		cmds.Name = plug.Metadata.Name
+
+		addPluginCommands(baseCmd, cmds)
+	}
+}
+
+// addPluginCommands is a recursive method that adds the different levels
+// of sub-commands and flags for the plugins that provide such information
+func addPluginCommands(baseCmd *cobra.Command, cmds *pluginCommand) {
+	if cmds == nil {
+		return
+	}
+
+	if len(cmds.Name) == 0 {
+		// Missing name for a command
+		if settings.Debug {
+			log.Output(2, fmt.Sprintf("[info] sub-command name field missing for %s", baseCmd.CommandPath()))
+		}
+		return
+	}
+
+	// Create a fake command just so the completion script will include it
+	c := &cobra.Command{
+		Use:       cmds.Name,
+		ValidArgs: cmds.ValidArgs,
+		// A Run is required for it to be a valid command without subcommands
+		Run: func(cmd *cobra.Command, args []string) {},
+	}
+	baseCmd.AddCommand(c)
+
+	// Create fake flags.
+	if len(cmds.Flags) > 0 {
+		// The flags can be created with any type, since we only need them for completion.
+		// pflag does not allow to create short flags without a corresponding long form
+		// so we look for all short flags and match them to any long flag.  This will allow
+		// plugins to provide short flags without a long form.
+		// If there are more short-flags than long ones, we'll create an extra long flag with
+		// the same single letter as the short form.
+		shorts := []string{}
+		longs := []string{}
+		for _, flag := range cmds.Flags {
+			if len(flag) == 1 {
+				shorts = append(shorts, flag)
+			} else {
+				longs = append(longs, flag)
+			}
+		}
+
+		f := c.Flags()
+		if len(longs) >= len(shorts) {
+			for i := range longs {
+				if i < len(shorts) {
+					f.BoolP(longs[i], shorts[i], false, "")
+				} else {
+					f.Bool(longs[i], false, "")
+				}
+			}
+		} else {
+			for i := range shorts {
+				if i < len(longs) {
+					f.BoolP(longs[i], shorts[i], false, "")
+				} else {
+					// Create a long flag with the same name as the short flag.
+					// Not a perfect solution, but its better than ignoring the extra short flags.
+					f.BoolP(shorts[i], shorts[i], false, "")
+				}
+			}
+		}
+	}
+
+	// Recursively add any sub-commands
+	for _, cmd := range cmds.Commands {
+		addPluginCommands(c, &cmd)
+	}
+}
+
+// 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)
+	if err != nil {
+		return cmds, errors.New(fmt.Sprintf("File (%s) not provided by plugin. No plugin auto-completion possible.", path))
+	}
+
+	err = yaml.Unmarshal(b, cmds)
+	return cmds, err
+}
diff --git a/cmd/helm/root.go b/cmd/helm/root.go
index 6ce1dcbf4..3ebea3bae 100644
--- a/cmd/helm/root.go
+++ b/cmd/helm/root.go
@@ -24,37 +24,13 @@ import (
 	"github.com/spf13/cobra"
 
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/client-go/tools/clientcmd"
 
-	"helm.sh/helm/v3/cmd/helm/require"
 	"helm.sh/helm/v3/internal/completion"
 	"helm.sh/helm/v3/internal/experimental/registry"
 	"helm.sh/helm/v3/pkg/action"
 )
 
-const (
-	contextCompFunc = `
-__helm_get_contexts()
-{
-    __helm_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}"
-    local template out
-    template="{{ range .contexts  }}{{ .name }} {{ end }}"
-    if out=$(kubectl config -o template --template="${template}" view 2>/dev/null); then
-        COMPREPLY=( $( compgen -W "${out[*]}" -- "$cur" ) )
-    fi
-}
-`
-)
-
-var (
-	// Mapping of global flags that can have dynamic completion and the
-	// completion function to be used.
-	bashCompletionFlags = map[string]string{
-		// Cannot convert the kube-context flag to Go completion yet because
-		// an incomplete kube-context will make actionConfig.Init() fail at the very start
-		"kube-context": "__helm_get_contexts",
-	}
-)
-
 var globalUsage = `The Kubernetes package manager
 
 Common actions for Helm:
@@ -100,15 +76,14 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string
 		Short:                  "The Helm package manager for Kubernetes.",
 		Long:                   globalUsage,
 		SilenceUsage:           true,
-		Args:                   require.NoArgs,
-		BashCompletionFunction: fmt.Sprintf("%s%s", contextCompFunc, completion.GetBashCustomFunction()),
+		BashCompletionFunction: completion.GetBashCustomFunction(),
 	}
 	flags := cmd.PersistentFlags()
 
 	settings.AddFlags(flags)
 
-	flag := flags.Lookup("namespace")
 	// Setup shell completion for the namespace flag
+	flag := flags.Lookup("namespace")
 	completion.RegisterFlagCompletionFunc(flag, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
 		if client, err := actionConfig.KubernetesClientSet(); err == nil {
 			// Choose a long enough timeout that the user notices somethings is not working
@@ -129,6 +104,29 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string
 		return nil, completion.BashCompDirectiveDefault
 	})
 
+	// Setup shell completion for the kube-context flag
+	flag = flags.Lookup("kube-context")
+	completion.RegisterFlagCompletionFunc(flag, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
+		completion.CompDebugln("About to get the different kube-contexts")
+
+		loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
+		if len(settings.KubeConfig) > 0 {
+			loadingRules = &clientcmd.ClientConfigLoadingRules{ExplicitPath: settings.KubeConfig}
+		}
+		if config, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
+			loadingRules,
+			&clientcmd.ConfigOverrides{}).RawConfig(); err == nil {
+			ctxs := []string{}
+			for name := range config.Contexts {
+				if strings.HasPrefix(name, toComplete) {
+					ctxs = append(ctxs, name)
+				}
+			}
+			return ctxs, completion.BashCompDirectiveNoFileComp
+		}
+		return nil, completion.BashCompDirectiveNoFileComp
+	})
+
 	// We can safely ignore any errors that flags.Parse encounters since
 	// those errors will be caught later during the call to cmd.Execution.
 	// This call is required to gather configuration information prior to
@@ -173,19 +171,6 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string
 		completion.NewCompleteCmd(settings, out),
 	)
 
-	// Add annotation to flags for which we can generate completion choices
-	for name, completion := range bashCompletionFlags {
-		if cmd.Flag(name) != nil {
-			if cmd.Flag(name).Annotations == nil {
-				cmd.Flag(name).Annotations = map[string][]string{}
-			}
-			cmd.Flag(name).Annotations[cobra.BashCompCustom] = append(
-				cmd.Flag(name).Annotations[cobra.BashCompCustom],
-				completion,
-			)
-		}
-	}
-
 	// Add *experimental* subcommands
 	registryClient, err := registry.NewClient(
 		registry.ClientOptDebug(settings.Debug),
diff --git a/cmd/helm/template.go b/cmd/helm/template.go
index 320718344..91a398429 100644
--- a/cmd/helm/template.go
+++ b/cmd/helm/template.go
@@ -24,8 +24,6 @@ import (
 	"regexp"
 	"strings"
 
-	"helm.sh/helm/v3/pkg/releaseutil"
-
 	"github.com/spf13/cobra"
 
 	"helm.sh/helm/v3/cmd/helm/require"
@@ -33,6 +31,7 @@ import (
 	"helm.sh/helm/v3/pkg/action"
 	"helm.sh/helm/v3/pkg/chartutil"
 	"helm.sh/helm/v3/pkg/cli/values"
+	"helm.sh/helm/v3/pkg/releaseutil"
 )
 
 const templateDesc = `
@@ -53,7 +52,7 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
 
 	cmd := &cobra.Command{
 		Use:   "template [NAME] [CHART]",
-		Short: fmt.Sprintf("locally render templates"),
+		Short: "locally render templates",
 		Long:  templateDesc,
 		Args:  require.MinimumNArgs(1),
 		RunE: func(_ *cobra.Command, args []string) error {
diff --git a/cmd/helm/upgrade.go b/cmd/helm/upgrade.go
index 119d79f8f..dea866e4d 100644
--- a/cmd/helm/upgrade.go
+++ b/cmd/helm/upgrade.go
@@ -65,6 +65,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
 	client := action.NewUpgrade(cfg)
 	valueOpts := &values.Options{}
 	var outfmt output.Format
+	var createNamespace bool
 
 	cmd := &cobra.Command{
 		Use:   "upgrade [RELEASE] [CHART]",
@@ -100,6 +101,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
 						fmt.Fprintf(out, "Release %q does not exist. Installing it now.\n", args[0])
 					}
 					instClient := action.NewInstall(cfg)
+					instClient.CreateNamespace = createNamespace
 					instClient.ChartPathOptions = client.ChartPathOptions
 					instClient.DryRun = client.DryRun
 					instClient.DisableHooks = client.DisableHooks
@@ -159,6 +161,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
 	})
 
 	f := cmd.Flags()
+	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")
diff --git a/go.mod b/go.mod
index 696c2b6d2..7ba7a5542 100644
--- a/go.mod
+++ b/go.mod
@@ -18,7 +18,7 @@ require (
 	github.com/gobwas/glob v0.2.3
 	github.com/gofrs/flock v0.7.1
 	github.com/gosuri/uitable v0.0.4
-	github.com/mattn/go-shellwords v1.0.9
+	github.com/mattn/go-shellwords v1.0.10
 	github.com/mitchellh/copystructure v1.0.0
 	github.com/opencontainers/go-digest v1.0.0-rc1
 	github.com/opencontainers/image-spec v1.0.1
@@ -29,13 +29,13 @@ require (
 	github.com/stretchr/testify v1.4.0
 	github.com/xeipuuv/gojsonschema v1.1.0
 	golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d
-	k8s.io/api v0.17.2
-	k8s.io/apiextensions-apiserver v0.17.2
-	k8s.io/apimachinery v0.17.2
-	k8s.io/cli-runtime v0.17.2
-	k8s.io/client-go v0.17.2
+	k8s.io/api v0.17.3
+	k8s.io/apiextensions-apiserver v0.17.3
+	k8s.io/apimachinery v0.17.3
+	k8s.io/cli-runtime v0.17.3
+	k8s.io/client-go v0.17.3
 	k8s.io/klog v1.0.0
-	k8s.io/kubectl v0.17.2
+	k8s.io/kubectl v0.17.3
 	sigs.k8s.io/yaml v1.1.0
 )
 
diff --git a/go.sum b/go.sum
index cc6b8dd9c..39b57b4f2 100644
--- a/go.sum
+++ b/go.sum
@@ -334,6 +334,8 @@ github.com/mattn/go-runewidth v0.0.2 h1:UnlwIPBGaTZfPQ6T1IGzPI0EkYAQmT9fAEJ/poFC
 github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
 github.com/mattn/go-shellwords v1.0.9 h1:eaB5JspOwiKKcHdqcjbfe5lA9cNn/4NRRtddXJCimqk=
 github.com/mattn/go-shellwords v1.0.9/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
+github.com/mattn/go-shellwords v1.0.10 h1:Y7Xqm8piKOO3v10Thp7Z36h4FYFjt5xB//6XvOrs2Gw=
+github.com/mattn/go-shellwords v1.0.10/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
 github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
 github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ=
@@ -630,25 +632,27 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
 gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
+gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
 gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-k8s.io/api v0.17.2 h1:NF1UFXcKN7/OOv1uxdRz3qfra8AHsPav5M93hlV9+Dc=
-k8s.io/api v0.17.2/go.mod h1:BS9fjjLc4CMuqfSO8vgbHPKMt5+SF0ET6u/RVDihTo4=
-k8s.io/apiextensions-apiserver v0.17.2 h1:cP579D2hSZNuO/rZj9XFRzwJNYb41DbNANJb6Kolpss=
-k8s.io/apiextensions-apiserver v0.17.2/go.mod h1:4KdMpjkEjjDI2pPfBA15OscyNldHWdBCfsWMDWAmSTs=
-k8s.io/apimachinery v0.17.2 h1:hwDQQFbdRlpnnsR64Asdi55GyCaIP/3WQpMmbNBeWr4=
-k8s.io/apimachinery v0.17.2/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg=
-k8s.io/apiserver v0.17.2/go.mod h1:lBmw/TtQdtxvrTk0e2cgtOxHizXI+d0mmGQURIHQZlo=
-k8s.io/cli-runtime v0.17.2 h1:YH4txSplyGudvxjhAJeHEtXc7Tr/16clKGfN076ydGk=
-k8s.io/cli-runtime v0.17.2/go.mod h1:aa8t9ziyQdbkuizkNLAw3qe3srSyWh9zlSB7zTqRNPI=
-k8s.io/client-go v0.17.2 h1:ndIfkfXEGrNhLIgkr0+qhRguSD3u6DCmonepn1O6NYc=
-k8s.io/client-go v0.17.2/go.mod h1:QAzRgsa0C2xl4/eVpeVAZMvikCn8Nm81yqVx3Kk9XYI=
-k8s.io/code-generator v0.17.2/go.mod h1:DVmfPQgxQENqDIzVR2ddLXMH34qeszkKSdH/N+s+38s=
-k8s.io/component-base v0.17.2 h1:0XHf+cerTvL9I5Xwn9v+0jmqzGAZI7zNydv4tL6Cw6A=
-k8s.io/component-base v0.17.2/go.mod h1:zMPW3g5aH7cHJpKYQ/ZsGMcgbsA/VyhEugF3QT1awLs=
+k8s.io/api v0.17.3 h1:XAm3PZp3wnEdzekNkcmj/9Y1zdmQYJ1I4GKSBBZ8aG0=
+k8s.io/api v0.17.3/go.mod h1:YZ0OTkuw7ipbe305fMpIdf3GLXZKRigjtZaV5gzC2J0=
+k8s.io/apiextensions-apiserver v0.17.3 h1:WDZWkPcbgvchEdDd7ysL21GGPx3UKZQLDZXEkevT6n4=
+k8s.io/apiextensions-apiserver v0.17.3/go.mod h1:CJbCyMfkKftAd/X/V6OTHYhVn7zXnDdnkUjS1h0GTeY=
+k8s.io/apimachinery v0.17.3 h1:f+uZV6rm4/tHE7xXgLyToprg6xWairaClGVkm2t8omg=
+k8s.io/apimachinery v0.17.3/go.mod h1:gxLnyZcGNdZTCLnq3fgzyg2A5BVCHTNDFrw8AmuJ+0g=
+k8s.io/apiserver v0.17.3/go.mod h1:iJtsPpu1ZpEnHaNawpSV0nYTGBhhX2dUlnn7/QS7QiY=
+k8s.io/cli-runtime v0.17.3 h1:0ZlDdJgJBKsu77trRUynNiWsRuAvAVPBNaQfnt/1qtc=
+k8s.io/cli-runtime v0.17.3/go.mod h1:X7idckYphH4SZflgNpOOViSxetiMj6xI0viMAjM81TA=
+k8s.io/client-go v0.17.3 h1:deUna1Ksx05XeESH6XGCyONNFfiQmDdqeqUvicvP6nU=
+k8s.io/client-go v0.17.3/go.mod h1:cLXlTMtWHkuK4tD360KpWz2gG2KtdWEr/OT02i3emRQ=
+k8s.io/code-generator v0.17.3/go.mod h1:l8BLVwASXQZTo2xamW5mQNFCe1XPiAesVq7Y1t7PiQQ=
+k8s.io/component-base v0.17.3 h1:hQzTSshY14aLSR6WGIYvmw+w+u6V4d+iDR2iDGMrlUg=
+k8s.io/component-base v0.17.3/go.mod h1:GeQf4BrgelWm64PXkIXiPh/XS0hnO42d9gx9BtbZRp8=
 k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
 k8s.io/gengo v0.0.0-20190822140433-26a664648505/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
 k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
@@ -657,10 +661,10 @@ k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8=
 k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
 k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a h1:UcxjrRMyNx/i/y8G7kPvLyy7rfbeuf1PYyBf973pgyU=
 k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E=
-k8s.io/kubectl v0.17.2 h1:QZR8Q6lWiVRjwKslekdbN5WPMp53dS/17j5e+oi5XVU=
-k8s.io/kubectl v0.17.2/go.mod h1:y4rfLV0n6aPmvbRCqZQjvOp3ezxsFgpqL+zF5jH/lxk=
+k8s.io/kubectl v0.17.3 h1:9HHYj07kuFkM+sMJMOyQX29CKWq4lvKAG1UIPxNPMQ4=
+k8s.io/kubectl v0.17.3/go.mod h1:NUn4IBY7f7yCMwSop2HCXlw/MVYP4HJBiUmOR3n9w28=
 k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk=
-k8s.io/metrics v0.17.2/go.mod h1:3TkNHET4ROd+NfzNxkjoVfQ0Ob4iZnaHmSEA4vYpwLw=
+k8s.io/metrics v0.17.3/go.mod h1:HEJGy1fhHOjHggW9rMDBJBD3YuGroH3Y1pnIRw9FFaI=
 k8s.io/utils v0.0.0-20191114184206-e782cd3c129f h1:GiPwtSzdP43eI1hpPCbROQCCIgCuiMMNF8YUVLF3vJo=
 k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
 modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw=
diff --git a/internal/completion/complete.go b/internal/completion/complete.go
index a24390fc0..eaea4e914 100644
--- a/internal/completion/complete.go
+++ b/internal/completion/complete.go
@@ -35,9 +35,9 @@ import (
 // This should ultimately be pushed down into Cobra.
 // ==================================================================================
 
-// compRequestCmd Hidden command to request completion results from the program.
+// CompRequestCmd Hidden command to request completion results from the program.
 // Used by the shell completion script.
-const compRequestCmd = "__complete"
+const CompRequestCmd = "__complete"
 
 // Global map allowing to find completion functions for commands or flags.
 var validArgsFunctions = map[interface{}]func(cmd *cobra.Command, args []string, toComplete string) ([]string, BashCompDirective){}
@@ -123,7 +123,7 @@ __helm_custom_func()
         done < <(compgen -W "${out[*]}" -- "$cur")
     fi
 }
-`, compRequestCmd, BashCompDirectiveError, BashCompDirectiveNoSpace, BashCompDirectiveNoFileComp)
+`, CompRequestCmd, BashCompDirectiveError, BashCompDirectiveNoSpace, BashCompDirectiveNoFileComp)
 }
 
 // RegisterValidArgsFunc should be called to register a function to provide argument completion for a command
@@ -177,40 +177,58 @@ func (d BashCompDirective) string() string {
 func NewCompleteCmd(settings *cli.EnvSettings, out io.Writer) *cobra.Command {
 	debug = settings.Debug
 	return &cobra.Command{
-		Use:                   fmt.Sprintf("%s [command-line]", compRequestCmd),
+		Use:                   fmt.Sprintf("%s [command-line]", CompRequestCmd),
 		DisableFlagsInUseLine: true,
 		Hidden:                true,
 		DisableFlagParsing:    true,
 		Args:                  require.MinimumNArgs(1),
 		Short:                 "Request shell completion choices for the specified command-line",
 		Long: fmt.Sprintf("%s is a special command that is used by the shell completion logic\n%s",
-			compRequestCmd, "to request completion choices for the specified command-line."),
+			CompRequestCmd, "to request completion choices for the specified command-line."),
 		Run: func(cmd *cobra.Command, args []string) {
 			CompDebugln(fmt.Sprintf("%s was called with args %v", cmd.Name(), args))
 
-			flag, trimmedArgs, toComplete, err := checkIfFlagCompletion(cmd.Root(), args[:len(args)-1], args[len(args)-1])
-			if err != nil {
-				// Error while attempting to parse flags
-				CompErrorln(err.Error())
-				return
-			}
+			// The last argument, which is not complete, should not be part of the list of arguments
+			toComplete := args[len(args)-1]
+			trimmedArgs := args[:len(args)-1]
+
 			// Find the real command for which completion must be performed
 			finalCmd, finalArgs, err := cmd.Root().Find(trimmedArgs)
 			if err != nil {
 				// Unable to find the real command. E.g., helm invalidCmd <TAB>
+				CompDebugln(fmt.Sprintf("Unable to find a command for arguments: %v", trimmedArgs))
 				return
 			}
 
 			CompDebugln(fmt.Sprintf("Found final command '%s', with finalArgs %v", finalCmd.Name(), finalArgs))
 
+			var flag *pflag.Flag
+			if !finalCmd.DisableFlagParsing {
+				// We only do flag completion if we are allowed to parse flags
+				// This is important for helm plugins which need to do their own flag completion.
+				flag, finalArgs, toComplete, err = checkIfFlagCompletion(finalCmd, finalArgs, toComplete)
+				if err != nil {
+					// Error while attempting to parse flags
+					CompErrorln(err.Error())
+					return
+				}
+			}
+
 			// Parse the flags and extract the arguments to prepare for calling the completion function
 			if err = finalCmd.ParseFlags(finalArgs); err != nil {
 				CompErrorln(fmt.Sprintf("Error while parsing flags from args %v: %s", finalArgs, err.Error()))
 				return
 			}
-			argsWoFlags := finalCmd.Flags().Args()
-			CompDebugln(fmt.Sprintf("Args without flags are '%v' with length %d", argsWoFlags, len(argsWoFlags)))
 
+			// We only remove the flags from the arguments if DisableFlagParsing is not set.
+			// This is important for helm plugins, which need to receive all flags.
+			// The plugin completion code will do its own flag parsing.
+			if !finalCmd.DisableFlagParsing {
+				finalArgs = finalCmd.Flags().Args()
+				CompDebugln(fmt.Sprintf("Args without flags are '%v' with length %d", finalArgs, len(finalArgs)))
+			}
+
+			// Find completion function for the flag or command
 			var key interface{}
 			var keyStr string
 			if flag != nil {
@@ -220,26 +238,28 @@ func NewCompleteCmd(settings *cli.EnvSettings, out io.Writer) *cobra.Command {
 				key = finalCmd
 				keyStr = finalCmd.Name()
 			}
-
-			// Find completion function for the flag or command
 			completionFn, ok := validArgsFunctions[key]
 			if !ok {
 				CompErrorln(fmt.Sprintf("Dynamic completion not supported/needed for flag or command: %s", keyStr))
 				return
 			}
 
-			CompDebugln(fmt.Sprintf("Calling completion method for subcommand '%s' with args '%v' and toComplete '%s'", finalCmd.Name(), argsWoFlags, toComplete))
-			completions, directive := completionFn(finalCmd, argsWoFlags, toComplete)
+			CompDebugln(fmt.Sprintf("Calling completion method for subcommand '%s' with args '%v' and toComplete '%s'", finalCmd.Name(), finalArgs, toComplete))
+			completions, directive := completionFn(finalCmd, finalArgs, toComplete)
 			for _, comp := range completions {
 				// Print each possible completion to stdout for the completion script to consume.
 				fmt.Fprintln(out, comp)
 			}
 
+			if directive > BashCompDirectiveError+BashCompDirectiveNoSpace+BashCompDirectiveNoFileComp {
+				directive = BashCompDirectiveDefault
+			}
+
 			// As the last printout, print the completion directive for the
 			// completion script to parse.
 			// The directive integer must be that last character following a single :
 			// The completion script expects :directive
-			fmt.Fprintln(out, fmt.Sprintf(":%d", directive))
+			fmt.Fprintf(out, ":%d\n", directive)
 
 			// Print some helpful info to stderr for the user to understand.
 			// Output from stderr should be ignored from the completion script.
@@ -252,7 +272,7 @@ func isFlag(arg string) bool {
 	return len(arg) > 0 && arg[0] == '-'
 }
 
-func checkIfFlagCompletion(rootCmd *cobra.Command, args []string, lastArg string) (*pflag.Flag, []string, string, error) {
+func checkIfFlagCompletion(finalCmd *cobra.Command, args []string, lastArg string) (*pflag.Flag, []string, string, error) {
 	var flagName string
 	trimmedArgs := args
 	flagWithEqual := false
@@ -287,19 +307,10 @@ func checkIfFlagCompletion(rootCmd *cobra.Command, args []string, lastArg string
 		return nil, trimmedArgs, lastArg, nil
 	}
 
-	// Find the real command for which completion must be performed
-	finalCmd, _, err := rootCmd.Find(trimmedArgs)
-	if err != nil {
-		// Unable to find the real command. E.g., helm invalidCmd <TAB>
-		return nil, nil, "", errors.New("Unable to find final command for completion")
-	}
-
-	CompDebugln(fmt.Sprintf("checkIfFlagCompletion: found final command '%s'", finalCmd.Name()))
-
 	flag := findFlag(finalCmd, flagName)
 	if flag == nil {
 		// Flag not supported by this command, nothing to complete
-		err = fmt.Errorf("Subcommand '%s' does not support flag '%s'", finalCmd.Name(), flagName)
+		err := fmt.Errorf("Subcommand '%s' does not support flag '%s'", finalCmd.Name(), flagName)
 		return nil, nil, "", err
 	}
 
diff --git a/pkg/action/action.go b/pkg/action/action.go
index 1af5b3d9a..e4db942c8 100644
--- a/pkg/action/action.go
+++ b/pkg/action/action.go
@@ -241,7 +241,18 @@ func (c *Configuration) Init(getter genericclioptions.RESTClientGetter, namespac
 		d.Log = log
 		store = storage.Init(d)
 	case "memory":
-		d := driver.NewMemory()
+		var d *driver.Memory
+		if c.Releases != nil {
+			if mem, ok := c.Releases.Driver.(*driver.Memory); ok {
+				// 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.
+				// We re-use it in case some releases where already created in the existing memory driver.
+				d = mem
+			}
+		}
+		if d == nil {
+			d = driver.NewMemory()
+		}
 		d.SetNamespace(namespace)
 		store = storage.Init(d)
 	default:
diff --git a/pkg/action/install.go b/pkg/action/install.go
index 79abfae33..e7b481d47 100644
--- a/pkg/action/install.go
+++ b/pkg/action/install.go
@@ -29,8 +29,11 @@ import (
 
 	"github.com/Masterminds/sprig/v3"
 	"github.com/pkg/errors"
+	v1 "k8s.io/api/core/v1"
 	apierrors "k8s.io/apimachinery/pkg/api/errors"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/cli-runtime/pkg/resource"
+	"sigs.k8s.io/yaml"
 
 	"helm.sh/helm/v3/pkg/chart"
 	"helm.sh/helm/v3/pkg/chartutil"
@@ -69,6 +72,7 @@ type Install struct {
 	ChartPathOptions
 
 	ClientOnly               bool
+	CreateNamespace          bool
 	DryRun                   bool
 	DisableHooks             bool
 	Replace                  bool
@@ -265,6 +269,32 @@ func (i *Install) Run(chrt *chart.Chart, vals map[string]interface{}) (*release.
 		return rel, nil
 	}
 
+	if i.CreateNamespace {
+		ns := &v1.Namespace{
+			TypeMeta: metav1.TypeMeta{
+				APIVersion: "v1",
+				Kind:       "Namespace",
+			},
+			ObjectMeta: metav1.ObjectMeta{
+				Name: i.Namespace,
+				Labels: map[string]string{
+					"name": i.Namespace,
+				},
+			},
+		}
+		buf, err := yaml.Marshal(ns)
+		if err != nil {
+			return nil, err
+		}
+		resourceList, err := i.cfg.KubeClient.Build(bytes.NewBuffer(buf), true)
+		if err != nil {
+			return nil, err
+		}
+		if _, err := i.cfg.KubeClient.Create(resourceList); err != nil && !apierrors.IsAlreadyExists(err) {
+			return nil, err
+		}
+	}
+
 	// If Replace is true, we need to supercede the last release.
 	if i.Replace {
 		if err := i.replaceRelease(rel); err != nil {
diff --git a/pkg/chartutil/dependencies.go b/pkg/chartutil/dependencies.go
index 4b389dc22..521e2abc4 100644
--- a/pkg/chartutil/dependencies.go
+++ b/pkg/chartutil/dependencies.go
@@ -233,7 +233,7 @@ func processImportValues(c *chart.Chart) error {
 				// get child table
 				vv, err := cvals.Table(r.Name + "." + child)
 				if err != nil {
-					log.Printf("Warning: ImportValues missing table: %v", err)
+					log.Printf("Warning: ImportValues missing table from chart %s: %v", r.Name, err)
 					continue
 				}
 				// create value map from child to be merged into parent
diff --git a/pkg/kube/client.go b/pkg/kube/client.go
index 9243f4361..31dabcc5d 100644
--- a/pkg/kube/client.go
+++ b/pkg/kube/client.go
@@ -88,7 +88,7 @@ func (c *Client) IsReachable() error {
 	client, _ := c.Factory.KubernetesClientSet()
 	_, err := client.ServerVersion()
 	if err != nil {
-		return errors.New("Kubernetes cluster unreachable")
+		return fmt.Errorf("Kubernetes cluster unreachable: %s", err.Error())
 	}
 	return nil
 }


Test patch:
diff --git a/cmd/helm/helm_test.go b/cmd/helm/helm_test.go
index 8c6c492f8..94646a5a3 100644
--- a/cmd/helm/helm_test.go
+++ b/cmd/helm/helm_test.go
@@ -18,7 +18,6 @@ package main
 
 import (
 	"bytes"
-	"fmt"
 	"io/ioutil"
 	"os"
 	"os/exec"
@@ -97,39 +96,11 @@ func storageFixture() *storage.Storage {
 	return storage.Init(driver.NewMemory())
 }
 
-// go-shellwords does not handle empty arguments properly
-// https://github.com/mattn/go-shellwords/issues/5#issuecomment-573431458
-//
-// This method checks if the last argument was an empty one,
-// and if go-shellwords missed it, we add it ourselves.
-//
-// This is important for completion tests as completion often
-// uses an empty last parameter.
-func checkLastEmpty(in string, out []string) []string {
-	lastIndex := len(in) - 1
-
-	if lastIndex >= 1 && (in[lastIndex] == '"' && in[lastIndex-1] == '"' ||
-		in[lastIndex] == '\'' && in[lastIndex-1] == '\'') {
-		// The last parameter of 'in' was empty ("" or ''), let's make sure it was detected.
-		if len(out) > 0 && out[len(out)-1] != "" {
-			// Bug from go-shellwords:
-			// 'out' does not have the empty parameter.  We add it ourselves as a workaround.
-			out = append(out, "")
-		} else {
-			fmt.Println("WARNING: go-shellwords seems to have been fixed.  This workaround can be removed.")
-		}
-	}
-	return out
-}
-
 func executeActionCommandC(store *storage.Storage, cmd string) (*cobra.Command, string, error) {
 	args, err := shellwords.Parse(cmd)
 	if err != nil {
 		return nil, "", err
 	}
-	// Workaround the bug in shellwords
-	args = checkLastEmpty(cmd, args)
-
 	buf := new(bytes.Buffer)
 
 	actionConfig := &action.Configuration{
diff --git a/cmd/helm/plugin_test.go b/cmd/helm/plugin_test.go
index 3fd3a4197..e43f277a5 100644
--- a/cmd/helm/plugin_test.go
+++ b/cmd/helm/plugin_test.go
@@ -19,10 +19,14 @@ import (
 	"bytes"
 	"os"
 	"runtime"
+	"sort"
 	"strings"
 	"testing"
 
 	"github.com/spf13/cobra"
+	"github.com/spf13/pflag"
+
+	"helm.sh/helm/v3/pkg/release"
 )
 
 func TestManuallyProcessArgs(t *testing.T) {
@@ -151,6 +155,134 @@ func TestLoadPlugins(t *testing.T) {
 	}
 }
 
+type staticCompletionDetails struct {
+	use       string
+	validArgs []string
+	flags     []string
+	next      []staticCompletionDetails
+}
+
+func TestLoadPluginsForCompletion(t *testing.T) {
+	settings.PluginsDirectory = "testdata/helmhome/helm/plugins"
+
+	var out bytes.Buffer
+
+	cmd := &cobra.Command{
+		Use: "completion",
+	}
+
+	loadPlugins(cmd, &out)
+
+	tests := []staticCompletionDetails{
+		{"args", []string{}, []string{}, []staticCompletionDetails{}},
+		{"echo", []string{}, []string{}, []staticCompletionDetails{}},
+		{"env", []string{}, []string{"global"}, []staticCompletionDetails{
+			{"list", []string{}, []string{"a", "all", "log"}, []staticCompletionDetails{}},
+			{"remove", []string{"all", "one"}, []string{}, []staticCompletionDetails{}},
+		}},
+		{"exitwith", []string{}, []string{}, []staticCompletionDetails{
+			{"code", []string{}, []string{"a", "b"}, []staticCompletionDetails{}},
+		}},
+		{"fullenv", []string{}, []string{"q", "z"}, []staticCompletionDetails{
+			{"empty", []string{}, []string{}, []staticCompletionDetails{}},
+			{"full", []string{}, []string{}, []staticCompletionDetails{
+				{"less", []string{}, []string{"a", "all"}, []staticCompletionDetails{}},
+				{"more", []string{"one", "two"}, []string{"b", "ball"}, []staticCompletionDetails{}},
+			}},
+		}},
+	}
+	checkCommand(t, cmd.Commands(), tests)
+}
+
+func checkCommand(t *testing.T, plugins []*cobra.Command, tests []staticCompletionDetails) {
+	if len(plugins) != len(tests) {
+		t.Fatalf("Expected commands %v, got %v", tests, plugins)
+	}
+
+	for i := 0; i < len(plugins); i++ {
+		pp := plugins[i]
+		tt := tests[i]
+		if pp.Use != tt.use {
+			t.Errorf("%s: Expected Use=%q, got %q", pp.Name(), tt.use, pp.Use)
+		}
+
+		targs := tt.validArgs
+		pargs := pp.ValidArgs
+		if len(targs) != len(pargs) {
+			t.Fatalf("%s: expected args %v, got %v", pp.Name(), targs, pargs)
+		}
+
+		sort.Strings(targs)
+		sort.Strings(pargs)
+		for j := range targs {
+			if targs[j] != pargs[j] {
+				t.Errorf("%s: expected validArg=%q, got %q", pp.Name(), targs[j], pargs[j])
+			}
+		}
+
+		tflags := tt.flags
+		var pflags []string
+		pp.LocalFlags().VisitAll(func(flag *pflag.Flag) {
+			pflags = append(pflags, flag.Name)
+			if len(flag.Shorthand) > 0 && flag.Shorthand != flag.Name {
+				pflags = append(pflags, flag.Shorthand)
+			}
+		})
+		if len(tflags) != len(pflags) {
+			t.Fatalf("%s: expected flags %v, got %v", pp.Name(), tflags, pflags)
+		}
+
+		sort.Strings(tflags)
+		sort.Strings(pflags)
+		for j := range tflags {
+			if tflags[j] != pflags[j] {
+				t.Errorf("%s: expected flag=%q, got %q", pp.Name(), tflags[j], pflags[j])
+			}
+		}
+		// Check the next level
+		checkCommand(t, pp.Commands(), tt.next)
+	}
+}
+
+func TestPluginDynamicCompletion(t *testing.T) {
+
+	tests := []cmdTestCase{{
+		name:   "completion for plugin",
+		cmd:    "__complete args ''",
+		golden: "output/plugin_args_comp.txt",
+		rels:   []*release.Release{},
+	}, {
+		name:   "completion for plugin with flag",
+		cmd:    "__complete args --myflag ''",
+		golden: "output/plugin_args_flag_comp.txt",
+		rels:   []*release.Release{},
+	}, {
+		name:   "completion for plugin with global flag",
+		cmd:    "__complete args --namespace mynamespace ''",
+		golden: "output/plugin_args_ns_comp.txt",
+		rels:   []*release.Release{},
+	}, {
+		name:   "completion for plugin with multiple args",
+		cmd:    "__complete args --myflag --namespace mynamespace start",
+		golden: "output/plugin_args_many_args_comp.txt",
+		rels:   []*release.Release{},
+	}, {
+		name:   "completion for plugin no directive",
+		cmd:    "__complete echo -n mynamespace ''",
+		golden: "output/plugin_echo_no_directive.txt",
+		rels:   []*release.Release{},
+	}, {
+		name:   "completion for plugin bad directive",
+		cmd:    "__complete echo ''",
+		golden: "output/plugin_echo_bad_directive.txt",
+		rels:   []*release.Release{},
+	}}
+	for _, test := range tests {
+		settings.PluginsDirectory = "testdata/helmhome/helm/plugins"
+		runTestCmd(t, []cmdTestCase{test})
+	}
+}
+
 func TestLoadPlugins_HelmNoPlugins(t *testing.T) {
 	settings.PluginsDirectory = "testdata/helmhome/helm/plugins"
 	settings.RepositoryConfig = "testdata/helmhome/helm/repository"
diff --git a/cmd/helm/root_test.go b/cmd/helm/root_test.go
index df592a96d..e1fa1fc27 100644
--- a/cmd/helm/root_test.go
+++ b/cmd/helm/root_test.go
@@ -95,3 +95,11 @@ func TestRootCmd(t *testing.T) {
 		})
 	}
 }
+
+func TestUnknownSubCmd(t *testing.T) {
+	_, _, err := executeActionCommand("foobar")
+
+	if err == nil || err.Error() != `unknown command "foobar" for "helm"` {
+		t.Errorf("Expect unknown command error, got %q", err)
+	}
+}
diff --git a/cmd/helm/testdata/helmhome/helm/plugins/args/plugin.complete b/cmd/helm/testdata/helmhome/helm/plugins/args/plugin.complete
new file mode 100755
index 000000000..2b00c2281
--- /dev/null
+++ b/cmd/helm/testdata/helmhome/helm/plugins/args/plugin.complete
@@ -0,0 +1,13 @@
+#!/usr/bin/env sh
+
+echo "plugin.complete was called"
+echo "Namespace: ${HELM_NAMESPACE:-NO_NS}"
+echo "Num args received: ${#}"
+echo "Args received: ${@}"
+
+# Final printout is the optional completion directive of the form :<directive>
+if [ "$HELM_NAMESPACE" = "default" ]; then
+    echo ":4"
+else
+    echo ":2"
+fi
diff --git a/cmd/helm/testdata/helmhome/helm/plugins/echo/completion.yaml b/cmd/helm/testdata/helmhome/helm/plugins/echo/completion.yaml
new file mode 100644
index 000000000..e69de29bb
diff --git a/cmd/helm/testdata/helmhome/helm/plugins/echo/plugin.complete b/cmd/helm/testdata/helmhome/helm/plugins/echo/plugin.complete
new file mode 100755
index 000000000..6bc73d130
--- /dev/null
+++ b/cmd/helm/testdata/helmhome/helm/plugins/echo/plugin.complete
@@ -0,0 +1,14 @@
+#!/usr/bin/env sh
+
+echo "echo plugin.complete was called"
+echo "Namespace: ${HELM_NAMESPACE:-NO_NS}"
+echo "Num args received: ${#}"
+echo "Args received: ${@}"
+
+# Final printout is the optional completion directive of the form :<directive>
+if [ "$HELM_NAMESPACE" = "default" ]; then
+    # Output an invalid directive, which should be ignored
+    echo ":2222"
+# else
+    # Don't include the directive, to test it is really optional
+fi
diff --git a/cmd/helm/testdata/helmhome/helm/plugins/env/completion.yaml b/cmd/helm/testdata/helmhome/helm/plugins/env/completion.yaml
new file mode 100644
index 000000000..e479a0503
--- /dev/null
+++ b/cmd/helm/testdata/helmhome/helm/plugins/env/completion.yaml
@@ -0,0 +1,13 @@
+name: env
+commands:
+  - name: list
+    flags:
+    - a
+    - all
+    - log
+  - name: remove
+    validArgs:
+    - all
+    - one
+flags:
+- global
diff --git a/cmd/helm/testdata/helmhome/helm/plugins/exitwith/completion.yaml b/cmd/helm/testdata/helmhome/helm/plugins/exitwith/completion.yaml
new file mode 100644
index 000000000..e5bf440f6
--- /dev/null
+++ b/cmd/helm/testdata/helmhome/helm/plugins/exitwith/completion.yaml
@@ -0,0 +1,5 @@
+commands:
+  - name: code
+    flags:
+    - a
+    - b
diff --git a/cmd/helm/testdata/helmhome/helm/plugins/fullenv/completion.yaml b/cmd/helm/testdata/helmhome/helm/plugins/fullenv/completion.yaml
new file mode 100644
index 000000000..e0b161c69
--- /dev/null
+++ b/cmd/helm/testdata/helmhome/helm/plugins/fullenv/completion.yaml
@@ -0,0 +1,19 @@
+name: wrongname
+commands:
+  - name: empty
+  - name: full
+    commands:
+    - name: more
+      validArgs:
+      - one
+      - two
+      flags:
+      - b
+      - ball
+    - name: less
+      flags:
+      - a
+      - all
+flags:
+- z
+- q
diff --git a/cmd/helm/testdata/output/plugin_args_comp.txt b/cmd/helm/testdata/output/plugin_args_comp.txt
new file mode 100644
index 000000000..8fb01cc23
--- /dev/null
+++ b/cmd/helm/testdata/output/plugin_args_comp.txt
@@ -0,0 +1,5 @@
+plugin.complete was called
+Namespace: default
+Num args received: 1
+Args received: 
+:4
diff --git a/cmd/helm/testdata/output/plugin_args_flag_comp.txt b/cmd/helm/testdata/output/plugin_args_flag_comp.txt
new file mode 100644
index 000000000..92f0e58a8
--- /dev/null
+++ b/cmd/helm/testdata/output/plugin_args_flag_comp.txt
@@ -0,0 +1,5 @@
+plugin.complete was called
+Namespace: default
+Num args received: 2
+Args received: --myflag 
+:4
diff --git a/cmd/helm/testdata/output/plugin_args_many_args_comp.txt b/cmd/helm/testdata/output/plugin_args_many_args_comp.txt
new file mode 100644
index 000000000..86fa768bb
--- /dev/null
+++ b/cmd/helm/testdata/output/plugin_args_many_args_comp.txt
@@ -0,0 +1,5 @@
+plugin.complete was called
+Namespace: mynamespace
+Num args received: 2
+Args received: --myflag start
+:2
diff --git a/cmd/helm/testdata/output/plugin_args_ns_comp.txt b/cmd/helm/testdata/output/plugin_args_ns_comp.txt
new file mode 100644
index 000000000..e12867daa
--- /dev/null
+++ b/cmd/helm/testdata/output/plugin_args_ns_comp.txt
@@ -0,0 +1,5 @@
+plugin.complete was called
+Namespace: mynamespace
+Num args received: 1
+Args received: 
+:2
diff --git a/cmd/helm/testdata/output/plugin_echo_bad_directive.txt b/cmd/helm/testdata/output/plugin_echo_bad_directive.txt
new file mode 100644
index 000000000..f4b86cd47
--- /dev/null
+++ b/cmd/helm/testdata/output/plugin_echo_bad_directive.txt
@@ -0,0 +1,5 @@
+echo plugin.complete was called
+Namespace: default
+Num args received: 1
+Args received: 
+:0
diff --git a/cmd/helm/testdata/output/plugin_echo_no_directive.txt b/cmd/helm/testdata/output/plugin_echo_no_directive.txt
new file mode 100644
index 000000000..6266dd4d9
--- /dev/null
+++ b/cmd/helm/testdata/output/plugin_echo_no_directive.txt
@@ -0,0 +1,5 @@
+echo plugin.complete was called
+Namespace: mynamespace
+Num args received: 1
+Args received: 
+:0
diff --git a/cmd/helm/testdata/testcharts/alpine/README.md b/cmd/helm/testdata/testcharts/alpine/README.md
index fcf7ee017..05d39dbbc 100644
--- a/cmd/helm/testdata/testcharts/alpine/README.md
+++ b/cmd/helm/testdata/testcharts/alpine/README.md
@@ -1,4 +1,4 @@
-#Alpine: A simple Helm chart
+# Alpine: A simple Helm chart
 
 Run a single pod of Alpine Linux.
 
diff --git a/pkg/storage/driver/records_test.go b/pkg/storage/driver/records_test.go
index 79b60044f..3ede92bcd 100644
--- a/pkg/storage/driver/records_test.go
+++ b/pkg/storage/driver/records_test.go
@@ -17,6 +17,7 @@ limitations under the License.
 package driver // import "helm.sh/helm/v3/pkg/storage/driver"
 
 import (
+	"reflect"
 	"testing"
 
 	rspb "helm.sh/helm/v3/pkg/release"
@@ -110,3 +111,96 @@ func TestRecordsRemoveAt(t *testing.T) {
 		t.Fatalf("Expected length of rs to be 1, got %d", len(rs))
 	}
 }
+
+func TestRecordsGet(t *testing.T) {
+	rs := records([]*record{
+		newRecord("rls-a.v1", releaseStub("rls-a", 1, "default", rspb.StatusSuperseded)),
+		newRecord("rls-a.v2", releaseStub("rls-a", 2, "default", rspb.StatusDeployed)),
+	})
+
+	var tests = []struct {
+		desc string
+		key  string
+		rec  *record
+	}{
+		{
+			"get valid key",
+			"rls-a.v1",
+			newRecord("rls-a.v1", releaseStub("rls-a", 1, "default", rspb.StatusSuperseded)),
+		},
+		{
+			"get invalid key",
+			"rls-a.v3",
+			nil,
+		},
+	}
+
+	for _, tt := range tests {
+		got := rs.Get(tt.key)
+		if !reflect.DeepEqual(tt.rec, got) {
+			t.Fatalf("Expected %v, got %v", tt.rec, got)
+		}
+	}
+}
+
+func TestRecordsIndex(t *testing.T) {
+	rs := records([]*record{
+		newRecord("rls-a.v1", releaseStub("rls-a", 1, "default", rspb.StatusSuperseded)),
+		newRecord("rls-a.v2", releaseStub("rls-a", 2, "default", rspb.StatusDeployed)),
+	})
+
+	var tests = []struct {
+		desc string
+		key  string
+		sort int
+	}{
+		{
+			"get valid key",
+			"rls-a.v1",
+			0,
+		},
+		{
+			"get invalid key",
+			"rls-a.v3",
+			-1,
+		},
+	}
+
+	for _, tt := range tests {
+		got, _ := rs.Index(tt.key)
+		if got != tt.sort {
+			t.Fatalf("Expected %d, got %d", tt.sort, got)
+		}
+	}
+}
+
+func TestRecordsExists(t *testing.T) {
+	rs := records([]*record{
+		newRecord("rls-a.v1", releaseStub("rls-a", 1, "default", rspb.StatusSuperseded)),
+		newRecord("rls-a.v2", releaseStub("rls-a", 2, "default", rspb.StatusDeployed)),
+	})
+
+	var tests = []struct {
+		desc string
+		key  string
+		ok   bool
+	}{
+		{
+			"get valid key",
+			"rls-a.v1",
+			true,
+		},
+		{
+			"get invalid key",
+			"rls-a.v3",
+			false,
+		},
+	}
+
+	for _, tt := range tests {
+		got := rs.Exists(tt.key)
+		if got != tt.ok {
+			t.Fatalf("Expected %t, got %t", tt.ok, got)
+		}
+	}
+}
diff --git a/testdata/releases.yaml b/testdata/releases.yaml
new file mode 100644
index 000000000..fef79f424
--- /dev/null
+++ b/testdata/releases.yaml
@@ -0,0 +1,43 @@
+# This file can be used as input to create test releases:
+# HELM_MEMORY_DRIVER_DATA=./testdata/releases.yaml HELM_DRIVER=memory helm list --all-namespaces
+- name: athos
+  version: 1
+  namespace: default
+  info:
+    status: deployed
+  chart:
+    metadata:
+      name: athos-chart
+      version: 1.0.0
+      appversion: 1.1.0
+- name: porthos
+  version: 2
+  namespace: default
+  info:
+    status: deployed
+  chart:
+    metadata:
+      name: prothos-chart
+      version: 0.2.0
+      appversion: 0.2.2
+- name: aramis
+  version: 3
+  namespace: default
+  info:
+    status: deployed
+  chart:
+    metadata:
+      name: aramis-chart
+      version: 0.0.3
+      appversion: 3.0.3
+- name: dartagnan
+  version: 4
+  namespace: gascony
+  info:
+    status: deployed
+  chart:
+    metadata:
+      name: dartagnan-chart
+      version: 0.4.4
+      appversion: 4.4.4
+