mirror of https://github.com/k3s-io/k3s
Add documentation and tests to conversion.
parent
5c0f5e85e2
commit
1cc7fce523
|
@ -31,6 +31,7 @@ func init() {
|
|||
conversionScheme = conversion.NewScheme()
|
||||
conversionScheme.InternalVersion = ""
|
||||
conversionScheme.ExternalVersion = "v1beta1"
|
||||
conversionScheme.MetaInsertionFactory = metaInsertion{}
|
||||
AddKnownTypes("",
|
||||
PodList{},
|
||||
Pod{},
|
||||
|
@ -248,3 +249,28 @@ func Decode(data []byte) (interface{}, error) {
|
|||
func DecodeInto(data []byte, obj interface{}) error {
|
||||
return conversionScheme.DecodeInto(data, obj)
|
||||
}
|
||||
|
||||
// metaInsertion implements conversion.MetaInsertionFactory, which lets the conversion
|
||||
// package figure out how to encode our object's types and versions. These fields are
|
||||
// located in our JSONBase.
|
||||
type metaInsertion struct {
|
||||
JSONBase struct {
|
||||
APIVersion string `json:"apiVersion,omitempty" yaml:"apiVersion,omitempty"`
|
||||
Kind string `json:"kind,omitempty" yaml:"kind,omitempty"`
|
||||
} `json:",inline" yaml:",inline"`
|
||||
}
|
||||
|
||||
// Create returns a new metaInsertion with the version and kind fields set.
|
||||
func (metaInsertion) Create(version, kind string) interface{} {
|
||||
m := metaInsertion{}
|
||||
m.JSONBase.APIVersion = version
|
||||
m.JSONBase.Kind = kind
|
||||
return &m
|
||||
}
|
||||
|
||||
// Interpret returns the version and kind information from in, which must be
|
||||
// a metaInsertion pointer object.
|
||||
func (metaInsertion) Interpret(in interface{}) (version, kind string) {
|
||||
m := in.(*metaInsertion)
|
||||
return m.JSONBase.APIVersion, m.JSONBase.Kind
|
||||
}
|
||||
|
|
|
@ -88,21 +88,21 @@ func (c *Converter) Register(conversionFunc interface{}) error {
|
|||
type FieldMatchingFlags int
|
||||
|
||||
const (
|
||||
// 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
|
||||
// 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,
|
||||
// this flag is the default.
|
||||
DestFromSource
|
||||
// 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.
|
||||
AllowDifferentFieldNames
|
||||
AllowDifferentFieldTypeNames
|
||||
)
|
||||
|
||||
// Returns true if the given flag or combination of flags is set.
|
||||
|
@ -147,7 +147,7 @@ func (c *Converter) convert(sv, dv reflect.Value, flags FieldMatchingFlags) erro
|
|||
return ret.(error)
|
||||
}
|
||||
|
||||
if !flags.IsSet(AllowDifferentFieldNames) && dt.Name() != st.Name() {
|
||||
if !flags.IsSet(AllowDifferentFieldTypeNames) && dt.Name() != st.Name() {
|
||||
return fmt.Errorf("Can't convert %v to %v because type names don't match.", st, dt)
|
||||
}
|
||||
|
||||
|
|
|
@ -18,10 +18,13 @@ package conversion
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
||||
)
|
||||
|
||||
func TestConverter(t *testing.T) {
|
||||
func TestConverter_CallsRegisteredFunctions(t *testing.T) {
|
||||
type A struct {
|
||||
Foo string
|
||||
}
|
||||
|
@ -79,3 +82,128 @@ func TestConverter(t *testing.T) {
|
|||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,12 +28,12 @@ import (
|
|||
// 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.DataAPIVersionAndKind(data)
|
||||
version, kind, err := s.DataVersionAndKind(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if version == "" {
|
||||
return nil, fmt.Errorf("API Version not set in '%s'", string(data))
|
||||
return nil, fmt.Errorf("version not set in '%s'", string(data))
|
||||
}
|
||||
obj, err := s.NewObject(version, kind)
|
||||
if err != nil {
|
||||
|
@ -47,8 +47,7 @@ func (s *Scheme) Decode(data []byte) (interface{}, error) {
|
|||
}
|
||||
|
||||
// Version and Kind should be blank in memory.
|
||||
blankVersionAndKind := s.MetaInsertionFactory.Create("", "")
|
||||
err = s.converter.Convert(blankVersionAndKind, obj, SourceToDest|IgnoreMissingFields|AllowDifferentFieldNames)
|
||||
err = s.SetVersionAndKind("", "", obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -71,14 +70,14 @@ func (s *Scheme) Decode(data []byte) (interface{}, error) {
|
|||
// 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 APIVersion doesn't match that in data, an attempt will be made to convert
|
||||
// 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.DataAPIVersionAndKind(data)
|
||||
dataVersion, dataKind, err := s.DataVersionAndKind(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
objVersion, objKind, err := s.ObjectAPIVersionAndKind(obj)
|
||||
objVersion, objKind, err := s.ObjectVersionAndKind(obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -120,10 +119,5 @@ func (s *Scheme) DecodeInto(data []byte, obj interface{}) error {
|
|||
}
|
||||
|
||||
// Version and Kind should be blank in memory.
|
||||
blankVersionAndKind := s.MetaInsertionFactory.Create("", "")
|
||||
err = s.converter.Convert(blankVersionAndKind, obj, SourceToDest|IgnoreMissingFields|AllowDifferentFieldNames)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return s.SetVersionAndKind("", "", obj)
|
||||
}
|
||||
|
|
|
@ -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
|
|
@ -17,7 +17,6 @@ limitations under the License.
|
|||
package conversion
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
@ -37,17 +36,17 @@ func (s *Scheme) EncodeOrDie(obj interface{}) string {
|
|||
// struct. The type must have been registered.
|
||||
//
|
||||
// Memory/wire format differences:
|
||||
// * Having to keep track of the Kind and APIVersion fields makes tests
|
||||
// * 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 APIVersion and Kind must both be empty.
|
||||
// * Note that the exception does not apply to the APIObject type, which
|
||||
// recursively does Encode()/Decode(), and is capable of expressing any
|
||||
// API object.
|
||||
// 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
|
||||
|
@ -71,7 +70,7 @@ func (s *Scheme) EncodeToVersion(obj interface{}, destVersion string) (data []by
|
|||
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.ObjectAPIVersionAndKind(obj)
|
||||
objVersion, objKind, err := s.ObjectVersionAndKind(obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -90,8 +89,7 @@ func (s *Scheme) EncodeToVersion(obj interface{}, destVersion string) (data []by
|
|||
}
|
||||
|
||||
// Version and Kind should be set on the wire.
|
||||
setVersionAndKind := s.MetaInsertionFactory.Create(destVersion, objKind)
|
||||
err = s.converter.Convert(setVersionAndKind, obj, SourceToDest|IgnoreMissingFields|AllowDifferentFieldNames)
|
||||
err = s.SetVersionAndKind(destVersion, objKind, obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -102,35 +100,12 @@ func (s *Scheme) EncodeToVersion(obj interface{}, destVersion string) (data []by
|
|||
return nil, err
|
||||
}
|
||||
|
||||
// Version and Kind should be blank in memory.
|
||||
blankVersionAndKind := s.MetaInsertionFactory.Create("", "")
|
||||
err = s.converter.Convert(blankVersionAndKind, obj, SourceToDest|IgnoreMissingFields|AllowDifferentFieldNames)
|
||||
// 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
|
||||
|
||||
meta, err := json.Marshal(s.MetaInsertionFactory.Create(destVersion, objKind))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Stick these together, omitting the last } from meta and the first { from
|
||||
// data. Add a comma to meta if necessary.
|
||||
metaN := len(meta)
|
||||
if len(data) > 2 {
|
||||
meta[metaN-1] = ',' // Add comma
|
||||
} else {
|
||||
meta = meta[:metaN-1] // Just remove }
|
||||
}
|
||||
together := append(meta, data[1:]...)
|
||||
if s.Indent {
|
||||
var out bytes.Buffer
|
||||
err := json.Indent(&out, together, "", " ")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out.Bytes(), nil
|
||||
}
|
||||
return together, nil
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ import (
|
|||
|
||||
// MetaInsertionFactory is used to create an object to store and retrieve
|
||||
// the version and kind information for all objects. The default uses the
|
||||
// keys "apiVersion" and "kind" respectively. The object produced by this
|
||||
// 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
|
||||
|
@ -34,15 +34,12 @@ 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(apiVersion, kind string) interface{}
|
||||
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{}) (apiVersion, kind string)
|
||||
Interpret(interface{}) (version, kind string)
|
||||
}
|
||||
|
||||
// Default is a global scheme.
|
||||
var Default = NewScheme()
|
||||
|
||||
// Scheme defines an entire encoding and decoding scheme.
|
||||
type Scheme struct {
|
||||
// versionMap allows one to figure out the go type of an object with
|
||||
|
@ -70,7 +67,7 @@ type Scheme struct {
|
|||
|
||||
// MetaInsertionFactory is used to create an object to store and retrieve
|
||||
// the version and kind information for all objects. The default uses the
|
||||
// keys "apiVersion" and "kind" respectively.
|
||||
// keys "version" and "kind" respectively.
|
||||
MetaInsertionFactory MetaInsertionFactory
|
||||
}
|
||||
|
||||
|
@ -86,8 +83,10 @@ func NewScheme() *Scheme {
|
|||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
// 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 {
|
||||
|
@ -117,17 +116,18 @@ func (s *Scheme) NewObject(versionName, typeName string) (interface{}, error) {
|
|||
}
|
||||
|
||||
// AddConversionFuncs adds functions to the list of conversion functions. The given
|
||||
// functions should know how to convert between two API objects. We deduce how to call
|
||||
// it from the types of its two parameters; see the comment for Converter.Register.
|
||||
// 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
|
||||
// Convert() inside your conversionFuncs, as long as you don't start a conversion
|
||||
// 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. It's OK if the destination type has
|
||||
// extra fields, but it must not remove any. So you only need to add a conversion
|
||||
// function for things with changed/removed fields.
|
||||
// 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)
|
||||
|
@ -138,8 +138,8 @@ func (s *Scheme) AddConversionFuncs(conversionFuncs ...interface{}) error {
|
|||
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
|
||||
// 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)
|
||||
|
@ -147,32 +147,30 @@ func (s *Scheme) Convert(in, out interface{}) error {
|
|||
|
||||
// metaInsertion provides a default implementation of MetaInsertionFactory.
|
||||
type metaInsertion struct {
|
||||
JSONBase struct {
|
||||
APIVersion string `json:"apiVersion,omitempty" yaml:"apiVersion,omitempty"`
|
||||
Kind string `json:"kind,omitempty" yaml:"kind,omitempty"`
|
||||
} `json:",inline" yaml:",inline"`
|
||||
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(apiVersion, kind string) interface{} {
|
||||
func (metaInsertion) Create(version, kind string) interface{} {
|
||||
m := metaInsertion{}
|
||||
m.JSONBase.APIVersion = apiVersion
|
||||
m.JSONBase.Kind = kind
|
||||
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{}) (apiVersion, kind string) {
|
||||
func (metaInsertion) Interpret(in interface{}) (version, kind string) {
|
||||
m := in.(*metaInsertion)
|
||||
return m.JSONBase.APIVersion, m.JSONBase.Kind
|
||||
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) DataAPIVersionAndKind(data []byte) (apiVersion, kind string, err 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.
|
||||
|
@ -180,13 +178,13 @@ func (s *Scheme) DataAPIVersionAndKind(data []byte) (apiVersion, kind string, er
|
|||
if err != nil {
|
||||
return "", "", fmt.Errorf("couldn't get version/kind: %v", err)
|
||||
}
|
||||
apiVersion, kind = s.MetaInsertionFactory.Interpret(findKind)
|
||||
return apiVersion, kind, nil
|
||||
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) ObjectAPIVersionAndKind(obj interface{}) (apiVersion, kind string, err error) {
|
||||
func (s *Scheme) ObjectVersionAndKind(obj interface{}) (apiVersion, kind string, err error) {
|
||||
v, err := enforcePtr(obj)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
|
@ -199,6 +197,14 @@ func (s *Scheme) ObjectAPIVersionAndKind(obj interface{}) (apiVersion, kind stri
|
|||
}
|
||||
}
|
||||
|
||||
// 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{} {
|
||||
|
|
|
@ -28,31 +28,33 @@ import (
|
|||
|
||||
var fuzzIters = flag.Int("fuzz_iters", 10, "How many fuzzing iterations to do.")
|
||||
|
||||
// Intended to be compatible with the default implementation of MetaInsertionFactory
|
||||
type JSONBase struct {
|
||||
// Test a weird version/kind embedding format.
|
||||
type MyWeirdCustomEmbeddedVersionKindField struct {
|
||||
ID string `yaml:"ID,omitempty" json:"ID,omitempty"`
|
||||
APIVersion string `json:"apiVersion,omitempty" yaml:"apiVersion,omitempty"`
|
||||
Kind string `json:"kind,omitempty" yaml:"kind,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 {
|
||||
JSONBase `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"`
|
||||
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 {
|
||||
|
@ -69,39 +71,39 @@ func externalTypeReturn() interface{} {
|
|||
B int `yaml:"B,omitempty" json:"B,omitempty"`
|
||||
}
|
||||
type TestType1 struct {
|
||||
JSONBase `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"`
|
||||
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 {
|
||||
JSONBase `json:",inline" yaml:",inline"`
|
||||
A TestType2 `yaml:"A,omitempty" json:"A,omitempty"`
|
||||
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 *JSONBase) {
|
||||
// We have to customize the randomization of JSONBases because their
|
||||
func(j *MyWeirdCustomEmbeddedVersionKindField) {
|
||||
// We have to customize the randomization of MyWeirdCustomEmbeddedVersionKindFields because their
|
||||
// APIVersion and Kind must remain blank in memory.
|
||||
j.APIVersion = ""
|
||||
j.Kind = ""
|
||||
j.ObjectKind = ""
|
||||
j.ID = util.RandString()
|
||||
},
|
||||
func(u *uint64) {
|
||||
|
@ -125,9 +127,32 @@ func GetTestScheme() *Scheme {
|
|||
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 {
|
||||
|
@ -162,22 +187,20 @@ func runTest(t *testing.T, source interface{}) {
|
|||
if err != nil {
|
||||
t.Errorf("%v: %v (%v)", name, err, string(data))
|
||||
return
|
||||
} else {
|
||||
if !reflect.DeepEqual(source, obj2) {
|
||||
t.Errorf("1: %v: diff: %v", name, objDiff(source, obj2))
|
||||
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
|
||||
} else {
|
||||
if !reflect.DeepEqual(source, obj3) {
|
||||
t.Errorf("3: %v: diff: %v", name, objDiff(source, obj3))
|
||||
return
|
||||
}
|
||||
}
|
||||
if !reflect.DeepEqual(source, obj3) {
|
||||
t.Errorf("3: %v: diff: %v", name, objDiff(source, obj3))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -231,17 +254,17 @@ func TestEncode_Ptr(t *testing.T) {
|
|||
func TestBadJSONRejection(t *testing.T) {
|
||||
s := GetTestScheme()
|
||||
badJSONs := [][]byte{
|
||||
[]byte(`{"apiVersion":"v1"}`), // Missing kind
|
||||
[]byte(`{"kind":"TestType1"}`), // Missing version
|
||||
[]byte(`{"apiVersion":"v1","kind":"bar"}`), // Unknown kind
|
||||
[]byte(`{"apiVersion":"bar","kind":"TestType1"}`), // Unknown version
|
||||
[]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(`{"apiVersion":"v1","kind": "ExternalInternalSame"}`)
|
||||
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