mirror of https://github.com/helm/helm.git
561 lines
15 KiB
Go
561 lines
15 KiB
Go
/*
|
||
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 strvals
|
||
|
||
import (
|
||
"bytes"
|
||
"encoding/json"
|
||
"errors"
|
||
"fmt"
|
||
"io"
|
||
"strconv"
|
||
"strings"
|
||
"unicode"
|
||
|
||
"sigs.k8s.io/yaml"
|
||
)
|
||
|
||
// ErrNotList indicates that a non-list was treated as a list.
|
||
var ErrNotList = errors.New("not a list")
|
||
|
||
// MaxIndex is the maximum index that will be allowed by setIndex.
|
||
// The default value 65536 = 1024 * 64
|
||
var MaxIndex = 65536
|
||
|
||
// MaxNestedNameLevel is the maximum level of nesting for a value name that
|
||
// will be allowed.
|
||
var MaxNestedNameLevel = 30
|
||
|
||
// ToYAML takes a string of arguments and converts to a YAML document.
|
||
func ToYAML(s string) (string, error) {
|
||
m, err := Parse(s)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
d, err := yaml.Marshal(m)
|
||
return strings.TrimSuffix(string(d), "\n"), err
|
||
}
|
||
|
||
// Parse parses a set line.
|
||
//
|
||
// A set line is of the form name1=value1,name2=value2
|
||
func Parse(s string) (map[string]interface{}, error) {
|
||
vals := map[string]interface{}{}
|
||
scanner := bytes.NewBufferString(s)
|
||
t := newParser(scanner, vals, false)
|
||
err := t.parse()
|
||
return vals, err
|
||
}
|
||
|
||
// ParseString parses a set line and forces a string value.
|
||
//
|
||
// A set line is of the form name1=value1,name2=value2
|
||
func ParseString(s string) (map[string]interface{}, error) {
|
||
vals := map[string]interface{}{}
|
||
scanner := bytes.NewBufferString(s)
|
||
t := newParser(scanner, vals, true)
|
||
err := t.parse()
|
||
return vals, err
|
||
}
|
||
|
||
// ParseInto parses a strvals line and merges the result into dest.
|
||
//
|
||
// If the strval string has a key that exists in dest, it overwrites the
|
||
// dest version.
|
||
func ParseInto(s string, dest map[string]interface{}) error {
|
||
scanner := bytes.NewBufferString(s)
|
||
t := newParser(scanner, dest, false)
|
||
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 and merges the result into dest.
|
||
//
|
||
// This method always returns a string as the value.
|
||
func ParseIntoString(s string, dest map[string]interface{}) error {
|
||
scanner := bytes.NewBufferString(s)
|
||
t := newParser(scanner, dest, true)
|
||
return t.parse()
|
||
}
|
||
|
||
// ParseJSON parses a string with format key1=val1, key2=val2, ...
|
||
// where values are json strings (null, or scalars, or arrays, or objects).
|
||
// An empty val is treated as null.
|
||
//
|
||
// If a key exists in dest, the new value overwrites the dest version.
|
||
func ParseJSON(s string, dest map[string]interface{}) error {
|
||
scanner := bytes.NewBufferString(s)
|
||
t := newJSONParser(scanner, dest)
|
||
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.
|
||
//
|
||
// where sc is the source of the original data being parsed
|
||
// where data is the final parsed data from the parses with correct types
|
||
type parser struct {
|
||
sc *bytes.Buffer
|
||
data map[string]interface{}
|
||
reader RunesValueReader
|
||
isjsonval bool
|
||
}
|
||
|
||
func newParser(sc *bytes.Buffer, data map[string]interface{}, stringBool bool) *parser {
|
||
stringConverter := func(rs []rune) (interface{}, error) {
|
||
return typedVal(rs, stringBool), nil
|
||
}
|
||
return &parser{sc: sc, data: data, reader: stringConverter}
|
||
}
|
||
|
||
func newJSONParser(sc *bytes.Buffer, data map[string]interface{}) *parser {
|
||
return &parser{sc: sc, data: data, reader: nil, isjsonval: true}
|
||
}
|
||
|
||
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 {
|
||
for {
|
||
err := t.key(t.data, 0)
|
||
if err == nil {
|
||
continue
|
||
}
|
||
if err == io.EOF {
|
||
return nil
|
||
}
|
||
return err
|
||
}
|
||
}
|
||
|
||
func runeSet(r []rune) map[rune]bool {
|
||
s := make(map[rune]bool, len(r))
|
||
for _, rr := range r {
|
||
s[rr] = true
|
||
}
|
||
return s
|
||
}
|
||
|
||
func (t *parser) key(data map[string]interface{}, nestedNameLevel int) (reterr error) {
|
||
defer func() {
|
||
if r := recover(); r != nil {
|
||
reterr = fmt.Errorf("unable to parse key: %s", r)
|
||
}
|
||
}()
|
||
stop := runeSet([]rune{'=', '[', ',', '.'})
|
||
for {
|
||
switch k, last, err := runesUntil(t.sc, stop); {
|
||
case err != nil:
|
||
if len(k) == 0 {
|
||
return err
|
||
}
|
||
return fmt.Errorf("key %q has no value", string(k))
|
||
//set(data, string(k), "")
|
||
//return err
|
||
case last == '[':
|
||
// We are in a list index context, so we need to set an index.
|
||
i, err := t.keyIndex()
|
||
if err != nil {
|
||
return fmt.Errorf("error parsing index: %w", err)
|
||
}
|
||
kk := string(k)
|
||
// Find or create target list
|
||
list := []interface{}{}
|
||
if _, ok := data[kk]; ok {
|
||
list = data[kk].([]interface{})
|
||
}
|
||
|
||
// Now we need to get the value after the ].
|
||
list, err = t.listItem(list, i, nestedNameLevel)
|
||
set(data, kk, list)
|
||
return err
|
||
case last == '=':
|
||
if t.isjsonval {
|
||
empval, err := t.emptyVal()
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if empval {
|
||
set(data, string(k), nil)
|
||
return nil
|
||
}
|
||
// parse jsonvals by using Go’s JSON standard library
|
||
// Decode is preferred to Unmarshal in order to parse just the json parts of the list key1=jsonval1,key2=jsonval2,...
|
||
// Since Decode has its own buffer that consumes more characters (from underlying t.sc) than the ones actually decoded,
|
||
// we invoke Decode on a separate reader built with a copy of what is left in t.sc. After Decode is executed, we
|
||
// discard in t.sc the chars of the decoded json value (the number of those characters is returned by InputOffset).
|
||
var jsonval interface{}
|
||
dec := json.NewDecoder(strings.NewReader(t.sc.String()))
|
||
if err = dec.Decode(&jsonval); err != nil {
|
||
return err
|
||
}
|
||
set(data, string(k), jsonval)
|
||
if _, err = io.CopyN(io.Discard, t.sc, dec.InputOffset()); err != nil {
|
||
return err
|
||
}
|
||
// skip possible blanks and comma
|
||
_, err = t.emptyVal()
|
||
return err
|
||
}
|
||
//End of key. Consume =, Get value.
|
||
// FIXME: Get value list first
|
||
vl, e := t.valList()
|
||
switch e {
|
||
case nil:
|
||
set(data, string(k), vl)
|
||
return nil
|
||
case io.EOF:
|
||
set(data, string(k), "")
|
||
return e
|
||
case ErrNotList:
|
||
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
|
||
}
|
||
case last == ',':
|
||
// No value given. Set the value to empty string. Return error.
|
||
set(data, string(k), "")
|
||
return fmt.Errorf("key %q has no value (cannot end with ,)", string(k))
|
||
case last == '.':
|
||
// Check value name is within the maximum nested name level
|
||
nestedNameLevel++
|
||
if nestedNameLevel > MaxNestedNameLevel {
|
||
return fmt.Errorf("value name nested level is greater than maximum supported nested level of %d", MaxNestedNameLevel)
|
||
}
|
||
|
||
// First, create or find the target map.
|
||
inner := map[string]interface{}{}
|
||
if _, ok := data[string(k)]; ok {
|
||
inner = data[string(k)].(map[string]interface{})
|
||
}
|
||
|
||
// Recurse
|
||
e := t.key(inner, nestedNameLevel)
|
||
if e == nil && len(inner) == 0 {
|
||
return fmt.Errorf("key map %q has no value", string(k))
|
||
}
|
||
if len(inner) != 0 {
|
||
set(data, string(k), inner)
|
||
}
|
||
return e
|
||
}
|
||
}
|
||
}
|
||
|
||
func set(data map[string]interface{}, key string, val interface{}) {
|
||
// If key is empty, don't set it.
|
||
if len(key) == 0 {
|
||
return
|
||
}
|
||
data[key] = val
|
||
}
|
||
|
||
func setIndex(list []interface{}, index int, val interface{}) (l2 []interface{}, err error) {
|
||
// There are possible index values that are out of range on a target system
|
||
// causing a panic. This will catch the panic and return an error instead.
|
||
// The value of the index that causes a panic varies from system to system.
|
||
defer func() {
|
||
if r := recover(); r != nil {
|
||
err = fmt.Errorf("error processing index %d: %s", index, r)
|
||
}
|
||
}()
|
||
|
||
if index < 0 {
|
||
return list, fmt.Errorf("negative %d index not allowed", index)
|
||
}
|
||
if index > MaxIndex {
|
||
return list, fmt.Errorf("index of %d is greater than maximum supported index of %d", index, MaxIndex)
|
||
}
|
||
if len(list) <= index {
|
||
newlist := make([]interface{}, index+1)
|
||
copy(newlist, list)
|
||
list = newlist
|
||
}
|
||
list[index] = val
|
||
return list, nil
|
||
}
|
||
|
||
func (t *parser) keyIndex() (int, error) {
|
||
// First, get the key.
|
||
stop := runeSet([]rune{']'})
|
||
v, _, err := runesUntil(t.sc, stop)
|
||
if err != nil {
|
||
return 0, err
|
||
}
|
||
// v should be the index
|
||
return strconv.Atoi(string(v))
|
||
|
||
}
|
||
|
||
func (t *parser) listItem(list []interface{}, i, nestedNameLevel int) ([]interface{}, error) {
|
||
if i < 0 {
|
||
return list, fmt.Errorf("negative %d index not allowed", i)
|
||
}
|
||
stop := runeSet([]rune{'[', '.', '='})
|
||
switch k, last, err := runesUntil(t.sc, stop); {
|
||
case len(k) > 0:
|
||
return list, fmt.Errorf("unexpected data at end of array index: %q", k)
|
||
case err != nil:
|
||
return list, err
|
||
case last == '=':
|
||
if t.isjsonval {
|
||
empval, err := t.emptyVal()
|
||
if err != nil {
|
||
return list, err
|
||
}
|
||
if empval {
|
||
return setIndex(list, i, nil)
|
||
}
|
||
// parse jsonvals by using Go’s JSON standard library
|
||
// Decode is preferred to Unmarshal in order to parse just the json parts of the list key1=jsonval1,key2=jsonval2,...
|
||
// Since Decode has its own buffer that consumes more characters (from underlying t.sc) than the ones actually decoded,
|
||
// we invoke Decode on a separate reader built with a copy of what is left in t.sc. After Decode is executed, we
|
||
// discard in t.sc the chars of the decoded json value (the number of those characters is returned by InputOffset).
|
||
var jsonval interface{}
|
||
dec := json.NewDecoder(strings.NewReader(t.sc.String()))
|
||
if err = dec.Decode(&jsonval); err != nil {
|
||
return list, err
|
||
}
|
||
if list, err = setIndex(list, i, jsonval); err != nil {
|
||
return list, err
|
||
}
|
||
if _, err = io.CopyN(io.Discard, t.sc, dec.InputOffset()); err != nil {
|
||
return list, err
|
||
}
|
||
// skip possible blanks and comma
|
||
_, err = t.emptyVal()
|
||
return list, err
|
||
}
|
||
vl, e := t.valList()
|
||
switch e {
|
||
case nil:
|
||
return setIndex(list, i, vl)
|
||
case io.EOF:
|
||
return setIndex(list, i, "")
|
||
case ErrNotList:
|
||
rs, e := t.val()
|
||
if e != nil && e != io.EOF {
|
||
return list, e
|
||
}
|
||
v, e := t.reader(rs)
|
||
if e != nil {
|
||
return list, e
|
||
}
|
||
return setIndex(list, i, v)
|
||
default:
|
||
return list, e
|
||
}
|
||
case last == '[':
|
||
// now we have a nested list. Read the index and handle.
|
||
nextI, err := t.keyIndex()
|
||
if err != nil {
|
||
return list, fmt.Errorf("error parsing index: %w", err)
|
||
}
|
||
var crtList []interface{}
|
||
if len(list) > i {
|
||
// If nested list already exists, take the value of list to next cycle.
|
||
existed := list[i]
|
||
if existed != nil {
|
||
crtList = list[i].([]interface{})
|
||
}
|
||
}
|
||
// Now we need to get the value after the ].
|
||
list2, err := t.listItem(crtList, nextI, nestedNameLevel)
|
||
if err != nil {
|
||
return list, err
|
||
}
|
||
return setIndex(list, i, list2)
|
||
case last == '.':
|
||
// We have a nested object. Send to t.key
|
||
inner := map[string]interface{}{}
|
||
if len(list) > i {
|
||
var ok bool
|
||
inner, ok = list[i].(map[string]interface{})
|
||
if !ok {
|
||
// We have indices out of order. Initialize empty value.
|
||
list[i] = map[string]interface{}{}
|
||
inner = list[i].(map[string]interface{})
|
||
}
|
||
}
|
||
|
||
// Recurse
|
||
e := t.key(inner, nestedNameLevel)
|
||
if e != nil {
|
||
return list, e
|
||
}
|
||
return setIndex(list, i, inner)
|
||
default:
|
||
return nil, fmt.Errorf("parse error: unexpected token %v", last)
|
||
}
|
||
}
|
||
|
||
// check for an empty value
|
||
// read and consume optional spaces until comma or EOF (empty val) or any other char (not empty val)
|
||
// comma and spaces are consumed, while any other char is not consumed
|
||
func (t *parser) emptyVal() (bool, error) {
|
||
for {
|
||
r, _, e := t.sc.ReadRune()
|
||
if e == io.EOF {
|
||
return true, nil
|
||
}
|
||
if e != nil {
|
||
return false, e
|
||
}
|
||
if r == ',' {
|
||
return true, nil
|
||
}
|
||
if !unicode.IsSpace(r) {
|
||
t.sc.UnreadRune()
|
||
return false, nil
|
||
}
|
||
}
|
||
}
|
||
|
||
func (t *parser) val() ([]rune, error) {
|
||
stop := runeSet([]rune{','})
|
||
v, _, err := runesUntil(t.sc, stop)
|
||
return v, err
|
||
}
|
||
|
||
func (t *parser) valList() ([]interface{}, error) {
|
||
r, _, e := t.sc.ReadRune()
|
||
if e != nil {
|
||
return []interface{}{}, e
|
||
}
|
||
|
||
if r != '{' {
|
||
t.sc.UnreadRune()
|
||
return []interface{}{}, ErrNotList
|
||
}
|
||
|
||
list := []interface{}{}
|
||
stop := runeSet([]rune{',', '}'})
|
||
for {
|
||
switch rs, last, err := runesUntil(t.sc, stop); {
|
||
case err != nil:
|
||
if err == io.EOF {
|
||
err = errors.New("list must terminate with '}'")
|
||
}
|
||
return list, err
|
||
case last == '}':
|
||
// If this is followed by ',', consume it.
|
||
if r, _, e := t.sc.ReadRune(); e == nil && r != ',' {
|
||
t.sc.UnreadRune()
|
||
}
|
||
v, e := t.reader(rs)
|
||
list = append(list, v)
|
||
return list, e
|
||
case last == ',':
|
||
v, e := t.reader(rs)
|
||
if e != nil {
|
||
return list, e
|
||
}
|
||
list = append(list, v)
|
||
}
|
||
}
|
||
}
|
||
|
||
func runesUntil(in io.RuneReader, stop map[rune]bool) ([]rune, rune, error) {
|
||
v := []rune{}
|
||
for {
|
||
switch r, _, e := in.ReadRune(); {
|
||
case e != nil:
|
||
return v, r, e
|
||
case inMap(r, stop):
|
||
return v, r, nil
|
||
case r == '\\':
|
||
next, _, e := in.ReadRune()
|
||
if e != nil {
|
||
return v, next, e
|
||
}
|
||
v = append(v, next)
|
||
default:
|
||
v = append(v, r)
|
||
}
|
||
}
|
||
}
|
||
|
||
func inMap(k rune, m map[rune]bool) bool {
|
||
_, ok := m[k]
|
||
return ok
|
||
}
|
||
|
||
func typedVal(v []rune, st bool) interface{} {
|
||
val := string(v)
|
||
|
||
if st {
|
||
return val
|
||
}
|
||
|
||
if strings.EqualFold(val, "true") {
|
||
return true
|
||
}
|
||
|
||
if strings.EqualFold(val, "false") {
|
||
return false
|
||
}
|
||
|
||
if strings.EqualFold(val, "null") {
|
||
return nil
|
||
}
|
||
|
||
if strings.EqualFold(val, "0") {
|
||
return int64(0)
|
||
}
|
||
|
||
// If this value does not start with zero, try parsing it to an int
|
||
if len(val) != 0 && val[0] != '0' {
|
||
if iv, err := strconv.ParseInt(val, 10, 64); err == nil {
|
||
return iv
|
||
}
|
||
}
|
||
|
||
return val
|
||
}
|