From 2007782aefe7f808241fe7a4423c46a89ef89fd1 Mon Sep 17 00:00:00 2001 From: Arash Deshmeh Date: Sat, 6 Jan 2018 12:19:29 -0500 Subject: [PATCH] feat: add --set and --values options to 'helm package' When 'helm package --set stringsArray' is run, this will set/override values in the packaged chart. 'helm package --values valueFiles' uses one or more value files to achieve the same. Closes #3141 Signed-off-by: Arash Deshmeh --- cmd/helm/package.go | 18 +++++ cmd/helm/package_test.go | 140 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 158 insertions(+) diff --git a/cmd/helm/package.go b/cmd/helm/package.go index ed44382c792..4b2e6b398be 100644 --- a/cmd/helm/package.go +++ b/cmd/helm/package.go @@ -53,6 +53,8 @@ type packageCmd struct { save bool sign bool path string + valueFiles valueFiles + values []string key string keyring string version string @@ -95,6 +97,8 @@ func newPackageCmd(out io.Writer) *cobra.Command { } f := cmd.Flags() + f.VarP(&pkg.valueFiles, "values", "f", "specify values in a YAML file or a URL(can specify multiple)") + f.StringArrayVar(&pkg.values, "set", []string{}, "set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)") f.BoolVar(&pkg.save, "save", true, "save packaged chart to local chart repository") f.BoolVar(&pkg.sign, "sign", false, "use a PGP private key to sign this package") f.StringVar(&pkg.key, "key", "", "name of the key to use when signing. Used if --sign is true") @@ -133,6 +137,20 @@ func (p *packageCmd) run() error { return err } + overrideVals, err := vals(p.valueFiles, p.values) + if err != nil { + return err + } + combinedVals, err := chartutil.CoalesceValues(ch, &chart.Config{Raw: string(overrideVals)}) + if err != nil { + return err + } + newVals, err := combinedVals.YAML() + if err != nil { + return err + } + ch.Values = &chart.Config{Raw: newVals} + // If version is set, modify the version. if len(p.version) != 0 { if err := setVersion(ch, p.version); err != nil { diff --git a/cmd/helm/package_test.go b/cmd/helm/package_test.go index d338c29cd7f..5acdee9120b 100644 --- a/cmd/helm/package_test.go +++ b/cmd/helm/package_test.go @@ -21,6 +21,7 @@ import ( "os" "path/filepath" "regexp" + "strings" "testing" "github.com/spf13/cobra" @@ -122,6 +123,13 @@ func TestPackage(t *testing.T) { hasfile: "chart-missing-deps-0.1.0.tgz", err: true, }, + { + name: "package --values does-not-exist", + args: []string{"testdata/testcharts/alpine"}, + flags: map[string]string{"values": "does-not-exist"}, + expect: "does-not-exist: no such file or directory", + err: true, + }, } // Because these tests are destructive, we run them in a tempdir. @@ -233,6 +241,138 @@ func TestSetAppVersion(t *testing.T) { } } +func TestPackageValues(t *testing.T) { + testCases := []struct { + desc string + args []string + valuefilesContents []string + flags map[string]string + expected []string + }{ + { + desc: "helm package, single values file", + args: []string{"testdata/testcharts/alpine"}, + valuefilesContents: []string{"Name: chart-name-foo"}, + expected: []string{"Name: chart-name-foo"}, + }, + { + desc: "helm package, multiple values files", + args: []string{"testdata/testcharts/alpine"}, + valuefilesContents: []string{"Name: chart-name-foo", "foo: bar"}, + expected: []string{"Name: chart-name-foo", "foo: bar"}, + }, + { + desc: "helm package, with set option", + args: []string{"testdata/testcharts/alpine"}, + flags: map[string]string{"set": "Name=chart-name-foo"}, + expected: []string{"Name: chart-name-foo"}, + }, + { + desc: "helm package, set takes precedence over value file", + args: []string{"testdata/testcharts/alpine"}, + valuefilesContents: []string{"Name: chart-name-foo"}, + flags: map[string]string{"set": "Name=chart-name-bar"}, + expected: []string{"Name: chart-name-bar"}, + }, + } + + for _, tc := range testCases { + var files []string + for _, contents := range tc.valuefilesContents { + f, err := createValuesFile(contents) + if err != nil { + t.Errorf("%q unexpected error creating temporary values file: %q", tc.desc, err) + } + defer os.Remove(f) + files = append(files, f) + } + valueFiles := strings.Join(files, ",") + + expected, err := chartutil.ReadValues([]byte(strings.Join(tc.expected, "\n"))) + if err != nil { + t.Errorf("unexpected error parsing values: %q", err) + } + + runAndVerifyPackageCommandValues(t, tc.args, tc.flags, valueFiles, expected) + } +} + +func runAndVerifyPackageCommandValues(t *testing.T, args []string, flags map[string]string, valueFiles string, expected chartutil.Values) { + outputDir, err := ioutil.TempDir("", "helm-package") + if err != nil { + t.Errorf("unexpected error creating temporary output directory: %q", err) + } + defer os.RemoveAll(outputDir) + + if len(flags) == 0 { + flags = make(map[string]string) + } + flags["destination"] = outputDir + + if len(valueFiles) > 0 { + flags["values"] = valueFiles + } + + cmd := newPackageCmd(&bytes.Buffer{}) + setFlags(cmd, flags) + err = cmd.RunE(cmd, args) + if err != nil { + t.Errorf("unexpected error: %q", err) + } + + outputFile := filepath.Join(outputDir, "alpine-0.1.0.tgz") + verifyOutputChartExists(t, outputFile) + + var actual chartutil.Values + actual, err = getChartValues(outputFile) + if err != nil { + t.Errorf("unexpected error extracting chart values: %q", err) + } + + verifyValues(t, actual, expected) +} + +func createValuesFile(data string) (string, error) { + outputDir, err := ioutil.TempDir("", "values-file") + if err != nil { + return "", err + } + + outputFile := filepath.Join(outputDir, "values.yaml") + if err = ioutil.WriteFile(outputFile, []byte(data), 0755); err != nil { + os.RemoveAll(outputFile) + return "", err + } + + return outputFile, nil +} + +func getChartValues(chartPath string) (chartutil.Values, error) { + + chart, err := chartutil.Load(chartPath) + if err != nil { + return nil, err + } + + return chartutil.ReadValues([]byte(chart.Values.Raw)) +} + +func verifyValues(t *testing.T, actual, expected chartutil.Values) { + for key, value := range expected.AsMap() { + if got := actual[key]; got != value { + t.Errorf("Expected %q, got %q (%v)", value, got, actual) + } + } +} + +func verifyOutputChartExists(t *testing.T, chartPath string) { + if chartFile, err := os.Stat(chartPath); err != nil { + t.Errorf("expected file %q, got err %q", chartPath, err) + } else if chartFile.Size() == 0 { + t.Errorf("file %q has zero bytes.", chartPath) + } +} + func setFlags(cmd *cobra.Command, flags map[string]string) { dest := cmd.Flags() for f, v := range flags {