diff --git a/cmd/helm/flags.go b/cmd/helm/flags.go index 246cb0dd5..5dca0bf71 100644 --- a/cmd/helm/flags.go +++ b/cmd/helm/flags.go @@ -25,6 +25,7 @@ import ( "helm.sh/helm/v3/internal/completion" "helm.sh/helm/v3/pkg/action" + "helm.sh/helm/v3/pkg/cli/files" "helm.sh/helm/v3/pkg/cli/output" "helm.sh/helm/v3/pkg/cli/values" "helm.sh/helm/v3/pkg/postrender" @@ -52,6 +53,11 @@ func addChartPathOptionsFlags(f *pflag.FlagSet, c *action.ChartPathOptions) { f.StringVar(&c.CaFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle") } +func addExternalFilesFlags(f *pflag.FlagSet, v *files.ExternalFiles) { + f.StringArrayVar(&v.Files, "include-file", []string{}, "paths to local external files to use during chart installation") + f.StringArrayVar(&v.Globs, "include-dir", []string{}, "globs to local external files to use during chart installation") +} + // bindOutputFlag will add the output flag to the given command and bind the // value to the given format pointer func bindOutputFlag(cmd *cobra.Command, varRef *output.Format) { diff --git a/cmd/helm/install.go b/cmd/helm/install.go index 44f7336c0..d5d58a5ac 100644 --- a/cmd/helm/install.go +++ b/cmd/helm/install.go @@ -19,6 +19,7 @@ package main import ( "fmt" "io" + "strings" "time" "github.com/pkg/errors" @@ -30,6 +31,7 @@ import ( "helm.sh/helm/v3/pkg/action" "helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/chart/loader" + "helm.sh/helm/v3/pkg/cli/files" "helm.sh/helm/v3/pkg/cli/output" "helm.sh/helm/v3/pkg/cli/values" "helm.sh/helm/v3/pkg/downloader" @@ -154,6 +156,7 @@ func addInstallFlags(f *pflag.FlagSet, client *action.Install, valueOpts *values f.BoolVar(&client.SubNotes, "render-subchart-notes", false, "if set, render subchart notes along with the parent") addValueOptionsFlags(f, valueOpts) addChartPathOptionsFlags(f, &client.ChartPathOptions) + addExternalFilesFlags(f, &client.ExternalFiles) } func runInstall(args []string, client *action.Install, valueOpts *values.Options, out io.Writer) (*release.Release, error) { @@ -226,6 +229,11 @@ func runInstall(args []string, client *action.Install, valueOpts *values.Options } } + err = loadExternalFiles(chartRequested, client.ExternalFiles) + if err != nil { + fmt.Fprintln(out, err) + } + client.Namespace = settings.Namespace() return client.Run(chartRequested, vals) } @@ -241,6 +249,30 @@ func isChartInstallable(ch *chart.Chart) (bool, error) { return false, errors.Errorf("%s charts are not installable", ch.Metadata.Type) } +func loadExternalFiles(ch *chart.Chart, exFiles files.ExternalFiles) error { + var errs []string + fs := make(map[string]string) + for _, s := range exFiles.Files { + if err := files.ParseIntoString(s, fs); err != nil { + debug("error parsing file option", s, err) + errs = append(errs, s) + } + } + + for name, path := range fs { + byt, err := loader.LoadLocalFile(path) + if err != nil { + errs = append(errs, path) + } + ch.Files = append(ch.Files, &chart.File{Name: name, Data: byt}) + } + + if len(errs) > 0 { + return errors.New(fmt.Sprint("Failed to load external files:", strings.Join(errs, ";"))) + } + return nil +} + // Provide dynamic auto-completion for the install and template commands func compInstall(args []string, toComplete string, client *action.Install) ([]string, completion.BashCompDirective) { requiredArgs := 1 diff --git a/cmd/helm/upgrade.go b/cmd/helm/upgrade.go index c263d32e7..cebc2f450 100644 --- a/cmd/helm/upgrade.go +++ b/cmd/helm/upgrade.go @@ -189,6 +189,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { addValueOptionsFlags(f, valueOpts) bindOutputFlag(cmd, &outfmt) bindPostRenderFlag(cmd, &client.PostRenderer) + addExternalFilesFlags(f, &client.ExternalFiles) return cmd } diff --git a/pkg/action/install.go b/pkg/action/install.go index 351e0928c..2a3563f90 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -38,6 +38,7 @@ import ( "helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/chartutil" "helm.sh/helm/v3/pkg/cli" + "helm.sh/helm/v3/pkg/cli/files" "helm.sh/helm/v3/pkg/downloader" "helm.sh/helm/v3/pkg/getter" "helm.sh/helm/v3/pkg/kube" @@ -100,6 +101,7 @@ type Install struct { // OutputDir/ UseReleaseName bool PostRenderer postrender.PostRenderer + ExternalFiles files.ExternalFiles } // ChartPathOptions captures common options used for controlling chart paths diff --git a/pkg/action/upgrade.go b/pkg/action/upgrade.go index fc289dbab..a78de7e67 100644 --- a/pkg/action/upgrade.go +++ b/pkg/action/upgrade.go @@ -29,6 +29,7 @@ import ( "helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/chartutil" + "helm.sh/helm/v3/pkg/cli/files" "helm.sh/helm/v3/pkg/kube" "helm.sh/helm/v3/pkg/postrender" "helm.sh/helm/v3/pkg/release" @@ -96,6 +97,7 @@ type Upgrade struct { PostRenderer postrender.PostRenderer // DisableOpenAPIValidation controls whether OpenAPI validation is enforced. DisableOpenAPIValidation bool + ExternalFiles files.ExternalFiles } // NewUpgrade creates a new Upgrade object with the given configuration. diff --git a/pkg/chart/loader/local.go b/pkg/chart/loader/local.go new file mode 100644 index 000000000..6666a96ef --- /dev/null +++ b/pkg/chart/loader/local.go @@ -0,0 +1,19 @@ +package loader + +import ( + "io/ioutil" + "os" + + "github.com/pkg/errors" +) + +// LoadLocalFile loads a file from the local filesystem. +func LoadLocalFile(path string) ([]byte, error) { + if fi, err := os.Stat(path); err != nil { + return nil, err + } else if fi.IsDir() { + return nil, errors.New("cannot load a directory") + } + + return ioutil.ReadFile(path) +} diff --git a/pkg/cli/files/files.go b/pkg/cli/files/files.go new file mode 100644 index 000000000..0015ee3e5 --- /dev/null +++ b/pkg/cli/files/files.go @@ -0,0 +1,7 @@ +package files + +// ExternalFiles holds the list of external files or globs +type ExternalFiles struct { + Files []string + Globs []string +} diff --git a/pkg/cli/files/parser.go b/pkg/cli/files/parser.go new file mode 100644 index 000000000..6fda1064a --- /dev/null +++ b/pkg/cli/files/parser.go @@ -0,0 +1,24 @@ +package files + +import ( + "errors" + "strings" +) + +// ParseIntoString parses a include-file line and merges the result into dest. +func ParseIntoString(s string, dest map[string]string) error { + for _, val := range strings.Split(s, ",") { + val = strings.TrimSpace(val) + splt := strings.SplitN(val, "=", 2) + + if len(splt) != 2 { + return errors.New("Could not parse line") + } + + name := strings.TrimSpace(splt[0]) + path := strings.TrimSpace(splt[1]) + dest[name] = path + } + + return nil +}