feat(helm): support command-line values for install

Add support for specifying key/value pairs on the command line, instead
of only in a YAML file.

This currently accepts either command line pairs or a YAML file, but
does not support combining the two.

Closes #944
pull/982/head
Matt Butcher 9 years ago
parent 1c598c2d13
commit 4b6fbbb67f

@ -24,6 +24,7 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/ghodss/yaml"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/helm/pkg/helm" "k8s.io/helm/pkg/helm"
@ -34,9 +35,21 @@ import (
const installDesc = ` const installDesc = `
This command installs a chart archive. This command installs a chart archive.
The install argument must be either a relative The install argument must be either a relative path to a chart directory or the
path to a chart directory or the name of a name of a chart in the current working directory.
chart in the current working directory.
To override values in a chart, use either the '--values' flag and pass in a file
or use the '--set' flag and pass configuration from the command line.
$ helm install -f myvalues.yaml redis
or
$ helm install --set name=prod redis
To check the generated manifests of a release without installing the chart,
the '--debug' and '--dry-run' flags can be combined. This will still require a
round-trip to the Tiller server.
` `
type installCmd struct { type installCmd struct {
@ -48,12 +61,14 @@ type installCmd struct {
disableHooks bool disableHooks bool
out io.Writer out io.Writer
client helm.Interface client helm.Interface
values *values
} }
func newInstallCmd(c helm.Interface, out io.Writer) *cobra.Command { func newInstallCmd(c helm.Interface, out io.Writer) *cobra.Command {
inst := &installCmd{ inst := &installCmd{
out: out, out: out,
client: c, client: c,
values: new(values),
} }
cmd := &cobra.Command{ cmd := &cobra.Command{
@ -76,12 +91,13 @@ func newInstallCmd(c helm.Interface, out io.Writer) *cobra.Command {
} }
f := cmd.Flags() f := cmd.Flags()
f.StringVarP(&inst.valuesFile, "values", "f", "", "path to a values YAML file") f.StringVarP(&inst.valuesFile, "values", "f", "", "specify values in a YAML file")
f.StringVarP(&inst.name, "name", "n", "", "the release name. If unspecified, it will autogenerate one for you.") f.StringVarP(&inst.name, "name", "n", "", "the release name. If unspecified, it will autogenerate one for you")
// TODO use kubeconfig default // TODO use kubeconfig default
f.StringVar(&inst.namespace, "namespace", "default", "the namespace to install the release into") f.StringVar(&inst.namespace, "namespace", "default", "the namespace to install the release into")
f.BoolVar(&inst.dryRun, "dry-run", false, "simulate an install") f.BoolVar(&inst.dryRun, "dry-run", false, "simulate an install")
f.BoolVar(&inst.disableHooks, "no-hooks", false, "prevent hooks from running during install") f.BoolVar(&inst.disableHooks, "no-hooks", false, "prevent hooks from running during install")
f.Var(inst.values, "set", "set values on the command line. Separate values with commas: key1=val1,key2=val2")
return cmd return cmd
} }
@ -106,6 +122,9 @@ func (i *installCmd) run() error {
} }
func (i *installCmd) vals() ([]byte, error) { func (i *installCmd) vals() ([]byte, error) {
if len(i.values.pairs) > 0 {
return i.values.yaml()
}
if i.valuesFile == "" { if i.valuesFile == "" {
return []byte{}, nil return []byte{}, nil
} }
@ -128,6 +147,62 @@ func (i *installCmd) printRelease(rel *release.Release) {
} }
} }
// values represents the command-line value pairs
type values struct {
pairs map[string]interface{}
}
func (v *values) yaml() ([]byte, error) {
return yaml.Marshal(v.pairs)
}
func (v *values) String() string {
out, _ := v.yaml()
return string(out)
}
func (v *values) Type() string {
// Added to pflags.Value interface, but not documented there.
return "struct"
}
func (v *values) Set(data string) error {
v.pairs = map[string]interface{}{}
items := strings.Split(data, ",")
for _, item := range items {
n, val := splitPair(item)
names := strings.Split(n, ".")
ln := len(names)
current := &v.pairs
for i := 0; i < ln; i++ {
if i+1 == ln {
// We're at the last element. Set it.
(*current)[names[i]] = val
} else {
//
if e, ok := (*current)[names[i]]; !ok {
m := map[string]interface{}{}
(*current)[names[i]] = m
current = &m
} else if m, ok := e.(map[string]interface{}); ok {
current = &m
}
}
}
}
fmt.Print(v.pairs)
return nil
}
func splitPair(item string) (name string, value interface{}) {
pair := strings.SplitN(item, "=", 2)
if len(pair) == 1 {
return pair[0], true
}
return pair[0], pair[1]
}
// locateChartPath looks for a chart directory in known places, and returns either the full path or an error. // locateChartPath looks for a chart directory in known places, and returns either the full path or an error.
// //
// This does not ensure that the chart is well-formed; only that the requested filename exists. // This does not ensure that the chart is well-formed; only that the requested filename exists.

@ -17,6 +17,7 @@ limitations under the License.
package main package main
import ( import (
"fmt"
"io" "io"
"strings" "strings"
"testing" "testing"
@ -42,6 +43,14 @@ func TestInstall(t *testing.T) {
expected: "juno", expected: "juno",
resp: releaseMock("juno"), resp: releaseMock("juno"),
}, },
// Install, values from cli
{
name: "install with values",
args: []string{"testdata/testcharts/alpine"},
flags: strings.Split("--set foo=bar", " "),
resp: releaseMock("virgil"),
expected: "virgil",
},
// Install, no charts // Install, no charts
{ {
name: "install with no chart specified", name: "install with no chart specified",
@ -54,3 +63,45 @@ func TestInstall(t *testing.T) {
return newInstallCmd(c, out) return newInstallCmd(c, out)
}) })
} }
func TestValues(t *testing.T) {
args := "sailor=sinbad,good,port.source=baghdad,port.destination=basrah"
vobj := new(values)
vobj.Set(args)
if vobj.Type() != "struct" {
t.Fatalf("Expected Type to be struct, got %s", vobj.Type())
}
vals := vobj.pairs
if fmt.Sprint(vals["good"]) != "true" {
t.Errorf("Expected good to be true. Got %v", vals["good"])
}
port := vals["port"].(map[string]interface{})
if fmt.Sprint(port["source"]) != "baghdad" {
t.Errorf("Expected source to be baghdad. Got %s", port["source"])
}
if fmt.Sprint(port["destination"]) != "basrah" {
t.Errorf("Expected source to be baghdad. Got %s", port["source"])
}
y := `good: true
port:
destination: basrah
source: baghdad
sailor: sinbad
`
out, err := vobj.yaml()
if err != nil {
t.Fatal(err)
}
if string(out) != y {
t.Errorf("Expected YAML to be \n%s\nGot\n%s\n", y, out)
}
if vobj.String() != y {
t.Errorf("Expected String() to be \n%s\nGot\n%s\n", y, out)
}
}

Loading…
Cancel
Save