mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-03 01:06:27 +00:00
commit
d1c04356e3
@ -1,110 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2014 Google Inc. All rights reserved.
|
|
||||||
|
|
||||||
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 api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestConverter(t *testing.T) {
|
|
||||||
type A struct {
|
|
||||||
Foo string
|
|
||||||
}
|
|
||||||
type B struct {
|
|
||||||
Bar string
|
|
||||||
}
|
|
||||||
type C struct{}
|
|
||||||
c := NewConverter()
|
|
||||||
err := c.Register(func(in *A, out *B) error {
|
|
||||||
out.Bar = in.Foo
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error %v", err)
|
|
||||||
}
|
|
||||||
err = c.Register(func(in *B, out *A) error {
|
|
||||||
out.Foo = in.Bar
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
x := A{"hello, intrepid test reader!"}
|
|
||||||
y := B{}
|
|
||||||
|
|
||||||
err = c.Convert(&x, &y)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error %v", err)
|
|
||||||
}
|
|
||||||
if e, a := x.Foo, y.Bar; e != a {
|
|
||||||
t.Errorf("expected %v, got %v", e, a)
|
|
||||||
}
|
|
||||||
|
|
||||||
z := B{"all your test are belong to us"}
|
|
||||||
w := A{}
|
|
||||||
|
|
||||||
err = c.Convert(&z, &w)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error %v", err)
|
|
||||||
}
|
|
||||||
if e, a := z.Bar, w.Foo; e != a {
|
|
||||||
t.Errorf("expected %v, got %v", e, a)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = c.Register(func(in *A, out *C) error {
|
|
||||||
return fmt.Errorf("C can't store an A, silly")
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = c.Convert(&A{}, &C{})
|
|
||||||
if err == nil {
|
|
||||||
t.Errorf("unexpected non-error")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func anonymousEmptyTypeNamedA() interface{} {
|
|
||||||
type A struct{}
|
|
||||||
return &A{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCopyConvertor(t *testing.T) {
|
|
||||||
type A struct {
|
|
||||||
Bar string
|
|
||||||
}
|
|
||||||
type B struct {
|
|
||||||
Bar string
|
|
||||||
}
|
|
||||||
c := NewConverter()
|
|
||||||
err := c.Convert(anonymousEmptyTypeNamedA(), &A{})
|
|
||||||
if err == nil {
|
|
||||||
t.Errorf("unexpected non-error")
|
|
||||||
}
|
|
||||||
|
|
||||||
err = c.Convert(&A{}, anonymousEmptyTypeNamedA())
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = c.Convert(&B{}, &A{})
|
|
||||||
if err == nil {
|
|
||||||
t.Errorf("unexpected non-error")
|
|
||||||
}
|
|
||||||
}
|
|
@ -17,28 +17,21 @@ limitations under the License.
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/conversion"
|
||||||
"gopkg.in/v1/yaml"
|
"gopkg.in/v1/yaml"
|
||||||
)
|
)
|
||||||
|
|
||||||
// versionMap allows one to figure out the go type of an object with
|
var conversionScheme *conversion.Scheme
|
||||||
// the given version and name.
|
|
||||||
var versionMap = map[string]map[string]reflect.Type{}
|
|
||||||
|
|
||||||
// typeToVersion allows one to figure out the version for a given go object.
|
|
||||||
// The reflect.Type we index by should *not* be a pointer. If the same type
|
|
||||||
// is registered for multiple versions, the last one wins.
|
|
||||||
var typeToVersion = map[reflect.Type]string{}
|
|
||||||
|
|
||||||
// theConverter stores all registered conversion functions. It also has
|
|
||||||
// default coverting behavior.
|
|
||||||
var theConverter = NewConverter()
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
conversionScheme = conversion.NewScheme()
|
||||||
|
conversionScheme.InternalVersion = ""
|
||||||
|
conversionScheme.ExternalVersion = "v1beta1"
|
||||||
|
conversionScheme.MetaInsertionFactory = metaInsertion{}
|
||||||
AddKnownTypes("",
|
AddKnownTypes("",
|
||||||
PodList{},
|
PodList{},
|
||||||
Pod{},
|
Pod{},
|
||||||
@ -98,31 +91,13 @@ func init() {
|
|||||||
// AddKnownTypes registers the types of the arguments to the marshaller of the package api.
|
// AddKnownTypes registers the types of the arguments to the marshaller of the package api.
|
||||||
// Encode() refuses the object unless its type is registered with AddKnownTypes.
|
// Encode() refuses the object unless its type is registered with AddKnownTypes.
|
||||||
func AddKnownTypes(version string, types ...interface{}) {
|
func AddKnownTypes(version string, types ...interface{}) {
|
||||||
knownTypes, found := versionMap[version]
|
conversionScheme.AddKnownTypes(version, types...)
|
||||||
if !found {
|
|
||||||
knownTypes = map[string]reflect.Type{}
|
|
||||||
versionMap[version] = knownTypes
|
|
||||||
}
|
|
||||||
for _, obj := range types {
|
|
||||||
t := reflect.TypeOf(obj)
|
|
||||||
if t.Kind() != reflect.Struct {
|
|
||||||
panic("All types must be structs.")
|
|
||||||
}
|
|
||||||
knownTypes[t.Name()] = t
|
|
||||||
typeToVersion[t] = version
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// New returns a new API object of the given version ("" for internal
|
// New returns a new API object of the given version ("" for internal
|
||||||
// representation) and name, or an error if it hasn't been registered.
|
// representation) and name, or an error if it hasn't been registered.
|
||||||
func New(versionName, typeName string) (interface{}, error) {
|
func New(versionName, typeName string) (interface{}, error) {
|
||||||
if types, ok := versionMap[versionName]; ok {
|
return conversionScheme.NewObject(versionName, typeName)
|
||||||
if t, ok := types[typeName]; ok {
|
|
||||||
return reflect.New(t).Interface(), nil
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("No type '%v' for version '%v'", typeName, versionName)
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("No version '%v'", versionName)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddConversionFuncs adds a function to the list of conversion functions. The given
|
// AddConversionFuncs adds a function to the list of conversion functions. The given
|
||||||
@ -138,20 +113,14 @@ func New(versionName, typeName string) (interface{}, error) {
|
|||||||
// extra fields, but it must not remove any. So you only need to add a conversion
|
// extra fields, but it must not remove any. So you only need to add a conversion
|
||||||
// function for things with changed/removed fields.
|
// function for things with changed/removed fields.
|
||||||
func AddConversionFuncs(conversionFuncs ...interface{}) error {
|
func AddConversionFuncs(conversionFuncs ...interface{}) error {
|
||||||
for _, f := range conversionFuncs {
|
return conversionScheme.AddConversionFuncs(conversionFuncs...)
|
||||||
err := theConverter.Register(f)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert will attempt to convert in into out. Both must be pointers to API objects.
|
// Convert will attempt to convert in into out. Both must be pointers to API objects.
|
||||||
// For easy testing of conversion functions. Returns an error if the conversion isn't
|
// For easy testing of conversion functions. Returns an error if the conversion isn't
|
||||||
// possible.
|
// possible.
|
||||||
func Convert(in, out interface{}) error {
|
func Convert(in, out interface{}) error {
|
||||||
return theConverter.Convert(in, out)
|
return conversionScheme.Convert(in, out)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FindJSONBase takes an arbitary api type, returns pointer to its JSONBase field.
|
// FindJSONBase takes an arbitary api type, returns pointer to its JSONBase field.
|
||||||
@ -196,11 +165,7 @@ func FindJSONBaseRO(obj interface{}) (JSONBase, error) {
|
|||||||
|
|
||||||
// EncodeOrDie is a version of Encode which will panic instead of returning an error. For tests.
|
// EncodeOrDie is a version of Encode which will panic instead of returning an error. For tests.
|
||||||
func EncodeOrDie(obj interface{}) string {
|
func EncodeOrDie(obj interface{}) string {
|
||||||
bytes, err := Encode(obj)
|
return conversionScheme.EncodeOrDie(obj)
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return string(bytes)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encode turns the given api object into an appropriate JSON string.
|
// Encode turns the given api object into an appropriate JSON string.
|
||||||
@ -238,93 +203,7 @@ func EncodeOrDie(obj interface{}) string {
|
|||||||
// upgraded.
|
// upgraded.
|
||||||
//
|
//
|
||||||
func Encode(obj interface{}) (data []byte, err error) {
|
func Encode(obj interface{}) (data []byte, err error) {
|
||||||
obj = maybeCopy(obj)
|
return conversionScheme.Encode(obj)
|
||||||
obj, err = maybeExternalize(obj)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
jsonBase, err := prepareEncode(obj)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err = json.MarshalIndent(obj, "", " ")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// Leave these blank in memory.
|
|
||||||
jsonBase.SetKind("")
|
|
||||||
jsonBase.SetAPIVersion("")
|
|
||||||
return data, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns the API version of the go object, or an error if it's not a
|
|
||||||
// pointer or is unregistered.
|
|
||||||
func objAPIVersionAndName(obj interface{}) (apiVersion, name string, err error) {
|
|
||||||
v, err := enforcePtr(obj)
|
|
||||||
if err != nil {
|
|
||||||
return "", "", err
|
|
||||||
}
|
|
||||||
t := v.Type()
|
|
||||||
if version, ok := typeToVersion[t]; !ok {
|
|
||||||
return "", "", fmt.Errorf("Unregistered type: %v", t)
|
|
||||||
} else {
|
|
||||||
return version, t.Name(), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// maybeExternalize converts obj to an external object if it isn't one already.
|
|
||||||
// obj must be a pointer.
|
|
||||||
func maybeExternalize(obj interface{}) (interface{}, error) {
|
|
||||||
version, _, err := objAPIVersionAndName(obj)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if version != "" {
|
|
||||||
// Object is already of an external versioned type.
|
|
||||||
return obj, nil
|
|
||||||
}
|
|
||||||
return externalize(obj)
|
|
||||||
}
|
|
||||||
|
|
||||||
// maybeCopy copies obj if it is not a pointer, to get a settable/addressable
|
|
||||||
// object. Guaranteed to return a pointer.
|
|
||||||
func maybeCopy(obj interface{}) interface{} {
|
|
||||||
v := reflect.ValueOf(obj)
|
|
||||||
if v.Kind() == reflect.Ptr {
|
|
||||||
return obj
|
|
||||||
}
|
|
||||||
v2 := reflect.New(v.Type())
|
|
||||||
v2.Elem().Set(v)
|
|
||||||
return v2.Interface()
|
|
||||||
}
|
|
||||||
|
|
||||||
// prepareEncode sets the APIVersion and Kind fields to match the go type in obj.
|
|
||||||
// Returns an error if the (version, name) pair isn't registered for the type or
|
|
||||||
// if the type is an internal, non-versioned object.
|
|
||||||
func prepareEncode(obj interface{}) (JSONBaseInterface, error) {
|
|
||||||
version, name, err := objAPIVersionAndName(obj)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if version == "" {
|
|
||||||
return nil, fmt.Errorf("No version for '%v' (%#v); extremely inadvisable to write it in wire format.", name, obj)
|
|
||||||
}
|
|
||||||
jsonBase, err := FindJSONBase(obj)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
knownTypes, found := versionMap[version]
|
|
||||||
if !found {
|
|
||||||
return nil, fmt.Errorf("struct %s, %s won't be unmarshalable because it's not in known versions", version, name)
|
|
||||||
}
|
|
||||||
if _, contains := knownTypes[name]; !contains {
|
|
||||||
return nil, fmt.Errorf("struct %s, %s won't be unmarshalable because it's not in knownTypes", version, name)
|
|
||||||
}
|
|
||||||
jsonBase.SetAPIVersion(version)
|
|
||||||
jsonBase.SetKind(name)
|
|
||||||
return jsonBase, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensures that obj is a pointer of some sort. Returns a reflect.Value of the
|
// Ensures that obj is a pointer of some sort. Returns a reflect.Value of the
|
||||||
@ -359,35 +238,7 @@ func VersionAndKind(data []byte) (version, kind string, err error) {
|
|||||||
// by Encode. Only versioned objects (APIVersion != "") are accepted. The object
|
// by Encode. Only versioned objects (APIVersion != "") are accepted. The object
|
||||||
// will be converted into the in-memory unversioned type before being returned.
|
// will be converted into the in-memory unversioned type before being returned.
|
||||||
func Decode(data []byte) (interface{}, error) {
|
func Decode(data []byte) (interface{}, error) {
|
||||||
version, kind, err := VersionAndKind(data)
|
return conversionScheme.Decode(data)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if version == "" {
|
|
||||||
return nil, fmt.Errorf("API Version not set in '%s'", string(data))
|
|
||||||
}
|
|
||||||
obj, err := New(version, kind)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Unable to create new object of type ('%s', '%s')", version, kind)
|
|
||||||
}
|
|
||||||
// yaml is a superset of json, so we use it to decode here. That way,
|
|
||||||
// we understand both.
|
|
||||||
err = yaml.Unmarshal(data, obj)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
obj, err = internalize(obj)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
jsonBase, err := FindJSONBase(obj)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// Don't leave these set. Type and version info is deducible from go's type.
|
|
||||||
jsonBase.SetKind("")
|
|
||||||
jsonBase.SetAPIVersion("")
|
|
||||||
return obj, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DecodeInto parses a YAML or JSON string and stores it in obj. Returns an error
|
// DecodeInto parses a YAML or JSON string and stores it in obj. Returns an error
|
||||||
@ -396,102 +247,30 @@ func Decode(data []byte) (interface{}, error) {
|
|||||||
// If obj's APIVersion doesn't match that in data, an attempt will be made to convert
|
// If obj's APIVersion doesn't match that in data, an attempt will be made to convert
|
||||||
// data into obj's version.
|
// data into obj's version.
|
||||||
func DecodeInto(data []byte, obj interface{}) error {
|
func DecodeInto(data []byte, obj interface{}) error {
|
||||||
dataVersion, dataKind, err := VersionAndKind(data)
|
return conversionScheme.DecodeInto(data, obj)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
objVersion, objKind, err := objAPIVersionAndName(obj)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if dataKind == "" {
|
|
||||||
// Assume objects with unset Kind fields are being unmarshalled into the
|
|
||||||
// correct type.
|
|
||||||
dataKind = objKind
|
|
||||||
}
|
|
||||||
if dataKind != objKind {
|
|
||||||
return fmt.Errorf("data of kind '%v', obj of type '%v'", dataKind, objKind)
|
|
||||||
}
|
|
||||||
if dataVersion == "" {
|
|
||||||
// Assume objects with unset Version fields are being unmarshalled into the
|
|
||||||
// correct type.
|
|
||||||
dataVersion = objVersion
|
|
||||||
}
|
|
||||||
|
|
||||||
if objVersion == dataVersion {
|
|
||||||
// Easy case!
|
|
||||||
err = yaml.Unmarshal(data, obj)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// TODO: look up in our map to see if we can do this dataVersion -> objVersion
|
|
||||||
// conversion.
|
|
||||||
if objVersion != "" || dataVersion != "v1beta1" {
|
|
||||||
return fmt.Errorf("Can't convert from '%v' to '%v' for type '%v'", dataVersion, objVersion, dataKind)
|
|
||||||
}
|
|
||||||
|
|
||||||
external, err := New(dataVersion, dataKind)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Unable to create new object of type ('%s', '%s')", dataVersion, dataKind)
|
|
||||||
}
|
|
||||||
// yaml is a superset of json, so we use it to decode here. That way,
|
|
||||||
// we understand both.
|
|
||||||
err = yaml.Unmarshal(data, external)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
internal, err := internalize(external)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// Copy to the provided object.
|
|
||||||
vObj := reflect.ValueOf(obj)
|
|
||||||
vInternal := reflect.ValueOf(internal)
|
|
||||||
if !vInternal.Type().AssignableTo(vObj.Type()) {
|
|
||||||
return fmt.Errorf("%s is not assignable to %s", vInternal.Type(), vObj.Type())
|
|
||||||
}
|
|
||||||
vObj.Elem().Set(vInternal.Elem())
|
|
||||||
}
|
|
||||||
|
|
||||||
jsonBase, err := FindJSONBase(obj)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// Don't leave these set. Type and version info is deducible from go's type.
|
|
||||||
jsonBase.SetKind("")
|
|
||||||
jsonBase.SetAPIVersion("")
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func internalize(obj interface{}) (interface{}, error) {
|
// metaInsertion implements conversion.MetaInsertionFactory, which lets the conversion
|
||||||
_, objKind, err := objAPIVersionAndName(obj)
|
// package figure out how to encode our object's types and versions. These fields are
|
||||||
if err != nil {
|
// located in our JSONBase.
|
||||||
return nil, err
|
type metaInsertion struct {
|
||||||
}
|
JSONBase struct {
|
||||||
objOut, err := New("", objKind)
|
APIVersion string `json:"apiVersion,omitempty" yaml:"apiVersion,omitempty"`
|
||||||
if err != nil {
|
Kind string `json:"kind,omitempty" yaml:"kind,omitempty"`
|
||||||
return nil, err
|
} `json:",inline" yaml:",inline"`
|
||||||
}
|
|
||||||
err = theConverter.Convert(obj, objOut)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return objOut, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func externalize(obj interface{}) (interface{}, error) {
|
// Create returns a new metaInsertion with the version and kind fields set.
|
||||||
_, objKind, err := objAPIVersionAndName(obj)
|
func (metaInsertion) Create(version, kind string) interface{} {
|
||||||
if err != nil {
|
m := metaInsertion{}
|
||||||
return nil, err
|
m.JSONBase.APIVersion = version
|
||||||
}
|
m.JSONBase.Kind = kind
|
||||||
objOut, err := New("v1beta1", objKind)
|
return &m
|
||||||
if err != nil {
|
}
|
||||||
return nil, err
|
|
||||||
}
|
// Interpret returns the version and kind information from in, which must be
|
||||||
err = theConverter.Convert(obj, objOut)
|
// a metaInsertion pointer object.
|
||||||
if err != nil {
|
func (metaInsertion) Interpret(in interface{}) (version, kind string) {
|
||||||
return nil, err
|
m := in.(*metaInsertion)
|
||||||
}
|
return m.JSONBase.APIVersion, m.JSONBase.Kind
|
||||||
return objOut, nil
|
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package api
|
package conversion
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -26,7 +26,8 @@ type typePair struct {
|
|||||||
dest reflect.Type
|
dest reflect.Type
|
||||||
}
|
}
|
||||||
|
|
||||||
type debugLogger interface {
|
// DebugLogger allows you to get debugging messages if necessary.
|
||||||
|
type DebugLogger interface {
|
||||||
Logf(format string, args ...interface{})
|
Logf(format string, args ...interface{})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -37,7 +38,7 @@ type Converter struct {
|
|||||||
funcs map[typePair]reflect.Value
|
funcs map[typePair]reflect.Value
|
||||||
|
|
||||||
// If true, print helpful debugging info. Quite verbose.
|
// If true, print helpful debugging info. Quite verbose.
|
||||||
debug debugLogger
|
Debug DebugLogger
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewConverter makes a new Converter object.
|
// NewConverter makes a new Converter object.
|
||||||
@ -82,11 +83,38 @@ func (c *Converter) Register(conversionFunc interface{}) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FieldMatchingType contains a list of ways in which struct fields could be
|
||||||
|
// copied. These constants may be | combined.
|
||||||
|
type FieldMatchingFlags int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Loop through destiation fields, search for matching source
|
||||||
|
// field to copy it from. Source fields with no corresponding
|
||||||
|
// destination field will be ignored. If SourceToDest is
|
||||||
|
// specified, this flag is ignored. If niether is specified,
|
||||||
|
// or no flags are passed, this flag is the default.
|
||||||
|
DestFromSource FieldMatchingFlags = 0
|
||||||
|
// Loop through source fields, search for matching dest field
|
||||||
|
// to copy it into. Destination fields with no corresponding
|
||||||
|
// source field will be ignored.
|
||||||
|
SourceToDest FieldMatchingFlags = 1 << iota
|
||||||
|
// Don't treat it as an error if the corresponding source or
|
||||||
|
// dest field can't be found.
|
||||||
|
IgnoreMissingFields
|
||||||
|
// Don't require type names to match.
|
||||||
|
AllowDifferentFieldTypeNames
|
||||||
|
)
|
||||||
|
|
||||||
|
// Returns true if the given flag or combination of flags is set.
|
||||||
|
func (f FieldMatchingFlags) IsSet(flag FieldMatchingFlags) bool {
|
||||||
|
return f&flag == flag
|
||||||
|
}
|
||||||
|
|
||||||
// Convert will translate src to dest if it knows how. Both must be pointers.
|
// Convert will translate src to dest if it knows how. Both must be pointers.
|
||||||
// If no conversion func is registered and the default copying mechanism
|
// If no conversion func is registered and the default copying mechanism
|
||||||
// doesn't work on this type pair, an error will be returned.
|
// doesn't work on this type pair, an error will be returned.
|
||||||
// Not safe for objects with cyclic references!
|
// Not safe for objects with cyclic references!
|
||||||
func (c *Converter) Convert(src, dest interface{}) error {
|
func (c *Converter) Convert(src, dest interface{}, flags FieldMatchingFlags) error {
|
||||||
dv, sv := reflect.ValueOf(dest), reflect.ValueOf(src)
|
dv, sv := reflect.ValueOf(dest), reflect.ValueOf(src)
|
||||||
if dv.Kind() != reflect.Ptr {
|
if dv.Kind() != reflect.Ptr {
|
||||||
return fmt.Errorf("Need pointer, but got %#v", dest)
|
return fmt.Errorf("Need pointer, but got %#v", dest)
|
||||||
@ -99,16 +127,16 @@ func (c *Converter) Convert(src, dest interface{}) error {
|
|||||||
if !dv.CanAddr() {
|
if !dv.CanAddr() {
|
||||||
return fmt.Errorf("Can't write to dest")
|
return fmt.Errorf("Can't write to dest")
|
||||||
}
|
}
|
||||||
return c.convert(sv, dv)
|
return c.convert(sv, dv, flags)
|
||||||
}
|
}
|
||||||
|
|
||||||
// convert recursively copies sv into dv, calling an appropriate conversion function if
|
// convert recursively copies sv into dv, calling an appropriate conversion function if
|
||||||
// one is registered.
|
// one is registered.
|
||||||
func (c *Converter) convert(sv, dv reflect.Value) error {
|
func (c *Converter) convert(sv, dv reflect.Value, flags FieldMatchingFlags) error {
|
||||||
dt, st := dv.Type(), sv.Type()
|
dt, st := dv.Type(), sv.Type()
|
||||||
if fv, ok := c.funcs[typePair{st, dt}]; ok {
|
if fv, ok := c.funcs[typePair{st, dt}]; ok {
|
||||||
if c.debug != nil {
|
if c.Debug != nil {
|
||||||
c.debug.Logf("Calling custom conversion of '%v' to '%v'", st, dt)
|
c.Debug.Logf("Calling custom conversion of '%v' to '%v'", st, dt)
|
||||||
}
|
}
|
||||||
ret := fv.Call([]reflect.Value{sv.Addr(), dv.Addr()})[0].Interface()
|
ret := fv.Call([]reflect.Value{sv.Addr(), dv.Addr()})[0].Interface()
|
||||||
// This convolution is necssary because nil interfaces won't convert
|
// This convolution is necssary because nil interfaces won't convert
|
||||||
@ -119,8 +147,8 @@ func (c *Converter) convert(sv, dv reflect.Value) error {
|
|||||||
return ret.(error)
|
return ret.(error)
|
||||||
}
|
}
|
||||||
|
|
||||||
if dt.Name() != st.Name() {
|
if !flags.IsSet(AllowDifferentFieldTypeNames) && dt.Name() != st.Name() {
|
||||||
return fmt.Errorf("Type names don't match: %v, %v", dt.Name(), st.Name())
|
return fmt.Errorf("Can't convert %v to %v because type names don't match.", st, dt)
|
||||||
}
|
}
|
||||||
|
|
||||||
// This should handle all simple types.
|
// This should handle all simple types.
|
||||||
@ -133,20 +161,32 @@ func (c *Converter) convert(sv, dv reflect.Value) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.debug != nil {
|
if c.Debug != nil {
|
||||||
c.debug.Logf("Trying to convert '%v' to '%v'", st, dt)
|
c.Debug.Logf("Trying to convert '%v' to '%v'", st, dt)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch dv.Kind() {
|
switch dv.Kind() {
|
||||||
case reflect.Struct:
|
case reflect.Struct:
|
||||||
for i := 0; i < dt.NumField(); i++ {
|
listType := dt
|
||||||
f := dv.Type().Field(i)
|
if flags.IsSet(SourceToDest) {
|
||||||
|
listType = st
|
||||||
|
}
|
||||||
|
for i := 0; i < listType.NumField(); i++ {
|
||||||
|
f := listType.Field(i)
|
||||||
|
df := dv.FieldByName(f.Name)
|
||||||
sf := sv.FieldByName(f.Name)
|
sf := sv.FieldByName(f.Name)
|
||||||
if !sf.IsValid() {
|
if !df.IsValid() || !sf.IsValid() {
|
||||||
return fmt.Errorf("%v not present in source %v for dest %v", f.Name, sv.Type(), dv.Type())
|
switch {
|
||||||
|
case flags.IsSet(IgnoreMissingFields):
|
||||||
|
// No error.
|
||||||
|
case flags.IsSet(SourceToDest):
|
||||||
|
return fmt.Errorf("%v not present in dest (%v to %v)", f.Name, st, dt)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("%v not present in src (%v to %v)", f.Name, st, dt)
|
||||||
|
}
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
df := dv.Field(i)
|
if err := c.convert(sf, df, flags); err != nil {
|
||||||
if err := c.convert(sf, df); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -158,7 +198,7 @@ func (c *Converter) convert(sv, dv reflect.Value) error {
|
|||||||
}
|
}
|
||||||
dv.Set(reflect.MakeSlice(dt, sv.Len(), sv.Cap()))
|
dv.Set(reflect.MakeSlice(dt, sv.Len(), sv.Cap()))
|
||||||
for i := 0; i < sv.Len(); i++ {
|
for i := 0; i < sv.Len(); i++ {
|
||||||
if err := c.convert(sv.Index(i), dv.Index(i)); err != nil {
|
if err := c.convert(sv.Index(i), dv.Index(i), flags); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -169,7 +209,7 @@ func (c *Converter) convert(sv, dv reflect.Value) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
dv.Set(reflect.New(dt.Elem()))
|
dv.Set(reflect.New(dt.Elem()))
|
||||||
return c.convert(sv.Elem(), dv.Elem())
|
return c.convert(sv.Elem(), dv.Elem(), flags)
|
||||||
case reflect.Map:
|
case reflect.Map:
|
||||||
if sv.IsNil() {
|
if sv.IsNil() {
|
||||||
// Don't copy a nil ptr!
|
// Don't copy a nil ptr!
|
||||||
@ -179,11 +219,11 @@ func (c *Converter) convert(sv, dv reflect.Value) error {
|
|||||||
dv.Set(reflect.MakeMap(dt))
|
dv.Set(reflect.MakeMap(dt))
|
||||||
for _, sk := range sv.MapKeys() {
|
for _, sk := range sv.MapKeys() {
|
||||||
dk := reflect.New(dt.Key()).Elem()
|
dk := reflect.New(dt.Key()).Elem()
|
||||||
if err := c.convert(sk, dk); err != nil {
|
if err := c.convert(sk, dk, flags); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
dkv := reflect.New(dt.Elem()).Elem()
|
dkv := reflect.New(dt.Elem()).Elem()
|
||||||
if err := c.convert(sv.MapIndex(sk), dkv); err != nil {
|
if err := c.convert(sv.MapIndex(sk), dkv, flags); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
dv.SetMapIndex(dk, dkv)
|
dv.SetMapIndex(dk, dkv)
|
209
pkg/conversion/converter_test.go
Normal file
209
pkg/conversion/converter_test.go
Normal file
@ -0,0 +1,209 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2014 Google Inc. All rights reserved.
|
||||||
|
|
||||||
|
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 conversion
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestConverter_CallsRegisteredFunctions(t *testing.T) {
|
||||||
|
type A struct {
|
||||||
|
Foo string
|
||||||
|
}
|
||||||
|
type B struct {
|
||||||
|
Bar string
|
||||||
|
}
|
||||||
|
type C struct{}
|
||||||
|
c := NewConverter()
|
||||||
|
err := c.Register(func(in *A, out *B) error {
|
||||||
|
out.Bar = in.Foo
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error %v", err)
|
||||||
|
}
|
||||||
|
err = c.Register(func(in *B, out *A) error {
|
||||||
|
out.Foo = in.Bar
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
x := A{"hello, intrepid test reader!"}
|
||||||
|
y := B{}
|
||||||
|
|
||||||
|
err = c.Convert(&x, &y, 0)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error %v", err)
|
||||||
|
}
|
||||||
|
if e, a := x.Foo, y.Bar; e != a {
|
||||||
|
t.Errorf("expected %v, got %v", e, a)
|
||||||
|
}
|
||||||
|
|
||||||
|
z := B{"all your test are belong to us"}
|
||||||
|
w := A{}
|
||||||
|
|
||||||
|
err = c.Convert(&z, &w, 0)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error %v", err)
|
||||||
|
}
|
||||||
|
if e, a := z.Bar, w.Foo; e != a {
|
||||||
|
t.Errorf("expected %v, got %v", e, a)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = c.Register(func(in *A, out *C) error {
|
||||||
|
return fmt.Errorf("C can't store an A, silly")
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = c.Convert(&A{}, &C{}, 0)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("unexpected non-error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConverter_fuzz(t *testing.T) {
|
||||||
|
newAnonType := func() interface{} {
|
||||||
|
return reflect.New(reflect.TypeOf(externalTypeReturn())).Interface()
|
||||||
|
}
|
||||||
|
// Use the same types from the scheme test.
|
||||||
|
table := []struct {
|
||||||
|
from, to, check interface{}
|
||||||
|
}{
|
||||||
|
{&TestType1{}, newAnonType(), &TestType1{}},
|
||||||
|
{newAnonType(), &TestType1{}, newAnonType()},
|
||||||
|
}
|
||||||
|
|
||||||
|
f := util.NewFuzzer()
|
||||||
|
c := NewConverter()
|
||||||
|
|
||||||
|
for i, item := range table {
|
||||||
|
for j := 0; j < *fuzzIters; j++ {
|
||||||
|
f.Fuzz(item.from)
|
||||||
|
err := c.Convert(item.from, item.to, 0)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("(%v, %v): unexpected error: %v", i, j, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
err = c.Convert(item.to, item.check, 0)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("(%v, %v): unexpected error: %v", i, j, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if e, a := item.from, item.check; !reflect.DeepEqual(e, a) {
|
||||||
|
t.Errorf("(%v, %v): unexpected diff: %v", i, j, objDiff(e, a))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConverter_flags(t *testing.T) {
|
||||||
|
type Foo struct{ A string }
|
||||||
|
type Bar struct{ A string }
|
||||||
|
table := []struct {
|
||||||
|
from, to interface{}
|
||||||
|
flags FieldMatchingFlags
|
||||||
|
shouldSucceed bool
|
||||||
|
}{
|
||||||
|
// Check that DestFromSource allows extra fields only in source.
|
||||||
|
{
|
||||||
|
from: &struct{ A string }{},
|
||||||
|
to: &struct{ A, B string }{},
|
||||||
|
flags: DestFromSource,
|
||||||
|
shouldSucceed: false,
|
||||||
|
}, {
|
||||||
|
from: &struct{ A, B string }{},
|
||||||
|
to: &struct{ A string }{},
|
||||||
|
flags: DestFromSource,
|
||||||
|
shouldSucceed: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Check that SourceToDest allows for extra fields only in dest.
|
||||||
|
{
|
||||||
|
from: &struct{ A string }{},
|
||||||
|
to: &struct{ A, B string }{},
|
||||||
|
flags: SourceToDest,
|
||||||
|
shouldSucceed: true,
|
||||||
|
}, {
|
||||||
|
from: &struct{ A, B string }{},
|
||||||
|
to: &struct{ A string }{},
|
||||||
|
flags: SourceToDest,
|
||||||
|
shouldSucceed: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Check that IgnoreMissingFields makes the above failure cases pass.
|
||||||
|
{
|
||||||
|
from: &struct{ A string }{},
|
||||||
|
to: &struct{ A, B string }{},
|
||||||
|
flags: DestFromSource | IgnoreMissingFields,
|
||||||
|
shouldSucceed: true,
|
||||||
|
}, {
|
||||||
|
from: &struct{ A, B string }{},
|
||||||
|
to: &struct{ A string }{},
|
||||||
|
flags: SourceToDest | IgnoreMissingFields,
|
||||||
|
shouldSucceed: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Check that the field type name must match unless
|
||||||
|
// AllowDifferentFieldTypeNames is specified.
|
||||||
|
{
|
||||||
|
from: &struct{ A, B Foo }{},
|
||||||
|
to: &struct{ A Bar }{},
|
||||||
|
flags: DestFromSource,
|
||||||
|
shouldSucceed: false,
|
||||||
|
}, {
|
||||||
|
from: &struct{ A Foo }{},
|
||||||
|
to: &struct{ A, B Bar }{},
|
||||||
|
flags: SourceToDest,
|
||||||
|
shouldSucceed: false,
|
||||||
|
}, {
|
||||||
|
from: &struct{ A, B Foo }{},
|
||||||
|
to: &struct{ A Bar }{},
|
||||||
|
flags: DestFromSource | AllowDifferentFieldTypeNames,
|
||||||
|
shouldSucceed: true,
|
||||||
|
}, {
|
||||||
|
from: &struct{ A Foo }{},
|
||||||
|
to: &struct{ A, B Bar }{},
|
||||||
|
flags: SourceToDest | AllowDifferentFieldTypeNames,
|
||||||
|
shouldSucceed: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
f := util.NewFuzzer()
|
||||||
|
c := NewConverter()
|
||||||
|
|
||||||
|
for i, item := range table {
|
||||||
|
for j := 0; j < *fuzzIters; j++ {
|
||||||
|
f.Fuzz(item.from)
|
||||||
|
err := c.Convert(item.from, item.to, item.flags)
|
||||||
|
if item.shouldSucceed && err != nil {
|
||||||
|
t.Errorf("(%v, %v): unexpected error: %v", i, j, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !item.shouldSucceed && err == nil {
|
||||||
|
t.Errorf("(%v, %v): unexpected non-error", i, j)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
123
pkg/conversion/decode.go
Normal file
123
pkg/conversion/decode.go
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2014 Google Inc. All rights reserved.
|
||||||
|
|
||||||
|
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 conversion
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"gopkg.in/v1/yaml"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Decode converts a YAML or JSON string back into a pointer to an api object.
|
||||||
|
// Deduces the type based upon the fields added by the MetaInsertionFactory
|
||||||
|
// technique. The object will be converted, if necessary, into the
|
||||||
|
// s.InternalVersion type before being returned. Decode will refuse to decode
|
||||||
|
// objects without a version, because that's probably an error.
|
||||||
|
func (s *Scheme) Decode(data []byte) (interface{}, error) {
|
||||||
|
version, kind, err := s.DataVersionAndKind(data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if version == "" {
|
||||||
|
return nil, fmt.Errorf("version not set in '%s'", string(data))
|
||||||
|
}
|
||||||
|
obj, err := s.NewObject(version, kind)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// yaml is a superset of json, so we use it to decode here. That way,
|
||||||
|
// we understand both.
|
||||||
|
err = yaml.Unmarshal(data, obj)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Version and Kind should be blank in memory.
|
||||||
|
err = s.SetVersionAndKind("", "", obj)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert if needed.
|
||||||
|
if s.InternalVersion != version {
|
||||||
|
objOut, err := s.NewObject(s.InternalVersion, kind)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = s.converter.Convert(obj, objOut, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
obj = objOut
|
||||||
|
}
|
||||||
|
return obj, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeInto parses a YAML or JSON string and stores it in obj. Returns an error
|
||||||
|
// if data.Kind is set and doesn't match the type of obj. Obj should be a
|
||||||
|
// pointer to an api type.
|
||||||
|
// If obj's version doesn't match that in data, an attempt will be made to convert
|
||||||
|
// data into obj's version.
|
||||||
|
func (s *Scheme) DecodeInto(data []byte, obj interface{}) error {
|
||||||
|
dataVersion, dataKind, err := s.DataVersionAndKind(data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
objVersion, objKind, err := s.ObjectVersionAndKind(obj)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if dataKind == "" {
|
||||||
|
// Assume objects with unset Kind fields are being unmarshalled into the
|
||||||
|
// correct type.
|
||||||
|
dataKind = objKind
|
||||||
|
}
|
||||||
|
if dataKind != objKind {
|
||||||
|
return fmt.Errorf("data of kind '%v', obj of type '%v'", dataKind, objKind)
|
||||||
|
}
|
||||||
|
if dataVersion == "" {
|
||||||
|
// Assume objects with unset Version fields are being unmarshalled into the
|
||||||
|
// correct type.
|
||||||
|
dataVersion = objVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
if objVersion == dataVersion {
|
||||||
|
// Easy case!
|
||||||
|
err = yaml.Unmarshal(data, obj)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
external, err := s.NewObject(dataVersion, dataKind)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Unable to create new object of type ('%s', '%s')", dataVersion, dataKind)
|
||||||
|
}
|
||||||
|
// yaml is a superset of json, so we use it to decode here. That way,
|
||||||
|
// we understand both.
|
||||||
|
err = yaml.Unmarshal(data, external)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = s.converter.Convert(external, obj, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Version and Kind should be blank in memory.
|
||||||
|
return s.SetVersionAndKind("", "", obj)
|
||||||
|
}
|
34
pkg/conversion/doc.go
Normal file
34
pkg/conversion/doc.go
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2014 Google Inc. All rights reserved.
|
||||||
|
|
||||||
|
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 conversion provides go object versioning and encoding/decoding
|
||||||
|
// mechanisms.
|
||||||
|
//
|
||||||
|
// Specifically, conversion provides a way for you to define multiple versions
|
||||||
|
// of the same object. You may write functions which implement conversion logic,
|
||||||
|
// but for the fields which did not change, copying is automated. This makes it
|
||||||
|
// easy to modify the structures you use in memory without affecting the format
|
||||||
|
// you store on disk or respond to in your external API calls.
|
||||||
|
//
|
||||||
|
// The second offering of this package is automated encoding/decoding. The version
|
||||||
|
// and type of the object is recorded in the output, so it can be recreated upon
|
||||||
|
// reading. Currently, conversion writes JSON output, and interprets both JSON
|
||||||
|
// and YAML input.
|
||||||
|
//
|
||||||
|
// In the future, we plan to more explicitly separate the above two mechanisms, and
|
||||||
|
// add more serialization options, such as gob.
|
||||||
|
//
|
||||||
|
package conversion
|
111
pkg/conversion/encode.go
Normal file
111
pkg/conversion/encode.go
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2014 Google Inc. All rights reserved.
|
||||||
|
|
||||||
|
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 conversion
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EncodeOrDie is a version of Encode which will panic instead of returning an error. For tests.
|
||||||
|
func (s *Scheme) EncodeOrDie(obj interface{}) string {
|
||||||
|
bytes, err := s.Encode(obj)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return string(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode turns the given api object into an appropriate JSON string.
|
||||||
|
// Obj may be a pointer to a struct, or a struct. If a struct, a copy
|
||||||
|
// will be made, therefore it's recommended to pass a pointer to a
|
||||||
|
// struct. The type must have been registered.
|
||||||
|
//
|
||||||
|
// Memory/wire format differences:
|
||||||
|
// * Having to keep track of the Kind and Version fields makes tests
|
||||||
|
// very annoying, so the rule is that they are set only in wire format
|
||||||
|
// (json), not when in native (memory) format. This is possible because
|
||||||
|
// both pieces of information are implicit in the go typed object.
|
||||||
|
// * An exception: note that, if there are embedded API objects of known
|
||||||
|
// type, for example, PodList{... Items []Pod ...}, these embedded
|
||||||
|
// objects must be of the same version of the object they are embedded
|
||||||
|
// within, and their Version and Kind must both be empty.
|
||||||
|
// * Note that the exception does not apply to a generic APIObject type
|
||||||
|
// which recursively does Encode()/Decode(), and is capable of
|
||||||
|
// expressing any API object.
|
||||||
|
// * Only versioned objects should be encoded. This means that, if you pass
|
||||||
|
// a native object, Encode will convert it to a versioned object. For
|
||||||
|
// example, an api.Pod will get converted to a v1beta1.Pod. However, if
|
||||||
|
// you pass in an object that's already versioned (v1beta1.Pod), Encode
|
||||||
|
// will not modify it.
|
||||||
|
//
|
||||||
|
// The purpose of the above complex conversion behavior is to allow us to
|
||||||
|
// change the memory format yet not break compatibility with any stored
|
||||||
|
// objects, whether they be in our storage layer (e.g., etcd), or in user's
|
||||||
|
// config files.
|
||||||
|
//
|
||||||
|
func (s *Scheme) Encode(obj interface{}) (data []byte, err error) {
|
||||||
|
return s.EncodeToVersion(obj, s.ExternalVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeToVersion is like Encode, but you may choose the version.
|
||||||
|
func (s *Scheme) EncodeToVersion(obj interface{}, destVersion string) (data []byte, err error) {
|
||||||
|
obj = maybeCopy(obj)
|
||||||
|
v, _ := enforcePtr(obj) // maybeCopy guarantees a pointer
|
||||||
|
if _, registered := s.typeToVersion[v.Type()]; !registered {
|
||||||
|
return nil, fmt.Errorf("type %v is not registered and it will be impossible to Decode it, therefore Encode will refuse to encode it.", v.Type())
|
||||||
|
}
|
||||||
|
|
||||||
|
objVersion, objKind, err := s.ObjectVersionAndKind(obj)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform a conversion if necessary.
|
||||||
|
if objVersion != destVersion {
|
||||||
|
objOut, err := s.NewObject(destVersion, objKind)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = s.converter.Convert(obj, objOut, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
obj = objOut
|
||||||
|
}
|
||||||
|
|
||||||
|
// Version and Kind should be set on the wire.
|
||||||
|
err = s.SetVersionAndKind(destVersion, objKind, obj)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// To add metadata, do some simple surgery on the JSON.
|
||||||
|
data, err = json.Marshal(obj)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Version and Kind should be blank in memory. Reset them, since it's
|
||||||
|
// possible that we modified a user object and not a copy above.
|
||||||
|
err = s.SetVersionAndKind("", "", obj)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return data, nil
|
||||||
|
}
|
229
pkg/conversion/scheme.go
Normal file
229
pkg/conversion/scheme.go
Normal file
@ -0,0 +1,229 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2014 Google Inc. All rights reserved.
|
||||||
|
|
||||||
|
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 conversion
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"gopkg.in/v1/yaml"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MetaInsertionFactory is used to create an object to store and retrieve
|
||||||
|
// the version and kind information for all objects. The default uses the
|
||||||
|
// keys "version" and "kind" respectively. The object produced by this
|
||||||
|
// factory is used to clear the version and kind fields in memory, so it
|
||||||
|
// must match the layout of your actual api structs. (E.g., if you have your
|
||||||
|
// version and kind field inside an inlined struct, this must produce an
|
||||||
|
// inlined struct with the same field name.)
|
||||||
|
type MetaInsertionFactory interface {
|
||||||
|
// Create should make a new object with two fields.
|
||||||
|
// This object will be used to encode this metadata along with your
|
||||||
|
// API objects, so the tags on the fields you use shouldn't conflict.
|
||||||
|
Create(version, kind string) interface{}
|
||||||
|
// Interpret should take the same type of object that Create creates.
|
||||||
|
// It should return the version and kind information from this object.
|
||||||
|
Interpret(interface{}) (version, kind string)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scheme defines an entire encoding and decoding scheme.
|
||||||
|
type Scheme struct {
|
||||||
|
// versionMap allows one to figure out the go type of an object with
|
||||||
|
// the given version and name.
|
||||||
|
versionMap map[string]map[string]reflect.Type
|
||||||
|
|
||||||
|
// typeToVersion allows one to figure out the version for a given go object.
|
||||||
|
// The reflect.Type we index by should *not* be a pointer. If the same type
|
||||||
|
// is registered for multiple versions, the last one wins.
|
||||||
|
typeToVersion map[reflect.Type]string
|
||||||
|
|
||||||
|
// converter stores all registered conversion functions. It also has
|
||||||
|
// default coverting behavior.
|
||||||
|
converter *Converter
|
||||||
|
|
||||||
|
// Indent will cause the JSON output from Encode to be indented, iff it is true.
|
||||||
|
Indent bool
|
||||||
|
|
||||||
|
// InternalVersion is the default internal version. It is recommended that
|
||||||
|
// you use "" for the internal version.
|
||||||
|
InternalVersion string
|
||||||
|
|
||||||
|
// ExternalVersion is the default external version.
|
||||||
|
ExternalVersion string
|
||||||
|
|
||||||
|
// MetaInsertionFactory is used to create an object to store and retrieve
|
||||||
|
// the version and kind information for all objects. The default uses the
|
||||||
|
// keys "version" and "kind" respectively.
|
||||||
|
MetaInsertionFactory MetaInsertionFactory
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewScheme manufactures a new scheme.
|
||||||
|
func NewScheme() *Scheme {
|
||||||
|
return &Scheme{
|
||||||
|
versionMap: map[string]map[string]reflect.Type{},
|
||||||
|
typeToVersion: map[reflect.Type]string{},
|
||||||
|
converter: NewConverter(),
|
||||||
|
InternalVersion: "",
|
||||||
|
ExternalVersion: "v1",
|
||||||
|
MetaInsertionFactory: metaInsertion{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddKnownTypes registers all types passed in 'types' as being members of version 'version.
|
||||||
|
// Encode() will refuse objects unless their type has been registered with AddKnownTypes.
|
||||||
|
// All objects passed to types should be structs, not pointers to structs. The name that go
|
||||||
|
// reports for the struct becomes the "kind" field when encoding.
|
||||||
|
func (s *Scheme) AddKnownTypes(version string, types ...interface{}) {
|
||||||
|
knownTypes, found := s.versionMap[version]
|
||||||
|
if !found {
|
||||||
|
knownTypes = map[string]reflect.Type{}
|
||||||
|
s.versionMap[version] = knownTypes
|
||||||
|
}
|
||||||
|
for _, obj := range types {
|
||||||
|
t := reflect.TypeOf(obj)
|
||||||
|
if t.Kind() != reflect.Struct {
|
||||||
|
panic("All types must be structs.")
|
||||||
|
}
|
||||||
|
knownTypes[t.Name()] = t
|
||||||
|
s.typeToVersion[t] = version
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewObject returns a new object of the given version and name,
|
||||||
|
// or an error if it hasn't been registered.
|
||||||
|
func (s *Scheme) NewObject(versionName, typeName string) (interface{}, error) {
|
||||||
|
if types, ok := s.versionMap[versionName]; ok {
|
||||||
|
if t, ok := types[typeName]; ok {
|
||||||
|
return reflect.New(t).Interface(), nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("No type '%v' for version '%v'", typeName, versionName)
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("No version '%v'", versionName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddConversionFuncs adds functions to the list of conversion functions. The given
|
||||||
|
// functions should know how to convert between two of your API objects, or their
|
||||||
|
// sub-objects. We deduce how to call these functions from the types of their two
|
||||||
|
// parameters; see the comment for Converter.Register.
|
||||||
|
//
|
||||||
|
// Note that, if you need to copy sub-objects that didn't change, it's safe to call
|
||||||
|
// s.Convert() inside your conversionFuncs, as long as you don't start a conversion
|
||||||
|
// chain that's infinitely recursive.
|
||||||
|
//
|
||||||
|
// Also note that the default behavior, if you don't add a conversion function, is to
|
||||||
|
// sanely copy fields that have the same names and same type names. It's OK if the
|
||||||
|
// destination type has extra fields, but it must not remove any. So you only need to
|
||||||
|
// add conversion functions for things with changed/removed fields.
|
||||||
|
func (s *Scheme) AddConversionFuncs(conversionFuncs ...interface{}) error {
|
||||||
|
for _, f := range conversionFuncs {
|
||||||
|
err := s.converter.Register(f)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert will attempt to convert in into out. Both must be pointers. For easy
|
||||||
|
// testing of conversion functions. Returns an error if the conversion isn't
|
||||||
|
// possible.
|
||||||
|
func (s *Scheme) Convert(in, out interface{}) error {
|
||||||
|
return s.converter.Convert(in, out, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// metaInsertion provides a default implementation of MetaInsertionFactory.
|
||||||
|
type metaInsertion struct {
|
||||||
|
Version string `json:"version,omitempty" yaml:"version,omitempty"`
|
||||||
|
Kind string `json:"kind,omitempty" yaml:"kind,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create should make a new object with two fields.
|
||||||
|
// This object will be used to encode this metadata along with your
|
||||||
|
// API objects, so the tags on the fields you use shouldn't conflict.
|
||||||
|
func (metaInsertion) Create(version, kind string) interface{} {
|
||||||
|
m := metaInsertion{}
|
||||||
|
m.Version = version
|
||||||
|
m.Kind = kind
|
||||||
|
return &m
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interpret should take the same type of object that Create creates.
|
||||||
|
// It should return the version and kind information from this object.
|
||||||
|
func (metaInsertion) Interpret(in interface{}) (version, kind string) {
|
||||||
|
m := in.(*metaInsertion)
|
||||||
|
return m.Version, m.Kind
|
||||||
|
}
|
||||||
|
|
||||||
|
// DataAPIVersionAndKind will return the APIVersion and Kind of the given wire-format
|
||||||
|
// enconding of an API Object, or an error.
|
||||||
|
func (s *Scheme) DataVersionAndKind(data []byte) (version, kind string, err error) {
|
||||||
|
findKind := s.MetaInsertionFactory.Create("", "")
|
||||||
|
// yaml is a superset of json, so we use it to decode here. That way,
|
||||||
|
// we understand both.
|
||||||
|
err = yaml.Unmarshal(data, findKind)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", fmt.Errorf("couldn't get version/kind: %v", err)
|
||||||
|
}
|
||||||
|
version, kind = s.MetaInsertionFactory.Interpret(findKind)
|
||||||
|
return version, kind, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ObjectVersionAndKind returns the API version and kind of the go object,
|
||||||
|
// or an error if it's not a pointer or is unregistered.
|
||||||
|
func (s *Scheme) ObjectVersionAndKind(obj interface{}) (apiVersion, kind string, err error) {
|
||||||
|
v, err := enforcePtr(obj)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
t := v.Type()
|
||||||
|
if version, ok := s.typeToVersion[t]; !ok {
|
||||||
|
return "", "", fmt.Errorf("Unregistered type: %v", t)
|
||||||
|
} else {
|
||||||
|
return version, t.Name(), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetVersionAndKind sets the version and kind fields (with help from
|
||||||
|
// MetaInsertionFactory). Returns an error if this isn't possible. obj
|
||||||
|
// must be a pointer.
|
||||||
|
func (s *Scheme) SetVersionAndKind(version, kind string, obj interface{}) error {
|
||||||
|
versionAndKind := s.MetaInsertionFactory.Create(version, kind)
|
||||||
|
return s.converter.Convert(versionAndKind, obj, SourceToDest|IgnoreMissingFields|AllowDifferentFieldTypeNames)
|
||||||
|
}
|
||||||
|
|
||||||
|
// maybeCopy copies obj if it is not a pointer, to get a settable/addressable
|
||||||
|
// object. Guaranteed to return a pointer.
|
||||||
|
func maybeCopy(obj interface{}) interface{} {
|
||||||
|
v := reflect.ValueOf(obj)
|
||||||
|
if v.Kind() == reflect.Ptr {
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
v2 := reflect.New(v.Type())
|
||||||
|
v2.Elem().Set(v)
|
||||||
|
return v2.Interface()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensures that obj is a pointer of some sort. Returns a reflect.Value of the
|
||||||
|
// dereferenced pointer, ensuring that it is settable/addressable.
|
||||||
|
// Returns an error if this is not possible.
|
||||||
|
func enforcePtr(obj interface{}) (reflect.Value, error) {
|
||||||
|
v := reflect.ValueOf(obj)
|
||||||
|
if v.Kind() != reflect.Ptr {
|
||||||
|
return reflect.Value{}, fmt.Errorf("expected pointer, but got %v", v.Type().Name())
|
||||||
|
}
|
||||||
|
return v.Elem(), nil
|
||||||
|
}
|
271
pkg/conversion/scheme_test.go
Normal file
271
pkg/conversion/scheme_test.go
Normal file
@ -0,0 +1,271 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2014 Google Inc. All rights reserved.
|
||||||
|
|
||||||
|
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 conversion
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
var fuzzIters = flag.Int("fuzz_iters", 10, "How many fuzzing iterations to do.")
|
||||||
|
|
||||||
|
// Test a weird version/kind embedding format.
|
||||||
|
type MyWeirdCustomEmbeddedVersionKindField struct {
|
||||||
|
ID string `yaml:"ID,omitempty" json:"ID,omitempty"`
|
||||||
|
APIVersion string `json:"myVersionKey,omitempty" yaml:"myVersionKey,omitempty"`
|
||||||
|
ObjectKind string `json:"myKindKey,omitempty" yaml:"myKindKey,omitempty"`
|
||||||
|
Z string `yaml:"Z,omitempty" json:"Z,omitempty"`
|
||||||
|
Y uint64 `yaml:"Y,omitempty" json:"Y,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TestType1 struct {
|
||||||
|
MyWeirdCustomEmbeddedVersionKindField `json:",inline" yaml:",inline"`
|
||||||
|
A string `yaml:"A,omitempty" json:"A,omitempty"`
|
||||||
|
B int `yaml:"B,omitempty" json:"B,omitempty"`
|
||||||
|
C int8 `yaml:"C,omitempty" json:"C,omitempty"`
|
||||||
|
D int16 `yaml:"D,omitempty" json:"D,omitempty"`
|
||||||
|
E int32 `yaml:"E,omitempty" json:"E,omitempty"`
|
||||||
|
F int64 `yaml:"F,omitempty" json:"F,omitempty"`
|
||||||
|
G uint `yaml:"G,omitempty" json:"G,omitempty"`
|
||||||
|
H uint8 `yaml:"H,omitempty" json:"H,omitempty"`
|
||||||
|
I uint16 `yaml:"I,omitempty" json:"I,omitempty"`
|
||||||
|
J uint32 `yaml:"J,omitempty" json:"J,omitempty"`
|
||||||
|
K uint64 `yaml:"K,omitempty" json:"K,omitempty"`
|
||||||
|
L bool `yaml:"L,omitempty" json:"L,omitempty"`
|
||||||
|
M map[string]int `yaml:"M,omitempty" json:"M,omitempty"`
|
||||||
|
N map[string]TestType2 `yaml:"N,omitempty" json:"N,omitempty"`
|
||||||
|
O *TestType2 `yaml:"O,omitempty" json:"O,omitempty"`
|
||||||
|
P []TestType2 `yaml:"Q,omitempty" json:"Q,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TestType2 struct {
|
||||||
|
A string `yaml:"A,omitempty" json:"A,omitempty"`
|
||||||
|
B int `yaml:"B,omitempty" json:"B,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// We depend on the name of the external and internal types matching. Ordinarily,
|
||||||
|
// we'd accomplish this with an additional package, but since this is a test, we
|
||||||
|
// can just enclose stuff in a function to simulate that.
|
||||||
|
func externalTypeReturn() interface{} {
|
||||||
|
type TestType2 struct {
|
||||||
|
A string `yaml:"A,omitempty" json:"A,omitempty"`
|
||||||
|
B int `yaml:"B,omitempty" json:"B,omitempty"`
|
||||||
|
}
|
||||||
|
type TestType1 struct {
|
||||||
|
MyWeirdCustomEmbeddedVersionKindField `json:",inline" yaml:",inline"`
|
||||||
|
A string `yaml:"A,omitempty" json:"A,omitempty"`
|
||||||
|
B int `yaml:"B,omitempty" json:"B,omitempty"`
|
||||||
|
C int8 `yaml:"C,omitempty" json:"C,omitempty"`
|
||||||
|
D int16 `yaml:"D,omitempty" json:"D,omitempty"`
|
||||||
|
E int32 `yaml:"E,omitempty" json:"E,omitempty"`
|
||||||
|
F int64 `yaml:"F,omitempty" json:"F,omitempty"`
|
||||||
|
G uint `yaml:"G,omitempty" json:"G,omitempty"`
|
||||||
|
H uint8 `yaml:"H,omitempty" json:"H,omitempty"`
|
||||||
|
I uint16 `yaml:"I,omitempty" json:"I,omitempty"`
|
||||||
|
J uint32 `yaml:"J,omitempty" json:"J,omitempty"`
|
||||||
|
K uint64 `yaml:"K,omitempty" json:"K,omitempty"`
|
||||||
|
L bool `yaml:"L,omitempty" json:"L,omitempty"`
|
||||||
|
M map[string]int `yaml:"M,omitempty" json:"M,omitempty"`
|
||||||
|
N map[string]TestType2 `yaml:"N,omitempty" json:"N,omitempty"`
|
||||||
|
O *TestType2 `yaml:"O,omitempty" json:"O,omitempty"`
|
||||||
|
P []TestType2 `yaml:"Q,omitempty" json:"Q,omitempty"`
|
||||||
|
}
|
||||||
|
return TestType1{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExternalInternalSame struct {
|
||||||
|
MyWeirdCustomEmbeddedVersionKindField `json:",inline" yaml:",inline"`
|
||||||
|
A TestType2 `yaml:"A,omitempty" json:"A,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestObjectFuzzer can randomly populate all the above objects.
|
||||||
|
var TestObjectFuzzer = util.NewFuzzer(
|
||||||
|
func(j *MyWeirdCustomEmbeddedVersionKindField) {
|
||||||
|
// We have to customize the randomization of MyWeirdCustomEmbeddedVersionKindFields because their
|
||||||
|
// APIVersion and Kind must remain blank in memory.
|
||||||
|
j.APIVersion = ""
|
||||||
|
j.ObjectKind = ""
|
||||||
|
j.ID = util.RandString()
|
||||||
|
},
|
||||||
|
func(u *uint64) {
|
||||||
|
// TODO: Fix JSON/YAML packages and/or write custom encoding
|
||||||
|
// for uint64's. Somehow the LS *byte* of this is lost, but
|
||||||
|
// only when all 8 bytes are set.
|
||||||
|
*u = util.RandUint64() >> 8
|
||||||
|
},
|
||||||
|
func(u *uint) {
|
||||||
|
// TODO: Fix JSON/YAML packages and/or write custom encoding
|
||||||
|
// for uint64's. Somehow the LS *byte* of this is lost, but
|
||||||
|
// only when all 8 bytes are set.
|
||||||
|
*u = uint(util.RandUint64() >> 8)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
// Returns a new Scheme set up with the test objects.
|
||||||
|
func GetTestScheme() *Scheme {
|
||||||
|
s := NewScheme()
|
||||||
|
s.AddKnownTypes("", TestType1{}, ExternalInternalSame{})
|
||||||
|
s.AddKnownTypes("v1", externalTypeReturn(), ExternalInternalSame{})
|
||||||
|
s.ExternalVersion = "v1"
|
||||||
|
s.InternalVersion = ""
|
||||||
|
s.MetaInsertionFactory = testMetaInsertionFactory{}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
type testMetaInsertionFactory struct {
|
||||||
|
MyWeirdCustomEmbeddedVersionKindField struct {
|
||||||
|
APIVersion string `json:"myVersionKey,omitempty" yaml:"myVersionKey,omitempty"`
|
||||||
|
ObjectKind string `json:"myKindKey,omitempty" yaml:"myKindKey,omitempty"`
|
||||||
|
} `json:",inline" yaml:",inline"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create returns a new testMetaInsertionFactory with the version and kind fields set.
|
||||||
|
func (testMetaInsertionFactory) Create(version, kind string) interface{} {
|
||||||
|
m := testMetaInsertionFactory{}
|
||||||
|
m.MyWeirdCustomEmbeddedVersionKindField.APIVersion = version
|
||||||
|
m.MyWeirdCustomEmbeddedVersionKindField.ObjectKind = kind
|
||||||
|
return &m
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interpret returns the version and kind information from in, which must be
|
||||||
|
// a testMetaInsertionFactory pointer object.
|
||||||
|
func (testMetaInsertionFactory) Interpret(in interface{}) (version, kind string) {
|
||||||
|
m := in.(*testMetaInsertionFactory)
|
||||||
|
return m.MyWeirdCustomEmbeddedVersionKindField.APIVersion, m.MyWeirdCustomEmbeddedVersionKindField.ObjectKind
|
||||||
|
}
|
||||||
|
|
||||||
|
func objDiff(a, b interface{}) string {
|
||||||
|
ab, err := json.Marshal(a)
|
||||||
|
if err != nil {
|
||||||
|
panic("a")
|
||||||
|
}
|
||||||
|
bb, err := json.Marshal(b)
|
||||||
|
if err != nil {
|
||||||
|
panic("b")
|
||||||
|
}
|
||||||
|
return util.StringDiff(string(ab), string(bb))
|
||||||
|
|
||||||
|
// An alternate diff attempt, in case json isn't showing you
|
||||||
|
// the difference. (reflect.DeepEqual makes a distinction between
|
||||||
|
// nil and empty slices, for example.)
|
||||||
|
return util.StringDiff(
|
||||||
|
fmt.Sprintf("%#v", a),
|
||||||
|
fmt.Sprintf("%#v", b),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func runTest(t *testing.T, source interface{}) {
|
||||||
|
name := reflect.TypeOf(source).Elem().Name()
|
||||||
|
TestObjectFuzzer.Fuzz(source)
|
||||||
|
|
||||||
|
s := GetTestScheme()
|
||||||
|
data, err := s.Encode(source)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%v: %v (%#v)", name, err, source)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
obj2, err := s.Decode(data)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%v: %v (%v)", name, err, string(data))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(source, obj2) {
|
||||||
|
t.Errorf("1: %v: diff: %v", name, objDiff(source, obj2))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
obj3 := reflect.New(reflect.TypeOf(source).Elem()).Interface()
|
||||||
|
err = s.DecodeInto(data, obj3)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("2: %v: %v", name, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(source, obj3) {
|
||||||
|
t.Errorf("3: %v: diff: %v", name, objDiff(source, obj3))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTypes(t *testing.T) {
|
||||||
|
table := []interface{}{
|
||||||
|
&TestType1{},
|
||||||
|
&ExternalInternalSame{},
|
||||||
|
}
|
||||||
|
for _, item := range table {
|
||||||
|
// Try a few times, since runTest uses random values.
|
||||||
|
for i := 0; i < *fuzzIters; i++ {
|
||||||
|
runTest(t, item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEncode_NonPtr(t *testing.T) {
|
||||||
|
s := GetTestScheme()
|
||||||
|
tt := TestType1{A: "I'm not a pointer object"}
|
||||||
|
obj := interface{}(tt)
|
||||||
|
data, err := s.Encode(obj)
|
||||||
|
obj2, err2 := s.Decode(data)
|
||||||
|
if err != nil || err2 != nil {
|
||||||
|
t.Fatalf("Failure: '%v' '%v'", err, err2)
|
||||||
|
}
|
||||||
|
if _, ok := obj2.(*TestType1); !ok {
|
||||||
|
t.Fatalf("Got wrong type")
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(obj2, &tt) {
|
||||||
|
t.Errorf("Expected:\n %#v,\n Got:\n %#v", &tt, obj2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEncode_Ptr(t *testing.T) {
|
||||||
|
s := GetTestScheme()
|
||||||
|
tt := &TestType1{A: "I am a pointer object"}
|
||||||
|
obj := interface{}(tt)
|
||||||
|
data, err := s.Encode(obj)
|
||||||
|
obj2, err2 := s.Decode(data)
|
||||||
|
if err != nil || err2 != nil {
|
||||||
|
t.Fatalf("Failure: '%v' '%v'", err, err2)
|
||||||
|
}
|
||||||
|
if _, ok := obj2.(*TestType1); !ok {
|
||||||
|
t.Fatalf("Got wrong type")
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(obj2, tt) {
|
||||||
|
t.Errorf("Expected:\n %#v,\n Got:\n %#v", &tt, obj2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBadJSONRejection(t *testing.T) {
|
||||||
|
s := GetTestScheme()
|
||||||
|
badJSONs := [][]byte{
|
||||||
|
[]byte(`{"myVersionKey":"v1"}`), // Missing kind
|
||||||
|
[]byte(`{"myKindKey":"TestType1"}`), // Missing version
|
||||||
|
[]byte(`{"myVersionKey":"v1","myKindKey":"bar"}`), // Unknown kind
|
||||||
|
[]byte(`{"myVersionKey":"bar","myKindKey":"TestType1"}`), // Unknown version
|
||||||
|
}
|
||||||
|
for _, b := range badJSONs {
|
||||||
|
if _, err := s.Decode(b); err == nil {
|
||||||
|
t.Errorf("Did not reject bad json: %s", string(b))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
badJSONKindMismatch := []byte(`{"myVersionKey":"v1","myKindKey":"ExternalInternalSame"}`)
|
||||||
|
if err := s.DecodeInto(badJSONKindMismatch, &TestType1{}); err == nil {
|
||||||
|
t.Errorf("Kind is set but doesn't match the object type: %s", badJSONKindMismatch)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user