Merge pull request #6142 from joelanford/decouple-cli-vals

cmd/*,pkg/*: move ValueOptions and decouple from SDK
pull/6168/head
Adam Reese 5 years ago committed by GitHub
commit 2aee21730d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -28,6 +28,7 @@ import (
"helm.sh/helm/pkg/action"
"helm.sh/helm/pkg/chart"
"helm.sh/helm/pkg/chart/loader"
"helm.sh/helm/pkg/cli/values"
"helm.sh/helm/pkg/downloader"
"helm.sh/helm/pkg/getter"
"helm.sh/helm/pkg/release"
@ -97,6 +98,7 @@ charts in a repository, use 'helm search'.
func newInstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
client := action.NewInstall(cfg)
valueOpts := &values.Options{}
cmd := &cobra.Command{
Use: "install [NAME] [CHART]",
@ -104,7 +106,7 @@ func newInstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
Long: installDesc,
Args: require.MinimumNArgs(1),
RunE: func(_ *cobra.Command, args []string) error {
rel, err := runInstall(args, client, out)
rel, err := runInstall(args, client, valueOpts, out)
if err != nil {
return err
}
@ -113,12 +115,12 @@ func newInstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
},
}
addInstallFlags(cmd.Flags(), client)
addInstallFlags(cmd.Flags(), client, valueOpts)
return cmd
}
func addInstallFlags(f *pflag.FlagSet, client *action.Install) {
func addInstallFlags(f *pflag.FlagSet, client *action.Install, valueOpts *values.Options) {
f.BoolVar(&client.DryRun, "dry-run", false, "simulate an install")
f.BoolVar(&client.DisableHooks, "no-hooks", false, "prevent hooks from running during install")
f.BoolVar(&client.Replace, "replace", false, "re-use the given name, even if that name is already used. This is unsafe in production")
@ -129,11 +131,11 @@ func addInstallFlags(f *pflag.FlagSet, client *action.Install) {
f.BoolVar(&client.Devel, "devel", false, "use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored.")
f.BoolVar(&client.DependencyUpdate, "dependency-update", false, "run helm dependency update before installing the chart")
f.BoolVar(&client.Atomic, "atomic", false, "if set, installation process purges chart on fail. The --wait flag will be set automatically if --atomic is used")
addValueOptionsFlags(f, &client.ValueOptions)
addValueOptionsFlags(f, valueOpts)
addChartPathOptionsFlags(f, &client.ChartPathOptions)
}
func addValueOptionsFlags(f *pflag.FlagSet, v *action.ValueOptions) {
func addValueOptionsFlags(f *pflag.FlagSet, v *values.Options) {
f.StringSliceVarP(&v.ValueFiles, "values", "f", []string{}, "specify values in a YAML file or a URL(can specify multiple)")
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)")
@ -151,7 +153,7 @@ 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 runInstall(args []string, client *action.Install, out io.Writer) (*release.Release, error) {
func runInstall(args []string, client *action.Install, valueOpts *values.Options, out io.Writer) (*release.Release, error) {
debug("Original chart version: %q", client.Version)
if client.Version == "" && client.Devel {
debug("setting version to >0.0.0-0")
@ -171,7 +173,8 @@ func runInstall(args []string, client *action.Install, out io.Writer) (*release.
debug("CHART PATH: %s\n", cp)
if err := client.ValueOptions.MergeValues(settings); err != nil {
vals, err := valueOpts.MergeValues(settings)
if err != nil {
return nil, err
}
@ -209,7 +212,7 @@ func runInstall(args []string, client *action.Install, out io.Writer) (*release.
}
client.Namespace = getNamespace()
return client.Run(chartRequested)
return client.Run(chartRequested, vals)
}
// isChartInstallable validates if a chart can be installed

@ -25,6 +25,7 @@ import (
"github.com/spf13/cobra"
"helm.sh/helm/pkg/action"
"helm.sh/helm/pkg/cli/values"
)
var longLintHelp = `
@ -38,6 +39,7 @@ or recommendation, it will emit [WARNING] messages.
func newLintCmd(out io.Writer) *cobra.Command {
client := action.NewLint()
valueOpts := &values.Options{}
cmd := &cobra.Command{
Use: "lint PATH",
@ -49,10 +51,11 @@ func newLintCmd(out io.Writer) *cobra.Command {
paths = args
}
client.Namespace = getNamespace()
if err := client.ValueOptions.MergeValues(settings); err != nil {
vals, err := valueOpts.MergeValues(settings)
if err != nil {
return err
}
result := client.Run(paths)
result := client.Run(paths, vals)
var message strings.Builder
fmt.Fprintf(&message, "%d chart(s) linted, %d chart(s) failed\n", result.TotalChartsLinted, len(result.Errors))
for _, err := range result.Errors {
@ -72,7 +75,7 @@ func newLintCmd(out io.Writer) *cobra.Command {
f := cmd.Flags()
f.BoolVar(&client.Strict, "strict", false, "fail on lint warnings")
addValueOptionsFlags(f, &client.ValueOptions)
addValueOptionsFlags(f, valueOpts)
return cmd
}

@ -26,6 +26,7 @@ import (
"github.com/spf13/cobra"
"helm.sh/helm/pkg/action"
"helm.sh/helm/pkg/cli/values"
"helm.sh/helm/pkg/downloader"
"helm.sh/helm/pkg/getter"
)
@ -43,6 +44,7 @@ Versioned chart archives are used by Helm package repositories.
func newPackageCmd(out io.Writer) *cobra.Command {
client := action.NewPackage()
valueOpts := &values.Options{}
cmd := &cobra.Command{
Use: "package [CHART_PATH] [...]",
@ -60,7 +62,8 @@ func newPackageCmd(out io.Writer) *cobra.Command {
return errors.New("--keyring is required for signing a package")
}
}
if err := client.ValueOptions.MergeValues(settings); err != nil {
vals, err := valueOpts.MergeValues(settings)
if err != nil {
return err
}
@ -83,7 +86,7 @@ func newPackageCmd(out io.Writer) *cobra.Command {
return err
}
}
p, err := client.Run(path)
p, err := client.Run(path, vals)
if err != nil {
return err
}
@ -101,7 +104,7 @@ func newPackageCmd(out io.Writer) *cobra.Command {
f.StringVar(&client.AppVersion, "app-version", "", "set the appVersion on the chart to this version")
f.StringVarP(&client.Destination, "destination", "d", ".", "location to write the chart.")
f.BoolVarP(&client.DependencyUpdate, "dependency-update", "u", false, `update dependencies from "Chart.yaml" to dir "charts/" before packaging`)
addValueOptionsFlags(f, &client.ValueOptions)
addValueOptionsFlags(f, valueOpts)
return cmd
}

@ -25,6 +25,7 @@ import (
"helm.sh/helm/cmd/helm/require"
"helm.sh/helm/pkg/action"
"helm.sh/helm/pkg/cli/values"
)
const templateDesc = `
@ -39,6 +40,7 @@ is done.
func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
var validate bool
client := action.NewInstall(cfg)
valueOpts := &values.Options{}
cmd := &cobra.Command{
Use: "template [NAME] [CHART]",
@ -50,7 +52,7 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
client.ReleaseName = "RELEASE-NAME"
client.Replace = true // Skip the name check
client.ClientOnly = !validate
rel, err := runInstall(args, client, out)
rel, err := runInstall(args, client, valueOpts, out)
if err != nil {
return err
}
@ -60,7 +62,7 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
}
f := cmd.Flags()
addInstallFlags(f, client)
addInstallFlags(f, client, valueOpts)
f.StringVar(&client.OutputDir, "output-dir", "", "writes the executed templates to files in output-dir instead of stdout")
f.BoolVar(&validate, "validate", false, "establish a connection to Kubernetes for schema validation")

@ -27,6 +27,7 @@ import (
"helm.sh/helm/cmd/helm/require"
"helm.sh/helm/pkg/action"
"helm.sh/helm/pkg/chart/loader"
"helm.sh/helm/pkg/cli/values"
"helm.sh/helm/pkg/storage/driver"
)
@ -57,6 +58,7 @@ set for a key called 'foo', the 'newbar' value would take precedence:
func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
client := action.NewUpgrade(cfg)
valueOpts := &values.Options{}
cmd := &cobra.Command{
Use: "upgrade [RELEASE] [CHART]",
@ -71,7 +73,8 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
client.Version = ">0.0.0-0"
}
if err := client.ValueOptions.MergeValues(settings); err != nil {
vals, err := valueOpts.MergeValues(settings)
if err != nil {
return err
}
@ -89,7 +92,6 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
fmt.Fprintf(out, "Release %q does not exist. Installing it now.\n", args[0])
instClient := action.NewInstall(cfg)
instClient.ChartPathOptions = client.ChartPathOptions
instClient.ValueOptions = client.ValueOptions
instClient.DryRun = client.DryRun
instClient.DisableHooks = client.DisableHooks
instClient.Timeout = client.Timeout
@ -98,7 +100,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
instClient.Namespace = client.Namespace
instClient.Atomic = client.Atomic
_, err := runInstall(args, instClient, out)
_, err := runInstall(args, instClient, valueOpts, out)
return err
}
}
@ -114,7 +116,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
}
}
resp, err := client.Run(args[0], ch)
resp, err := client.Run(args[0], ch, vals)
if err != nil {
return errors.Wrap(err, "UPGRADE FAILED")
}
@ -151,7 +153,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
f.BoolVar(&client.Atomic, "atomic", false, "if set, upgrade process rolls back changes made in case of failed upgrade. The --wait flag will be set automatically if --atomic is used")
f.IntVar(&client.MaxHistory, "history-max", 0, "limit the maximum number of revisions saved per release. Use 0 for no limit.")
addChartPathOptionsFlags(f, &client.ChartPathOptions)
addValueOptionsFlags(f, &client.ValueOptions)
addValueOptionsFlags(f, valueOpts)
return cmd
}

@ -20,7 +20,6 @@ import (
"bytes"
"fmt"
"io/ioutil"
"net/url"
"os"
"path"
"path/filepath"
@ -30,7 +29,6 @@ import (
"github.com/Masterminds/sprig"
"github.com/pkg/errors"
"sigs.k8s.io/yaml"
"helm.sh/helm/pkg/chart"
"helm.sh/helm/pkg/chartutil"
@ -46,7 +44,6 @@ import (
"helm.sh/helm/pkg/repo"
"helm.sh/helm/pkg/storage"
"helm.sh/helm/pkg/storage/driver"
"helm.sh/helm/pkg/strvals"
"helm.sh/helm/pkg/version"
)
@ -70,7 +67,6 @@ type Install struct {
cfg *Configuration
ChartPathOptions
ValueOptions
ClientOnly bool
DryRun bool
@ -88,13 +84,6 @@ type Install struct {
Atomic bool
}
type ValueOptions struct {
ValueFiles []string
StringValues []string
Values []string
rawValues map[string]interface{}
}
type ChartPathOptions struct {
CaFile string // --ca-file
CertFile string // --cert-file
@ -117,7 +106,7 @@ func NewInstall(cfg *Configuration) *Install {
// Run executes the installation
//
// If DryRun is set to true, this will prepare the release, but not install it
func (i *Install) Run(chrt *chart.Chart) (*release.Release, error) {
func (i *Install) Run(chrt *chart.Chart, vals map[string]interface{}) (*release.Release, error) {
if err := i.availableName(); err != nil {
return nil, err
}
@ -130,7 +119,7 @@ func (i *Install) Run(chrt *chart.Chart) (*release.Release, error) {
i.cfg.Releases = storage.Init(driver.NewMemory())
}
if err := chartutil.ProcessDependencies(chrt, i.rawValues); err != nil {
if err := chartutil.ProcessDependencies(chrt, vals); err != nil {
return nil, err
}
@ -148,12 +137,12 @@ func (i *Install) Run(chrt *chart.Chart) (*release.Release, error) {
Namespace: i.Namespace,
IsInstall: true,
}
valuesToRender, err := chartutil.ToRenderValues(chrt, i.rawValues, options, caps)
valuesToRender, err := chartutil.ToRenderValues(chrt, vals, options, caps)
if err != nil {
return nil, err
}
rel := i.createRelease(chrt, i.rawValues)
rel := i.createRelease(chrt, vals)
var manifestDoc *bytes.Buffer
rel.Hooks, manifestDoc, rel.Info.Notes, err = i.cfg.renderResources(chrt, valuesToRender, i.OutputDir)
// Even for errors, attach this if available
@ -635,114 +624,3 @@ func (c *ChartPathOptions) LocateChart(name string, settings cli.EnvSettings) (s
return filename, errors.Errorf("failed to download %q (hint: running `helm repo update` may help)", name)
}
// MergeValues merges values from files specified via -f/--values and
// directly via --set or --set-string, marshaling them to YAML
func (v *ValueOptions) MergeValues(settings cli.EnvSettings) error {
base := map[string]interface{}{}
// User specified a values files via -f/--values
for _, filePath := range v.ValueFiles {
currentMap := map[string]interface{}{}
bytes, err := readFile(filePath, settings)
if err != nil {
return err
}
if err := yaml.Unmarshal(bytes, &currentMap); err != nil {
return errors.Wrapf(err, "failed to parse %s", filePath)
}
// Merge with the previous map
base = mergeMaps(base, currentMap)
}
// User specified a value via --set
for _, value := range v.Values {
if err := strvals.ParseInto(value, base); err != nil {
return errors.Wrap(err, "failed parsing --set data")
}
}
// User specified a value via --set-string
for _, value := range v.StringValues {
if err := strvals.ParseIntoString(value, base); err != nil {
return errors.Wrap(err, "failed parsing --set-string data")
}
}
v.rawValues = base
return nil
}
func NewValueOptions(values map[string]interface{}) ValueOptions {
return ValueOptions{
rawValues: values,
}
}
// mergeValues merges source and destination map, preferring values from the source map
func mergeValues(dest, src map[string]interface{}) map[string]interface{} {
out := make(map[string]interface{})
for k, v := range dest {
out[k] = v
}
for k, v := range src {
if _, ok := out[k]; !ok {
// If the key doesn't exist already, then just set the key to that value
} else if nextMap, ok := v.(map[string]interface{}); !ok {
// If it isn't another map, overwrite the value
} else if destMap, isMap := out[k].(map[string]interface{}); !isMap {
// Edge case: If the key exists in the destination, but isn't a map
// If the source map has a map for this key, prefer it
} else {
// If we got to this point, it is a map in both, so merge them
out[k] = mergeValues(destMap, nextMap)
continue
}
out[k] = v
}
return out
}
func mergeMaps(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] = mergeMaps(bv, v)
continue
}
}
}
out[k] = v
}
return out
}
// readFile load a file from stdin, the local directory, or a remote file with a url.
func readFile(filePath string, settings cli.EnvSettings) ([]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(getter.WithURL(filePath))
if err != nil {
return []byte{}, err
}
data, err := getter.Get(filePath)
return data.Bytes(), err
}

@ -22,7 +22,6 @@ import (
"log"
"os"
"path/filepath"
"reflect"
"regexp"
"testing"
@ -53,8 +52,8 @@ func installAction(t *testing.T) *Install {
func TestInstallRelease(t *testing.T) {
is := assert.New(t)
instAction := installAction(t)
instAction.rawValues = map[string]interface{}{}
res, err := instAction.Run(buildChart())
vals := map[string]interface{}{}
res, err := instAction.Run(buildChart(), vals)
if err != nil {
t.Fatalf("Failed install: %s", err)
}
@ -79,7 +78,7 @@ func TestInstallReleaseClientOnly(t *testing.T) {
is := assert.New(t)
instAction := installAction(t)
instAction.ClientOnly = true
instAction.Run(buildChart()) // disregard output
instAction.Run(buildChart(), nil) // disregard output
is.Equal(instAction.cfg.Capabilities, chartutil.DefaultCapabilities)
is.Equal(instAction.cfg.KubeClient, &kubefake.PrintingKubeClient{Out: ioutil.Discard})
@ -88,8 +87,8 @@ func TestInstallReleaseClientOnly(t *testing.T) {
func TestInstallRelease_NoName(t *testing.T) {
instAction := installAction(t)
instAction.ReleaseName = ""
instAction.rawValues = map[string]interface{}{}
_, err := instAction.Run(buildChart())
vals := map[string]interface{}{}
_, err := instAction.Run(buildChart(), vals)
if err == nil {
t.Fatal("expected failure when no name is specified")
}
@ -100,8 +99,8 @@ func TestInstallRelease_WithNotes(t *testing.T) {
is := assert.New(t)
instAction := installAction(t)
instAction.ReleaseName = "with-notes"
instAction.rawValues = map[string]interface{}{}
res, err := instAction.Run(buildChart(withNotes("note here")))
vals := map[string]interface{}{}
res, err := instAction.Run(buildChart(withNotes("note here")), vals)
if err != nil {
t.Fatalf("Failed install: %s", err)
}
@ -127,8 +126,8 @@ func TestInstallRelease_WithNotesRendered(t *testing.T) {
is := assert.New(t)
instAction := installAction(t)
instAction.ReleaseName = "with-notes"
instAction.rawValues = map[string]interface{}{}
res, err := instAction.Run(buildChart(withNotes("got-{{.Release.Name}}")))
vals := map[string]interface{}{}
res, err := instAction.Run(buildChart(withNotes("got-{{.Release.Name}}")), vals)
if err != nil {
t.Fatalf("Failed install: %s", err)
}
@ -146,8 +145,8 @@ func TestInstallRelease_WithChartAndDependencyNotes(t *testing.T) {
is := assert.New(t)
instAction := installAction(t)
instAction.ReleaseName = "with-notes"
instAction.rawValues = map[string]interface{}{}
res, err := instAction.Run(buildChart(withNotes("parent"), withDependency(withNotes("child"))))
vals := map[string]interface{}{}
res, err := instAction.Run(buildChart(withNotes("parent"), withDependency(withNotes("child"))), vals)
if err != nil {
t.Fatalf("Failed install: %s", err)
}
@ -163,8 +162,8 @@ func TestInstallRelease_DryRun(t *testing.T) {
is := assert.New(t)
instAction := installAction(t)
instAction.DryRun = true
instAction.rawValues = map[string]interface{}{}
res, err := instAction.Run(buildChart(withSampleTemplates()))
vals := map[string]interface{}{}
res, err := instAction.Run(buildChart(withSampleTemplates()), vals)
if err != nil {
t.Fatalf("Failed install: %s", err)
}
@ -189,8 +188,8 @@ func TestInstallRelease_NoHooks(t *testing.T) {
instAction.ReleaseName = "no-hooks"
instAction.cfg.Releases.Create(releaseStub())
instAction.rawValues = map[string]interface{}{}
res, err := instAction.Run(buildChart())
vals := map[string]interface{}{}
res, err := instAction.Run(buildChart(), vals)
if err != nil {
t.Fatalf("Failed install: %s", err)
}
@ -206,8 +205,8 @@ func TestInstallRelease_FailedHooks(t *testing.T) {
failer.WatchUntilReadyError = fmt.Errorf("Failed watch")
instAction.cfg.KubeClient = failer
instAction.rawValues = map[string]interface{}{}
res, err := instAction.Run(buildChart())
vals := map[string]interface{}{}
res, err := instAction.Run(buildChart(), vals)
is.Error(err)
is.Contains(res.Info.Description, "failed post-install")
is.Equal(res.Info.Status, release.StatusFailed)
@ -223,8 +222,8 @@ func TestInstallRelease_ReplaceRelease(t *testing.T) {
instAction.cfg.Releases.Create(rel)
instAction.ReleaseName = rel.Name
instAction.rawValues = map[string]interface{}{}
res, err := instAction.Run(buildChart())
vals := map[string]interface{}{}
res, err := instAction.Run(buildChart(), vals)
is.NoError(err)
// This should have been auto-incremented
@ -239,14 +238,14 @@ func TestInstallRelease_ReplaceRelease(t *testing.T) {
func TestInstallRelease_KubeVersion(t *testing.T) {
is := assert.New(t)
instAction := installAction(t)
instAction.rawValues = map[string]interface{}{}
_, err := instAction.Run(buildChart(withKube(">=0.0.0")))
vals := map[string]interface{}{}
_, err := instAction.Run(buildChart(withKube(">=0.0.0")), vals)
is.NoError(err)
// This should fail for a few hundred years
instAction.ReleaseName = "should-fail"
instAction.rawValues = map[string]interface{}{}
_, err = instAction.Run(buildChart(withKube(">=99.0.0")))
vals = map[string]interface{}{}
_, err = instAction.Run(buildChart(withKube(">=99.0.0")), vals)
is.Error(err)
is.Contains(err.Error(), "chart requires kubernetesVersion")
}
@ -259,9 +258,9 @@ func TestInstallRelease_Wait(t *testing.T) {
failer.WaitError = fmt.Errorf("I timed out")
instAction.cfg.KubeClient = failer
instAction.Wait = true
instAction.rawValues = map[string]interface{}{}
vals := map[string]interface{}{}
res, err := instAction.Run(buildChart())
res, err := instAction.Run(buildChart(), vals)
is.Error(err)
is.Contains(res.Info.Description, "I timed out")
is.Equal(res.Info.Status, release.StatusFailed)
@ -277,9 +276,9 @@ func TestInstallRelease_Atomic(t *testing.T) {
failer.WaitError = fmt.Errorf("I timed out")
instAction.cfg.KubeClient = failer
instAction.Atomic = true
instAction.rawValues = map[string]interface{}{}
vals := map[string]interface{}{}
res, err := instAction.Run(buildChart())
res, err := instAction.Run(buildChart(), vals)
is.Error(err)
is.Contains(err.Error(), "I timed out")
is.Contains(err.Error(), "atomic")
@ -298,9 +297,9 @@ func TestInstallRelease_Atomic(t *testing.T) {
failer.DeleteError = fmt.Errorf("uninstall fail")
instAction.cfg.KubeClient = failer
instAction.Atomic = true
instAction.rawValues = map[string]interface{}{}
vals := map[string]interface{}{}
_, err := instAction.Run(buildChart())
_, err := instAction.Run(buildChart(), vals)
is.Error(err)
is.Contains(err.Error(), "I timed out")
is.Contains(err.Error(), "uninstall fail")
@ -377,65 +376,10 @@ func TestNameTemplate(t *testing.T) {
}
}
func TestMergeValues(t *testing.T) {
nestedMap := map[string]interface{}{
"foo": "bar",
"baz": map[string]string{
"cool": "stuff",
},
}
anotherNestedMap := map[string]interface{}{
"foo": "bar",
"baz": map[string]string{
"cool": "things",
"awesome": "stuff",
},
}
flatMap := map[string]interface{}{
"foo": "bar",
"baz": "stuff",
}
anotherFlatMap := map[string]interface{}{
"testing": "fun",
}
testMap := mergeValues(flatMap, nestedMap)
equal := reflect.DeepEqual(testMap, nestedMap)
if !equal {
t.Errorf("Expected a nested map to overwrite a flat value. Expected: %v, got %v", nestedMap, testMap)
}
testMap = mergeValues(nestedMap, flatMap)
equal = reflect.DeepEqual(testMap, flatMap)
if !equal {
t.Errorf("Expected a flat value to overwrite a map. Expected: %v, got %v", flatMap, testMap)
}
testMap = mergeValues(nestedMap, anotherNestedMap)
equal = reflect.DeepEqual(testMap, anotherNestedMap)
if !equal {
t.Errorf("Expected a nested map to overwrite another nested map. Expected: %v, got %v", anotherNestedMap, testMap)
}
testMap = mergeValues(anotherFlatMap, anotherNestedMap)
expectedMap := map[string]interface{}{
"testing": "fun",
"foo": "bar",
"baz": map[string]string{
"cool": "things",
"awesome": "stuff",
},
}
equal = reflect.DeepEqual(testMap, expectedMap)
if !equal {
t.Errorf("Expected a map with different keys to merge properly with another map. Expected: %v, got %v", expectedMap, testMap)
}
}
func TestInstallReleaseOutputDir(t *testing.T) {
is := assert.New(t)
instAction := installAction(t)
instAction.rawValues = map[string]interface{}{}
vals := map[string]interface{}{}
dir, err := ioutil.TempDir("", "output-dir")
if err != nil {
@ -445,7 +389,7 @@ func TestInstallReleaseOutputDir(t *testing.T) {
instAction.OutputDir = dir
_, err = instAction.Run(buildChart(withSampleTemplates(), withMultipleManifestTemplate()))
_, err = instAction.Run(buildChart(withSampleTemplates(), withMultipleManifestTemplate()), vals)
if err != nil {
t.Fatalf("Failed install: %s", err)
}

@ -35,8 +35,6 @@ var errLintNoChart = errors.New("no chart found for linting (missing Chart.yaml)
//
// It provides the implementation of 'helm lint'.
type Lint struct {
ValueOptions
Strict bool
Namespace string
}
@ -53,7 +51,7 @@ func NewLint() *Lint {
}
// Run executes 'helm Lint' against the given chart.
func (l *Lint) Run(paths []string) *LintResult {
func (l *Lint) Run(paths []string, vals map[string]interface{}) *LintResult {
lowestTolerance := support.ErrorSev
if l.Strict {
lowestTolerance = support.WarningSev
@ -61,7 +59,7 @@ func (l *Lint) Run(paths []string) *LintResult {
result := &LintResult{}
for _, path := range paths {
if linter, err := lintChart(path, l.ValueOptions.rawValues, l.Namespace, l.Strict); err != nil {
if linter, err := lintChart(path, vals, l.Namespace, l.Strict); err != nil {
if err == errLintNoChart {
result.Errors = append(result.Errors, err)
}

@ -36,8 +36,6 @@ import (
//
// It provides the implementation of 'helm package'.
type Package struct {
ValueOptions
Sign bool
Key string
Keyring string
@ -53,13 +51,13 @@ func NewPackage() *Package {
}
// Run executes 'helm package' against the given chart and returns the path to the packaged chart.
func (p *Package) Run(path string) (string, error) {
func (p *Package) Run(path string, vals map[string]interface{}) (string, error) {
ch, err := loader.LoadDir(path)
if err != nil {
return "", err
}
combinedVals, err := chartutil.CoalesceValues(ch, p.ValueOptions.rawValues)
combinedVals, err := chartutil.CoalesceValues(ch, vals)
if err != nil {
return "", err
}

@ -39,7 +39,6 @@ type Upgrade struct {
cfg *Configuration
ChartPathOptions
ValueOptions
Install bool
Devel bool
@ -66,8 +65,8 @@ func NewUpgrade(cfg *Configuration) *Upgrade {
}
// Run executes the upgrade on the given release.
func (u *Upgrade) Run(name string, chart *chart.Chart) (*release.Release, error) {
if err := chartutil.ProcessDependencies(chart, u.rawValues); err != nil {
func (u *Upgrade) Run(name string, chart *chart.Chart, vals map[string]interface{}) (*release.Release, error) {
if err := chartutil.ProcessDependencies(chart, vals); err != nil {
return nil, err
}
@ -79,7 +78,7 @@ func (u *Upgrade) Run(name string, chart *chart.Chart) (*release.Release, error)
return nil, errors.Errorf("release name is invalid: %s", name)
}
u.cfg.Log("preparing upgrade for %s", name)
currentRelease, upgradedRelease, err := u.prepareUpgrade(name, chart)
currentRelease, upgradedRelease, err := u.prepareUpgrade(name, chart, vals)
if err != nil {
return nil, err
}
@ -122,7 +121,7 @@ func validateReleaseName(releaseName string) error {
}
// prepareUpgrade builds an upgraded release for an upgrade operation.
func (u *Upgrade) prepareUpgrade(name string, chart *chart.Chart) (*release.Release, *release.Release, error) {
func (u *Upgrade) prepareUpgrade(name string, chart *chart.Chart, vals map[string]interface{}) (*release.Release, *release.Release, error) {
if chart == nil {
return nil, nil, errMissingChart
}
@ -134,7 +133,8 @@ func (u *Upgrade) prepareUpgrade(name string, chart *chart.Chart) (*release.Rele
}
// determine if values will be reused
if err := u.reuseValues(chart, currentRelease); err != nil {
vals, err = u.reuseValues(chart, currentRelease, vals)
if err != nil {
return nil, nil, err
}
@ -158,7 +158,7 @@ func (u *Upgrade) prepareUpgrade(name string, chart *chart.Chart) (*release.Rele
if err != nil {
return nil, nil, err
}
valuesToRender, err := chartutil.ToRenderValues(chart, u.rawValues, options, caps)
valuesToRender, err := chartutil.ToRenderValues(chart, vals, options, caps)
if err != nil {
return nil, nil, err
}
@ -173,7 +173,7 @@ func (u *Upgrade) prepareUpgrade(name string, chart *chart.Chart) (*release.Rele
Name: name,
Namespace: currentRelease.Namespace,
Chart: chart,
Config: u.rawValues,
Config: vals,
Info: &release.Info{
FirstDeployed: currentRelease.Info.FirstDeployed,
LastDeployed: Timestamper(),
@ -310,11 +310,11 @@ func (u *Upgrade) failRelease(rel *release.Release, err error) (*release.Release
//
// This is skipped if the u.ResetValues flag is set, in which case the
// request values are not altered.
func (u *Upgrade) reuseValues(chart *chart.Chart, current *release.Release) error {
func (u *Upgrade) reuseValues(chart *chart.Chart, current *release.Release, newVals map[string]interface{}) (map[string]interface{}, error) {
if u.ResetValues {
// If ResetValues is set, we completely ignore current.Config.
u.cfg.Log("resetting values to the chart's original version")
return nil
return newVals, nil
}
// If the ReuseValues flag is set, we always copy the old values over the new config's values.
@ -324,21 +324,21 @@ func (u *Upgrade) reuseValues(chart *chart.Chart, current *release.Release) erro
// We have to regenerate the old coalesced values:
oldVals, err := chartutil.CoalesceValues(current.Chart, current.Config)
if err != nil {
return errors.Wrap(err, "failed to rebuild old values")
return nil, errors.Wrap(err, "failed to rebuild old values")
}
u.rawValues = chartutil.CoalesceTables(u.rawValues, current.Config)
newVals = chartutil.CoalesceTables(newVals, current.Config)
chart.Values = oldVals
return nil
return newVals, nil
}
if len(u.rawValues) == 0 && len(current.Config) > 0 {
if len(newVals) == 0 && len(current.Config) > 0 {
u.cfg.Log("copying values from %s (v%d) to new release.", current.Name, current.Version)
u.rawValues = current.Config
newVals = current.Config
}
return nil
return newVals, nil
}
func validateManifest(c kube.Interface, manifest []byte) error {

@ -49,9 +49,9 @@ func TestUpgradeRelease_Wait(t *testing.T) {
failer.WaitError = fmt.Errorf("I timed out")
upAction.cfg.KubeClient = failer
upAction.Wait = true
upAction.rawValues = map[string]interface{}{}
vals := map[string]interface{}{}
res, err := upAction.Run(rel.Name, buildChart())
res, err := upAction.Run(rel.Name, buildChart(), vals)
req.Error(err)
is.Contains(res.Info.Description, "I timed out")
is.Equal(res.Info.Status, release.StatusFailed)
@ -74,9 +74,9 @@ func TestUpgradeRelease_Atomic(t *testing.T) {
failer.WatchUntilReadyError = fmt.Errorf("arming key removed")
upAction.cfg.KubeClient = failer
upAction.Atomic = true
upAction.rawValues = map[string]interface{}{}
vals := map[string]interface{}{}
res, err := upAction.Run(rel.Name, buildChart())
res, err := upAction.Run(rel.Name, buildChart(), vals)
req.Error(err)
is.Contains(err.Error(), "arming key removed")
is.Contains(err.Error(), "atomic")
@ -99,9 +99,9 @@ func TestUpgradeRelease_Atomic(t *testing.T) {
failer.UpdateError = fmt.Errorf("update fail")
upAction.cfg.KubeClient = failer
upAction.Atomic = true
upAction.rawValues = map[string]interface{}{}
vals := map[string]interface{}{}
_, err := upAction.Run(rel.Name, buildChart())
_, err := upAction.Run(rel.Name, buildChart(), vals)
req.Error(err)
is.Contains(err.Error(), "update fail")
is.Contains(err.Error(), "an error occurred while rolling back the release")
@ -141,8 +141,7 @@ func TestUpgradeRelease_ReuseValues(t *testing.T) {
upAction.ReuseValues = true
// setting newValues and upgrading
upAction.rawValues = newValues
res, err := upAction.Run(rel.Name, buildChart())
res, err := upAction.Run(rel.Name, buildChart(), newValues)
is.NoError(err)
// Now make sure it is actually upgraded

@ -0,0 +1,117 @@
/*
Copyright The Helm Authors.
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 values
import (
"io/ioutil"
"net/url"
"os"
"strings"
"github.com/pkg/errors"
"sigs.k8s.io/yaml"
"helm.sh/helm/pkg/cli"
"helm.sh/helm/pkg/getter"
"helm.sh/helm/pkg/strvals"
)
type Options struct {
ValueFiles []string
StringValues []string
Values []string
}
// MergeValues merges values from files specified via -f/--values and
// directly via --set or --set-string, marshaling them to YAML
func (opts *Options) MergeValues(settings cli.EnvSettings) (map[string]interface{}, error) {
base := map[string]interface{}{}
// User specified a values files via -f/--values
for _, filePath := range opts.ValueFiles {
currentMap := map[string]interface{}{}
bytes, err := readFile(filePath, settings)
if err != nil {
return nil, err
}
if err := yaml.Unmarshal(bytes, &currentMap); err != nil {
return nil, errors.Wrapf(err, "failed to parse %s", filePath)
}
// Merge with the previous map
base = mergeMaps(base, currentMap)
}
// User specified a value via --set
for _, value := range opts.Values {
if err := strvals.ParseInto(value, base); err != nil {
return nil, errors.Wrap(err, "failed parsing --set data")
}
}
// User specified a value via --set-string
for _, value := range opts.StringValues {
if err := strvals.ParseIntoString(value, base); err != nil {
return nil, errors.Wrap(err, "failed parsing --set-string data")
}
}
return base, nil
}
func mergeMaps(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] = mergeMaps(bv, v)
continue
}
}
}
out[k] = v
}
return out
}
// readFile load a file from stdin, the local directory, or a remote file with a url.
func readFile(filePath string, settings cli.EnvSettings) ([]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(getter.WithURL(filePath))
if err != nil {
return []byte{}, err
}
data, err := getter.Get(filePath)
return data.Bytes(), err
}

@ -0,0 +1,77 @@
/*
Copyright The Helm Authors.
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 values
import (
"reflect"
"testing"
)
func TestMergeValues(t *testing.T) {
nestedMap := map[string]interface{}{
"foo": "bar",
"baz": map[string]string{
"cool": "stuff",
},
}
anotherNestedMap := map[string]interface{}{
"foo": "bar",
"baz": map[string]string{
"cool": "things",
"awesome": "stuff",
},
}
flatMap := map[string]interface{}{
"foo": "bar",
"baz": "stuff",
}
anotherFlatMap := map[string]interface{}{
"testing": "fun",
}
testMap := mergeMaps(flatMap, nestedMap)
equal := reflect.DeepEqual(testMap, nestedMap)
if !equal {
t.Errorf("Expected a nested map to overwrite a flat value. Expected: %v, got %v", nestedMap, testMap)
}
testMap = mergeMaps(nestedMap, flatMap)
equal = reflect.DeepEqual(testMap, flatMap)
if !equal {
t.Errorf("Expected a flat value to overwrite a map. Expected: %v, got %v", flatMap, testMap)
}
testMap = mergeMaps(nestedMap, anotherNestedMap)
equal = reflect.DeepEqual(testMap, anotherNestedMap)
if !equal {
t.Errorf("Expected a nested map to overwrite another nested map. Expected: %v, got %v", anotherNestedMap, testMap)
}
testMap = mergeMaps(anotherFlatMap, anotherNestedMap)
expectedMap := map[string]interface{}{
"testing": "fun",
"foo": "bar",
"baz": map[string]string{
"cool": "things",
"awesome": "stuff",
},
}
equal = reflect.DeepEqual(testMap, expectedMap)
if !equal {
t.Errorf("Expected a map with different keys to merge properly with another map. Expected: %v, got %v", expectedMap, testMap)
}
}
Loading…
Cancel
Save