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
This commit is contained in:
committed by
Marcus Efraimsson
parent
82f4fc2783
commit
3f144bdd05
@@ -114,11 +114,13 @@ func (val *JSONValue) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
val.Raw = unmarshaled
|
||||
interpolated := make(map[string]interface{})
|
||||
raw := make(map[string]interface{})
|
||||
for key, val := range unmarshaled {
|
||||
interpolated[key] = tranformInterface(val)
|
||||
interpolated[key], raw[key] = transformInterface(val)
|
||||
}
|
||||
|
||||
val.Raw = raw
|
||||
val.value = interpolated
|
||||
return err
|
||||
}
|
||||
@@ -138,11 +140,12 @@ func (val *StringMapValue) UnmarshalYAML(unmarshal func(interface{}) error) erro
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
val.Raw = unmarshaled
|
||||
interpolated := make(map[string]string)
|
||||
raw := make(map[string]string)
|
||||
for key, val := range unmarshaled {
|
||||
interpolated[key] = interpolateValue(val)
|
||||
interpolated[key], raw[key] = interpolateValue(val)
|
||||
}
|
||||
val.Raw = raw
|
||||
val.value = interpolated
|
||||
return err
|
||||
}
|
||||
@@ -151,14 +154,15 @@ func (val *StringMapValue) Value() map[string]string {
|
||||
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
|
||||
// map or slice value instead of modifying them in place.
|
||||
func tranformInterface(i interface{}) interface{} {
|
||||
// map or slice value instead of modifying them in place and also return value without interpolation but with converted
|
||||
// type as a second value.
|
||||
func transformInterface(i interface{}) (interface{}, interface{}) {
|
||||
typeOf := reflect.TypeOf(i)
|
||||
|
||||
if typeOf == nil {
|
||||
return nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
switch typeOf.Kind() {
|
||||
@@ -170,36 +174,43 @@ func tranformInterface(i interface{}) interface{} {
|
||||
return interpolateValue(i.(string))
|
||||
default:
|
||||
// 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{} {
|
||||
var transformed []interface{}
|
||||
func transformSlice(i []interface{}) (interface{}, interface{}) {
|
||||
var transformedSlice []interface{}
|
||||
var rawSlice []interface{}
|
||||
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{} {
|
||||
transformed := make(map[interface{}]interface{})
|
||||
func transformMap(i map[interface{}]interface{}) (interface{}, interface{}) {
|
||||
transformed := make(map[string]interface{})
|
||||
raw := make(map[string]interface{})
|
||||
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
|
||||
// here but in the future something like interpolation from file could be also done here.
|
||||
// For a literal '$', '$$' can be used to avoid interpolation.
|
||||
func interpolateValue(val string) string {
|
||||
func interpolateValue(val string) (string, string) {
|
||||
parts := strings.Split(val, "$$")
|
||||
interpolated := make([]string, len(parts))
|
||||
for i, v := range parts {
|
||||
interpolated[i] = os.ExpandEnv(v)
|
||||
}
|
||||
return strings.Join(interpolated, "$")
|
||||
return strings.Join(interpolated, "$"), val
|
||||
}
|
||||
|
||||
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
|
||||
// value type to convert this string value to appropriate type.
|
||||
func getInterpolated(unmarshal func(interface{}) error) (*interpolated, error) {
|
||||
var raw string
|
||||
err := unmarshal(&raw)
|
||||
var veryRaw string
|
||||
err := unmarshal(&veryRaw)
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -143,26 +143,26 @@ func TestValues(t *testing.T) {
|
||||
`
|
||||
unmarshalingTest(doc, d)
|
||||
|
||||
type anyMap = map[interface{}]interface{}
|
||||
So(d.Val.Value(), ShouldResemble, map[string]interface{}{
|
||||
type stringMap = map[string]interface{}
|
||||
So(d.Val.Value(), ShouldResemble, stringMap{
|
||||
"one": 1,
|
||||
"two": "test",
|
||||
"three": []interface{}{
|
||||
1,
|
||||
"two",
|
||||
anyMap{
|
||||
"three": anyMap{
|
||||
stringMap{
|
||||
"three": stringMap{
|
||||
"inside": "test",
|
||||
},
|
||||
},
|
||||
anyMap{
|
||||
"six": anyMap{
|
||||
stringMap{
|
||||
"six": stringMap{
|
||||
"empty": interface{}(nil),
|
||||
},
|
||||
},
|
||||
},
|
||||
"four": anyMap{
|
||||
"nested": anyMap{
|
||||
"four": stringMap{
|
||||
"nested": stringMap{
|
||||
"onemore": "1",
|
||||
},
|
||||
},
|
||||
@@ -171,25 +171,25 @@ func TestValues(t *testing.T) {
|
||||
"anchored": "1",
|
||||
})
|
||||
|
||||
So(d.Val.Raw, ShouldResemble, map[string]interface{}{
|
||||
So(d.Val.Raw, ShouldResemble, stringMap{
|
||||
"one": 1,
|
||||
"two": "$STRING",
|
||||
"three": []interface{}{
|
||||
1,
|
||||
"two",
|
||||
anyMap{
|
||||
"three": anyMap{
|
||||
stringMap{
|
||||
"three": stringMap{
|
||||
"inside": "$STRING",
|
||||
},
|
||||
},
|
||||
anyMap{
|
||||
"six": anyMap{
|
||||
stringMap{
|
||||
"six": stringMap{
|
||||
"empty": interface{}(nil),
|
||||
},
|
||||
},
|
||||
},
|
||||
"four": anyMap{
|
||||
"nested": anyMap{
|
||||
"four": stringMap{
|
||||
"nested": stringMap{
|
||||
"onemore": "$INT",
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user