feat: (6715) Add --env-values argument to helm command

Signed-off-by: Oscar Mauricio Forero Carrillo <oforero@ieee.org>
pull/6742/head
Oscar Mauricio Forero Carrillo 6 years ago
parent bf8318ea0b
commit 119996b50d

@ -35,6 +35,7 @@ func addValueOptionsFlags(f *pflag.FlagSet, v *values.Options) {
f.StringArrayVar(&v.Values, "set", []string{}, "set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)") f.StringArrayVar(&v.Values, "set", []string{}, "set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)")
f.StringArrayVar(&v.StringValues, "set-string", []string{}, "set STRING values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)") f.StringArrayVar(&v.StringValues, "set-string", []string{}, "set STRING values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)")
f.StringArrayVar(&v.FileValues, "set-file", []string{}, "set values from respective files specified via the command line (can specify multiple or separate values with commas: key1=path1,key2=path2)") f.StringArrayVar(&v.FileValues, "set-file", []string{}, "set values from respective files specified via the command line (can specify multiple or separate values with commas: key1=path1,key2=path2)")
f.StringVarP(&v.EnvValuesFile, "env-values", "e", "", "specify the name of a YAML file inside (the path should be relative to the root of the chart) the chart to override the default values, it won't fail if it does not exist")
} }
func addChartPathOptionsFlags(f *pflag.FlagSet, c *action.ChartPathOptions) { func addChartPathOptionsFlags(f *pflag.FlagSet, c *action.ChartPathOptions) {

@ -171,7 +171,7 @@ func runInstall(args []string, client *action.Install, valueOpts *values.Options
} }
// Check chart dependencies to make sure all are present in /charts // Check chart dependencies to make sure all are present in /charts
chartRequested, err := loader.Load(cp) chartRequested, err := loader.LoadWithEnvValues(cp, valueOpts.EnvValuesFile)
if err != nil { if err != nil {
return nil, err return nil, err
} }

@ -59,6 +59,12 @@ func TestInstall(t *testing.T) {
cmd: "install virgil testdata/testcharts/alpine -f testdata/testcharts/alpine/extra_values.yaml -f testdata/testcharts/alpine/more_values.yaml", cmd: "install virgil testdata/testcharts/alpine -f testdata/testcharts/alpine/extra_values.yaml -f testdata/testcharts/alpine/more_values.yaml",
golden: "output/install-with-multiple-values-files.txt", golden: "output/install-with-multiple-values-files.txt",
}, },
// Install, values environment values yaml
{
name: "install with environment values",
cmd: "install virgil testdata/testcharts/alpine -e more_values.yaml",
golden: "output/install-with-multiple-values-files.txt",
},
// Install, no charts // Install, no charts
{ {
name: "install with no chart specified", name: "install with no chart specified",

@ -117,7 +117,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
} }
// Check chart dependencies to make sure all are present in /charts // Check chart dependencies to make sure all are present in /charts
ch, err := loader.Load(chartPath) ch, err := loader.LoadWithEnvValues(chartPath, valueOpts.EnvValuesFile)
if err != nil { if err != nil {
return err return err
} }

@ -127,3 +127,24 @@ func (ch *Chart) CRDs() []*File {
} }
return files return files
} }
// MergeValues takes two maps from string to anything and returns a new map with the combined key:value pairs
// the values in the second map argument will override those in the first
func MergeValues(a, b map[string]interface{}) map[string]interface{} {
out := make(map[string]interface{}, len(a))
for k, v := range a {
out[k] = v
}
for k, v := range b {
if v, ok := v.(map[string]interface{}); ok {
if bv, ok := out[k]; ok {
if bv, ok := bv.(map[string]interface{}); ok {
out[k] = MergeValues(bv, v)
continue
}
}
}
out[k] = v
}
return out
}

@ -39,12 +39,17 @@ var drivePathPattern = regexp.MustCompile(`^[a-zA-Z]:/`)
type FileLoader string type FileLoader string
// Load loads a chart // Load loads a chart
func (l FileLoader) Load() (*chart.Chart, error) { func (l FileLoader) Load(envYaml string) (*chart.Chart, error) {
return LoadFile(string(l)) return LoadFileWithEnvValues(string(l), envYaml)
} }
// LoadFile loads from an archive file. // LoadFile loads from an archive file.
func LoadFile(name string) (*chart.Chart, error) { func LoadFile(name string) (*chart.Chart, error) {
return LoadFileWithEnvValues(name, "")
}
// LoadFileWithEnvValues loads from an archive file.
func LoadFileWithEnvValues(name string, envYaml string) (*chart.Chart, error) {
if fi, err := os.Stat(name); err != nil { if fi, err := os.Stat(name); err != nil {
return nil, err return nil, err
} else if fi.IsDir() { } else if fi.IsDir() {
@ -62,7 +67,7 @@ func LoadFile(name string) (*chart.Chart, error) {
return nil, err return nil, err
} }
c, err := LoadArchive(raw) c, err := LoadArchiveWithEnvValues(raw, envYaml)
if err != nil { if err != nil {
if err == gzip.ErrHeader { if err == gzip.ErrHeader {
return nil, fmt.Errorf("file '%s' does not appear to be a valid chart file (details: %s)", name, err) return nil, fmt.Errorf("file '%s' does not appear to be a valid chart file (details: %s)", name, err)
@ -179,10 +184,16 @@ func LoadArchiveFiles(in io.Reader) ([]*BufferedFile, error) {
// LoadArchive loads from a reader containing a compressed tar archive. // LoadArchive loads from a reader containing a compressed tar archive.
func LoadArchive(in io.Reader) (*chart.Chart, error) { func LoadArchive(in io.Reader) (*chart.Chart, error) {
return LoadArchiveWithEnvValues(in, "")
}
// LoadArchiveWithEnvValues loads from a reader containing a compressed tar archive,
// overriding the default values with the values from the environment file in the chart.
func LoadArchiveWithEnvValues(in io.Reader, envYaml string) (*chart.Chart, error) {
files, err := LoadArchiveFiles(in) files, err := LoadArchiveFiles(in)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return LoadFiles(files) return LoadFilesWithEnvValues(files, envYaml)
} }

@ -34,14 +34,22 @@ import (
type DirLoader string type DirLoader string
// Load loads the chart // Load loads the chart
func (l DirLoader) Load() (*chart.Chart, error) { func (l DirLoader) Load(envYaml string) (*chart.Chart, error) {
return LoadDir(string(l)) return LoadDirWithEnvValues(string(l), envYaml)
} }
// LoadDir loads from a directory. // LoadDir loads from a directory.
// //
// This loads charts only from directories. // This loads charts only from directories.
func LoadDir(dir string) (*chart.Chart, error) { func LoadDir(dir string) (*chart.Chart, error) {
return LoadDirWithEnvValues(dir, "")
}
// LoadDirWithEnvValues loads from a directory,
// overriding the default values with the values from the environment file in the chart.
//
// This loads charts only from directories.
func LoadDirWithEnvValues(dir string, envYaml string) (*chart.Chart, error) {
topdir, err := filepath.Abs(dir) topdir, err := filepath.Abs(dir)
if err != nil { if err != nil {
return nil, err return nil, err
@ -111,5 +119,5 @@ func LoadDir(dir string) (*chart.Chart, error) {
return c, err return c, err
} }
return LoadFiles(files) return LoadFilesWithEnvValues(files, envYaml)
} }

@ -31,7 +31,7 @@ import (
// ChartLoader loads a chart. // ChartLoader loads a chart.
type ChartLoader interface { type ChartLoader interface {
Load() (*chart.Chart, error) Load(envYaml string) (*chart.Chart, error)
} }
// Loader returns a new ChartLoader appropriate for the given chart name // Loader returns a new ChartLoader appropriate for the given chart name
@ -55,11 +55,22 @@ func Loader(name string) (ChartLoader, error) {
// If a .helmignore file is present, the directory loader will skip loading any files // If a .helmignore file is present, the directory loader will skip loading any files
// matching it. But .helmignore is not evaluated when reading out of an archive. // matching it. But .helmignore is not evaluated when reading out of an archive.
func Load(name string) (*chart.Chart, error) { func Load(name string) (*chart.Chart, error) {
return LoadWithEnvValues(name, "")
}
// LoadWithEnvValues takes a string name, tries to resolve it to a file or directory, and then loads it.
//
// This is the preferred way to load a chart. It will discover the chart encoding
// and hand off to the appropriate chart reader.
//
// If a .helmignore file is present, the directory loader will skip loading any files
// matching it. But .helmignore is not evaluated when reading out of an archive.
func LoadWithEnvValues(name string, envYaml string) (*chart.Chart, error) {
l, err := Loader(name) l, err := Loader(name)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return l.Load() return l.Load(envYaml)
} }
// BufferedFile represents an archive file buffered for later processing. // BufferedFile represents an archive file buffered for later processing.
@ -70,8 +81,15 @@ type BufferedFile struct {
// LoadFiles loads from in-memory files. // LoadFiles loads from in-memory files.
func LoadFiles(files []*BufferedFile) (*chart.Chart, error) { func LoadFiles(files []*BufferedFile) (*chart.Chart, error) {
return LoadFilesWithEnvValues(files, "")
}
// LoadFilesWithEnvValues loads from in-memory files.
func LoadFilesWithEnvValues(files []*BufferedFile, envYaml string) (*chart.Chart, error) {
c := new(chart.Chart) c := new(chart.Chart)
subcharts := make(map[string][]*BufferedFile) subcharts := make(map[string][]*BufferedFile)
values := make(map[string]interface{})
env := make(map[string]interface{})
for _, f := range files { for _, f := range files {
switch { switch {
@ -94,10 +112,13 @@ func LoadFiles(files []*BufferedFile) (*chart.Chart, error) {
return c, errors.Wrap(err, "cannot load Chart.lock") return c, errors.Wrap(err, "cannot load Chart.lock")
} }
case f.Name == "values.yaml": case f.Name == "values.yaml":
c.Values = make(map[string]interface{}) if err := yaml.Unmarshal(f.Data, &values); err != nil {
if err := yaml.Unmarshal(f.Data, &c.Values); err != nil {
return c, errors.Wrap(err, "cannot load values.yaml") return c, errors.Wrap(err, "cannot load values.yaml")
} }
case f.Name == envYaml:
if err := yaml.Unmarshal(f.Data, &env); err != nil {
return c, errors.Wrapf(err, "cannot load %s", envYaml)
}
case f.Name == "values.schema.json": case f.Name == "values.schema.json":
c.Schema = f.Data c.Schema = f.Data
@ -136,6 +157,9 @@ func LoadFiles(files []*BufferedFile) (*chart.Chart, error) {
} }
} }
if len(values) != 0 || len(env) != 0 {
c.Values = chart.MergeValues(values, env)
}
if err := c.Validate(); err != nil { if err := c.Validate(); err != nil {
return c, err return c, err
} }
@ -152,7 +176,7 @@ func LoadFiles(files []*BufferedFile) (*chart.Chart, error) {
return c, errors.Errorf("error unpacking tar in %s: expected %s, got %s", c.Name(), n, file.Name) return c, errors.Errorf("error unpacking tar in %s: expected %s, got %s", c.Name(), n, file.Name)
} }
// Untar the chart and add to c.Dependencies // Untar the chart and add to c.Dependencies
sc, err = LoadArchive(bytes.NewBuffer(file.Data)) sc, err = LoadArchiveWithEnvValues(bytes.NewBuffer(file.Data), envYaml)
default: default:
// We have to trim the prefix off of every file, and ignore any file // We have to trim the prefix off of every file, and ignore any file
// that is in charts/, but isn't actually a chart. // that is in charts/, but isn't actually a chart.
@ -165,7 +189,7 @@ func LoadFiles(files []*BufferedFile) (*chart.Chart, error) {
f.Name = parts[1] f.Name = parts[1]
buff = append(buff, f) buff = append(buff, f)
} }
sc, err = LoadFiles(buff) sc, err = LoadFilesWithEnvValues(buff, envYaml)
} }
if err != nil { if err != nil {

@ -36,7 +36,7 @@ func TestLoadDir(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("Failed to load testdata: %s", err) t.Fatalf("Failed to load testdata: %s", err)
} }
c, err := l.Load() c, err := l.Load("")
if err != nil { if err != nil {
t.Fatalf("Failed to load testdata: %s", err) t.Fatalf("Failed to load testdata: %s", err)
} }
@ -55,7 +55,7 @@ func TestLoadDirWithDevNull(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("Failed to load testdata: %s", err) t.Fatalf("Failed to load testdata: %s", err)
} }
if _, err := l.Load(); err == nil { if _, err := l.Load(""); err == nil {
t.Errorf("packages with an irregular file (/dev/null) should not load") t.Errorf("packages with an irregular file (/dev/null) should not load")
} }
} }
@ -75,7 +75,7 @@ func TestLoadDirWithSymlink(t *testing.T) {
t.Fatalf("Failed to load testdata: %s", err) t.Fatalf("Failed to load testdata: %s", err)
} }
c, err := l.Load() c, err := l.Load("")
if err != nil { if err != nil {
t.Fatalf("Failed to load testdata: %s", err) t.Fatalf("Failed to load testdata: %s", err)
} }
@ -90,7 +90,7 @@ func TestLoadV1(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("Failed to load testdata: %s", err) t.Fatalf("Failed to load testdata: %s", err)
} }
c, err := l.Load() c, err := l.Load("")
if err != nil { if err != nil {
t.Fatalf("Failed to load testdata: %s", err) t.Fatalf("Failed to load testdata: %s", err)
} }
@ -103,7 +103,7 @@ func TestLoadFileV1(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("Failed to load testdata: %s", err) t.Fatalf("Failed to load testdata: %s", err)
} }
c, err := l.Load() c, err := l.Load("")
if err != nil { if err != nil {
t.Fatalf("Failed to load testdata: %s", err) t.Fatalf("Failed to load testdata: %s", err)
} }
@ -116,7 +116,7 @@ func TestLoadFile(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("Failed to load testdata: %s", err) t.Fatalf("Failed to load testdata: %s", err)
} }
c, err := l.Load() c, err := l.Load("")
if err != nil { if err != nil {
t.Fatalf("Failed to load testdata: %s", err) t.Fatalf("Failed to load testdata: %s", err)
} }
@ -212,7 +212,7 @@ func TestLoadV2WithReqs(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("Failed to load testdata: %s", err) t.Fatalf("Failed to load testdata: %s", err)
} }
c, err := l.Load() c, err := l.Load("")
if err != nil { if err != nil {
t.Fatalf("Failed to load testdata: %s", err) t.Fatalf("Failed to load testdata: %s", err)
} }

@ -30,10 +30,11 @@ import (
) )
type Options struct { type Options struct {
ValueFiles []string ValueFiles []string
StringValues []string StringValues []string
Values []string Values []string
FileValues []string FileValues []string
EnvValuesFile string
} }
// MergeValues merges values from files specified via -f/--values and directly // MergeValues merges values from files specified via -f/--values and directly

@ -31,6 +31,6 @@ func All(basedir string, values map[string]interface{}, namespace string, strict
linter := support.Linter{ChartDir: chartDir} linter := support.Linter{ChartDir: chartDir}
rules.Chartfile(&linter) rules.Chartfile(&linter)
rules.Values(&linter) rules.Values(&linter)
rules.Templates(&linter, values, namespace, strict) rules.Templates(&linter, values, namespace, strict, "")
return linter return linter
} }

@ -36,7 +36,7 @@ var (
) )
// Templates lints the templates in the Linter. // Templates lints the templates in the Linter.
func Templates(linter *support.Linter, values map[string]interface{}, namespace string, strict bool) { func Templates(linter *support.Linter, values map[string]interface{}, namespace string, strict bool, envYaml string) {
path := "templates/" path := "templates/"
templatesPath := filepath.Join(linter.ChartDir, path) templatesPath := filepath.Join(linter.ChartDir, path)
@ -48,7 +48,7 @@ func Templates(linter *support.Linter, values map[string]interface{}, namespace
} }
// Load chart and parse templates, based on tiller/release_server // Load chart and parse templates, based on tiller/release_server
chart, err := loader.Load(linter.ChartDir) chart, err := loader.LoadWithEnvValues(linter.ChartDir, envYaml)
chartLoaded := linter.RunLinterRule(support.ErrorSev, path, err) chartLoaded := linter.RunLinterRule(support.ErrorSev, path, err)

@ -51,7 +51,7 @@ const strict = false
func TestTemplateParsing(t *testing.T) { func TestTemplateParsing(t *testing.T) {
linter := support.Linter{ChartDir: templateTestBasedir} linter := support.Linter{ChartDir: templateTestBasedir}
Templates(&linter, values, namespace, strict) Templates(&linter, values, namespace, strict, "")
res := linter.Messages res := linter.Messages
if len(res) != 1 { if len(res) != 1 {
@ -74,7 +74,7 @@ func TestTemplateIntegrationHappyPath(t *testing.T) {
defer os.Rename(ignoredTemplatePath, wrongTemplatePath) defer os.Rename(ignoredTemplatePath, wrongTemplatePath)
linter := support.Linter{ChartDir: templateTestBasedir} linter := support.Linter{ChartDir: templateTestBasedir}
Templates(&linter, values, namespace, strict) Templates(&linter, values, namespace, strict, "")
res := linter.Messages res := linter.Messages
if len(res) != 0 { if len(res) != 0 {
@ -84,7 +84,7 @@ func TestTemplateIntegrationHappyPath(t *testing.T) {
func TestV3Fail(t *testing.T) { func TestV3Fail(t *testing.T) {
linter := support.Linter{ChartDir: "./testdata/v3-fail"} linter := support.Linter{ChartDir: "./testdata/v3-fail"}
Templates(&linter, values, namespace, strict) Templates(&linter, values, namespace, strict, "")
res := linter.Messages res := linter.Messages
if len(res) != 3 { if len(res) != 3 {

Loading…
Cancel
Save