fix(helm): fix race conditions in flag parsing

* fix a number of issues with flag parsing
* add support for `HELM_DEBUG`
* lazy expand flag default envars
parent 6643a212
......@@ -87,7 +87,7 @@ func TestCreateStarterCmd(t *testing.T) {
if err != nil {
t.Fatal(err)
}
old := helmpath.Home(environment.DefaultHelmHome())
old := helmpath.Home(environment.DefaultHelmHome)
settings.Home = thome
defer func() {
settings.Home = old
......
......@@ -52,12 +52,12 @@ func newDeleteCmd(c helm.Interface, out io.Writer) *cobra.Command {
}
cmd := &cobra.Command{
Use: "delete [flags] RELEASE_NAME [...]",
Aliases: []string{"del"},
SuggestFor: []string{"remove", "rm"},
Short: "given a release name, delete the release from Kubernetes",
Long: deleteDesc,
PersistentPreRunE: setupConnection,
Use: "delete [flags] RELEASE_NAME [...]",
Aliases: []string{"del"},
SuggestFor: []string{"remove", "rm"},
Short: "given a release name, delete the release from Kubernetes",
Long: deleteDesc,
PreRunE: setupConnection,
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
return errors.New("command 'delete' requires a release name")
......
......@@ -34,7 +34,7 @@ func TestFetchCmd(t *testing.T) {
if err != nil {
t.Fatal(err)
}
old := helmpath.Home(environment.DefaultHelmHome())
old := helmpath.Home(environment.DefaultHelmHome)
settings.Home = hh
defer func() {
settings.Home = old
......
......@@ -54,10 +54,10 @@ func newGetCmd(client helm.Interface, out io.Writer) *cobra.Command {
}
cmd := &cobra.Command{
Use: "get [flags] RELEASE_NAME",
Short: "download a named release",
Long: getHelp,
PersistentPreRunE: setupConnection,
Use: "get [flags] RELEASE_NAME",
Short: "download a named release",
Long: getHelp,
PreRunE: setupConnection,
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
return errReleaseRequired
......
......@@ -39,10 +39,6 @@ import (
"k8s.io/helm/pkg/tlsutil"
)
const (
localRepoIndexFilePath = "index.yaml"
)
var (
tlsCaCertFile string // path to TLS CA certificate file
tlsCertFile string // path to TLS certificate file
......@@ -52,10 +48,9 @@ var (
)
var (
kubeContext string
settings helm_env.EnvSettings
// TODO refactor out this global var
kubeContext string
tillerTunnel *kube.Tunnel
settings helm_env.EnvSettings
)
var globalUsage = `The Kubernetes package manager
......@@ -82,34 +77,57 @@ Environment:
$KUBECONFIG set an alternative Kubernetes configuration file (default "~/.kube/config")
`
func setFlagFromEnv(name, envar string, cmd *cobra.Command) {
if cmd.Flags().Changed(name) {
return
}
if v, ok := os.LookupEnv(envar); ok {
cmd.Flags().Set(name, v)
}
}
func setFlagsFromEnv(flags map[string]string, cmd *cobra.Command) {
for name, envar := range flags {
setFlagFromEnv(name, envar, cmd)
}
}
func addRootFlags(cmd *cobra.Command) {
pf := cmd.PersistentFlags()
pf.StringVar((*string)(&settings.Home), "home", helm_env.DefaultHelmHome, "location of your Helm config. Overrides $HELM_HOME")
pf.StringVar(&settings.TillerHost, "host", "", "address of tiller. Overrides $HELM_HOST")
pf.StringVar(&kubeContext, "kube-context", "", "name of the kubeconfig context to use")
pf.BoolVar(&settings.Debug, "debug", false, "enable verbose output")
pf.StringVar(&settings.TillerNamespace, "tiller-namespace", tiller_env.DefaultTillerNamespace, "namespace of tiller")
}
func initRootFlags(cmd *cobra.Command) {
setFlagsFromEnv(map[string]string{
"debug": helm_env.DebugEnvVar,
"home": helm_env.HomeEnvVar,
"host": helm_env.HostEnvVar,
"tiller-namespace": tiller_env.TillerNamespaceEnvVar,
}, cmd.Root())
tlsCaCertFile = os.ExpandEnv(tlsCaCertFile)
tlsCertFile = os.ExpandEnv(tlsCertFile)
tlsKeyFile = os.ExpandEnv(tlsKeyFile)
}
func newRootCmd(out io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "helm",
Short: "The Helm package manager for Kubernetes.",
Long: globalUsage,
SilenceUsage: true,
PersistentPreRun: func(cmd *cobra.Command, args []string) {
tlsCaCertFile = os.ExpandEnv(tlsCaCertFile)
tlsCertFile = os.ExpandEnv(tlsCertFile)
tlsKeyFile = os.ExpandEnv(tlsKeyFile)
PersistentPreRun: func(cmd *cobra.Command, _ []string) {
initRootFlags(cmd)
},
PersistentPostRun: func(cmd *cobra.Command, args []string) {
PersistentPostRun: func(_ *cobra.Command, _ []string) {
teardown()
},
}
p := cmd.PersistentFlags()
p.StringVar((*string)(&settings.Home), "home", helm_env.DefaultHelmHome(), "location of your Helm config. Overrides $HELM_HOME")
p.StringVar(&settings.TillerHost, "host", helm_env.DefaultHelmHost(), "address of tiller. Overrides $HELM_HOST")
p.StringVar(&kubeContext, "kube-context", "", "name of the kubeconfig context to use")
p.BoolVar(&settings.Debug, "debug", false, "enable verbose output")
p.StringVar(&settings.TillerNamespace, "tiller-namespace", tiller_env.GetTillerNamespace(), "namespace of tiller")
if os.Getenv(helm_env.PluginDisableEnvVar) != "1" {
settings.PlugDirs = os.Getenv(helm_env.PluginEnvVar)
if settings.PlugDirs == "" {
settings.PlugDirs = settings.Home.Plugins()
}
}
addRootFlags(cmd)
cmd.AddCommand(
// chart commands
......
......@@ -321,7 +321,7 @@ func ensureTestHome(home helmpath.Home, t *testing.T) error {
}
}
localRepoIndexFile := home.LocalRepository(localRepoIndexFilePath)
localRepoIndexFile := home.LocalRepository(localRepositoryIndexFile)
if fi, err := os.Stat(localRepoIndexFile); err != nil {
i := repo.NewIndexFile()
if err := i.WriteFile(localRepoIndexFile, 0644); err != nil {
......
......@@ -56,11 +56,11 @@ func newHistoryCmd(c helm.Interface, w io.Writer) *cobra.Command {
his := &historyCmd{out: w, helmc: c}
cmd := &cobra.Command{
Use: "history [flags] RELEASE_NAME",
Long: historyHelp,
Short: "fetch release history",
Aliases: []string{"hist"},
PersistentPreRunE: setupConnection,
Use: "history [flags] RELEASE_NAME",
Long: historyHelp,
Short: "fetch release history",
Aliases: []string{"hist"},
PreRunE: setupConnection,
RunE: func(cmd *cobra.Command, args []string) error {
switch {
case len(args) == 0:
......
......@@ -54,8 +54,9 @@ To dump a manifest containing the Tiller deployment YAML, combine the
`
const (
stableRepository = "stable"
localRepository = "local"
stableRepository = "stable"
localRepository = "local"
localRepositoryIndexFile = "index.yaml"
)
var (
......@@ -296,7 +297,7 @@ func ensureDefaultRepos(home helmpath.Home, out io.Writer, skipRefresh bool) err
if err != nil {
return err
}
lr, err := initLocalRepo(home.LocalRepository(localRepoIndexFilePath), home.CacheIndex("local"))
lr, err := initLocalRepo(home.LocalRepository(localRepositoryIndexFile), home.CacheIndex("local"))
if err != nil {
return err
}
......
......@@ -214,7 +214,7 @@ func TestEnsureHome(t *testing.T) {
t.Errorf("%s should not be a directory", fi)
}
if fi, err := os.Stat(hh.LocalRepository(localRepoIndexFilePath)); err != nil {
if fi, err := os.Stat(hh.LocalRepository(localRepositoryIndexFile)); err != nil {
t.Errorf("%s", err)
} else if fi.IsDir() {
t.Errorf("%s should not be a directory", fi)
......
......@@ -148,10 +148,10 @@ func newInstallCmd(c helm.Interface, out io.Writer) *cobra.Command {
}
cmd := &cobra.Command{
Use: "install [CHART]",
Short: "install a chart archive",
Long: installDesc,
PersistentPreRunE: setupConnection,
Use: "install [CHART]",
Short: "install a chart archive",
Long: installDesc,
PreRunE: setupConnection,
RunE: func(cmd *cobra.Command, args []string) error {
if err := checkArgsLength(len(args), "chart name"); err != nil {
return err
......
......@@ -82,11 +82,11 @@ func newListCmd(client helm.Interface, out io.Writer) *cobra.Command {
}
cmd := &cobra.Command{
Use: "list [flags] [FILTER]",
Short: "list releases",
Long: listHelp,
Aliases: []string{"ls"},
PersistentPreRunE: setupConnection,
Use: "list [flags] [FILTER]",
Short: "list releases",
Long: listHelp,
Aliases: []string{"ls"},
PreRunE: setupConnection,
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) > 0 {
list.filter = strings.Join(args, " ")
......
......@@ -25,19 +25,10 @@ import (
"github.com/spf13/cobra"
"k8s.io/helm/pkg/helm/helmpath"
helm_env "k8s.io/helm/pkg/helm/environment"
"k8s.io/helm/pkg/plugin"
)
const pluginEnvVar = "HELM_PLUGIN"
func pluginDirs(home helmpath.Home) string {
if dirs := os.Getenv(pluginEnvVar); dirs != "" {
return dirs
}
return home.Plugins()
}
// loadPlugins loads plugins into the command list.
//
// This follows a different pattern than the other commands because it has
......@@ -46,16 +37,25 @@ func pluginDirs(home helmpath.Home) string {
func loadPlugins(baseCmd *cobra.Command, out io.Writer) {
// If HELM_NO_PLUGINS is set to 1, do not load plugins.
if settings.PlugDirs == "" {
if os.Getenv(helm_env.PluginDisableEnvVar) == "1" {
return
}
found, err := findPlugins(settings.PlugDirs)
found, err := findPlugins(settings.PluginDirs())
if err != nil {
fmt.Fprintf(os.Stderr, "failed to load plugins: %s", err)
return
}
processParent := func(cmd *cobra.Command, args []string) ([]string, error) {
k, u := manuallyProcessArgs(args)
if err := cmd.Parent().ParseFlags(k); err != nil {
return nil, err
}
initRootFlags(cmd)
return u, nil
}
// Now we create commands for all of these.
for _, plug := range found {
plug := plug
......@@ -69,9 +69,8 @@ func loadPlugins(baseCmd *cobra.Command, out io.Writer) {
Short: md.Usage,
Long: md.Description,
RunE: func(cmd *cobra.Command, args []string) error {
k, u := manuallyProcessArgs(args)
if err := cmd.Parent().ParseFlags(k); err != nil {
u, err := processParent(cmd, args)
if err != nil {
return err
}
......@@ -99,10 +98,9 @@ func loadPlugins(baseCmd *cobra.Command, out io.Writer) {
}
if md.UseTunnel {
c.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {
c.PreRunE = func(cmd *cobra.Command, args []string) error {
// Parse the parent flag, but not the local flags.
k, _ := manuallyProcessArgs(args)
if err := c.Parent().ParseFlags(k); err != nil {
if _, err := processParent(cmd, args); err != nil {
return err
}
return setupConnection(cmd, args)
......
......@@ -44,11 +44,8 @@ func newPluginListCmd(out io.Writer) *cobra.Command {
}
func (pcmd *pluginListCmd) run() error {
plugdirs := pluginDirs(pcmd.home)
debug("pluginDirs: %s", plugdirs)
plugins, err := findPlugins(plugdirs)
debug("pluginDirs: %s", settings.PluginDirs())
plugins, err := findPlugins(settings.PluginDirs())
if err != nil {
return err
}
......
......@@ -59,9 +59,8 @@ func (pcmd *pluginRemoveCmd) complete(args []string) error {
}
func (pcmd *pluginRemoveCmd) run() error {
plugdirs := pluginDirs(pcmd.home)
debug("loading installed plugins from %s", plugdirs)
plugins, err := findPlugins(plugdirs)
debug("loading installed plugins from %s", settings.PluginDirs())
plugins, err := findPlugins(settings.PluginDirs())
if err != nil {
return err
}
......
......@@ -23,6 +23,7 @@ import (
"strings"
"testing"
helm_env "k8s.io/helm/pkg/helm/environment"
"k8s.io/helm/pkg/helm/helmpath"
"k8s.io/helm/pkg/plugin"
......@@ -71,7 +72,6 @@ func TestLoadPlugins(t *testing.T) {
settings.Home = old
}()
hh := settings.Home
settings.PlugDirs = hh.Plugins()
out := bytes.NewBuffer(nil)
cmd := &cobra.Command{}
......@@ -139,12 +139,11 @@ func TestLoadPlugins(t *testing.T) {
func TestLoadPlugins_HelmNoPlugins(t *testing.T) {
// Set helm home to point to testdata
old := settings.Home
oldPlugDirs := settings.PlugDirs
settings.Home = "testdata/helmhome"
settings.PlugDirs = ""
os.Setenv(helm_env.PluginDisableEnvVar, "1")
defer func() {
settings.Home = old
settings.PlugDirs = oldPlugDirs
os.Unsetenv(helm_env.PluginDisableEnvVar)
}()
out := bytes.NewBuffer(nil)
......@@ -161,7 +160,6 @@ func TestSetupEnv(t *testing.T) {
name := "pequod"
settings.Home = helmpath.Home("testdata/helmhome")
base := filepath.Join(settings.Home.Plugins(), name)
settings.PlugDirs = settings.Home.Plugins()
settings.Debug = true
defer func() {
settings.Debug = false
......
......@@ -61,9 +61,8 @@ func (pcmd *pluginUpdateCmd) complete(args []string) error {
func (pcmd *pluginUpdateCmd) run() error {
installer.Debug = settings.Debug
plugdirs := pluginDirs(pcmd.home)
debug("loading installed plugins from %s", plugdirs)
plugins, err := findPlugins(plugdirs)
debug("loading installed plugins from %s", settings.PluginDirs())
plugins, err := findPlugins(settings.PluginDirs())
if err != nil {
return err
}
......
......@@ -48,10 +48,10 @@ func newReleaseTestCmd(c helm.Interface, out io.Writer) *cobra.Command {
}
cmd := &cobra.Command{
Use: "test [RELEASE]",
Short: "test a release",
Long: releaseTestDesc,
PersistentPreRunE: setupConnection,
Use: "test [RELEASE]",
Short: "test a release",
Long: releaseTestDesc,
PreRunE: setupConnection,
RunE: func(cmd *cobra.Command, args []string) error {
if err := checkArgsLength(len(args), "release name"); err != nil {
return err
......
......@@ -57,7 +57,7 @@ func newResetCmd(client helm.Interface, out io.Writer) *cobra.Command {
Use: "reset",
Short: "uninstalls Tiller from a cluster",
Long: resetDesc,
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
PreRunE: func(cmd *cobra.Command, args []string) error {
if err := setupConnection(cmd, args); !d.force && err != nil {
return err
}
......
......@@ -30,7 +30,7 @@ const rollbackDesc = `
This command rolls back a release to a previous revision.
The first argument of the rollback command is the name of a release, and the
second is a revision (version) number. To see revision numbers, run
second is a revision (version) number. To see revision numbers, run
'helm history RELEASE'.
`
......@@ -54,10 +54,10 @@ func newRollbackCmd(c helm.Interface, out io.Writer) *cobra.Command {
}
cmd := &cobra.Command{
Use: "rollback [flags] [RELEASE] [REVISION]",
Short: "roll back a release to a previous revision",
Long: rollbackDesc,
PersistentPreRunE: setupConnection,
Use: "rollback [flags] [RELEASE] [REVISION]",
Short: "roll back a release to a previous revision",
Long: rollbackDesc,
PreRunE: setupConnection,
RunE: func(cmd *cobra.Command, args []string) error {
if err := checkArgsLength(len(args), "release name", "revision number"); err != nil {
return err
......
......@@ -57,10 +57,10 @@ func newStatusCmd(client helm.Interface, out io.Writer) *cobra.Command {
}
cmd := &cobra.Command{
Use: "status [flags] RELEASE_NAME",
Short: "displays the status of the named release",
Long: statusHelp,
PersistentPreRunE: setupConnection,
Use: "status [flags] RELEASE_NAME",
Short: "displays the status of the named release",
Long: statusHelp,
PreRunE: setupConnection,
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
return errReleaseRequired
......
......@@ -91,10 +91,10 @@ func newUpgradeCmd(client helm.Interface, out io.Writer) *cobra.Command {
}
cmd := &cobra.Command{
Use: "upgrade [RELEASE] [CHART]",
Short: "upgrade a release",
Long: upgradeDesc,
PersistentPreRunE: setupConnection,
Use: "upgrade [RELEASE] [CHART]",
Short: "upgrade a release",
Long: upgradeDesc,
PreRunE: setupConnection,
RunE: func(cmd *cobra.Command, args []string) error {
if err := checkArgsLength(len(args), "release name", "chart path"); err != nil {
return err
......
......@@ -29,7 +29,7 @@ import (
// collectPlugins scans for getter plugins.
// This will load plugins according to the environment.
func collectPlugins(settings environment.EnvSettings) (Providers, error) {
plugins, err := plugin.FindPlugins(settings.PlugDirs)
plugins, err := plugin.FindPlugins(settings.PluginDirs())
if err != nil {
return nil, err
}
......
......@@ -32,9 +32,8 @@ func hh(debug bool) environment.EnvSettings {
}
hp := helmpath.Home(apath)
return environment.EnvSettings{
Home: hp,
PlugDirs: hp.Plugins(),
Debug: debug,
Home: hp,
Debug: debug,
}
}
......
......@@ -38,20 +38,12 @@ const (
PluginDisableEnvVar = "HELM_NO_PLUGINS"
// HostEnvVar is the HELM_HOST environment variable key.
HostEnvVar = "HELM_HOST"
// DebugEnvVar is the HELM_DEBUG environment variable key.
DebugEnvVar = "HELM_DEBUG"
)
// DefaultHelmHome gets the configured HELM_HOME, or returns the default.
func DefaultHelmHome() string {
if home := os.Getenv(HomeEnvVar); home != "" {
return home
}
return filepath.Join(os.Getenv("HOME"), ".helm")
}
// DefaultHelmHost returns the configured HELM_HOST or an empty string.
func DefaultHelmHost() string {
return os.Getenv(HostEnvVar)
}
// DefaultHelmHome is the default HELM_HOME.
var DefaultHelmHome = filepath.Join("$HOME", ".helm")
// EnvSettings describes all of the environment settings.
type EnvSettings struct {
......@@ -61,8 +53,14 @@ type EnvSettings struct {
TillerNamespace string
// Home is the local path to the Helm home directory.
Home helmpath.Home
// PluginDirs is the path to the plugin directories.
PlugDirs string
// Debug indicates whether or not Helm is running in Debug mode.
Debug bool
}
// PluginDirs is the path to the plugin directories.
func (s EnvSettings) PluginDirs() string {
if d := os.Getenv(PluginEnvVar); d != "" {
return d
}
return s.Home.Plugins()
}
......@@ -17,6 +17,7 @@ package helmpath
import (
"fmt"
"os"
"path/filepath"
)
......@@ -29,12 +30,12 @@ type Home string
//
// Implements fmt.Stringer.
func (h Home) String() string {
return string(h)
return os.ExpandEnv(string(h))
}
// Path returns Home with elements appended.
func (h Home) Path(elem ...string) string {
p := []string{string(h)}
p := []string{h.String()}
p = append(p, elem...)
return filepath.Join(p...)
}
......
......@@ -38,3 +38,9 @@ func TestHelmHome(t *testing.T) {
isEq(t, hh.CacheIndex("t"), "/r/repository/cache/t-index.yaml")
isEq(t, hh.Starters(), "/r/starters")
}
func TestHelmHome_expand(t *testing.T) {
if Home("$HOME").String() == "$HOME" {
t.Error("expected variable expansion")
}
}
......@@ -179,7 +179,7 @@ func SetupPluginEnv(settings helm_env.EnvSettings,
// Set vars that may not have been set, and save client the
// trouble of re-parsing.
helm_env.PluginEnvVar: settings.PlugDirs,
helm_env.PluginEnvVar: settings.PluginDirs(),
helm_env.HomeEnvVar: settings.Home.String(),
// Set vars that convey common information.
......
......@@ -24,7 +24,6 @@ package environment
import (
"io"
"os"
"time"
"k8s.io/helm/pkg/chartutil"
......@@ -44,14 +43,6 @@ const TillerNamespaceEnvVar = "TILLER_NAMESPACE"
// DefaultTillerNamespace is the default namespace for tiller.
const DefaultTillerNamespace = "kube-system"
// GetTillerNamespace returns the right tiller namespace.
func GetTillerNamespace() string {
if ns := os.Getenv(TillerNamespaceEnvVar); ns != "" {
return ns
}
return DefaultTillerNamespace
}
// GoTplEngine is the name of the Go template engine, as registered in the EngineYard.
const GoTplEngine = "gotpl"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment