mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-21 10:51:29 +00:00
Merge pull request #39418 from wojtek-t/reflection_based_conversion_to_unstructured_2
Automatic merge from submit-queue (batch tested with PRs 39418, 41175, 40355, 41114, 32325) runtime.Object <-> Unstructured conversion Ref #39017
This commit is contained in:
commit
85882ade1d
@ -93,6 +93,7 @@ go_test(
|
||||
"//vendor:k8s.io/apimachinery/pkg/api/testing",
|
||||
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
|
||||
"//vendor:k8s.io/apimachinery/pkg/conversion",
|
||||
"//vendor:k8s.io/apimachinery/pkg/conversion/unstructured",
|
||||
"//vendor:k8s.io/apimachinery/pkg/runtime",
|
||||
"//vendor:k8s.io/apimachinery/pkg/runtime/schema",
|
||||
"//vendor:k8s.io/apimachinery/pkg/runtime/serializer/protobuf",
|
||||
|
@ -28,6 +28,8 @@ import (
|
||||
kapitesting "k8s.io/kubernetes/pkg/api/testing"
|
||||
"k8s.io/kubernetes/pkg/api/v1"
|
||||
|
||||
"k8s.io/apimachinery/pkg/conversion/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/diff"
|
||||
"k8s.io/apimachinery/pkg/util/json"
|
||||
|
||||
@ -95,27 +97,23 @@ func doRoundTrip(t *testing.T, group testapi.TestGroup, kind string) {
|
||||
return
|
||||
}
|
||||
|
||||
// TODO; Enable the following part of test once to/from unstructured
|
||||
// format conversions are implemented.
|
||||
/*
|
||||
newUnstr := make(map[string]interface{})
|
||||
err = unstructured.NewConverter().ToUnstructured(item, &newUnstr)
|
||||
if err != nil {
|
||||
t.Errorf("ToUnstructured failed: %v", err)
|
||||
return
|
||||
}
|
||||
newUnstr := make(map[string]interface{})
|
||||
err = unstructured.NewConverter().ToUnstructured(item, &newUnstr)
|
||||
if err != nil {
|
||||
t.Errorf("ToUnstructured failed: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
newObj := reflect.New(reflect.TypeOf(item).Elem()).Interface().(runtime.Object)
|
||||
err = unstructured.NewConverter().FromUnstructured(newUnstr, newObj)
|
||||
if err != nil {
|
||||
t.Errorf("FromUnstructured failed: %v", err)
|
||||
return
|
||||
}
|
||||
newObj := reflect.New(reflect.TypeOf(item).Elem()).Interface().(runtime.Object)
|
||||
err = unstructured.NewConverter().FromUnstructured(newUnstr, newObj)
|
||||
if err != nil {
|
||||
t.Errorf("FromUnstructured failed: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if !apiequality.Semantic.DeepEqual(item, newObj) {
|
||||
t.Errorf("Object changed, diff: %v", diff.ObjectReflectDiff(item, newObj))
|
||||
}
|
||||
*/
|
||||
if !apiequality.Semantic.DeepEqual(item, newObj) {
|
||||
t.Errorf("Object changed, diff: %v", diff.ObjectReflectDiff(item, newObj))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRoundTrip(t *testing.T) {
|
||||
@ -135,11 +133,8 @@ func TestRoundTrip(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO; Enable the following benchmark once to/from unstructured
|
||||
// format conversions are implemented.
|
||||
/*
|
||||
func BenchmarkToFromUnstructured(b *testing.B) {
|
||||
items := benchmarkItems()
|
||||
items := benchmarkItems(b)
|
||||
size := len(items)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
@ -154,7 +149,6 @@ func BenchmarkToFromUnstructured(b *testing.B) {
|
||||
}
|
||||
b.StopTimer()
|
||||
}
|
||||
*/
|
||||
|
||||
func BenchmarkToFromUnstructuredViaJSON(b *testing.B) {
|
||||
items := benchmarkItems(b)
|
||||
|
@ -0,0 +1,517 @@
|
||||
/*
|
||||
Copyright 2017 The Kubernetes 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 unstructured
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
encodingjson "encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/json"
|
||||
)
|
||||
|
||||
type structField struct {
|
||||
structType reflect.Type
|
||||
field int
|
||||
}
|
||||
|
||||
type fieldInfo struct {
|
||||
name string
|
||||
nameValue reflect.Value
|
||||
}
|
||||
|
||||
type fieldsCacheMap map[structField]*fieldInfo
|
||||
|
||||
type fieldsCache struct {
|
||||
sync.Mutex
|
||||
value atomic.Value
|
||||
}
|
||||
|
||||
func newFieldsCache() *fieldsCache {
|
||||
cache := &fieldsCache{}
|
||||
cache.value.Store(make(fieldsCacheMap))
|
||||
return cache
|
||||
}
|
||||
|
||||
var (
|
||||
marshalerType = reflect.TypeOf(new(encodingjson.Marshaler)).Elem()
|
||||
unmarshalerType = reflect.TypeOf(new(encodingjson.Unmarshaler)).Elem()
|
||||
mapStringInterfaceType = reflect.TypeOf(map[string]interface{}{})
|
||||
fieldCache = newFieldsCache()
|
||||
)
|
||||
|
||||
// Converter knows how to convert betweek runtime.Object and
|
||||
// Unstructured in both ways.
|
||||
type Converter struct {
|
||||
}
|
||||
|
||||
func NewConverter() *Converter {
|
||||
return &Converter{}
|
||||
}
|
||||
|
||||
func (c *Converter) FromUnstructured(u map[string]interface{}, obj runtime.Object) error {
|
||||
return fromUnstructured(reflect.ValueOf(u), reflect.ValueOf(obj).Elem())
|
||||
}
|
||||
|
||||
func fromUnstructured(sv, dv reflect.Value) error {
|
||||
sv = unwrapInterface(sv)
|
||||
if !sv.IsValid() {
|
||||
dv.Set(reflect.Zero(dv.Type()))
|
||||
return nil
|
||||
}
|
||||
st, dt := sv.Type(), dv.Type()
|
||||
|
||||
switch dt.Kind() {
|
||||
case reflect.Map, reflect.Slice, reflect.Ptr, reflect.Struct, reflect.Interface:
|
||||
// Those require non-trivial conversion.
|
||||
default:
|
||||
// This should handle all simple types.
|
||||
if st.AssignableTo(dt) {
|
||||
dv.Set(sv)
|
||||
return nil
|
||||
}
|
||||
// We cannot simply use "ConvertibleTo", as JSON doesn't support conversions
|
||||
// between those four groups: bools, integers, floats and string. We need to
|
||||
// do the same.
|
||||
if st.ConvertibleTo(dt) {
|
||||
switch st.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
|
||||
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
switch dt.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
|
||||
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
dv.Set(sv.Convert(dt))
|
||||
return nil
|
||||
}
|
||||
case reflect.Float32, reflect.Float64:
|
||||
switch dt.Kind() {
|
||||
case reflect.Float32, reflect.Float64:
|
||||
dv.Set(sv.Convert(dt))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("cannot convert %s to %d", st.String(), dt.String())
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the object has a custom JSON marshaller/unmarshaller.
|
||||
if reflect.PtrTo(dt).Implements(unmarshalerType) {
|
||||
data, err := json.Marshal(sv.Interface())
|
||||
if err != nil {
|
||||
return fmt.Errorf("error encoding %s to json: %v", st.String(), err)
|
||||
}
|
||||
unmarshaler := dv.Addr().Interface().(encodingjson.Unmarshaler)
|
||||
return unmarshaler.UnmarshalJSON(data)
|
||||
}
|
||||
|
||||
switch dt.Kind() {
|
||||
case reflect.Map:
|
||||
return mapFromUnstructured(sv, dv)
|
||||
case reflect.Slice:
|
||||
return sliceFromUnstructured(sv, dv)
|
||||
case reflect.Ptr:
|
||||
return pointerFromUnstructured(sv, dv)
|
||||
case reflect.Struct:
|
||||
return structFromUnstructured(sv, dv)
|
||||
case reflect.Interface:
|
||||
return interfaceFromUnstructured(sv, dv)
|
||||
default:
|
||||
return fmt.Errorf("unrecognized type: %v", dt.Kind())
|
||||
}
|
||||
}
|
||||
|
||||
func fieldInfoFromField(structType reflect.Type, field int) *fieldInfo {
|
||||
fieldCacheMap := fieldCache.value.Load().(fieldsCacheMap)
|
||||
if info, ok := fieldCacheMap[structField{structType, field}]; ok {
|
||||
return info
|
||||
}
|
||||
|
||||
// Cache miss - we need to compute the field name.
|
||||
info := &fieldInfo{}
|
||||
typeField := structType.Field(field)
|
||||
jsonTag := typeField.Tag.Get("json")
|
||||
if len(jsonTag) == 0 {
|
||||
// Make the first character lowercase.
|
||||
if typeField.Name == "" {
|
||||
info.name = typeField.Name
|
||||
} else {
|
||||
info.name = strings.ToLower(typeField.Name[:1]) + typeField.Name[1:]
|
||||
}
|
||||
} else {
|
||||
info.name = strings.Split(jsonTag, ",")[0]
|
||||
}
|
||||
info.nameValue = reflect.ValueOf(info.name)
|
||||
|
||||
fieldCache.Lock()
|
||||
defer fieldCache.Unlock()
|
||||
fieldCacheMap = fieldCache.value.Load().(fieldsCacheMap)
|
||||
newFieldCacheMap := make(fieldsCacheMap)
|
||||
for k, v := range fieldCacheMap {
|
||||
newFieldCacheMap[k] = v
|
||||
}
|
||||
newFieldCacheMap[structField{structType, field}] = info
|
||||
fieldCache.value.Store(newFieldCacheMap)
|
||||
return info
|
||||
}
|
||||
|
||||
func unwrapInterface(v reflect.Value) reflect.Value {
|
||||
for v.Kind() == reflect.Interface {
|
||||
v = v.Elem()
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func mapFromUnstructured(sv, dv reflect.Value) error {
|
||||
st, dt := sv.Type(), dv.Type()
|
||||
if st.Kind() != reflect.Map {
|
||||
return fmt.Errorf("cannot restore map from %v", st.Kind())
|
||||
}
|
||||
|
||||
if !st.Key().AssignableTo(dt.Key()) && !st.Key().ConvertibleTo(dt.Key()) {
|
||||
return fmt.Errorf("cannot copy map with non-assignable keys: %v %v", st.Key(), dt.Key())
|
||||
}
|
||||
|
||||
if sv.IsNil() {
|
||||
dv.Set(reflect.Zero(dt))
|
||||
return nil
|
||||
}
|
||||
dv.Set(reflect.MakeMap(dt))
|
||||
for _, key := range sv.MapKeys() {
|
||||
value := reflect.New(dt.Elem()).Elem()
|
||||
if val := unwrapInterface(sv.MapIndex(key)); val.IsValid() {
|
||||
if err := fromUnstructured(val, value); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
value.Set(reflect.Zero(dt.Elem()))
|
||||
}
|
||||
if st.Key().AssignableTo(dt.Key()) {
|
||||
dv.SetMapIndex(key, value)
|
||||
} else {
|
||||
dv.SetMapIndex(key.Convert(dt.Key()), value)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func sliceFromUnstructured(sv, dv reflect.Value) error {
|
||||
st, dt := sv.Type(), dv.Type()
|
||||
if st.Kind() == reflect.String && dt.Elem().Kind() == reflect.Uint8 {
|
||||
// We store original []byte representation as string.
|
||||
// This conversion is allowed, but we need to be careful about
|
||||
// marshaling data appropriately.
|
||||
if len(sv.Interface().(string)) > 0 {
|
||||
marshalled, err := json.Marshal(sv.Interface())
|
||||
if err != nil {
|
||||
return fmt.Errorf("error encoding %s to json: %v", st, err)
|
||||
}
|
||||
// TODO: Is this Unmarshal needed?
|
||||
var data []byte
|
||||
err = json.Unmarshal(marshalled, &data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error decoding from json: %v", err)
|
||||
}
|
||||
dv.SetBytes(data)
|
||||
} else {
|
||||
dv.Set(reflect.Zero(dt))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if st.Kind() != reflect.Slice {
|
||||
return fmt.Errorf("cannot restore slice from %v", st.Kind())
|
||||
}
|
||||
|
||||
if sv.IsNil() {
|
||||
dv.Set(reflect.Zero(dt))
|
||||
return nil
|
||||
}
|
||||
dv.Set(reflect.MakeSlice(dt, sv.Len(), sv.Cap()))
|
||||
for i := 0; i < sv.Len(); i++ {
|
||||
if err := fromUnstructured(sv.Index(i), dv.Index(i)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func pointerFromUnstructured(sv, dv reflect.Value) error {
|
||||
st, dt := sv.Type(), dv.Type()
|
||||
|
||||
if st.Kind() == reflect.Ptr && sv.IsNil() {
|
||||
dv.Set(reflect.Zero(dt))
|
||||
return nil
|
||||
}
|
||||
dv.Set(reflect.New(dt.Elem()))
|
||||
switch st.Kind() {
|
||||
case reflect.Ptr, reflect.Interface:
|
||||
return fromUnstructured(sv.Elem(), dv.Elem())
|
||||
default:
|
||||
return fromUnstructured(sv, dv.Elem())
|
||||
}
|
||||
}
|
||||
|
||||
func structFromUnstructured(sv, dv reflect.Value) error {
|
||||
st, dt := sv.Type(), dv.Type()
|
||||
if st.Kind() != reflect.Map {
|
||||
return fmt.Errorf("cannot restore struct from: %v", st.Kind())
|
||||
}
|
||||
|
||||
for i := 0; i < dt.NumField(); i++ {
|
||||
fieldInfo := fieldInfoFromField(dt, i)
|
||||
fv := dv.Field(i)
|
||||
|
||||
if len(fieldInfo.name) == 0 {
|
||||
// This field is inlined.
|
||||
if err := fromUnstructured(sv, fv); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
value := unwrapInterface(sv.MapIndex(fieldInfo.nameValue))
|
||||
if value.IsValid() {
|
||||
if err := fromUnstructured(value, fv); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
fv.Set(reflect.Zero(fv.Type()))
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func interfaceFromUnstructured(sv, dv reflect.Value) error {
|
||||
// TODO: Is this conversion safe?
|
||||
dv.Set(sv)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Converter) ToUnstructured(obj runtime.Object, u *map[string]interface{}) error {
|
||||
return toUnstructured(reflect.ValueOf(obj).Elem(), reflect.ValueOf(u).Elem())
|
||||
}
|
||||
|
||||
func toUnstructured(sv, dv reflect.Value) error {
|
||||
st, dt := sv.Type(), dv.Type()
|
||||
|
||||
// Check if the object has a custom JSON marshaller/unmarshaller.
|
||||
if st.Implements(marshalerType) {
|
||||
if sv.Kind() == reflect.Ptr && sv.IsNil() {
|
||||
// We're done - we don't need to store anything.
|
||||
return nil
|
||||
}
|
||||
|
||||
marshaler := sv.Interface().(encodingjson.Marshaler)
|
||||
data, err := marshaler.MarshalJSON()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if bytes.Equal(data, []byte("null")) {
|
||||
// We're done - we don't need to store anything.
|
||||
} else {
|
||||
switch {
|
||||
case len(data) > 0 && data[0] == '"':
|
||||
var result string
|
||||
err := json.Unmarshal(data, &result)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error decoding from json: %v", err)
|
||||
}
|
||||
dv.Set(reflect.ValueOf(result))
|
||||
case len(data) > 0 && data[0] == '{':
|
||||
result := make(map[string]interface{})
|
||||
err := json.Unmarshal(data, &result)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error decoding from json: %v", err)
|
||||
}
|
||||
dv.Set(reflect.ValueOf(result))
|
||||
default:
|
||||
var result int64
|
||||
err := json.Unmarshal(data, &result)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error decoding from json: %v", err)
|
||||
}
|
||||
dv.Set(reflect.ValueOf(result))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
switch st.Kind() {
|
||||
case reflect.String, reflect.Bool,
|
||||
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
|
||||
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
|
||||
reflect.Float32, reflect.Float64:
|
||||
if dt.Kind() == reflect.Interface && dv.NumMethod() == 0 {
|
||||
dv.Set(reflect.New(st))
|
||||
}
|
||||
dv.Set(sv)
|
||||
return nil
|
||||
case reflect.Map:
|
||||
return mapToUnstructured(sv, dv)
|
||||
case reflect.Slice:
|
||||
return sliceToUnstructured(sv, dv)
|
||||
case reflect.Ptr:
|
||||
return pointerToUnstructured(sv, dv)
|
||||
case reflect.Struct:
|
||||
return structToUnstructured(sv, dv)
|
||||
case reflect.Interface:
|
||||
return interfaceToUnstructured(sv, dv)
|
||||
default:
|
||||
return fmt.Errorf("unrecognized type: %v", st.Kind())
|
||||
}
|
||||
}
|
||||
|
||||
func mapToUnstructured(sv, dv reflect.Value) error {
|
||||
st, dt := sv.Type(), dv.Type()
|
||||
if sv.IsNil() {
|
||||
dv.Set(reflect.Zero(dt))
|
||||
return nil
|
||||
}
|
||||
if dt.Kind() == reflect.Interface && dv.NumMethod() == 0 {
|
||||
if st.Key().Kind() == reflect.String {
|
||||
switch st.Elem().Kind() {
|
||||
// TODO It should be possible to reuse the slice for primitive types.
|
||||
// However, it is panicing in the following form.
|
||||
// case reflect.String, reflect.Bool,
|
||||
// reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
|
||||
// reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
// sv.Set(sv)
|
||||
// return nil
|
||||
default:
|
||||
// We need to do a proper conversion.
|
||||
}
|
||||
}
|
||||
dv.Set(reflect.MakeMap(mapStringInterfaceType))
|
||||
dv = dv.Elem()
|
||||
dt = dv.Type()
|
||||
}
|
||||
if dt.Kind() != reflect.Map {
|
||||
return fmt.Errorf("cannot convert struct to: %v", dt.Kind())
|
||||
}
|
||||
|
||||
if !st.Key().AssignableTo(dt.Key()) && !st.Key().ConvertibleTo(dt.Key()) {
|
||||
return fmt.Errorf("cannot copy map with non-assignable keys: %v %v", st.Key(), dt.Key())
|
||||
}
|
||||
|
||||
for _, key := range sv.MapKeys() {
|
||||
value := reflect.New(dt.Elem()).Elem()
|
||||
if err := toUnstructured(sv.MapIndex(key), value); err != nil {
|
||||
return err
|
||||
}
|
||||
if st.Key().AssignableTo(dt.Key()) {
|
||||
dv.SetMapIndex(key, value)
|
||||
} else {
|
||||
dv.SetMapIndex(key.Convert(dt.Key()), value)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func sliceToUnstructured(sv, dv reflect.Value) error {
|
||||
st, dt := sv.Type(), dv.Type()
|
||||
if sv.IsNil() {
|
||||
dv.Set(reflect.Zero(dt))
|
||||
return nil
|
||||
}
|
||||
if dt.Kind() == reflect.Interface && dv.NumMethod() == 0 {
|
||||
switch st.Elem().Kind() {
|
||||
// TODO It should be possible to reuse the slice for primitive types.
|
||||
// However, it is panicing in the following form.
|
||||
// case reflect.String, reflect.Bool,
|
||||
// reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
|
||||
// reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
// sv.Set(sv)
|
||||
// return nil
|
||||
default:
|
||||
// We need to do a proper conversion.
|
||||
dv.Set(reflect.MakeSlice(reflect.SliceOf(dt), sv.Len(), sv.Cap()))
|
||||
dv = dv.Elem()
|
||||
dt = dv.Type()
|
||||
}
|
||||
}
|
||||
if dt.Kind() != reflect.Slice {
|
||||
return fmt.Errorf("cannot convert slice to: %v", dt.Kind())
|
||||
}
|
||||
for i := 0; i < sv.Len(); i++ {
|
||||
if err := toUnstructured(sv.Index(i), dv.Index(i)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func pointerToUnstructured(sv, dv reflect.Value) error {
|
||||
if sv.IsNil() {
|
||||
// We're done - we don't need to store anything.
|
||||
return nil
|
||||
}
|
||||
return toUnstructured(sv.Elem(), dv)
|
||||
}
|
||||
|
||||
func structToUnstructured(sv, dv reflect.Value) error {
|
||||
st, dt := sv.Type(), dv.Type()
|
||||
if dt.Kind() == reflect.Interface && dv.NumMethod() == 0 {
|
||||
dv.Set(reflect.MakeMap(mapStringInterfaceType))
|
||||
dv = dv.Elem()
|
||||
dt = dv.Type()
|
||||
}
|
||||
if dt.Kind() != reflect.Map {
|
||||
return fmt.Errorf("cannot convert struct to: %v", dt.Kind())
|
||||
}
|
||||
realMap := dv.Interface().(map[string]interface{})
|
||||
|
||||
for i := 0; i < st.NumField(); i++ {
|
||||
fieldInfo := fieldInfoFromField(st, i)
|
||||
fv := sv.Field(i)
|
||||
|
||||
if len(fieldInfo.name) == 0 {
|
||||
// This field is inlined.
|
||||
if err := toUnstructured(fv, dv); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
if !fv.IsValid() {
|
||||
// No fource field, skip.
|
||||
continue
|
||||
}
|
||||
switch fv.Type().Kind() {
|
||||
case reflect.String, reflect.Bool,
|
||||
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
|
||||
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
realMap[fieldInfo.name] = fv.Interface()
|
||||
default:
|
||||
subv := reflect.New(dt.Elem()).Elem()
|
||||
if err := toUnstructured(fv, subv); err != nil {
|
||||
return err
|
||||
}
|
||||
dv.SetMapIndex(fieldInfo.nameValue, subv)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func interfaceToUnstructured(sv, dv reflect.Value) error {
|
||||
if !sv.IsValid() || sv.IsNil() {
|
||||
dv.Set(reflect.Zero(dv.Type()))
|
||||
return nil
|
||||
}
|
||||
return toUnstructured(sv.Elem(), dv)
|
||||
}
|
@ -0,0 +1,440 @@
|
||||
/*
|
||||
Copyright 2015 The Kubernetes 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 unstructured
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/diff"
|
||||
"k8s.io/apimachinery/pkg/util/json"
|
||||
)
|
||||
|
||||
// Definte a number of test types.
|
||||
type A struct {
|
||||
A int `json:"aa,omitempty"`
|
||||
B string `json:"ab,omitempty"`
|
||||
C bool `json:"ac,omitempty"`
|
||||
}
|
||||
|
||||
type B struct {
|
||||
A A `json:"ba"`
|
||||
B string `json:"bb"`
|
||||
C map[string]string `json:"bc"`
|
||||
D []string `json:"bd"`
|
||||
}
|
||||
|
||||
type C struct {
|
||||
A []A `json:"ca"`
|
||||
B B `json:",inline"`
|
||||
C string `json:"cc"`
|
||||
D *int64 `json:"cd"`
|
||||
E map[string]int `json:"ce"`
|
||||
F []bool `json:"cf"`
|
||||
G []int `json"cg"`
|
||||
H float32 `json:ch"`
|
||||
I []interface{} `json:"ci"`
|
||||
}
|
||||
|
||||
type D struct {
|
||||
A []interface{} `json:"da"`
|
||||
}
|
||||
|
||||
type E struct {
|
||||
A interface{} `json:"ea"`
|
||||
}
|
||||
|
||||
type F struct {
|
||||
A string `json:"fa"`
|
||||
B map[string]string `json:"fb"`
|
||||
C []A `json:"fc"`
|
||||
D int `json:"fd"`
|
||||
E float32 `json:"fe"`
|
||||
F []string `json:"ff"`
|
||||
G []int `json:"fg"`
|
||||
H []bool `json:"fh"`
|
||||
I []float32 `json:"fi"`
|
||||
}
|
||||
|
||||
// Implement runtime.Object to make types usable for tests.
|
||||
|
||||
func (c *C) GetObjectKind() schema.ObjectKind {
|
||||
return schema.EmptyObjectKind
|
||||
}
|
||||
|
||||
func (d *D) GetObjectKind() schema.ObjectKind {
|
||||
return schema.EmptyObjectKind
|
||||
}
|
||||
|
||||
func (e *E) GetObjectKind() schema.ObjectKind {
|
||||
return schema.EmptyObjectKind
|
||||
}
|
||||
|
||||
func (f *F) GetObjectKind() schema.ObjectKind {
|
||||
return schema.EmptyObjectKind
|
||||
}
|
||||
|
||||
func doRoundTrip(t *testing.T, item runtime.Object) {
|
||||
data, err := json.Marshal(item)
|
||||
if err != nil {
|
||||
t.Errorf("Error when marshaling object: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
unstr := make(map[string]interface{})
|
||||
err = json.Unmarshal(data, &unstr)
|
||||
if err != nil {
|
||||
t.Errorf("Error when unmarshaling to unstructured: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
data, err = json.Marshal(unstr)
|
||||
if err != nil {
|
||||
t.Errorf("Error when marshaling unstructured: %v", err)
|
||||
return
|
||||
}
|
||||
unmarshalledObj := reflect.New(reflect.TypeOf(item).Elem()).Interface()
|
||||
err = json.Unmarshal(data, &unmarshalledObj)
|
||||
if err != nil {
|
||||
t.Errorf("Error when unmarshaling to object: %v", err)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(item, unmarshalledObj) {
|
||||
t.Errorf("Object changed during JSON operations, diff: %v", diff.ObjectReflectDiff(item, unmarshalledObj))
|
||||
return
|
||||
}
|
||||
|
||||
newUnstr := make(map[string]interface{})
|
||||
err = NewConverter().ToUnstructured(item, &newUnstr)
|
||||
if err != nil {
|
||||
t.Errorf("ToUnstructured failed: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
newObj := reflect.New(reflect.TypeOf(item).Elem()).Interface().(runtime.Object)
|
||||
err = NewConverter().FromUnstructured(newUnstr, newObj)
|
||||
if err != nil {
|
||||
t.Errorf("FromUnstructured failed: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(item, newObj) {
|
||||
t.Errorf("Object changed, diff: %v", diff.ObjectReflectDiff(item, newObj))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRoundTrip(t *testing.T) {
|
||||
intVal := int64(42)
|
||||
testCases := []struct{
|
||||
obj runtime.Object
|
||||
}{
|
||||
{
|
||||
// This (among others) tests nil map, slice and pointer.
|
||||
obj: &C{
|
||||
C: "ccc",
|
||||
},
|
||||
},
|
||||
{
|
||||
// This (among others) tests empty map and slice.
|
||||
obj: &C{
|
||||
A: []A{},
|
||||
C: "ccc",
|
||||
E: map[string]int{},
|
||||
I: []interface{}{},
|
||||
},
|
||||
},
|
||||
{
|
||||
obj: &C{
|
||||
A: []A{
|
||||
{
|
||||
A: 1,
|
||||
B: "11",
|
||||
C: true,
|
||||
},
|
||||
{
|
||||
A: 2,
|
||||
B: "22",
|
||||
C: false,
|
||||
},
|
||||
},
|
||||
B: B{
|
||||
A: A{
|
||||
A: 3,
|
||||
B: "33",
|
||||
},
|
||||
B: "bbb",
|
||||
C: map[string]string{
|
||||
"k1": "v1",
|
||||
"k2": "v2",
|
||||
},
|
||||
D: []string{"s1", "s2"},
|
||||
},
|
||||
C: "ccc",
|
||||
D: &intVal,
|
||||
E: map[string]int{
|
||||
"k1": 1,
|
||||
"k2": 2,
|
||||
},
|
||||
F: []bool{true, false, false},
|
||||
G: []int{1, 2, 5},
|
||||
H: 3.3,
|
||||
I: []interface{}{nil, nil, nil},
|
||||
},
|
||||
},
|
||||
{
|
||||
// Test slice of interface{} with empty slices.
|
||||
obj: &D{
|
||||
A: []interface{}{[]interface{}{}, []interface{}{}},
|
||||
},
|
||||
},
|
||||
{
|
||||
// Test slice of interface{} with different values.
|
||||
obj: &D{
|
||||
A: []interface{}{3.0, "3.0", nil},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for i := range testCases {
|
||||
doRoundTrip(t, testCases[i].obj)
|
||||
if t.Failed() {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Verifies that:
|
||||
// 1) serialized json -> object
|
||||
// 2) serialized json -> map[string]interface{} -> object
|
||||
// produces the same object.
|
||||
func doUnrecognized(t *testing.T, jsonData string, item runtime.Object, expectedErr error) {
|
||||
unmarshalledObj := reflect.New(reflect.TypeOf(item).Elem()).Interface()
|
||||
err := json.Unmarshal([]byte(jsonData), &unmarshalledObj)
|
||||
if (err != nil) != (expectedErr != nil) {
|
||||
t.Errorf("Unexpected error when unmarshaling to object: %v, expected: %v", err, expectedErr)
|
||||
return
|
||||
}
|
||||
|
||||
unstr := make(map[string]interface{})
|
||||
err = json.Unmarshal([]byte(jsonData), &unstr)
|
||||
if err != nil {
|
||||
t.Errorf("Error when unmarshaling to unstructured: %v", err)
|
||||
return
|
||||
}
|
||||
newObj := reflect.New(reflect.TypeOf(item).Elem()).Interface().(runtime.Object)
|
||||
err = NewConverter().FromUnstructured(unstr, newObj)
|
||||
if (err != nil) != (expectedErr != nil) {
|
||||
t.Errorf("Unexpected error in FromUnstructured: %v, expected: %v", err, expectedErr)
|
||||
}
|
||||
|
||||
if expectedErr == nil && !reflect.DeepEqual(unmarshalledObj, newObj) {
|
||||
t.Errorf("Object changed, diff: %v", diff.ObjectReflectDiff(unmarshalledObj, newObj))
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnrecognized(t *testing.T) {
|
||||
testCases := []struct{
|
||||
data string
|
||||
obj runtime.Object
|
||||
err error
|
||||
}{
|
||||
{
|
||||
data: "{\"da\":[3.0,\"3.0\",null]}",
|
||||
obj: &D{},
|
||||
},
|
||||
{
|
||||
data: "{\"ea\":[3.0,\"3.0\",null]}",
|
||||
obj: &E{},
|
||||
},
|
||||
{
|
||||
data: "{\"ea\":[null,null,null]}",
|
||||
obj: &E{},
|
||||
},
|
||||
{
|
||||
data: "{\"ea\":[[],[null]]}",
|
||||
obj: &E{},
|
||||
},
|
||||
{
|
||||
data: "{\"ea\":{\"a\":[],\"b\":null}}",
|
||||
obj: &E{},
|
||||
},
|
||||
{
|
||||
data: "{\"fa\":\"fa\",\"fb\":{\"a\":\"a\"}}",
|
||||
obj: &F{},
|
||||
},
|
||||
{
|
||||
data: "{\"fa\":\"fa\",\"fb\":{\"a\":null}}",
|
||||
obj: &F{},
|
||||
},
|
||||
{
|
||||
data: "{\"fc\":[null]}",
|
||||
obj: &F{},
|
||||
},
|
||||
{
|
||||
data: "{\"fc\":[{\"aa\":123,\"ab\":\"bbb\"}]}",
|
||||
obj: &F{},
|
||||
},
|
||||
{
|
||||
// Only unknown fields
|
||||
data: "{\"fx\":[{\"aa\":123,\"ab\":\"bbb\"}],\"fz\":123}",
|
||||
obj: &F{},
|
||||
},
|
||||
{
|
||||
data: "{\"fc\":[{\"aa\":\"aaa\",\"ab\":\"bbb\"}]}",
|
||||
obj: &F{},
|
||||
err: fmt.Errorf("json: cannot unmarshal string into Go value of type int"),
|
||||
},
|
||||
{
|
||||
data: "{\"fd\":123,\"fe\":3.5}",
|
||||
obj: &F{},
|
||||
},
|
||||
{
|
||||
data: "{\"ff\":[\"abc\"],\"fg\":[123],\"fh\":[true,false]}",
|
||||
obj: &F{},
|
||||
},
|
||||
{
|
||||
// Invalid string data
|
||||
data: "{\"fa\":123}",
|
||||
obj: &F{},
|
||||
err: fmt.Errorf("json: cannot unmarshal number into Go value of type string"),
|
||||
},
|
||||
{
|
||||
// Invalid string data
|
||||
data: "{\"fa\":13.5}",
|
||||
obj: &F{},
|
||||
err: fmt.Errorf("json: cannot unmarshal number into Go value of type string"),
|
||||
},
|
||||
{
|
||||
// Invalid string data
|
||||
data: "{\"fa\":true}",
|
||||
obj: &F{},
|
||||
err: fmt.Errorf("json: cannot unmarshal bool into Go value of type string"),
|
||||
},
|
||||
{
|
||||
// Invalid []string data
|
||||
data: "{\"ff\":123}",
|
||||
obj: &F{},
|
||||
err: fmt.Errorf("json: cannot unmarshal number into Go value of type []string"),
|
||||
},
|
||||
{
|
||||
// Invalid []string data
|
||||
data: "{\"ff\":3.5}",
|
||||
obj: &F{},
|
||||
err: fmt.Errorf("json: cannot unmarshal number into Go value of type []string"),
|
||||
},
|
||||
{
|
||||
// Invalid []string data
|
||||
data: "{\"ff\":[123,345]}",
|
||||
obj: &F{},
|
||||
err: fmt.Errorf("json: cannot unmarshal number into Go value of type string"),
|
||||
},
|
||||
{
|
||||
// Invalid []int data
|
||||
data: "{\"fg\":123}",
|
||||
obj: &F{},
|
||||
err: fmt.Errorf("json: cannot unmarshal number into Go value of type []int"),
|
||||
},
|
||||
{
|
||||
// Invalid []int data
|
||||
data: "{\"fg\":\"abc\"}",
|
||||
obj: &F{},
|
||||
err: fmt.Errorf("json: cannot unmarshal string into Go value of type []int"),
|
||||
},
|
||||
{
|
||||
// Invalid []int data
|
||||
data: "{\"fg\":[\"abc\"]}",
|
||||
obj: &F{},
|
||||
err: fmt.Errorf("json: cannot unmarshal string into Go value of type int"),
|
||||
},
|
||||
{
|
||||
// Invalid []int data
|
||||
data: "{\"fg\":[3.5]}",
|
||||
obj: &F{},
|
||||
err: fmt.Errorf("json: cannot unmarshal number 3.5 into Go value of type int"),
|
||||
},
|
||||
{
|
||||
// Invalid []int data
|
||||
data: "{\"fg\":[true,false]}",
|
||||
obj: &F{},
|
||||
err: fmt.Errorf("json: cannot unmarshal number 3.5 into Go value of type int"),
|
||||
},
|
||||
{
|
||||
// Invalid []bool data
|
||||
data: "{\"fh\":123}",
|
||||
obj: &F{},
|
||||
err: fmt.Errorf("json: cannot unmarshal number into Go value of type []bool"),
|
||||
},
|
||||
{
|
||||
// Invalid []bool data
|
||||
data: "{\"fh\":\"abc\"}",
|
||||
obj: &F{},
|
||||
err: fmt.Errorf("json: cannot unmarshal string into Go value of type []bool"),
|
||||
},
|
||||
{
|
||||
// Invalid []bool data
|
||||
data: "{\"fh\":[\"abc\"]}",
|
||||
obj: &F{},
|
||||
err: fmt.Errorf("json: cannot unmarshal string into Go value of type bool"),
|
||||
},
|
||||
{
|
||||
// Invalid []bool data
|
||||
data: "{\"fh\":[3.5]}",
|
||||
obj: &F{},
|
||||
err: fmt.Errorf("json: cannot unmarshal number into Go value of type bool"),
|
||||
},
|
||||
{
|
||||
// Invalid []bool data
|
||||
data: "{\"fh\":[123]}",
|
||||
obj: &F{},
|
||||
err: fmt.Errorf("json: cannot unmarshal number into Go value of type bool"),
|
||||
},
|
||||
{
|
||||
// Invalid []float data
|
||||
data: "{\"fi\":123}",
|
||||
obj: &F{},
|
||||
err: fmt.Errorf("json: cannot unmarshal number into Go value of type []float32"),
|
||||
},
|
||||
{
|
||||
// Invalid []float data
|
||||
data: "{\"fi\":\"abc\"}",
|
||||
obj: &F{},
|
||||
err: fmt.Errorf("json: cannot unmarshal string into Go value of type []float32"),
|
||||
},
|
||||
{
|
||||
// Invalid []float data
|
||||
data: "{\"fi\":[\"abc\"]}",
|
||||
obj: &F{},
|
||||
err: fmt.Errorf("json: cannot unmarshal string into Go value of type float32"),
|
||||
},
|
||||
{
|
||||
// Invalid []float data
|
||||
data: "{\"fi\":[true]}",
|
||||
obj: &F{},
|
||||
err: fmt.Errorf("json: cannot unmarshal bool into Go value of type float32"),
|
||||
},
|
||||
}
|
||||
|
||||
for i := range testCases {
|
||||
doUnrecognized(t, testCases[i].data, testCases[i].obj, testCases[i].err)
|
||||
if t.Failed() {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
/*
|
||||
Copyright 2017 The Kubernetes 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 unstructured provides conversion from runtime objects
|
||||
// to map[string]interface{} representation.
|
||||
package unstructured // import "k8s.io/apimachinery/pkg/conversion/unstructured"
|
26
vendor/BUILD
vendored
26
vendor/BUILD
vendored
@ -15414,3 +15414,29 @@ go_library(
|
||||
"//vendor:k8s.io/sample-apiserver/pkg/apis/wardle",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "k8s.io/apimachinery/pkg/conversion/unstructured_test",
|
||||
srcs = ["k8s.io/apimachinery/pkg/conversion/unstructured/converter_test.go"],
|
||||
library = ":k8s.io/apimachinery/pkg/conversion/unstructured",
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//vendor:k8s.io/apimachinery/pkg/runtime",
|
||||
"//vendor:k8s.io/apimachinery/pkg/runtime/schema",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/diff",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/json",
|
||||
],
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "k8s.io/apimachinery/pkg/conversion/unstructured",
|
||||
srcs = [
|
||||
"k8s.io/apimachinery/pkg/conversion/unstructured/converter.go",
|
||||
"k8s.io/apimachinery/pkg/conversion/unstructured/doc.go",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//vendor:k8s.io/apimachinery/pkg/runtime",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/json",
|
||||
],
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user