feat(*): Ports `--set-file` flag to v3

I made a few modifications from the original code to fit in with the new
code layout and to clarify a few things. This is a port of #3758

Signed-off-by: Taylor Thomas <taylor.thomas@microsoft.com>
pull/6494/head
Taylor Thomas 5 years ago
parent 6419ff6961
commit 7599c5d489

@ -42,7 +42,9 @@ a path to an unpacked chart directory or a URL.
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, to force
a string value use '--set-string'.
a string value use '--set-string'. In case a value is large and therefore
you want not to use neither '--values' nor '--set', use '--set-file' to read the
single large value from file.
$ helm install -f myvalues.yaml myredis ./redis
@ -54,6 +56,9 @@ or
$ helm install --set-string long_int=1234567890 myredis ./redis
or
$ helm install --set-file my_script=dothings.sh myredis ./redis
You can specify the '--values'/'-f' flag multiple times. The priority will be given to the
last (right-most) file specified. For example, if both myvalues.yaml and override.yaml
contained a key called 'Test', the value set in override.yaml would take precedence:
@ -140,6 +145,7 @@ 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)")
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)")
}
func addChartPathOptionsFlags(f *pflag.FlagSet, c *action.ChartPathOptions) {

@ -42,7 +42,9 @@ version will be specified unless the '--version' flag is set.
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, to force string
values, use '--set-string'.
values, use '--set-string'. In case a value is large and therefore
you want not to use neither '--values' nor '--set', use '--set-file' to read the
single large value from file.
You can specify the '--values'/'-f' flag multiple times. The priority will be given to the
last (right-most) file specified. For example, if both myvalues.yaml and override.yaml

@ -33,10 +33,11 @@ type Options struct {
ValueFiles []string
StringValues []string
Values []string
FileValues []string
}
// MergeValues merges values from files specified via -f/--values and
// directly via --set or --set-string, marshaling them to YAML
// MergeValues merges values from files specified via -f/--values and directly
// via --set, --set-string, or --set-file, marshaling them to YAML
func (opts *Options) MergeValues(p getter.Providers) (map[string]interface{}, error) {
base := map[string]interface{}{}
@ -70,6 +71,17 @@ func (opts *Options) MergeValues(p getter.Providers) (map[string]interface{}, er
}
}
// User specified a value via --set-file
for _, value := range opts.FileValues {
reader := func(rs []rune) (interface{}, error) {
bytes, err := readFile(string(rs), p)
return string(bytes), err
}
if err := strvals.ParseIntoFile(value, base, reader); err != nil {
return nil, errors.Wrap(err, "failed parsing --set-file data")
}
}
return base, nil
}

@ -70,6 +70,20 @@ func ParseInto(s string, dest map[string]interface{}) error {
return t.parse()
}
// ParseFile parses a set line, but its final value is loaded from the file at the path specified by the original value.
//
// A set line is of the form name1=path1,name2=path2
//
// When the files at path1 and path2 contained "val1" and "val2" respectively, the set line is consumed as
// name1=val1,name2=val2
func ParseFile(s string, reader RunesValueReader) (map[string]interface{}, error) {
vals := map[string]interface{}{}
scanner := bytes.NewBufferString(s)
t := newFileParser(scanner, vals, reader)
err := t.parse()
return vals, err
}
// ParseIntoString parses a strvals line nad merges the result into dest.
//
// This method always returns a string as the value.
@ -79,16 +93,36 @@ func ParseIntoString(s string, dest map[string]interface{}) error {
return t.parse()
}
// ParseIntoFile parses a filevals line and merges the result into dest.
//
// This method always returns a string as the value.
func ParseIntoFile(s string, dest map[string]interface{}, reader RunesValueReader) error {
scanner := bytes.NewBufferString(s)
t := newFileParser(scanner, dest, reader)
return t.parse()
}
// RunesValueReader is a function that takes the given value (a slice of runes)
// and returns the parsed value
type RunesValueReader func([]rune) (interface{}, error)
// parser is a simple parser that takes a strvals line and parses it into a
// map representation.
type parser struct {
sc *bytes.Buffer
data map[string]interface{}
st bool
sc *bytes.Buffer
data map[string]interface{}
reader RunesValueReader
}
func newParser(sc *bytes.Buffer, data map[string]interface{}, stringBool bool) *parser {
return &parser{sc: sc, data: data, st: stringBool}
stringConverter := func(rs []rune) (interface{}, error) {
return typedVal(rs, stringBool), nil
}
return &parser{sc: sc, data: data, reader: stringConverter}
}
func newFileParser(sc *bytes.Buffer, data map[string]interface{}, reader RunesValueReader) *parser {
return &parser{sc: sc, data: data, reader: reader}
}
func (t *parser) parse() error {
@ -152,8 +186,12 @@ func (t *parser) key(data map[string]interface{}) error {
set(data, string(k), "")
return e
case ErrNotList:
v, e := t.val()
set(data, string(k), typedVal(v, t.st))
rs, e := t.val()
if e != nil && e != io.EOF {
return e
}
v, e := t.reader(rs)
set(data, string(k), v)
return e
default:
return e
@ -225,8 +263,12 @@ func (t *parser) listItem(list []interface{}, i int) ([]interface{}, error) {
case io.EOF:
return setIndex(list, i, ""), err
case ErrNotList:
v, e := t.val()
return setIndex(list, i, typedVal(v, t.st)), e
rs, e := t.val()
if e != nil && e != io.EOF {
return list, e
}
v, e := t.reader(rs)
return setIndex(list, i, v), e
default:
return list, e
}
@ -274,7 +316,7 @@ func (t *parser) valList() ([]interface{}, error) {
list := []interface{}{}
stop := runeSet([]rune{',', '}'})
for {
switch v, last, err := runesUntil(t.sc, stop); {
switch rs, last, err := runesUntil(t.sc, stop); {
case err != nil:
if err == io.EOF {
err = errors.New("list must terminate with '}'")
@ -285,10 +327,15 @@ func (t *parser) valList() ([]interface{}, error) {
if r, _, e := t.sc.ReadRune(); e == nil && r != ',' {
t.sc.UnreadRune()
}
list = append(list, typedVal(v, t.st))
return list, nil
v, e := t.reader(rs)
list = append(list, v)
return list, e
case last == ',':
list = append(list, typedVal(v, t.st))
v, e := t.reader(rs)
if e != nil {
return list, e
}
list = append(list, v)
}
}
}

@ -358,6 +358,39 @@ func TestParseInto(t *testing.T) {
}
}
func TestParseIntoFile(t *testing.T) {
got := map[string]interface{}{}
input := "name1=path1"
expect := map[string]interface{}{
"name1": "value1",
}
rs2v := func(rs []rune) (interface{}, error) {
v := string(rs)
if v != "path1" {
t.Errorf("%s: RunesValueReader: Expected value path1, got %s", input, v)
return "", nil
}
return "value1", nil
}
if err := ParseIntoFile(input, got, rs2v); err != nil {
t.Fatal(err)
}
y1, err := yaml.Marshal(expect)
if err != nil {
t.Fatal(err)
}
y2, err := yaml.Marshal(got)
if err != nil {
t.Fatalf("Error serializing parsed value: %s", err)
}
if string(y1) != string(y2) {
t.Errorf("%s: Expected:\n%s\nGot:\n%s", input, y1, y2)
}
}
func TestToYAML(t *testing.T) {
// The TestParse does the hard part. We just verify that YAML formatting is
// happening.

Loading…
Cancel
Save