Commit 3f144bdd by Andrej Ocenas Committed by Marcus Efraimsson

Provisioning: Fix unmarshaling nested jsonData values (#20399)

Problem was that yaml unmarshal returned nested maps as
 map[interface{}]interface{} which are then not marshal-able 
to json because of that interface{} key type. This adds explicit 
casting of the keys in the yaml value types to string which 
then makes the values marshal-able to JSON in DB.

Fixes: #11537
parent 82f4fc27
...@@ -114,11 +114,13 @@ func (val *JSONValue) UnmarshalYAML(unmarshal func(interface{}) error) error { ...@@ -114,11 +114,13 @@ func (val *JSONValue) UnmarshalYAML(unmarshal func(interface{}) error) error {
if err != nil { if err != nil {
return err return err
} }
val.Raw = unmarshaled
interpolated := make(map[string]interface{}) interpolated := make(map[string]interface{})
raw := make(map[string]interface{})
for key, val := range unmarshaled { for key, val := range unmarshaled {
interpolated[key] = tranformInterface(val) interpolated[key], raw[key] = transformInterface(val)
} }
val.Raw = raw
val.value = interpolated val.value = interpolated
return err return err
} }
...@@ -138,11 +140,12 @@ func (val *StringMapValue) UnmarshalYAML(unmarshal func(interface{}) error) erro ...@@ -138,11 +140,12 @@ func (val *StringMapValue) UnmarshalYAML(unmarshal func(interface{}) error) erro
if err != nil { if err != nil {
return err return err
} }
val.Raw = unmarshaled
interpolated := make(map[string]string) interpolated := make(map[string]string)
raw := make(map[string]string)
for key, val := range unmarshaled { for key, val := range unmarshaled {
interpolated[key] = interpolateValue(val) interpolated[key], raw[key] = interpolateValue(val)
} }
val.Raw = raw
val.value = interpolated val.value = interpolated
return err return err
} }
...@@ -151,14 +154,15 @@ func (val *StringMapValue) Value() map[string]string { ...@@ -151,14 +154,15 @@ func (val *StringMapValue) Value() map[string]string {
return val.value return val.value
} }
// tranformInterface tries to transform any interface type into proper value with env expansion. It travers maps and // transformInterface tries to transform any interface type into proper value with env expansion. It travers maps and
// slices and the actual interpolation is done on all simple string values in the structure. It returns a copy of any // slices and the actual interpolation is done on all simple string values in the structure. It returns a copy of any
// map or slice value instead of modifying them in place. // map or slice value instead of modifying them in place and also return value without interpolation but with converted
func tranformInterface(i interface{}) interface{} { // type as a second value.
func transformInterface(i interface{}) (interface{}, interface{}) {
typeOf := reflect.TypeOf(i) typeOf := reflect.TypeOf(i)
if typeOf == nil { if typeOf == nil {
return nil return nil, nil
} }
switch typeOf.Kind() { switch typeOf.Kind() {
...@@ -170,36 +174,43 @@ func tranformInterface(i interface{}) interface{} { ...@@ -170,36 +174,43 @@ func tranformInterface(i interface{}) interface{} {
return interpolateValue(i.(string)) return interpolateValue(i.(string))
default: default:
// Was int, float or some other value that we do not need to do any transform on. // Was int, float or some other value that we do not need to do any transform on.
return i return i, i
} }
} }
func transformSlice(i []interface{}) interface{} { func transformSlice(i []interface{}) (interface{}, interface{}) {
var transformed []interface{} var transformedSlice []interface{}
var rawSlice []interface{}
for _, val := range i { for _, val := range i {
transformed = append(transformed, tranformInterface(val)) transformed, raw := transformInterface(val)
transformedSlice = append(transformedSlice, transformed)
rawSlice = append(rawSlice, raw)
} }
return transformed return transformedSlice, rawSlice
} }
func transformMap(i map[interface{}]interface{}) interface{} { func transformMap(i map[interface{}]interface{}) (interface{}, interface{}) {
transformed := make(map[interface{}]interface{}) transformed := make(map[string]interface{})
raw := make(map[string]interface{})
for key, val := range i { for key, val := range i {
transformed[key] = tranformInterface(val) stringKey, ok := key.(string)
if ok {
transformed[stringKey], raw[stringKey] = transformInterface(val)
}
} }
return transformed return transformed, raw
} }
// interpolateValue returns final value after interpolation. At the moment only env var interpolation is done // interpolateValue returns final value after interpolation. At the moment only env var interpolation is done
// here but in the future something like interpolation from file could be also done here. // here but in the future something like interpolation from file could be also done here.
// For a literal '$', '$$' can be used to avoid interpolation. // For a literal '$', '$$' can be used to avoid interpolation.
func interpolateValue(val string) string { func interpolateValue(val string) (string, string) {
parts := strings.Split(val, "$$") parts := strings.Split(val, "$$")
interpolated := make([]string, len(parts)) interpolated := make([]string, len(parts))
for i, v := range parts { for i, v := range parts {
interpolated[i] = os.ExpandEnv(v) interpolated[i] = os.ExpandEnv(v)
} }
return strings.Join(interpolated, "$") return strings.Join(interpolated, "$"), val
} }
type interpolated struct { type interpolated struct {
...@@ -210,11 +221,13 @@ type interpolated struct { ...@@ -210,11 +221,13 @@ type interpolated struct {
// getInterpolated unmarshals the value as string and runs interpolation on it. It is the responsibility of each // getInterpolated unmarshals the value as string and runs interpolation on it. It is the responsibility of each
// value type to convert this string value to appropriate type. // value type to convert this string value to appropriate type.
func getInterpolated(unmarshal func(interface{}) error) (*interpolated, error) { func getInterpolated(unmarshal func(interface{}) error) (*interpolated, error) {
var raw string var veryRaw string
err := unmarshal(&raw) err := unmarshal(&veryRaw)
if err != nil { if err != nil {
return &interpolated{}, err return &interpolated{}, err
} }
value := interpolateValue(raw) // We get new raw value here which can have a bit different type, as yaml types nested maps as
// map[interface{}]interface and we want it to be map[string]interface{}
value, raw := interpolateValue(veryRaw)
return &interpolated{raw: raw, value: value}, nil return &interpolated{raw: raw, value: value}, nil
} }
...@@ -143,26 +143,26 @@ func TestValues(t *testing.T) { ...@@ -143,26 +143,26 @@ func TestValues(t *testing.T) {
` `
unmarshalingTest(doc, d) unmarshalingTest(doc, d)
type anyMap = map[interface{}]interface{} type stringMap = map[string]interface{}
So(d.Val.Value(), ShouldResemble, map[string]interface{}{ So(d.Val.Value(), ShouldResemble, stringMap{
"one": 1, "one": 1,
"two": "test", "two": "test",
"three": []interface{}{ "three": []interface{}{
1, 1,
"two", "two",
anyMap{ stringMap{
"three": anyMap{ "three": stringMap{
"inside": "test", "inside": "test",
}, },
}, },
anyMap{ stringMap{
"six": anyMap{ "six": stringMap{
"empty": interface{}(nil), "empty": interface{}(nil),
}, },
}, },
}, },
"four": anyMap{ "four": stringMap{
"nested": anyMap{ "nested": stringMap{
"onemore": "1", "onemore": "1",
}, },
}, },
...@@ -171,25 +171,25 @@ func TestValues(t *testing.T) { ...@@ -171,25 +171,25 @@ func TestValues(t *testing.T) {
"anchored": "1", "anchored": "1",
}) })
So(d.Val.Raw, ShouldResemble, map[string]interface{}{ So(d.Val.Raw, ShouldResemble, stringMap{
"one": 1, "one": 1,
"two": "$STRING", "two": "$STRING",
"three": []interface{}{ "three": []interface{}{
1, 1,
"two", "two",
anyMap{ stringMap{
"three": anyMap{ "three": stringMap{
"inside": "$STRING", "inside": "$STRING",
}, },
}, },
anyMap{ stringMap{
"six": anyMap{ "six": stringMap{
"empty": interface{}(nil), "empty": interface{}(nil),
}, },
}, },
}, },
"four": anyMap{ "four": stringMap{
"nested": anyMap{ "nested": stringMap{
"onemore": "$INT", "onemore": "$INT",
}, },
}, },
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment