diff --git a/cmd/helm/fetch.go b/cmd/helm/fetch.go index e06b1402e..0a4ff2635 100644 --- a/cmd/helm/fetch.go +++ b/cmd/helm/fetch.go @@ -49,22 +49,15 @@ result in an error, and the chart will not be saved locally. ` type fetchOptions struct { - caFile string // --ca-file - certFile string // --cert-file destdir string // --destination devel bool // --devel - keyFile string // --key-file - keyring string // --keyring - password string // --password - repoURL string // --repo untar bool // --untar untardir string // --untardir - username string // --username - verify bool // --verify verifyLater bool // --prov - version string // --version chartRef string + + chartPathOptions } func newFetchCmd(out io.Writer) *cobra.Command { @@ -94,19 +87,12 @@ func newFetchCmd(out io.Writer) *cobra.Command { f := cmd.Flags() f.BoolVar(&o.devel, "devel", false, "use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored.") f.BoolVar(&o.untar, "untar", false, "if set to true, will untar the chart after downloading it") - f.BoolVar(&o.verify, "verify", false, "verify the package against its signature") f.BoolVar(&o.verifyLater, "prov", false, "fetch the provenance file, but don't perform verification") - f.StringVar(&o.caFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle") - f.StringVar(&o.certFile, "cert-file", "", "identify HTTPS client using this SSL certificate file") - f.StringVar(&o.keyFile, "key-file", "", "identify HTTPS client using this SSL key file") - f.StringVar(&o.keyring, "keyring", defaultKeyring(), "keyring containing public keys") - f.StringVar(&o.password, "password", "", "chart repository password") - f.StringVar(&o.repoURL, "repo", "", "chart repository url where to locate the requested chart") f.StringVar(&o.untardir, "untardir", ".", "if untar is specified, this flag specifies the name of the directory into which the chart is expanded") - f.StringVar(&o.username, "username", "", "chart repository username") - f.StringVar(&o.version, "version", "", "specific version of a chart. Without this, the latest version is fetched") f.StringVarP(&o.destdir, "destination", "d", ".", "location to write the chart. If this and tardir are specified, tardir is appended to this") + o.chartPathOptions.addFlags(f) + return cmd } @@ -175,8 +161,3 @@ func (o *fetchOptions) run(out io.Writer) error { } return nil } - -// defaultKeyring returns the expanded path to the default keyring. -func defaultKeyring() string { - return os.ExpandEnv("$HOME/.gnupg/pubring.gpg") -} diff --git a/cmd/helm/inspect.go b/cmd/helm/inspect.go index 787433f1e..4f8327898 100644 --- a/cmd/helm/inspect.go +++ b/cmd/helm/inspect.go @@ -54,15 +54,8 @@ of the README file type inspectOptions struct { chartpath string output string - verify bool - keyring string - version string - repoURL string - username string - password string - certFile string - keyFile string - caFile string + + chartPathOptions } const ( @@ -83,8 +76,7 @@ func newInspectCmd(out io.Writer) *cobra.Command { Long: inspectDesc, Args: require.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - cp, err := locateChartPath(o.repoURL, o.username, o.password, args[0], o.version, o.verify, o.keyring, - o.certFile, o.keyFile, o.caFile) + cp, err := o.locateChart(args[0]) if err != nil { return err } @@ -100,8 +92,7 @@ func newInspectCmd(out io.Writer) *cobra.Command { Args: require.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { o.output = valuesOnly - cp, err := locateChartPath(o.repoURL, o.username, o.password, args[0], o.version, o.verify, o.keyring, - o.certFile, o.keyFile, o.caFile) + cp, err := o.locateChart(args[0]) if err != nil { return err } @@ -117,8 +108,7 @@ func newInspectCmd(out io.Writer) *cobra.Command { Args: require.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { o.output = chartOnly - cp, err := locateChartPath(o.repoURL, o.username, o.password, args[0], o.version, o.verify, o.keyring, - o.certFile, o.keyFile, o.caFile) + cp, err := o.locateChart(args[0]) if err != nil { return err } @@ -134,8 +124,7 @@ func newInspectCmd(out io.Writer) *cobra.Command { Args: require.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { o.output = readmeOnly - cp, err := locateChartPath(o.repoURL, o.username, o.password, args[0], o.version, o.verify, o.keyring, - o.certFile, o.keyFile, o.caFile) + cp, err := o.locateChart(args[0]) if err != nil { return err } @@ -145,59 +134,8 @@ func newInspectCmd(out io.Writer) *cobra.Command { } cmds := []*cobra.Command{inspectCommand, readmeSubCmd, valuesSubCmd, chartSubCmd} - vflag := "verify" - vdesc := "verify the provenance data for this chart" - for _, subCmd := range cmds { - subCmd.Flags().BoolVar(&o.verify, vflag, false, vdesc) - } - - kflag := "keyring" - kdesc := "path to the keyring containing public verification keys" - kdefault := defaultKeyring() - for _, subCmd := range cmds { - subCmd.Flags().StringVar(&o.keyring, kflag, kdefault, kdesc) - } - - verflag := "version" - verdesc := "version of the chart. By default, the newest chart is shown" - for _, subCmd := range cmds { - subCmd.Flags().StringVar(&o.version, verflag, "", verdesc) - } - - repoURL := "repo" - repoURLdesc := "chart repository url where to locate the requested chart" - for _, subCmd := range cmds { - subCmd.Flags().StringVar(&o.repoURL, repoURL, "", repoURLdesc) - } - - username := "username" - usernamedesc := "chart repository username where to locate the requested chart" - inspectCommand.Flags().StringVar(&o.username, username, "", usernamedesc) - valuesSubCmd.Flags().StringVar(&o.username, username, "", usernamedesc) - chartSubCmd.Flags().StringVar(&o.username, username, "", usernamedesc) - - password := "password" - passworddesc := "chart repository password where to locate the requested chart" - inspectCommand.Flags().StringVar(&o.password, password, "", passworddesc) - valuesSubCmd.Flags().StringVar(&o.password, password, "", passworddesc) - chartSubCmd.Flags().StringVar(&o.password, password, "", passworddesc) - - certFile := "cert-file" - certFiledesc := "verify certificates of HTTPS-enabled servers using this CA bundle" - for _, subCmd := range cmds { - subCmd.Flags().StringVar(&o.certFile, certFile, "", certFiledesc) - } - - keyFile := "key-file" - keyFiledesc := "identify HTTPS client using this SSL key file" - for _, subCmd := range cmds { - subCmd.Flags().StringVar(&o.keyFile, keyFile, "", keyFiledesc) - } - - caFile := "ca-file" - caFiledesc := "chart repository url where to locate the requested chart" for _, subCmd := range cmds { - subCmd.Flags().StringVar(&o.caFile, caFile, "", caFiledesc) + o.chartPathOptions.addFlags(subCmd.Flags()) } for _, subCmd := range cmds[1:] { diff --git a/cmd/helm/install.go b/cmd/helm/install.go index cbf5ff8e5..e7910d57e 100644 --- a/cmd/helm/install.go +++ b/cmd/helm/install.go @@ -20,15 +20,10 @@ import ( "bytes" "fmt" "io" - "io/ioutil" - "net/url" - "os" - "path/filepath" "strings" "text/template" "github.com/Masterminds/sprig" - "github.com/ghodss/yaml" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -39,8 +34,6 @@ import ( "k8s.io/helm/pkg/hapi/chart" "k8s.io/helm/pkg/hapi/release" "k8s.io/helm/pkg/helm" - "k8s.io/helm/pkg/repo" - "k8s.io/helm/pkg/strvals" ) const installDesc = ` @@ -106,49 +99,23 @@ charts in a repository, use 'helm search'. ` type installOptions struct { - name string // --name - valueFiles valueFiles // --values - dryRun bool // --dry-run - disableHooks bool // --disable-hooks - replace bool // --replace - verify bool // --verify - keyring string // --keyring - values []string // --set - stringValues []string // --set-string - nameTemplate string // --name-template - version string // --version - timeout int64 // --timeout - wait bool // --wait - repoURL string // --repo - username string // --username - password string // --password - devel bool // --devel - depUp bool // --dep-up - certFile string // --cert-file - keyFile string // --key-file - caFile string // --ca-file - chartPath string // arg + name string // --name + dryRun bool // --dry-run + disableHooks bool // --disable-hooks + replace bool // --replace + nameTemplate string // --name-template + timeout int64 // --timeout + wait bool // --wait + devel bool // --devel + depUp bool // --dep-up + chartPath string // arg + + valuesOptions + chartPathOptions client helm.Interface } -type valueFiles []string - -func (v *valueFiles) String() string { - return fmt.Sprint(*v) -} - -func (v *valueFiles) Type() string { - return "valueFiles" -} - -func (v *valueFiles) Set(value string) error { - for _, filePath := range strings.Split(value, ",") { - *v = append(*v, filePath) - } - return nil -} - func newInstallCmd(c helm.Interface, out io.Writer) *cobra.Command { o := &installOptions{client: c} @@ -164,8 +131,7 @@ func newInstallCmd(c helm.Interface, out io.Writer) *cobra.Command { o.version = ">0.0.0-0" } - cp, err := locateChartPath(o.repoURL, o.username, o.password, args[0], o.version, o.verify, o.keyring, - o.certFile, o.keyFile, o.caFile) + cp, err := o.locateChart(args[0]) if err != nil { return err } @@ -176,27 +142,17 @@ func newInstallCmd(c helm.Interface, out io.Writer) *cobra.Command { } f := cmd.Flags() - f.VarP(&o.valueFiles, "values", "f", "specify values in a YAML file or a URL(can specify multiple)") f.StringVarP(&o.name, "name", "", "", "release name. If unspecified, it will autogenerate one for you") f.BoolVar(&o.dryRun, "dry-run", false, "simulate an install") f.BoolVar(&o.disableHooks, "no-hooks", false, "prevent hooks from running during install") f.BoolVar(&o.replace, "replace", false, "re-use the given name, even if that name is already used. This is unsafe in production") - f.StringArrayVar(&o.values, "set", []string{}, "set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)") - f.StringArrayVar(&o.stringValues, "set-string", []string{}, "set STRING values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)") f.StringVar(&o.nameTemplate, "name-template", "", "specify template used to name the release") - f.BoolVar(&o.verify, "verify", false, "verify the package before installing it") - f.StringVar(&o.keyring, "keyring", defaultKeyring(), "location of public keys used for verification") - f.StringVar(&o.version, "version", "", "specify the exact chart version to install. If this is not specified, the latest version is installed") f.Int64Var(&o.timeout, "timeout", 300, "time in seconds to wait for any individual Kubernetes operation (like Jobs for hooks)") f.BoolVar(&o.wait, "wait", false, "if set, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment are in a ready state before marking the release as successful. It will wait for as long as --timeout") - f.StringVar(&o.repoURL, "repo", "", "chart repository url where to locate the requested chart") - f.StringVar(&o.username, "username", "", "chart repository username where to locate the requested chart") - f.StringVar(&o.password, "password", "", "chart repository password where to locate the requested chart") - f.StringVar(&o.certFile, "cert-file", "", "identify HTTPS client using this SSL certificate file") - f.StringVar(&o.keyFile, "key-file", "", "identify HTTPS client using this SSL key file") - f.StringVar(&o.caFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle") f.BoolVar(&o.devel, "devel", false, "use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored.") f.BoolVar(&o.depUp, "dep-up", false, "run helm dependency update before installing the chart") + o.valuesOptions.addFlags(f) + o.chartPathOptions.addFlags(f) return cmd } @@ -204,7 +160,7 @@ func newInstallCmd(c helm.Interface, out io.Writer) *cobra.Command { func (o *installOptions) run(out io.Writer) error { debug("CHART PATH: %s\n", o.chartPath) - rawVals, err := vals(o.valueFiles, o.values, o.stringValues) + rawVals, err := o.mergedValues() if err != nil { return err } @@ -235,7 +191,7 @@ func (o *installOptions) run(out io.Writer) error { Out: out, ChartPath: o.chartPath, HelmHome: settings.Home, - Keyring: defaultKeyring(), + Keyring: o.keyring, SkipUpdate: false, Getters: getter.All(settings), } @@ -311,51 +267,6 @@ func mergeValues(dest, src map[string]interface{}) map[string]interface{} { return dest } -// vals merges values from files specified via -f/--values and -// directly via --set or --set-string, marshaling them to YAML -func vals(valueFiles valueFiles, values, stringValues []string) ([]byte, error) { - base := map[string]interface{}{} - - // User specified a values files via -f/--values - for _, filePath := range valueFiles { - currentMap := map[string]interface{}{} - - var bytes []byte - var err error - if strings.TrimSpace(filePath) == "-" { - bytes, err = ioutil.ReadAll(os.Stdin) - } else { - bytes, err = readFile(filePath) - } - - if err != nil { - return []byte{}, err - } - - if err := yaml.Unmarshal(bytes, ¤tMap); err != nil { - return []byte{}, errors.Wrapf(err, "failed to parse %s", filePath) - } - // Merge with the previous map - base = mergeValues(base, currentMap) - } - - // User specified a value via --set - for _, value := range values { - if err := strvals.ParseInto(value, base); err != nil { - return []byte{}, errors.Wrap(err, "failed parsing --set data") - } - } - - // User specified a value via --set-string - for _, value := range stringValues { - if err := strvals.ParseIntoString(value, base); err != nil { - return []byte{}, errors.Wrap(err, "failed parsing --set-string data") - } - } - - return yaml.Marshal(base) -} - // printRelease prints info about a release if the Debug is true. func (o *installOptions) printRelease(out io.Writer, rel *release.Release) { if rel == nil { @@ -368,84 +279,6 @@ func (o *installOptions) printRelease(out io.Writer, rel *release.Release) { } } -// locateChartPath looks for a chart directory in known places, and returns either the full path or an error. -// -// This does not ensure that the chart is well-formed; only that the requested filename exists. -// -// Order of resolution: -// - current working directory -// - if path is absolute or begins with '.', error out here -// - chart repos in $HELM_HOME -// - URL -// -// If 'verify' is true, this will attempt to also verify the chart. -func locateChartPath(repoURL, username, password, name, version string, verify bool, keyring, - certFile, keyFile, caFile string) (string, error) { - name = strings.TrimSpace(name) - version = strings.TrimSpace(version) - if fi, err := os.Stat(name); err == nil { - abs, err := filepath.Abs(name) - if err != nil { - return abs, err - } - if verify { - if fi.IsDir() { - return "", errors.New("cannot verify a directory") - } - if _, err := downloader.VerifyChart(abs, keyring); err != nil { - return "", err - } - } - return abs, nil - } - if filepath.IsAbs(name) || strings.HasPrefix(name, ".") { - return name, errors.Errorf("path %q not found", name) - } - - crepo := filepath.Join(settings.Home.Repository(), name) - if _, err := os.Stat(crepo); err == nil { - return filepath.Abs(crepo) - } - - dl := downloader.ChartDownloader{ - HelmHome: settings.Home, - Out: os.Stdout, - Keyring: keyring, - Getters: getter.All(settings), - Username: username, - Password: password, - } - if verify { - dl.Verify = downloader.VerifyAlways - } - if repoURL != "" { - chartURL, err := repo.FindChartInAuthRepoURL(repoURL, username, password, name, version, - certFile, keyFile, caFile, getter.All(settings)) - if err != nil { - return "", err - } - name = chartURL - } - - if _, err := os.Stat(settings.Home.Archive()); os.IsNotExist(err) { - os.MkdirAll(settings.Home.Archive(), 0744) - } - - filename, _, err := dl.DownloadTo(name, version, settings.Home.Archive()) - if err == nil { - lname, err := filepath.Abs(filename) - if err != nil { - return filename, err - } - debug("Fetched %s to %s\n", name, filename) - return lname, nil - } else if settings.Debug { - return filename, err - } - - return filename, errors.Errorf("failed to download %q (hint: running `helm repo update` may help)", name) -} - func generateName(nameTemplate string) (string, error) { t, err := template.New("name-template").Funcs(sprig.TxtFuncMap()).Parse(nameTemplate) if err != nil { @@ -481,23 +314,3 @@ func checkDependencies(ch *chart.Chart, reqs *chartutil.Requirements) error { } return nil } - -//readFile load a file from the local directory or a remote file with a url. -func readFile(filePath string) ([]byte, error) { - u, _ := url.Parse(filePath) - p := getter.All(settings) - - // FIXME: maybe someone handle other protocols like ftp. - getterConstructor, err := p.ByScheme(u.Scheme) - - if err != nil { - return ioutil.ReadFile(filePath) - } - - getter, err := getterConstructor(filePath, "", "", "") - if err != nil { - return []byte{}, err - } - data, err := getter.Get(filePath) - return data.Bytes(), err -} diff --git a/cmd/helm/lint.go b/cmd/helm/lint.go index 3dd8d23b8..5483e61c0 100644 --- a/cmd/helm/lint.go +++ b/cmd/helm/lint.go @@ -44,11 +44,10 @@ or recommendation, it will emit [WARNING] messages. ` type lintOptions struct { - valueFiles valueFiles - values []string - sValues []string - strict bool - paths []string + strict bool + paths []string + + valuesOptions } func newLintCmd(out io.Writer) *cobra.Command { @@ -66,10 +65,9 @@ func newLintCmd(out io.Writer) *cobra.Command { }, } - cmd.Flags().VarP(&o.valueFiles, "values", "f", "specify values in a YAML file (can specify multiple)") - cmd.Flags().StringArrayVar(&o.values, "set", []string{}, "set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)") - cmd.Flags().StringArrayVar(&o.sValues, "set-string", []string{}, "set STRING values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)") - cmd.Flags().BoolVar(&o.strict, "strict", false, "fail on lint warnings") + fs := cmd.Flags() + fs.BoolVar(&o.strict, "strict", false, "fail on lint warnings") + o.valuesOptions.addFlags(fs) return cmd } @@ -193,7 +191,7 @@ func (o *lintOptions) vals() ([]byte, error) { } // User specified a value via --set-string - for _, value := range o.sValues { + for _, value := range o.stringValues { if err := strvals.ParseIntoString(value, base); err != nil { return []byte{}, errors.Wrap(err, "failed parsing --set-string data") } diff --git a/cmd/helm/list.go b/cmd/helm/list.go index 2a020d0a3..f159d207f 100644 --- a/cmd/helm/list.go +++ b/cmd/helm/list.go @@ -74,7 +74,6 @@ type listOptions struct { sortDesc bool // --reverse superseded bool // --superseded - // args filter string client helm.Interface diff --git a/cmd/helm/options.go b/cmd/helm/options.go new file mode 100644 index 000000000..0a6a9ff8c --- /dev/null +++ b/cmd/helm/options.go @@ -0,0 +1,225 @@ +/* +Copyright 2018 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main // import "k8s.io/helm/cmd/helm" + +import ( + "io/ioutil" + "net/url" + "os" + "path/filepath" + "strings" + + "github.com/ghodss/yaml" + "github.com/pkg/errors" + "github.com/spf13/pflag" + + "k8s.io/helm/pkg/downloader" + "k8s.io/helm/pkg/getter" + "k8s.io/helm/pkg/repo" + "k8s.io/helm/pkg/strvals" +) + +// ----------------------------------------------------------------------------- +// Values Options + +type valuesOptions struct { + valueFiles []string // --values + values []string // --set + stringValues []string // --set-string +} + +func (o *valuesOptions) addFlags(fs *pflag.FlagSet) { + fs.StringSliceVarP(&o.valueFiles, "values", "f", []string{}, "specify values in a YAML file or a URL(can specify multiple)") + fs.StringArrayVar(&o.values, "set", []string{}, "set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)") + fs.StringArrayVar(&o.stringValues, "set-string", []string{}, "set STRING values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)") +} + +// mergeValues merges values from files specified via -f/--values and +// directly via --set or --set-string, marshaling them to YAML +func (o *valuesOptions) mergedValues() ([]byte, error) { + base := map[string]interface{}{} + + // User specified a values files via -f/--values + for _, filePath := range o.valueFiles { + currentMap := map[string]interface{}{} + + bytes, err := readFile(filePath) + if err != nil { + return []byte{}, err + } + + if err := yaml.Unmarshal(bytes, ¤tMap); err != nil { + return []byte{}, errors.Wrapf(err, "failed to parse %s", filePath) + } + // Merge with the previous map + base = mergeValues(base, currentMap) + } + + // User specified a value via --set + for _, value := range o.values { + if err := strvals.ParseInto(value, base); err != nil { + return []byte{}, errors.Wrap(err, "failed parsing --set data") + } + } + + // User specified a value via --set-string + for _, value := range o.stringValues { + if err := strvals.ParseIntoString(value, base); err != nil { + return []byte{}, errors.Wrap(err, "failed parsing --set-string data") + } + } + + return yaml.Marshal(base) +} + +// readFile load a file from stdin, the local directory, or a remote file with a url. +func readFile(filePath string) ([]byte, error) { + if strings.TrimSpace(filePath) == "-" { + return ioutil.ReadAll(os.Stdin) + } + u, _ := url.Parse(filePath) + p := getter.All(settings) + + // FIXME: maybe someone handle other protocols like ftp. + getterConstructor, err := p.ByScheme(u.Scheme) + + if err != nil { + return ioutil.ReadFile(filePath) + } + + getter, err := getterConstructor(filePath, "", "", "") + if err != nil { + return []byte{}, err + } + data, err := getter.Get(filePath) + return data.Bytes(), err +} + +// ----------------------------------------------------------------------------- +// Chart Path Options + +type chartPathOptions struct { + caFile string // --ca-file + certFile string // --cert-file + keyFile string // --key-file + keyring string // --keyring + password string // --password + repoURL string // --repo + username string // --username + verify bool // --verify + version string // --version +} + +// defaultKeyring returns the expanded path to the default keyring. +func defaultKeyring() string { + if v, ok := os.LookupEnv("GNUPGHOME"); ok { + return filepath.Join(v, "pubring.gpg") + } + return os.ExpandEnv("$HOME/.gnupg/pubring.gpg") +} + +func (o *chartPathOptions) addFlags(fs *pflag.FlagSet) { + fs.StringVar(&o.version, "version", "", "specify the exact chart version to install. If this is not specified, the latest version is installed") + fs.BoolVar(&o.verify, "verify", false, "verify the package before installing it") + fs.StringVar(&o.keyring, "keyring", defaultKeyring(), "location of public keys used for verification") + fs.StringVar(&o.repoURL, "repo", "", "chart repository url where to locate the requested chart") + fs.StringVar(&o.username, "username", "", "chart repository username where to locate the requested chart") + fs.StringVar(&o.password, "password", "", "chart repository password where to locate the requested chart") + fs.StringVar(&o.certFile, "cert-file", "", "identify HTTPS client using this SSL certificate file") + fs.StringVar(&o.keyFile, "key-file", "", "identify HTTPS client using this SSL key file") + fs.StringVar(&o.caFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle") +} + +func (o *chartPathOptions) locateChart(name string) (string, error) { + return locateChartPath(o.repoURL, o.username, o.password, name, o.version, o.keyring, o.certFile, o.keyFile, o.caFile, o.verify) +} + +// locateChartPath looks for a chart directory in known places, and returns either the full path or an error. +// +// This does not ensure that the chart is well-formed; only that the requested filename exists. +// +// Order of resolution: +// - relative to current working directory +// - if path is absolute or begins with '.', error out here +// - chart repos in $HELM_HOME +// - URL +// +// If 'verify' is true, this will attempt to also verify the chart. +func locateChartPath(repoURL, username, password, name, version, keyring, + certFile, keyFile, caFile string, verify bool) (string, error) { + name = strings.TrimSpace(name) + version = strings.TrimSpace(version) + + if _, err := os.Stat(name); err == nil { + abs, err := filepath.Abs(name) + if err != nil { + return abs, err + } + if verify { + if _, err := downloader.VerifyChart(abs, keyring); err != nil { + return "", err + } + } + return abs, nil + } + if filepath.IsAbs(name) || strings.HasPrefix(name, ".") { + return name, errors.Errorf("path %q not found", name) + } + + crepo := filepath.Join(settings.Home.Repository(), name) + if _, err := os.Stat(crepo); err == nil { + return filepath.Abs(crepo) + } + + dl := downloader.ChartDownloader{ + HelmHome: settings.Home, + Out: os.Stdout, + Keyring: keyring, + Getters: getter.All(settings), + Username: username, + Password: password, + } + if verify { + dl.Verify = downloader.VerifyAlways + } + if repoURL != "" { + chartURL, err := repo.FindChartInAuthRepoURL(repoURL, username, password, name, version, + certFile, keyFile, caFile, getter.All(settings)) + if err != nil { + return "", err + } + name = chartURL + } + + if _, err := os.Stat(settings.Home.Archive()); os.IsNotExist(err) { + os.MkdirAll(settings.Home.Archive(), 0744) + } + + filename, _, err := dl.DownloadTo(name, version, settings.Home.Archive()) + if err == nil { + lname, err := filepath.Abs(filename) + if err != nil { + return filename, err + } + debug("Fetched %s to %s\n", name, filename) + return lname, nil + } else if settings.Debug { + return filename, err + } + + return filename, errors.Errorf("failed to download %q (hint: running `helm repo update` may help)", name) +} diff --git a/cmd/helm/package.go b/cmd/helm/package.go index 4ce446368..a41ee4f12 100644 --- a/cmd/helm/package.go +++ b/cmd/helm/package.go @@ -50,18 +50,16 @@ Versioned chart archives are used by Helm package repositories. ` type packageOptions struct { - appVersion string // --app-version - dependencyUpdate bool // --dependency-update - destination string // --destination - key string // --key - keyring string // --keyring - sign bool // --sign - stringValues []string // --set-string - valueFiles valueFiles // --values - values []string // --set - version string // --version - - // args + appVersion string // --app-version + dependencyUpdate bool // --dependency-update + destination string // --destination + key string // --key + keyring string // --keyring + sign bool // --sign + version string // --version + + valuesOptions + path string home helmpath.Home @@ -98,9 +96,6 @@ func newPackageCmd(out io.Writer) *cobra.Command { } f := cmd.Flags() - f.VarP(&o.valueFiles, "values", "f", "specify values in a YAML file or a URL(can specify multiple)") - f.StringArrayVar(&o.values, "set", []string{}, "set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)") - f.StringArrayVar(&o.stringValues, "set-string", []string{}, "set STRING values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)") f.BoolVar(&o.sign, "sign", false, "use a PGP private key to sign this package") f.StringVar(&o.key, "key", "", "name of the key to use when signing. Used if --sign is true") f.StringVar(&o.keyring, "keyring", defaultKeyring(), "location of a public keyring") @@ -108,6 +103,7 @@ func newPackageCmd(out io.Writer) *cobra.Command { f.StringVar(&o.appVersion, "app-version", "", "set the appVersion on the chart to this version") f.StringVarP(&o.destination, "destination", "d", ".", "location to write the chart.") f.BoolVarP(&o.dependencyUpdate, "dependency-update", "u", false, `update dependencies from "requirements.yaml" to dir "charts/" before packaging`) + o.valuesOptions.addFlags(f) return cmd } @@ -138,7 +134,7 @@ func (o *packageOptions) run(out io.Writer) error { return err } - overrideVals, err := vals(o.valueFiles, o.values, o.stringValues) + overrideVals, err := o.mergedValues() if err != nil { return err } diff --git a/cmd/helm/template.go b/cmd/helm/template.go index 3674fa841..97a490045 100644 --- a/cmd/helm/template.go +++ b/cmd/helm/template.go @@ -62,16 +62,16 @@ To render just one template in a chart, use '-x': ` type templateOptions struct { - valueFiles valueFiles - chartPath string - values []string - stringValues []string - nameTemplate string - showNotes bool - releaseName string - renderFiles []string - kubeVersion string - outputDir string + nameTemplate string // --name-template + showNotes bool // --notes + releaseName string // --name + renderFiles []string // --execute + kubeVersion string // --kube-version + outputDir string // --output-dir + + valuesOptions + + chartPath string } func newTemplateCmd(out io.Writer) *cobra.Command { @@ -99,12 +99,10 @@ func newTemplateCmd(out io.Writer) *cobra.Command { f.BoolVar(&o.showNotes, "notes", false, "show the computed NOTES.txt file as well") f.StringVarP(&o.releaseName, "name", "", "RELEASE-NAME", "release name") f.StringArrayVarP(&o.renderFiles, "execute", "x", []string{}, "only execute the given templates") - f.VarP(&o.valueFiles, "values", "f", "specify values in a YAML file (can specify multiple)") - f.StringArrayVar(&o.values, "set", []string{}, "set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)") - f.StringArrayVar(&o.stringValues, "set-string", []string{}, "set STRING values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)") f.StringVar(&o.nameTemplate, "name-template", "", "specify template used to name the release") f.StringVar(&o.kubeVersion, "kube-version", defaultKubeVersion, "kubernetes version used as Capabilities.KubeVersion.Major/Minor") f.StringVar(&o.outputDir, "output-dir", "", "writes the executed templates to files in output-dir instead of stdout") + o.valuesOptions.addFlags(f) return cmd } @@ -140,7 +138,7 @@ func (o *templateOptions) run(out io.Writer) error { } // get combined values and create config - config, err := vals(o.valueFiles, o.values, o.stringValues) + config, err := o.mergedValues() if err != nil { return err } diff --git a/cmd/helm/upgrade.go b/cmd/helm/upgrade.go index cdbbc1f4a..83e2bb24d 100644 --- a/cmd/helm/upgrade.go +++ b/cmd/helm/upgrade.go @@ -56,32 +56,24 @@ set for a key called 'foo', the 'newbar' value would take precedence: ` type upgradeOptions struct { - release string - chart string - client helm.Interface - dryRun bool - recreate bool - force bool - disableHooks bool - valueFiles valueFiles - values []string - stringValues []string - verify bool - keyring string - install bool - version string - timeout int64 - resetValues bool - reuseValues bool - wait bool - repoURL string - username string - password string - devel bool - - certFile string - keyFile string - caFile string + devel bool // --devel + disableHooks bool // --disable-hooks + dryRun bool // --dry-run + force bool // --force + install bool // --install + recreate bool // --recreate-pods + resetValues bool // --reset-values + reuseValues bool // --reuse-values + timeout int64 // --timeout + wait bool // --wait + + valuesOptions + chartPathOptions + + release string + chart string + + client helm.Interface } func newUpgradeCmd(client helm.Interface, out io.Writer) *cobra.Command { @@ -107,35 +99,25 @@ func newUpgradeCmd(client helm.Interface, out io.Writer) *cobra.Command { } f := cmd.Flags() - f.VarP(&o.valueFiles, "values", "f", "specify values in a YAML file or a URL(can specify multiple)") f.BoolVar(&o.dryRun, "dry-run", false, "simulate an upgrade") f.BoolVar(&o.recreate, "recreate-pods", false, "performs pods restart for the resource if applicable") f.BoolVar(&o.force, "force", false, "force resource update through delete/recreate if needed") - f.StringArrayVar(&o.values, "set", []string{}, "set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)") - f.StringArrayVar(&o.stringValues, "set-string", []string{}, "set STRING values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)") f.BoolVar(&o.disableHooks, "disable-hooks", false, "disable pre/post upgrade hooks. DEPRECATED. Use no-hooks") f.BoolVar(&o.disableHooks, "no-hooks", false, "disable pre/post upgrade hooks") - f.BoolVar(&o.verify, "verify", false, "verify the provenance of the chart before upgrading") - f.StringVar(&o.keyring, "keyring", defaultKeyring(), "path to the keyring that contains public signing keys") f.BoolVarP(&o.install, "install", "i", false, "if a release by this name doesn't already exist, run an install") - f.StringVar(&o.version, "version", "", "specify the exact chart version to use. If this is not specified, the latest version is used") f.Int64Var(&o.timeout, "timeout", 300, "time in seconds to wait for any individual Kubernetes operation (like Jobs for hooks)") f.BoolVar(&o.resetValues, "reset-values", false, "when upgrading, reset the values to the ones built into the chart") f.BoolVar(&o.reuseValues, "reuse-values", false, "when upgrading, reuse the last release's values and merge in any overrides from the command line via --set and -f. If '--reset-values' is specified, this is ignored.") f.BoolVar(&o.wait, "wait", false, "if set, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment are in a ready state before marking the release as successful. It will wait for as long as --timeout") - f.StringVar(&o.repoURL, "repo", "", "chart repository url where to locate the requested chart") - f.StringVar(&o.username, "username", "", "chart repository username where to locate the requested chart") - f.StringVar(&o.password, "password", "", "chart repository password where to locate the requested chart") - f.StringVar(&o.certFile, "cert-file", "", "identify HTTPS client using this SSL certificate file") - f.StringVar(&o.keyFile, "key-file", "", "identify HTTPS client using this SSL key file") - f.StringVar(&o.caFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle") f.BoolVar(&o.devel, "devel", false, "use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored.") + o.valuesOptions.addFlags(f) + o.chartPathOptions.addFlags(f) return cmd } func (o *upgradeOptions) run(out io.Writer) error { - chartPath, err := locateChartPath(o.repoURL, o.username, o.password, o.chart, o.version, o.verify, o.keyring, o.certFile, o.keyFile, o.caFile) + chartPath, err := o.locateChart(o.chart) if err != nil { return err } @@ -148,24 +130,21 @@ func (o *upgradeOptions) run(out io.Writer) error { if err != nil && strings.Contains(err.Error(), driver.ErrReleaseNotFound(o.release).Error()) { fmt.Fprintf(out, "Release %q does not exist. Installing it now.\n", o.release) io := &installOptions{ - chartPath: chartPath, - client: o.client, - name: o.release, - valueFiles: o.valueFiles, - dryRun: o.dryRun, - verify: o.verify, - disableHooks: o.disableHooks, - keyring: o.keyring, - values: o.values, - stringValues: o.stringValues, - timeout: o.timeout, - wait: o.wait, + chartPath: chartPath, + client: o.client, + name: o.release, + dryRun: o.dryRun, + disableHooks: o.disableHooks, + timeout: o.timeout, + wait: o.wait, + valuesOptions: o.valuesOptions, + chartPathOptions: o.chartPathOptions, } return io.run(out) } } - rawVals, err := vals(o.valueFiles, o.values, o.stringValues) + rawVals, err := o.mergedValues() if err != nil { return err }