From a930eb7ff4dcba13831174496dda2445e81ebeaa Mon Sep 17 00:00:00 2001
From: Arash Deshmeh <adeshmeh@ca.ibm.com>
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 <adeshmeh@ca.ibm.com>
---
 cmd/helm/package.go       |  18 +++++
 cmd/helm/package_test.go  | 152 ++++++++++++++++++++++++++++++++++++++
 docs/helm/helm_package.md |   4 +-
 3 files changed, 173 insertions(+), 1 deletion(-)

diff --git a/cmd/helm/package.go b/cmd/helm/package.go
index ed44382c7..4b2e6b398 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 4404586e0..4a2df3f54 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.
@@ -245,6 +253,150 @@ 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"},
+		},
+	}
+
+	thome, err := tempHelmHome(t)
+	if err != nil {
+		t.Fatal(err)
+	}
+	cleanup := resetEnv()
+	defer func() {
+		os.RemoveAll(thome.String())
+		cleanup()
+	}()
+
+	settings.Home = thome
+
+	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.RemoveAll(filepath.Dir(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 {
diff --git a/docs/helm/helm_package.md b/docs/helm/helm_package.md
index c51aa7032..daceef32c 100644
--- a/docs/helm/helm_package.md
+++ b/docs/helm/helm_package.md
@@ -29,7 +29,9 @@ helm package [flags] [CHART_PATH] [...]
       --key string           name of the key to use when signing. Used if --sign is true
       --keyring string       location of a public keyring (default "~/.gnupg/pubring.gpg")
       --save                 save packaged chart to local chart repository (default true)
+      --set stringArray      set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)
       --sign                 use a PGP private key to sign this package
+  -f, --values valueFiles    specify values in a YAML file or a URL(can specify multiple) (default [])
       --version string       set the version on the chart to this semver version
 ```
 
@@ -47,4 +49,4 @@ helm package [flags] [CHART_PATH] [...]
 ### SEE ALSO
 * [helm](helm.md)	 - The Helm package manager for Kubernetes.
 
-###### Auto generated by spf13/cobra on 8-Mar-2018
+###### Auto generated by spf13/cobra on 14-Mar-2018
-- 
GitLab