mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-03 17:30:00 +00:00
Remove codec awareness from conversion
Allow convertors to be specialized, some cleanup to how conversion functions are stored internally to allow better reuse.
This commit is contained in:
parent
9d5df20efe
commit
6582b4c2ea
@ -40,8 +40,11 @@ type DebugLogger interface {
|
||||
type Converter struct {
|
||||
// Map from the conversion pair to a function which can
|
||||
// do the conversion.
|
||||
conversionFuncs map[typePair]reflect.Value
|
||||
generatedConversionFuncs map[typePair]reflect.Value
|
||||
conversionFuncs ConversionFuncs
|
||||
generatedConversionFuncs ConversionFuncs
|
||||
|
||||
// Set of conversions that should be treated as a no-op
|
||||
ignoredConversions map[typePair]struct{}
|
||||
|
||||
// This is a map from a source field type and name, to a list of destination
|
||||
// field type and name.
|
||||
@ -76,21 +79,30 @@ type Converter struct {
|
||||
// NewConverter creates a new Converter object.
|
||||
func NewConverter() *Converter {
|
||||
c := &Converter{
|
||||
conversionFuncs: map[typePair]reflect.Value{},
|
||||
generatedConversionFuncs: map[typePair]reflect.Value{},
|
||||
defaultingFuncs: map[reflect.Type]reflect.Value{},
|
||||
defaultingInterfaces: map[reflect.Type]interface{}{},
|
||||
conversionFuncs: NewConversionFuncs(),
|
||||
generatedConversionFuncs: NewConversionFuncs(),
|
||||
ignoredConversions: make(map[typePair]struct{}),
|
||||
defaultingFuncs: make(map[reflect.Type]reflect.Value),
|
||||
defaultingInterfaces: make(map[reflect.Type]interface{}),
|
||||
nameFunc: func(t reflect.Type) string { return t.Name() },
|
||||
structFieldDests: map[typeNamePair][]typeNamePair{},
|
||||
structFieldSources: map[typeNamePair][]typeNamePair{},
|
||||
structFieldDests: make(map[typeNamePair][]typeNamePair),
|
||||
structFieldSources: make(map[typeNamePair][]typeNamePair),
|
||||
|
||||
inputFieldMappingFuncs: map[reflect.Type]FieldMappingFunc{},
|
||||
inputDefaultFlags: map[reflect.Type]FieldMatchingFlags{},
|
||||
inputFieldMappingFuncs: make(map[reflect.Type]FieldMappingFunc),
|
||||
inputDefaultFlags: make(map[reflect.Type]FieldMatchingFlags),
|
||||
}
|
||||
c.RegisterConversionFunc(ByteSliceCopy)
|
||||
return c
|
||||
}
|
||||
|
||||
// WithConversions returns a Converter that is a copy of c but with the additional
|
||||
// fns merged on top.
|
||||
func (c *Converter) WithConversions(fns ConversionFuncs) *Converter {
|
||||
copied := *c
|
||||
copied.conversionFuncs = c.conversionFuncs.Merge(fns)
|
||||
return &copied
|
||||
}
|
||||
|
||||
// ByteSliceCopy prevents recursing into every byte
|
||||
func ByteSliceCopy(in *[]byte, out *[]byte, s Scope) error {
|
||||
*out = make([]byte, len(*in))
|
||||
@ -130,6 +142,42 @@ type Scope interface {
|
||||
// the value of the source or destination struct tags.
|
||||
type FieldMappingFunc func(key string, sourceTag, destTag reflect.StructTag) (source string, dest string)
|
||||
|
||||
func NewConversionFuncs() ConversionFuncs {
|
||||
return ConversionFuncs{fns: make(map[typePair]reflect.Value)}
|
||||
}
|
||||
|
||||
type ConversionFuncs struct {
|
||||
fns map[typePair]reflect.Value
|
||||
}
|
||||
|
||||
// Add adds the provided conversion functions to the lookup table - they must have the signature
|
||||
// `func(type1, type2, Scope) error`. Functions are added in the order passed and will override
|
||||
// previously registered pairs.
|
||||
func (c ConversionFuncs) Add(fns ...interface{}) error {
|
||||
for _, fn := range fns {
|
||||
fv := reflect.ValueOf(fn)
|
||||
ft := fv.Type()
|
||||
if err := verifyConversionFunctionSignature(ft); err != nil {
|
||||
return err
|
||||
}
|
||||
c.fns[typePair{ft.In(0).Elem(), ft.In(1).Elem()}] = fv
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Merge returns a new ConversionFuncs that contains all conversions from
|
||||
// both other and c, with other conversions taking precedence.
|
||||
func (c ConversionFuncs) Merge(other ConversionFuncs) ConversionFuncs {
|
||||
merged := NewConversionFuncs()
|
||||
for k, v := range c.fns {
|
||||
merged.fns[k] = v
|
||||
}
|
||||
for k, v := range other.fns {
|
||||
merged.fns[k] = v
|
||||
}
|
||||
return merged
|
||||
}
|
||||
|
||||
// Meta is supplied by Scheme, when it calls Convert.
|
||||
type Meta struct {
|
||||
SrcVersion string
|
||||
@ -296,34 +344,44 @@ func verifyConversionFunctionSignature(ft reflect.Type) error {
|
||||
// return nil
|
||||
// })
|
||||
func (c *Converter) RegisterConversionFunc(conversionFunc interface{}) error {
|
||||
fv := reflect.ValueOf(conversionFunc)
|
||||
ft := fv.Type()
|
||||
if err := verifyConversionFunctionSignature(ft); err != nil {
|
||||
return err
|
||||
}
|
||||
c.conversionFuncs[typePair{ft.In(0).Elem(), ft.In(1).Elem()}] = fv
|
||||
return nil
|
||||
return c.conversionFuncs.Add(conversionFunc)
|
||||
}
|
||||
|
||||
// Similar to RegisterConversionFunc, but registers conversion function that were
|
||||
// automatically generated.
|
||||
func (c *Converter) RegisterGeneratedConversionFunc(conversionFunc interface{}) error {
|
||||
fv := reflect.ValueOf(conversionFunc)
|
||||
ft := fv.Type()
|
||||
if err := verifyConversionFunctionSignature(ft); err != nil {
|
||||
return err
|
||||
return c.generatedConversionFuncs.Add(conversionFunc)
|
||||
}
|
||||
|
||||
// RegisterIgnoredConversion registers a "no-op" for conversion, where any requested
|
||||
// conversion between from and to is ignored.
|
||||
func (c *Converter) RegisterIgnoredConversion(from, to interface{}) error {
|
||||
typeFrom := reflect.TypeOf(from)
|
||||
typeTo := reflect.TypeOf(to)
|
||||
if reflect.TypeOf(from).Kind() != reflect.Ptr {
|
||||
return fmt.Errorf("expected pointer arg for 'from' param 0, got: %v", typeFrom)
|
||||
}
|
||||
c.generatedConversionFuncs[typePair{ft.In(0).Elem(), ft.In(1).Elem()}] = fv
|
||||
if typeTo.Kind() != reflect.Ptr {
|
||||
return fmt.Errorf("expected pointer arg for 'to' param 1, got: %v", typeTo)
|
||||
}
|
||||
c.ignoredConversions[typePair{typeFrom.Elem(), typeTo.Elem()}] = struct{}{}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsConversionIgnored returns true if the specified objects should be dropped during
|
||||
// conversion.
|
||||
func (c *Converter) IsConversionIgnored(inType, outType reflect.Type) bool {
|
||||
_, found := c.ignoredConversions[typePair{inType, outType}]
|
||||
return found
|
||||
}
|
||||
|
||||
func (c *Converter) HasConversionFunc(inType, outType reflect.Type) bool {
|
||||
_, found := c.conversionFuncs[typePair{inType, outType}]
|
||||
_, found := c.conversionFuncs.fns[typePair{inType, outType}]
|
||||
return found
|
||||
}
|
||||
|
||||
func (c *Converter) ConversionFuncValue(inType, outType reflect.Type) (reflect.Value, bool) {
|
||||
value, found := c.conversionFuncs[typePair{inType, outType}]
|
||||
value, found := c.conversionFuncs.fns[typePair{inType, outType}]
|
||||
return value, found
|
||||
}
|
||||
|
||||
@ -509,16 +567,26 @@ func (c *Converter) convert(sv, dv reflect.Value, scope *scope) error {
|
||||
fv.Call(args)
|
||||
}
|
||||
|
||||
pair := typePair{st, dt}
|
||||
|
||||
// ignore conversions of this type
|
||||
if _, ok := c.ignoredConversions[pair]; ok {
|
||||
if c.Debug != nil {
|
||||
c.Debug.Logf("Ignoring conversion of '%v' to '%v'", st, dt)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert sv to dv.
|
||||
if fv, ok := c.conversionFuncs[typePair{st, dt}]; ok {
|
||||
if fv, ok := c.conversionFuncs.fns[pair]; ok {
|
||||
if c.Debug != nil {
|
||||
c.Debug.Logf("Calling custom conversion of '%v' to '%v'", st, dt)
|
||||
}
|
||||
return c.callCustom(sv, dv, fv, scope)
|
||||
}
|
||||
if fv, ok := c.generatedConversionFuncs[typePair{st, dt}]; ok {
|
||||
if fv, ok := c.generatedConversionFuncs.fns[pair]; ok {
|
||||
if c.Debug != nil {
|
||||
c.Debug.Logf("Calling custom conversion of '%v' to '%v'", st, dt)
|
||||
c.Debug.Logf("Calling generated conversion of '%v' to '%v'", st, dt)
|
||||
}
|
||||
return c.callCustom(sv, dv, fv, scope)
|
||||
}
|
||||
|
@ -24,6 +24,8 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/gofuzz"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api/unversioned"
|
||||
)
|
||||
|
||||
func testLogger(t *testing.T) DebugLogger {
|
||||
@ -221,6 +223,55 @@ func TestConverter_CallsRegisteredFunctions(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestConverter_IgnoredConversion(t *testing.T) {
|
||||
type A struct{}
|
||||
type B struct{}
|
||||
|
||||
count := 0
|
||||
c := NewConverter()
|
||||
if err := c.RegisterConversionFunc(func(in *A, out *B, s Scope) error {
|
||||
count++
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Fatalf("unexpected error %v", err)
|
||||
}
|
||||
if err := c.RegisterIgnoredConversion(&A{}, &B{}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
a := A{}
|
||||
b := B{}
|
||||
if err := c.Convert(&a, &b, 0, nil); err != nil {
|
||||
t.Errorf("%v", err)
|
||||
}
|
||||
if count != 0 {
|
||||
t.Errorf("unexpected number of conversion invocations")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConverter_IgnoredConversionNested(t *testing.T) {
|
||||
type C string
|
||||
type A struct {
|
||||
C C
|
||||
}
|
||||
type B struct {
|
||||
C C
|
||||
}
|
||||
|
||||
c := NewConverter()
|
||||
typed := C("")
|
||||
if err := c.RegisterIgnoredConversion(&typed, &typed); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
a := A{C: C("test")}
|
||||
b := B{C: C("other")}
|
||||
if err := c.Convert(&a, &b, AllowDifferentFieldTypeNames, nil); err != nil {
|
||||
t.Errorf("%v", err)
|
||||
}
|
||||
if b.C != C("other") {
|
||||
t.Errorf("expected no conversion of field C: %#v", b)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConverter_GeneratedConversionOverriden(t *testing.T) {
|
||||
type A struct{}
|
||||
type B struct{}
|
||||
@ -243,6 +294,37 @@ func TestConverter_GeneratedConversionOverriden(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestConverter_WithConversionOverriden(t *testing.T) {
|
||||
type A struct{}
|
||||
type B struct{}
|
||||
c := NewConverter()
|
||||
if err := c.RegisterConversionFunc(func(in *A, out *B, s Scope) error {
|
||||
return fmt.Errorf("conversion function should be overriden")
|
||||
}); err != nil {
|
||||
t.Fatalf("unexpected error %v", err)
|
||||
}
|
||||
if err := c.RegisterGeneratedConversionFunc(func(in *A, out *B, s Scope) error {
|
||||
return fmt.Errorf("generated function should be overriden")
|
||||
}); err != nil {
|
||||
t.Fatalf("unexpected error %v", err)
|
||||
}
|
||||
|
||||
ext := NewConversionFuncs()
|
||||
ext.Add(func(in *A, out *B, s Scope) error {
|
||||
return nil
|
||||
})
|
||||
newc := c.WithConversions(ext)
|
||||
|
||||
a := A{}
|
||||
b := B{}
|
||||
if err := c.Convert(&a, &b, 0, nil); err == nil || err.Error() != "conversion function should be overriden" {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
if err := newc.Convert(&a, &b, 0, nil); err != nil {
|
||||
t.Errorf("%v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConverter_MapsStringArrays(t *testing.T) {
|
||||
type A struct {
|
||||
Foo string
|
||||
@ -681,3 +763,132 @@ func TestConverter_FieldRename(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMetaValues(t *testing.T) {
|
||||
type InternalSimple struct {
|
||||
APIVersion string `json:"apiVersion,omitempty"`
|
||||
Kind string `json:"kind,omitempty"`
|
||||
TestString string `json:"testString"`
|
||||
}
|
||||
type ExternalSimple struct {
|
||||
APIVersion string `json:"apiVersion,omitempty"`
|
||||
Kind string `json:"kind,omitempty"`
|
||||
TestString string `json:"testString"`
|
||||
}
|
||||
internalGV := unversioned.GroupVersion{Group: "test.group", Version: "__internal"}
|
||||
externalGV := unversioned.GroupVersion{Group: "test.group", Version: "externalVersion"}
|
||||
|
||||
s := NewScheme()
|
||||
s.AddKnownTypeWithName(internalGV.WithKind("Simple"), &InternalSimple{})
|
||||
s.AddKnownTypeWithName(externalGV.WithKind("Simple"), &ExternalSimple{})
|
||||
|
||||
internalToExternalCalls := 0
|
||||
externalToInternalCalls := 0
|
||||
|
||||
// Register functions to verify that scope.Meta() gets set correctly.
|
||||
err := s.AddConversionFuncs(
|
||||
func(in *InternalSimple, out *ExternalSimple, scope Scope) error {
|
||||
t.Logf("internal -> external")
|
||||
if e, a := internalGV.String(), scope.Meta().SrcVersion; e != a {
|
||||
t.Fatalf("Expected '%v', got '%v'", e, a)
|
||||
}
|
||||
if e, a := externalGV.String(), scope.Meta().DestVersion; e != a {
|
||||
t.Fatalf("Expected '%v', got '%v'", e, a)
|
||||
}
|
||||
scope.Convert(&in.TestString, &out.TestString, 0)
|
||||
internalToExternalCalls++
|
||||
return nil
|
||||
},
|
||||
func(in *ExternalSimple, out *InternalSimple, scope Scope) error {
|
||||
t.Logf("external -> internal")
|
||||
if e, a := externalGV.String(), scope.Meta().SrcVersion; e != a {
|
||||
t.Errorf("Expected '%v', got '%v'", e, a)
|
||||
}
|
||||
if e, a := internalGV.String(), scope.Meta().DestVersion; e != a {
|
||||
t.Fatalf("Expected '%v', got '%v'", e, a)
|
||||
}
|
||||
scope.Convert(&in.TestString, &out.TestString, 0)
|
||||
externalToInternalCalls++
|
||||
return nil
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
simple := &InternalSimple{
|
||||
TestString: "foo",
|
||||
}
|
||||
|
||||
s.Log(t)
|
||||
|
||||
out, err := s.ConvertToVersion(simple, externalGV.String())
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
internal, err := s.ConvertToVersion(out, internalGV.String())
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if e, a := simple, internal; !reflect.DeepEqual(e, a) {
|
||||
t.Errorf("Expected:\n %#v,\n Got:\n %#v", e, a)
|
||||
}
|
||||
|
||||
if e, a := 1, internalToExternalCalls; e != a {
|
||||
t.Errorf("Expected %v, got %v", e, a)
|
||||
}
|
||||
if e, a := 1, externalToInternalCalls; e != a {
|
||||
t.Errorf("Expected %v, got %v", e, a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMetaValuesUnregisteredConvert(t *testing.T) {
|
||||
type InternalSimple struct {
|
||||
Version string `json:"apiVersion,omitempty"`
|
||||
Kind string `json:"kind,omitempty"`
|
||||
TestString string `json:"testString"`
|
||||
}
|
||||
type ExternalSimple struct {
|
||||
Version string `json:"apiVersion,omitempty"`
|
||||
Kind string `json:"kind,omitempty"`
|
||||
TestString string `json:"testString"`
|
||||
}
|
||||
s := NewScheme()
|
||||
// We deliberately don't register the types.
|
||||
|
||||
internalToExternalCalls := 0
|
||||
|
||||
// Register functions to verify that scope.Meta() gets set correctly.
|
||||
err := s.AddConversionFuncs(
|
||||
func(in *InternalSimple, out *ExternalSimple, scope Scope) error {
|
||||
if e, a := "unknown/unknown", scope.Meta().SrcVersion; e != a {
|
||||
t.Fatalf("Expected '%v', got '%v'", e, a)
|
||||
}
|
||||
if e, a := "unknown/unknown", scope.Meta().DestVersion; e != a {
|
||||
t.Fatalf("Expected '%v', got '%v'", e, a)
|
||||
}
|
||||
scope.Convert(&in.TestString, &out.TestString, 0)
|
||||
internalToExternalCalls++
|
||||
return nil
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
simple := &InternalSimple{TestString: "foo"}
|
||||
external := &ExternalSimple{}
|
||||
err = s.Convert(simple, external)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
if e, a := simple.TestString, external.TestString; e != a {
|
||||
t.Errorf("Expected %v, got %v", e, a)
|
||||
}
|
||||
|
||||
// Verify that our conversion handler got called.
|
||||
if e, a := 1, internalToExternalCalls; e != a {
|
||||
t.Errorf("Expected %v, got %v", e, a)
|
||||
}
|
||||
}
|
||||
|
@ -1,194 +0,0 @@
|
||||
/*
|
||||
Copyright 2014 The Kubernetes Authors 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 (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"github.com/ugorji/go/codec"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api/unversioned"
|
||||
)
|
||||
|
||||
func (s *Scheme) DecodeToVersionedObject(data []byte) (interface{}, unversioned.GroupVersionKind, error) {
|
||||
kind, err := s.DataKind(data)
|
||||
if err != nil {
|
||||
return nil, unversioned.GroupVersionKind{}, err
|
||||
}
|
||||
|
||||
internalGV, exists := s.InternalVersions[kind.Group]
|
||||
if !exists {
|
||||
return nil, unversioned.GroupVersionKind{}, fmt.Errorf("no internalVersion specified for %v", kind)
|
||||
}
|
||||
|
||||
if len(kind.Group) == 0 && len(internalGV.Group) != 0 {
|
||||
return nil, unversioned.GroupVersionKind{}, fmt.Errorf("group not set in '%s'", string(data))
|
||||
}
|
||||
if len(kind.Version) == 0 && len(internalGV.Version) != 0 {
|
||||
return nil, unversioned.GroupVersionKind{}, fmt.Errorf("version not set in '%s'", string(data))
|
||||
}
|
||||
if kind.Kind == "" {
|
||||
return nil, unversioned.GroupVersionKind{}, fmt.Errorf("kind not set in '%s'", string(data))
|
||||
}
|
||||
|
||||
obj, err := s.NewObject(kind)
|
||||
if err != nil {
|
||||
return nil, unversioned.GroupVersionKind{}, err
|
||||
}
|
||||
|
||||
if err := codec.NewDecoderBytes(data, new(codec.JsonHandle)).Decode(obj); err != nil {
|
||||
return nil, unversioned.GroupVersionKind{}, err
|
||||
}
|
||||
return obj, kind, nil
|
||||
}
|
||||
|
||||
// Decode converts a 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 not decode
|
||||
// objects without version set unless InternalVersion is also "".
|
||||
func (s *Scheme) Decode(data []byte) (interface{}, error) {
|
||||
return s.DecodeToVersion(data, unversioned.GroupVersion{})
|
||||
}
|
||||
|
||||
// DecodeToVersion converts a 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 versioned
|
||||
// type before being returned. Decode will not decode objects without version
|
||||
// set unless version is also "".
|
||||
// a GroupVersion with .IsEmpty() == true is means "use the internal version for
|
||||
// the object's group"
|
||||
func (s *Scheme) DecodeToVersion(data []byte, targetVersion unversioned.GroupVersion) (interface{}, error) {
|
||||
obj, sourceKind, err := s.DecodeToVersionedObject(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Version and Kind should be blank in memory.
|
||||
if err := s.SetVersionAndKind("", "", obj); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// if the targetVersion is empty, then we want the internal version, but the internal version varies by
|
||||
// group. We can lookup the group now because we have knowledge of the group
|
||||
if targetVersion.IsEmpty() {
|
||||
exists := false
|
||||
targetVersion, exists = s.InternalVersions[sourceKind.Group]
|
||||
if !exists {
|
||||
return nil, fmt.Errorf("no internalVersion specified for %v", targetVersion)
|
||||
}
|
||||
}
|
||||
|
||||
// Convert if needed.
|
||||
if targetVersion != sourceKind.GroupVersion() {
|
||||
objOut, err := s.NewObject(targetVersion.WithKind(sourceKind.Kind))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
flags, meta := s.generateConvertMeta(sourceKind.GroupVersion(), targetVersion, obj)
|
||||
if err := s.converter.Convert(obj, objOut, flags, meta); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
obj = objOut
|
||||
}
|
||||
return obj, nil
|
||||
}
|
||||
|
||||
// DecodeInto parses a 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 {
|
||||
return s.DecodeIntoWithSpecifiedVersionKind(data, obj, unversioned.GroupVersionKind{})
|
||||
}
|
||||
|
||||
// DecodeIntoWithSpecifiedVersionKind compares the passed in requestGroupVersionKind
|
||||
// with data.Version and data.Kind, defaulting data.Version and
|
||||
// data.Kind to the specified value if they are empty, or generating an error if
|
||||
// data.Version and data.Kind are not empty and differ from the specified value.
|
||||
// The function then implements the functionality of DecodeInto.
|
||||
// If specifiedVersion and specifiedKind are empty, the function degenerates to
|
||||
// DecodeInto.
|
||||
func (s *Scheme) DecodeIntoWithSpecifiedVersionKind(data []byte, obj interface{}, requestedGVK unversioned.GroupVersionKind) error {
|
||||
if len(data) == 0 {
|
||||
return errors.New("empty input")
|
||||
}
|
||||
dataKind, err := s.DataKind(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(dataKind.Group) == 0 {
|
||||
dataKind.Group = requestedGVK.Group
|
||||
}
|
||||
if len(dataKind.Version) == 0 {
|
||||
dataKind.Version = requestedGVK.Version
|
||||
}
|
||||
if len(dataKind.Kind) == 0 {
|
||||
dataKind.Kind = requestedGVK.Kind
|
||||
}
|
||||
|
||||
if len(requestedGVK.Group) > 0 && requestedGVK.Group != dataKind.Group {
|
||||
return errors.New(fmt.Sprintf("The fully qualified kind in the data (%v) does not match the specified apiVersion(%v)", dataKind, requestedGVK))
|
||||
}
|
||||
if len(requestedGVK.Version) > 0 && requestedGVK.Version != dataKind.Version {
|
||||
return errors.New(fmt.Sprintf("The fully qualified kind in the data (%v) does not match the specified apiVersion(%v)", dataKind, requestedGVK))
|
||||
}
|
||||
if len(requestedGVK.Kind) > 0 && requestedGVK.Kind != dataKind.Kind {
|
||||
return errors.New(fmt.Sprintf("The fully qualified kind in the data (%v) does not match the specified apiVersion(%v)", dataKind, requestedGVK))
|
||||
}
|
||||
|
||||
objGVK, err := s.ObjectKind(obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Assume objects with unset fields are being unmarshalled into the
|
||||
// correct type.
|
||||
if len(dataKind.Group) == 0 {
|
||||
dataKind.Group = objGVK.Group
|
||||
}
|
||||
if len(dataKind.Version) == 0 {
|
||||
dataKind.Version = objGVK.Version
|
||||
}
|
||||
if len(dataKind.Kind) == 0 {
|
||||
dataKind.Kind = objGVK.Kind
|
||||
}
|
||||
|
||||
external, err := s.NewObject(dataKind)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := codec.NewDecoderBytes(data, new(codec.JsonHandle)).Decode(external); err != nil {
|
||||
return err
|
||||
}
|
||||
flags, meta := s.generateConvertMeta(dataKind.GroupVersion(), objGVK.GroupVersion(), external)
|
||||
if err := s.converter.Convert(external, obj, flags, meta); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Version and Kind should be blank in memory.
|
||||
return s.SetVersionAndKind("", "", obj)
|
||||
}
|
||||
|
||||
func (s *Scheme) DecodeParametersInto(parameters url.Values, obj interface{}) error {
|
||||
if err := s.Convert(¶meters, obj); err != nil {
|
||||
return err
|
||||
}
|
||||
// TODO: Should we do any convertion here?
|
||||
return nil
|
||||
}
|
@ -14,18 +14,11 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package conversion provides go object versioning and encoding/decoding
|
||||
// mechanisms.
|
||||
// Package conversion provides go object versioning.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
package conversion
|
||||
|
@ -1,155 +0,0 @@
|
||||
/*
|
||||
Copyright 2014 The Kubernetes Authors 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 (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"path"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api/unversioned"
|
||||
)
|
||||
|
||||
// EncodeToVersion 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 v1.Pod. However, if
|
||||
// you pass in an object that's already versioned (v1.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) EncodeToVersion(obj interface{}, destVersion string) (data []byte, err error) {
|
||||
buff := &bytes.Buffer{}
|
||||
if err := s.EncodeToVersionStream(obj, destVersion, buff); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buff.Bytes(), nil
|
||||
}
|
||||
|
||||
func (s *Scheme) EncodeToVersionStream(obj interface{}, destGroupVersionString string, stream io.Writer) error {
|
||||
obj = maybeCopy(obj)
|
||||
v, _ := EnforcePtr(obj) // maybeCopy guarantees a pointer
|
||||
|
||||
// Don't encode an object defined in the unversioned package, unless if the
|
||||
// destGroupVersionString is v1, encode it to v1 for backward compatibility.
|
||||
pkg := path.Base(v.Type().PkgPath())
|
||||
if pkg == "unversioned" && destGroupVersionString != "v1" {
|
||||
// TODO: convert this to streaming too
|
||||
data, err := s.encodeUnversionedObject(obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = stream.Write(data)
|
||||
return err
|
||||
}
|
||||
|
||||
if _, registered := s.typeToGVK[v.Type()]; !registered {
|
||||
return fmt.Errorf("type %v is not registered for %q and it will be impossible to Decode it, therefore Encode will refuse to encode it.", v.Type(), destGroupVersionString)
|
||||
}
|
||||
|
||||
objKind, err := s.ObjectKind(obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
destVersion, err := unversioned.ParseGroupVersion(destGroupVersionString)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Perform a conversion if necessary.
|
||||
if objKind.GroupVersion() != destVersion {
|
||||
objOut, err := s.NewObject(destVersion.WithKind(objKind.Kind))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
flags, meta := s.generateConvertMeta(objKind.GroupVersion(), destVersion, obj)
|
||||
err = s.converter.Convert(obj, objOut, flags, meta)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
obj = objOut
|
||||
|
||||
// ensure the output object name comes from the destination type
|
||||
newGroupVersionKind, err := s.ObjectKind(obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
objKind.Kind = newGroupVersionKind.Kind
|
||||
}
|
||||
|
||||
// Version and Kind should be set on the wire.
|
||||
err = s.SetVersionAndKind(destVersion.String(), objKind.Kind, obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// To add metadata, do some simple surgery on the JSON.
|
||||
encoder := json.NewEncoder(stream)
|
||||
if err := encoder.Encode(obj); err != nil {
|
||||
return 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 err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Scheme) encodeUnversionedObject(obj interface{}) (data []byte, err error) {
|
||||
objGVK, err := s.ObjectKind(obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = s.SetVersionAndKind("", objGVK.Kind, obj); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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)
|
||||
return data, nil
|
||||
}
|
@ -28,6 +28,11 @@ type notRegisteredErr struct {
|
||||
t reflect.Type
|
||||
}
|
||||
|
||||
// NewNotRegisteredErr is exposed for testing.
|
||||
func NewNotRegisteredErr(gvk unversioned.GroupVersionKind, t reflect.Type) error {
|
||||
return ¬RegisteredErr{gvk: gvk, t: t}
|
||||
}
|
||||
|
||||
func (k *notRegisteredErr) Error() string {
|
||||
if k.t != nil {
|
||||
return fmt.Sprintf("no kind is registered for the type %v", k.t)
|
||||
|
39
pkg/conversion/helper.go
Normal file
39
pkg/conversion/helper.go
Normal file
@ -0,0 +1,39 @@
|
||||
/*
|
||||
Copyright 2014 The Kubernetes Authors 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"
|
||||
)
|
||||
|
||||
// EnforcePtr 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 {
|
||||
if v.Kind() == reflect.Invalid {
|
||||
return reflect.Value{}, fmt.Errorf("expected pointer, but got invalid kind")
|
||||
}
|
||||
return reflect.Value{}, fmt.Errorf("expected pointer, but got %v type", v.Type())
|
||||
}
|
||||
if v.IsNil() {
|
||||
return reflect.Value{}, fmt.Errorf("expected pointer, but got nil")
|
||||
}
|
||||
return v.Elem(), nil
|
||||
}
|
38
pkg/conversion/helper_test.go
Normal file
38
pkg/conversion/helper_test.go
Normal file
@ -0,0 +1,38 @@
|
||||
/*
|
||||
Copyright 2014 The Kubernetes Authors 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 "testing"
|
||||
|
||||
func TestInvalidPtrValueKind(t *testing.T) {
|
||||
var simple interface{}
|
||||
switch obj := simple.(type) {
|
||||
default:
|
||||
_, err := EnforcePtr(obj)
|
||||
if err == nil {
|
||||
t.Errorf("Expected error on invalid kind")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnforceNilPtr(t *testing.T) {
|
||||
var nilPtr *struct{}
|
||||
_, err := EnforcePtr(nilPtr)
|
||||
if err == nil {
|
||||
t.Errorf("Expected error on nil pointer")
|
||||
}
|
||||
}
|
@ -1,153 +0,0 @@
|
||||
/*
|
||||
Copyright 2014 The Kubernetes Authors 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"
|
||||
"path"
|
||||
"reflect"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api/unversioned"
|
||||
)
|
||||
|
||||
// MetaFactory is used to store and retrieve the version and kind
|
||||
// information for all objects in a scheme.
|
||||
type MetaFactory interface {
|
||||
// Update sets the given version and kind onto the object.
|
||||
Update(version, kind string, obj interface{}) error
|
||||
// Interpret should return the group,version,kind of the wire-format of
|
||||
// the object.
|
||||
Interpret(data []byte) (gvk unversioned.GroupVersionKind, err error)
|
||||
}
|
||||
|
||||
// DefaultMetaFactory is a default factory for versioning objects in JSON. The object
|
||||
// in memory and in the default JSON serialization will use the "kind" and "apiVersion"
|
||||
// fields.
|
||||
var DefaultMetaFactory = SimpleMetaFactory{KindField: "Kind", VersionField: "APIVersion"}
|
||||
|
||||
// SimpleMetaFactory provides default methods for retrieving the type and version of objects
|
||||
// that are identified with an "apiVersion" and "kind" fields in their JSON
|
||||
// serialization. It may be parameterized with the names of the fields in memory, or an
|
||||
// optional list of base structs to search for those fields in memory.
|
||||
type SimpleMetaFactory struct {
|
||||
// The name of the API version field in memory of the struct
|
||||
VersionField string
|
||||
// The name of the kind field in memory of the struct.
|
||||
KindField string
|
||||
// Optional, if set will look in the named inline structs to find the fields to set.
|
||||
BaseFields []string
|
||||
}
|
||||
|
||||
// Interpret will return the group,version,kind of the JSON wire-format
|
||||
// encoding of an object, or an error.
|
||||
func (SimpleMetaFactory) Interpret(data []byte) (unversioned.GroupVersionKind, error) {
|
||||
findKind := struct {
|
||||
APIVersion string `json:"apiVersion,omitempty"`
|
||||
Kind string `json:"kind,omitempty"`
|
||||
}{}
|
||||
err := json.Unmarshal(data, &findKind)
|
||||
if err != nil {
|
||||
return unversioned.GroupVersionKind{}, fmt.Errorf("couldn't get version/kind; json parse error: %v", err)
|
||||
}
|
||||
gv, err := unversioned.ParseGroupVersion(findKind.APIVersion)
|
||||
if err != nil {
|
||||
return unversioned.GroupVersionKind{}, fmt.Errorf("couldn't parse apiVersion: %v", err)
|
||||
}
|
||||
|
||||
return gv.WithKind(findKind.Kind), nil
|
||||
}
|
||||
|
||||
func (f SimpleMetaFactory) Update(version, kind string, obj interface{}) error {
|
||||
return UpdateVersionAndKind(f.BaseFields, f.VersionField, version, f.KindField, kind, obj)
|
||||
}
|
||||
|
||||
// UpdateVersionAndKind uses reflection to find and set the versionField and kindField fields
|
||||
// on a pointer to a struct to version and kind. Provided as a convenience for others
|
||||
// implementing MetaFactory. Pass an array to baseFields to check one or more nested structs
|
||||
// for the named fields. The version field is treated as optional if it is not present in the struct.
|
||||
// TODO: this method is on its way out
|
||||
func UpdateVersionAndKind(baseFields []string, versionField, version, kindField, kind string, obj interface{}) error {
|
||||
if typed, ok := obj.(unversioned.ObjectKind); ok {
|
||||
if len(version) == 0 && len(kind) == 0 {
|
||||
typed.SetGroupVersionKind(nil)
|
||||
} else {
|
||||
gv, err := unversioned.ParseGroupVersion(version)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
typed.SetGroupVersionKind(&unversioned.GroupVersionKind{Group: gv.Group, Version: gv.Version, Kind: kind})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
v, err := EnforcePtr(obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pkg := path.Base(v.Type().PkgPath())
|
||||
t := v.Type()
|
||||
name := t.Name()
|
||||
if v.Kind() != reflect.Struct {
|
||||
return fmt.Errorf("expected struct, but got %v: %v (%#v)", v.Kind(), name, v.Interface())
|
||||
}
|
||||
|
||||
for i := range baseFields {
|
||||
base := v.FieldByName(baseFields[i])
|
||||
if !base.IsValid() {
|
||||
continue
|
||||
}
|
||||
v = base
|
||||
}
|
||||
|
||||
field := v.FieldByName(kindField)
|
||||
if !field.IsValid() {
|
||||
// Types defined in the unversioned package are allowed to not have a
|
||||
// kindField. Clients will have to know what they are based on the
|
||||
// context.
|
||||
// TODO: add some type trait here, or some way of indicating whether
|
||||
// this feature is allowed on a per-type basis. Using package name is
|
||||
// overly broad and a bit hacky.
|
||||
if pkg == "unversioned" {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("couldn't find %v field in %#v", kindField, v.Interface())
|
||||
}
|
||||
field.SetString(kind)
|
||||
|
||||
if field := v.FieldByName(versionField); field.IsValid() {
|
||||
field.SetString(version)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// EnforcePtr 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 {
|
||||
if v.Kind() == reflect.Invalid {
|
||||
return reflect.Value{}, fmt.Errorf("expected pointer, but got invalid kind")
|
||||
}
|
||||
return reflect.Value{}, fmt.Errorf("expected pointer, but got %v type", v.Type())
|
||||
}
|
||||
if v.IsNil() {
|
||||
return reflect.Value{}, fmt.Errorf("expected pointer, but got nil")
|
||||
}
|
||||
return v.Elem(), nil
|
||||
}
|
@ -1,289 +0,0 @@
|
||||
/*
|
||||
Copyright 2014 The Kubernetes Authors 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"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api/unversioned"
|
||||
)
|
||||
|
||||
func TestSimpleMetaFactoryInterpret(t *testing.T) {
|
||||
factory := SimpleMetaFactory{}
|
||||
fqKind, err := factory.Interpret([]byte(`{"apiVersion":"g/1","kind":"object"}`))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
expectedFQKind := unversioned.GroupVersionKind{Group: "g", Version: "1", Kind: "object"}
|
||||
if expectedFQKind != fqKind {
|
||||
t.Errorf("unexpected interpret: %s %s", expectedFQKind, fqKind)
|
||||
}
|
||||
|
||||
// no kind or version
|
||||
fqKind, err = factory.Interpret([]byte(`{}`))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if !fqKind.IsEmpty() {
|
||||
t.Errorf("unexpected interpret: %s %s", fqKind)
|
||||
}
|
||||
|
||||
// unparsable
|
||||
fqKind, err = factory.Interpret([]byte(`{`))
|
||||
if err == nil {
|
||||
t.Errorf("unexpected non-error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSimpleMetaFactoryUpdate(t *testing.T) {
|
||||
factory := SimpleMetaFactory{VersionField: "V", KindField: "K"}
|
||||
|
||||
obj := struct {
|
||||
V string
|
||||
K string
|
||||
}{"1", "2"}
|
||||
|
||||
// must pass a pointer
|
||||
if err := factory.Update("test", "other", obj); err == nil {
|
||||
t.Errorf("unexpected non-error")
|
||||
}
|
||||
if obj.V != "1" || obj.K != "2" {
|
||||
t.Errorf("unexpected update: %v", obj)
|
||||
}
|
||||
|
||||
// updates
|
||||
if err := factory.Update("test", "other", &obj); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if obj.V != "test" || obj.K != "other" {
|
||||
t.Errorf("unexpected update: %v", obj)
|
||||
}
|
||||
}
|
||||
|
||||
// Test Updating objects that don't have a Kind field.
|
||||
func TestSimpleMetaFactoryUpdateNoKindField(t *testing.T) {
|
||||
factory := SimpleMetaFactory{VersionField: "APIVersion", KindField: "Kind"}
|
||||
// obj does not have a Kind field and is not defined in the unversioned package.
|
||||
obj := struct {
|
||||
SomeField string
|
||||
}{"1"}
|
||||
expectedError := fmt.Errorf("couldn't find %v field in %#v", factory.KindField, obj)
|
||||
if err := factory.Update("test", "other", &obj); err == nil || expectedError.Error() != err.Error() {
|
||||
t.Fatalf("expected error: %v, got: %v", expectedError, err)
|
||||
}
|
||||
|
||||
// ListMeta does not have a Kind field, but is defined in the unversioned package.
|
||||
listMeta := unversioned.ListMeta{}
|
||||
if err := factory.Update("test", "other", &listMeta); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSimpleMetaFactoryUpdateStruct(t *testing.T) {
|
||||
factory := SimpleMetaFactory{BaseFields: []string{"Test"}, VersionField: "V", KindField: "K"}
|
||||
|
||||
type Inner struct {
|
||||
V string
|
||||
K string
|
||||
}
|
||||
obj := struct {
|
||||
Test Inner
|
||||
}{Test: Inner{"1", "2"}}
|
||||
|
||||
// updates
|
||||
if err := factory.Update("test", "other", &obj); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if obj.Test.V != "test" || obj.Test.K != "other" {
|
||||
t.Errorf("unexpected update: %v", obj)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMetaValues(t *testing.T) {
|
||||
type InternalSimple struct {
|
||||
APIVersion string `json:"apiVersion,omitempty"`
|
||||
Kind string `json:"kind,omitempty"`
|
||||
TestString string `json:"testString"`
|
||||
}
|
||||
type ExternalSimple struct {
|
||||
APIVersion string `json:"apiVersion,omitempty"`
|
||||
Kind string `json:"kind,omitempty"`
|
||||
TestString string `json:"testString"`
|
||||
}
|
||||
internalGV := unversioned.GroupVersion{Group: "test.group", Version: ""}
|
||||
externalGV := unversioned.GroupVersion{Group: "test.group", Version: "externalVersion"}
|
||||
|
||||
s := NewScheme()
|
||||
s.InternalVersions[internalGV.Group] = internalGV
|
||||
s.AddKnownTypeWithName(internalGV.WithKind("Simple"), &InternalSimple{})
|
||||
s.AddKnownTypeWithName(externalGV.WithKind("Simple"), &ExternalSimple{})
|
||||
|
||||
internalToExternalCalls := 0
|
||||
externalToInternalCalls := 0
|
||||
|
||||
// Register functions to verify that scope.Meta() gets set correctly.
|
||||
err := s.AddConversionFuncs(
|
||||
func(in *InternalSimple, out *ExternalSimple, scope Scope) error {
|
||||
t.Logf("internal -> external")
|
||||
if e, a := internalGV.String(), scope.Meta().SrcVersion; e != a {
|
||||
t.Fatalf("Expected '%v', got '%v'", e, a)
|
||||
}
|
||||
if e, a := externalGV.String(), scope.Meta().DestVersion; e != a {
|
||||
t.Fatalf("Expected '%v', got '%v'", e, a)
|
||||
}
|
||||
scope.Convert(&in.TestString, &out.TestString, 0)
|
||||
internalToExternalCalls++
|
||||
return nil
|
||||
},
|
||||
func(in *ExternalSimple, out *InternalSimple, scope Scope) error {
|
||||
t.Logf("external -> internal")
|
||||
if e, a := externalGV.String(), scope.Meta().SrcVersion; e != a {
|
||||
t.Errorf("Expected '%v', got '%v'", e, a)
|
||||
}
|
||||
if e, a := internalGV.String(), scope.Meta().DestVersion; e != a {
|
||||
t.Fatalf("Expected '%v', got '%v'", e, a)
|
||||
}
|
||||
scope.Convert(&in.TestString, &out.TestString, 0)
|
||||
externalToInternalCalls++
|
||||
return nil
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
simple := &InternalSimple{
|
||||
TestString: "foo",
|
||||
}
|
||||
|
||||
s.Log(t)
|
||||
|
||||
// Test Encode, Decode, and DecodeInto
|
||||
data, err := s.EncodeToVersion(simple, externalGV.String())
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
t.Logf(string(data))
|
||||
obj2, err := s.Decode(data)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if _, ok := obj2.(*InternalSimple); !ok {
|
||||
t.Fatalf("Got wrong type")
|
||||
}
|
||||
if e, a := simple, obj2; !reflect.DeepEqual(e, a) {
|
||||
t.Errorf("Expected:\n %#v,\n Got:\n %#v", e, a)
|
||||
}
|
||||
|
||||
obj3 := &InternalSimple{}
|
||||
if err := s.DecodeInto(data, obj3); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if e, a := simple, obj3; !reflect.DeepEqual(e, a) {
|
||||
t.Errorf("Expected:\n %#v,\n Got:\n %#v", e, a)
|
||||
}
|
||||
|
||||
// Test Convert
|
||||
external := &ExternalSimple{}
|
||||
err = s.Convert(simple, external)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
if e, a := simple.TestString, external.TestString; e != a {
|
||||
t.Errorf("Expected %v, got %v", e, a)
|
||||
}
|
||||
|
||||
// Encode and Convert should each have caused an increment.
|
||||
if e, a := 2, internalToExternalCalls; e != a {
|
||||
t.Errorf("Expected %v, got %v", e, a)
|
||||
}
|
||||
// Decode and DecodeInto should each have caused an increment.
|
||||
if e, a := 2, externalToInternalCalls; e != a {
|
||||
t.Errorf("Expected %v, got %v", e, a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMetaValuesUnregisteredConvert(t *testing.T) {
|
||||
type InternalSimple struct {
|
||||
Version string `json:"apiVersion,omitempty"`
|
||||
Kind string `json:"kind,omitempty"`
|
||||
TestString string `json:"testString"`
|
||||
}
|
||||
type ExternalSimple struct {
|
||||
Version string `json:"apiVersion,omitempty"`
|
||||
Kind string `json:"kind,omitempty"`
|
||||
TestString string `json:"testString"`
|
||||
}
|
||||
s := NewScheme()
|
||||
// We deliberately don't register the types.
|
||||
|
||||
internalToExternalCalls := 0
|
||||
|
||||
// Register functions to verify that scope.Meta() gets set correctly.
|
||||
err := s.AddConversionFuncs(
|
||||
func(in *InternalSimple, out *ExternalSimple, scope Scope) error {
|
||||
if e, a := "unknown/unknown", scope.Meta().SrcVersion; e != a {
|
||||
t.Fatalf("Expected '%v', got '%v'", e, a)
|
||||
}
|
||||
if e, a := "unknown/unknown", scope.Meta().DestVersion; e != a {
|
||||
t.Fatalf("Expected '%v', got '%v'", e, a)
|
||||
}
|
||||
scope.Convert(&in.TestString, &out.TestString, 0)
|
||||
internalToExternalCalls++
|
||||
return nil
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
simple := &InternalSimple{TestString: "foo"}
|
||||
external := &ExternalSimple{}
|
||||
err = s.Convert(simple, external)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
if e, a := simple.TestString, external.TestString; e != a {
|
||||
t.Errorf("Expected %v, got %v", e, a)
|
||||
}
|
||||
|
||||
// Verify that our conversion handler got called.
|
||||
if e, a := 1, internalToExternalCalls; e != a {
|
||||
t.Errorf("Expected %v, got %v", e, a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvalidPtrValueKind(t *testing.T) {
|
||||
var simple interface{}
|
||||
switch obj := simple.(type) {
|
||||
default:
|
||||
_, err := EnforcePtr(obj)
|
||||
if err == nil {
|
||||
t.Errorf("Expected error on invalid kind")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnforceNilPtr(t *testing.T) {
|
||||
var nilPtr *struct{}
|
||||
_, err := EnforcePtr(nilPtr)
|
||||
if err == nil {
|
||||
t.Errorf("Expected error on nil pointer")
|
||||
}
|
||||
}
|
@ -21,8 +21,6 @@ import (
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"k8s.io/kubernetes/pkg/runtime"
|
||||
)
|
||||
|
||||
func jsonTag(field reflect.StructField) (string, bool) {
|
||||
@ -93,10 +91,10 @@ func addListOfParams(values url.Values, tag string, omitempty bool, list reflect
|
||||
}
|
||||
}
|
||||
|
||||
// Convert takes a versioned runtime.Object and serializes it to a url.Values object
|
||||
// using JSON tags as parameter names. Only top-level simple values, arrays, and slices
|
||||
// are serialized. Embedded structs, maps, etc. will not be serialized.
|
||||
func Convert(obj runtime.Object) (url.Values, error) {
|
||||
// Convert takes an object and converts it to a url.Values object using JSON tags as
|
||||
// parameter names. Only top-level simple values, arrays, and slices are serialized.
|
||||
// Embedded structs, maps, etc. will not be serialized.
|
||||
func Convert(obj interface{}) (url.Values, error) {
|
||||
result := url.Values{}
|
||||
if obj == nil {
|
||||
return result, nil
|
||||
|
@ -23,7 +23,6 @@ import (
|
||||
|
||||
"k8s.io/kubernetes/pkg/api/unversioned"
|
||||
"k8s.io/kubernetes/pkg/conversion/queryparams"
|
||||
"k8s.io/kubernetes/pkg/runtime"
|
||||
)
|
||||
|
||||
type namedString string
|
||||
@ -85,7 +84,7 @@ func validateResult(t *testing.T, input interface{}, actual, expected url.Values
|
||||
|
||||
func TestConvert(t *testing.T) {
|
||||
tests := []struct {
|
||||
input runtime.Object
|
||||
input interface{}
|
||||
expected url.Values
|
||||
}{
|
||||
{
|
||||
|
@ -33,6 +33,13 @@ type Scheme struct {
|
||||
// The reflect.Type we index by should *not* be a pointer.
|
||||
typeToGVK map[reflect.Type][]unversioned.GroupVersionKind
|
||||
|
||||
// unversionedTypes are transformed without conversion in ConvertToVersion.
|
||||
unversionedTypes map[reflect.Type]unversioned.GroupVersionKind
|
||||
// unversionedKinds are the names of kinds that can be created in the context of any group
|
||||
// or version
|
||||
// TODO: resolve the status of unversioned types.
|
||||
unversionedKinds map[string]reflect.Type
|
||||
|
||||
// converter stores all registered conversion functions. It also has
|
||||
// default coverting behavior.
|
||||
converter *Converter
|
||||
@ -44,17 +51,6 @@ type Scheme struct {
|
||||
// Indent will cause the JSON output from Encode to be indented,
|
||||
// if and only if it is true.
|
||||
Indent bool
|
||||
|
||||
// InternalVersion is the default internal version. It is recommended that
|
||||
// you use "" for the internal version.
|
||||
// TODO logically the InternalVersion is different for every Group, so this structure
|
||||
// must be map
|
||||
InternalVersions map[string]unversioned.GroupVersion
|
||||
|
||||
// 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.
|
||||
MetaFactory MetaFactory
|
||||
}
|
||||
|
||||
// NewScheme manufactures a new scheme.
|
||||
@ -62,16 +58,10 @@ func NewScheme() *Scheme {
|
||||
s := &Scheme{
|
||||
gvkToType: map[unversioned.GroupVersionKind]reflect.Type{},
|
||||
typeToGVK: map[reflect.Type][]unversioned.GroupVersionKind{},
|
||||
unversionedTypes: map[reflect.Type]unversioned.GroupVersionKind{},
|
||||
unversionedKinds: map[string]reflect.Type{},
|
||||
converter: NewConverter(),
|
||||
cloner: NewCloner(),
|
||||
// TODO remove this hard coded list. As step one, hardcode it here so this pull doesn't become even bigger
|
||||
InternalVersions: map[string]unversioned.GroupVersion{
|
||||
"": {},
|
||||
"componentconfig": {Group: "componentconfig"},
|
||||
"extensions": {Group: "extensions"},
|
||||
"metrics": {Group: "metrics"},
|
||||
},
|
||||
MetaFactory: DefaultMetaFactory,
|
||||
}
|
||||
s.converter.nameFunc = s.nameFunc
|
||||
return s
|
||||
@ -92,11 +82,8 @@ func (s *Scheme) nameFunc(t reflect.Type) string {
|
||||
}
|
||||
|
||||
for _, gvk := range gvks {
|
||||
internalGV, exists := s.InternalVersions[gvk.Group]
|
||||
if !exists {
|
||||
internalGV := gvk.GroupVersion()
|
||||
internalGV.Version = ""
|
||||
}
|
||||
internalGV.Version = "__internal" // this is hacky and maybe should be passed in
|
||||
internalGVK := internalGV.WithKind(gvk.Kind)
|
||||
|
||||
if internalType, exists := s.gvkToType[internalGVK]; exists {
|
||||
@ -107,11 +94,30 @@ func (s *Scheme) nameFunc(t reflect.Type) string {
|
||||
return gvks[0].Kind
|
||||
}
|
||||
|
||||
// AddUnversionedTypes registers all types passed in 'types' as being members of version 'version',
|
||||
// and marks them as being convertible to all API versions.
|
||||
// All objects passed to types should be pointers to structs. The name that go reports for
|
||||
// the struct becomes the "kind" field when encoding.
|
||||
func (s *Scheme) AddUnversionedTypes(version unversioned.GroupVersion, types ...interface{}) {
|
||||
s.AddKnownTypes(version, types...)
|
||||
for _, obj := range types {
|
||||
t := reflect.TypeOf(obj).Elem()
|
||||
gvk := version.WithKind(t.Name())
|
||||
s.unversionedTypes[t] = gvk
|
||||
if _, ok := s.unversionedKinds[gvk.Kind]; ok {
|
||||
panic(fmt.Sprintf("%v has already been registered as unversioned kind %q - kind name must be unique", reflect.TypeOf(t), gvk.Kind))
|
||||
}
|
||||
s.unversionedKinds[gvk.Kind] = t
|
||||
}
|
||||
}
|
||||
|
||||
// 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 pointers to structs. The name that go reports for
|
||||
// the struct becomes the "kind" field when encoding.
|
||||
func (s *Scheme) AddKnownTypes(gv unversioned.GroupVersion, types ...interface{}) {
|
||||
if len(gv.Version) == 0 {
|
||||
panic(fmt.Sprintf("version is required on all types: %s %v", gv, types[0]))
|
||||
}
|
||||
for _, obj := range types {
|
||||
t := reflect.TypeOf(obj)
|
||||
if t.Kind() != reflect.Ptr {
|
||||
@ -133,6 +139,9 @@ func (s *Scheme) AddKnownTypes(gv unversioned.GroupVersion, types ...interface{}
|
||||
// your structs.
|
||||
func (s *Scheme) AddKnownTypeWithName(gvk unversioned.GroupVersionKind, obj interface{}) {
|
||||
t := reflect.TypeOf(obj)
|
||||
if len(gvk.Version) == 0 {
|
||||
panic(fmt.Sprintf("version is required on all types: %s %v", gvk, t))
|
||||
}
|
||||
if t.Kind() != reflect.Ptr {
|
||||
panic("All types must be pointers to structs.")
|
||||
}
|
||||
@ -168,6 +177,9 @@ func (s *Scheme) NewObject(kind unversioned.GroupVersionKind) (interface{}, erro
|
||||
return reflect.New(t).Interface(), nil
|
||||
}
|
||||
|
||||
if t, exists := s.unversionedKinds[kind.Kind]; exists {
|
||||
return reflect.New(t).Interface(), nil
|
||||
}
|
||||
return nil, ¬RegisteredErr{gvk: kind}
|
||||
}
|
||||
|
||||
@ -221,6 +233,13 @@ func (s *Scheme) AddGeneratedConversionFuncs(conversionFuncs ...interface{}) err
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddIgnoredConversionType identifies a pair of types that should be skipped by
|
||||
// dynamic conversion (because the data inside them is explicitly dropped during
|
||||
// conversion).
|
||||
func (s *Scheme) AddIgnoredConversionType(from, to interface{}) error {
|
||||
return s.converter.RegisterIgnoredConversion(from, to)
|
||||
}
|
||||
|
||||
// AddDeepCopyFuncs adds functions to the list of deep copy functions.
|
||||
// Note that to copy sub-objects, you can use the conversion.Cloner object that
|
||||
// will be passed to your deep-copy function.
|
||||
@ -282,6 +301,22 @@ func (s *Scheme) Recognizes(gvk unversioned.GroupVersionKind) bool {
|
||||
return exists
|
||||
}
|
||||
|
||||
// IsUnversioned returns true if the Go object is registered as an unversioned type, or sets
|
||||
// ok to false if the provided object is not registered in the scheme.
|
||||
func (s *Scheme) IsUnversioned(obj interface{}) (unversioned bool, registered bool) {
|
||||
v, err := EnforcePtr(obj)
|
||||
if err != nil {
|
||||
return false, false
|
||||
}
|
||||
t := v.Type()
|
||||
|
||||
if _, ok := s.typeToGVK[t]; !ok {
|
||||
return false, false
|
||||
}
|
||||
_, ok := s.unversionedTypes[t]
|
||||
return ok, true
|
||||
}
|
||||
|
||||
// RegisterInputDefaults sets the provided field mapping function and field matching
|
||||
// as the defaults for the provided input type. The fn may be nil, in which case no
|
||||
// mapping will happen by default. Use this method to register a mechanism for handling
|
||||
@ -330,15 +365,23 @@ func (s *Scheme) ConvertToVersion(in interface{}, outGroupVersionString string)
|
||||
return nil, fmt.Errorf("only pointers to struct types may be converted: %v", t)
|
||||
}
|
||||
|
||||
gvks, ok := s.typeToGVK[t]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("%v cannot be converted into version %q", t, outGroupVersionString)
|
||||
}
|
||||
outVersion, err := unversioned.ParseGroupVersion(outGroupVersionString)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
outKind := outVersion.WithKind(gvks[0].Kind)
|
||||
|
||||
var kind unversioned.GroupVersionKind
|
||||
if unversionedKind, ok := s.unversionedTypes[t]; ok {
|
||||
kind = unversionedKind
|
||||
} else {
|
||||
kinds, ok := s.typeToGVK[t]
|
||||
if !ok || len(kinds) == 0 {
|
||||
return nil, fmt.Errorf("%v is not a registered type and cannot be converted into version %q", t, outGroupVersionString)
|
||||
}
|
||||
kind = kinds[0]
|
||||
}
|
||||
|
||||
outKind := outVersion.WithKind(kind.Kind)
|
||||
|
||||
inKind, err := s.ObjectKind(in)
|
||||
if err != nil {
|
||||
@ -355,10 +398,6 @@ func (s *Scheme) ConvertToVersion(in interface{}, outGroupVersionString string)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := s.SetVersionAndKind(outVersion.String(), outKind.Kind, out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
@ -367,6 +406,14 @@ func (s *Scheme) Converter() *Converter {
|
||||
return s.converter
|
||||
}
|
||||
|
||||
// WithConversions returns a scheme with additional conversion functions
|
||||
func (s *Scheme) WithConversions(fns ConversionFuncs) *Scheme {
|
||||
c := s.converter.WithConversions(fns)
|
||||
copied := *s
|
||||
copied.converter = c
|
||||
return &copied
|
||||
}
|
||||
|
||||
// generateConvertMeta constructs the meta value we pass to Convert.
|
||||
func (s *Scheme) generateConvertMeta(srcGroupVersion, destGroupVersion unversioned.GroupVersion, in interface{}) (FieldMatchingFlags, *Meta) {
|
||||
t := reflect.TypeOf(in)
|
||||
@ -377,12 +424,6 @@ func (s *Scheme) generateConvertMeta(srcGroupVersion, destGroupVersion unversion
|
||||
}
|
||||
}
|
||||
|
||||
// DataKind will return the group,version,kind of the given wire-format
|
||||
// encoding of an API Object, or an error.
|
||||
func (s *Scheme) DataKind(data []byte) (unversioned.GroupVersionKind, error) {
|
||||
return s.MetaFactory.Interpret(data)
|
||||
}
|
||||
|
||||
// ObjectKind returns the group,version,kind of the go object,
|
||||
// or an error if it's not a pointer or is unregistered.
|
||||
func (s *Scheme) ObjectKind(obj interface{}) (unversioned.GroupVersionKind, error) {
|
||||
@ -390,7 +431,6 @@ func (s *Scheme) ObjectKind(obj interface{}) (unversioned.GroupVersionKind, erro
|
||||
if err != nil {
|
||||
return unversioned.GroupVersionKind{}, err
|
||||
}
|
||||
|
||||
return gvks[0], nil
|
||||
}
|
||||
|
||||
@ -399,22 +439,16 @@ func (s *Scheme) ObjectKind(obj interface{}) (unversioned.GroupVersionKind, erro
|
||||
func (s *Scheme) ObjectKinds(obj interface{}) ([]unversioned.GroupVersionKind, error) {
|
||||
v, err := EnforcePtr(obj)
|
||||
if err != nil {
|
||||
return []unversioned.GroupVersionKind{}, err
|
||||
return nil, err
|
||||
}
|
||||
t := v.Type()
|
||||
|
||||
gvks, ok := s.typeToGVK[t]
|
||||
if !ok {
|
||||
return []unversioned.GroupVersionKind{}, ¬RegisteredErr{t: t}
|
||||
return nil, ¬RegisteredErr{t: t}
|
||||
}
|
||||
return gvks, 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 {
|
||||
return s.MetaFactory.Update(version, kind, obj)
|
||||
return gvks, nil
|
||||
}
|
||||
|
||||
// maybeCopy copies obj if it is not a pointer, to get a settable/addressable
|
||||
|
@ -18,15 +18,11 @@ package conversion
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api/unversioned"
|
||||
"k8s.io/kubernetes/pkg/util"
|
||||
|
||||
"github.com/ghodss/yaml"
|
||||
"github.com/google/gofuzz"
|
||||
flag "github.com/spf13/pflag"
|
||||
)
|
||||
@ -109,7 +105,7 @@ var TestObjectFuzzer = fuzz.New().NilChance(.5).NumElements(1, 100).Funcs(
|
||||
|
||||
// Returns a new Scheme set up with the test objects.
|
||||
func GetTestScheme() *Scheme {
|
||||
internalGV := unversioned.GroupVersion{}
|
||||
internalGV := unversioned.GroupVersion{Version: "__internal"}
|
||||
externalGV := unversioned.GroupVersion{Version: "v1"}
|
||||
|
||||
s := NewScheme()
|
||||
@ -122,34 +118,9 @@ func GetTestScheme() *Scheme {
|
||||
s.AddKnownTypeWithName(externalGV.WithKind("TestType2"), &ExternalTestType2{})
|
||||
s.AddKnownTypeWithName(internalGV.WithKind("TestType3"), &TestType1{})
|
||||
s.AddKnownTypeWithName(externalGV.WithKind("TestType3"), &ExternalTestType1{})
|
||||
s.MetaFactory = testMetaFactory{}
|
||||
return s
|
||||
}
|
||||
|
||||
type testMetaFactory struct{}
|
||||
|
||||
func (testMetaFactory) Interpret(data []byte) (unversioned.GroupVersionKind, error) {
|
||||
findKind := struct {
|
||||
APIVersion string `json:"myVersionKey,omitempty"`
|
||||
ObjectKind string `json:"myKindKey,omitempty"`
|
||||
}{}
|
||||
// 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 unversioned.GroupVersionKind{}, fmt.Errorf("couldn't get version/kind: %v", err)
|
||||
}
|
||||
gv, err := unversioned.ParseGroupVersion(findKind.APIVersion)
|
||||
if err != nil {
|
||||
return unversioned.GroupVersionKind{}, err
|
||||
}
|
||||
return gv.WithKind(findKind.ObjectKind), nil
|
||||
}
|
||||
|
||||
func (testMetaFactory) Update(version, kind string, obj interface{}) error {
|
||||
return UpdateVersionAndKind(nil, "APIVersion", version, "ObjectKind", kind, obj)
|
||||
}
|
||||
|
||||
func objDiff(a, b interface{}) string {
|
||||
ab, err := json.Marshal(a)
|
||||
if err != nil {
|
||||
@ -170,111 +141,6 @@ func objDiff(a, b interface{}) string {
|
||||
//)
|
||||
}
|
||||
|
||||
func runTest(t *testing.T, source interface{}) {
|
||||
name := reflect.TypeOf(source).Elem().Name()
|
||||
TestObjectFuzzer.Fuzz(source)
|
||||
|
||||
s := GetTestScheme()
|
||||
data, err := s.EncodeToVersion(source, "v1")
|
||||
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 TestMultipleNames(t *testing.T) {
|
||||
s := GetTestScheme()
|
||||
|
||||
obj, err := s.Decode([]byte(`{"myKindKey":"TestType3","myVersionKey":"v1","A":"value"}`))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
internal := obj.(*TestType1)
|
||||
if internal.A != "value" {
|
||||
t.Fatalf("unexpected decoded object: %#v", internal)
|
||||
}
|
||||
|
||||
out, err := s.EncodeToVersion(internal, "v1")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if !strings.Contains(string(out), `"myKindKey":"TestType1"`) {
|
||||
t.Errorf("unexpected encoded output: %s", string(out))
|
||||
}
|
||||
}
|
||||
|
||||
func TestConvertTypesWhenDefaultNamesMatch(t *testing.T) {
|
||||
internalGV := unversioned.GroupVersion{}
|
||||
externalGV := unversioned.GroupVersion{Version: "v1"}
|
||||
|
||||
s := NewScheme()
|
||||
// create two names internally, with TestType1 being preferred
|
||||
s.AddKnownTypeWithName(internalGV.WithKind("TestType1"), &TestType1{})
|
||||
s.AddKnownTypeWithName(internalGV.WithKind("OtherType1"), &TestType1{})
|
||||
// create two names externally, with TestType1 being preferred
|
||||
s.AddKnownTypeWithName(externalGV.WithKind("TestType1"), &ExternalTestType1{})
|
||||
s.AddKnownTypeWithName(externalGV.WithKind("OtherType1"), &ExternalTestType1{})
|
||||
s.MetaFactory = testMetaFactory{}
|
||||
|
||||
ext := &ExternalTestType1{}
|
||||
ext.APIVersion = "v1"
|
||||
ext.ObjectKind = "OtherType1"
|
||||
ext.A = "test"
|
||||
data, err := json.Marshal(ext)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
expect := &TestType1{A: "test"}
|
||||
|
||||
obj, err := s.Decode(data)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(expect, obj) {
|
||||
t.Errorf("unexpected object: %#v", obj)
|
||||
}
|
||||
|
||||
into := &TestType1{}
|
||||
if err := s.DecodeInto(data, into); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(expect, obj) {
|
||||
t.Errorf("unexpected object: %#v", obj)
|
||||
}
|
||||
}
|
||||
|
||||
func TestKnownTypes(t *testing.T) {
|
||||
s := GetTestScheme()
|
||||
if len(s.KnownTypes(unversioned.GroupVersion{Group: "group", Version: "v2"})) != 0 {
|
||||
@ -313,75 +179,3 @@ func TestConvertToVersionErr(t *testing.T) {
|
||||
t.Fatalf("unexpected non-error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncode_NonPtr(t *testing.T) {
|
||||
s := GetTestScheme()
|
||||
tt := TestType1{A: "I'm not a pointer object"}
|
||||
obj := interface{}(tt)
|
||||
data, err := s.EncodeToVersion(obj, "v1")
|
||||
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.EncodeToVersion(obj, "v1")
|
||||
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(`{"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)
|
||||
}
|
||||
if err := s.DecodeInto([]byte(``), &TestType1{}); err == nil {
|
||||
t.Errorf("Did not give error for empty data")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBadJSONRejectionForSetInternalVersion(t *testing.T) {
|
||||
s := GetTestScheme()
|
||||
s.InternalVersions[""] = unversioned.GroupVersion{Version: "v1"}
|
||||
badJSONs := [][]byte{
|
||||
[]byte(`{"myKindKey":"TestType1"}`), // Missing 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)
|
||||
}
|
||||
}
|
||||
|
@ -23,10 +23,10 @@ import (
|
||||
|
||||
// TODO: Ideally we should create the necessary package structure in e.g.,
|
||||
// pkg/conversion/test/... instead of importing pkg/api here.
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/latest"
|
||||
"k8s.io/kubernetes/pkg/api/testapi"
|
||||
"k8s.io/kubernetes/pkg/api/unversioned"
|
||||
"k8s.io/kubernetes/pkg/apis/extensions/v1beta1"
|
||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||
"k8s.io/kubernetes/pkg/runtime"
|
||||
)
|
||||
|
||||
@ -67,8 +67,8 @@ func TestV1EncodeDecodeStatus(t *testing.T) {
|
||||
func TestExperimentalEncodeDecodeStatus(t *testing.T) {
|
||||
// TODO: caesarxuchao: use the testapi.Extensions.Codec() once the PR that
|
||||
// moves experimental from v1 to v1beta1 got merged.
|
||||
expCodec := runtime.CodecFor(api.Scheme, v1beta1.SchemeGroupVersion)
|
||||
encoded, err := expCodec.Encode(status)
|
||||
expCodec := latest.Codecs.LegacyCodec(extensions.SchemeGroupVersion)
|
||||
encoded, err := runtime.Encode(expCodec, status)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
@ -79,7 +79,7 @@ func TestExperimentalEncodeDecodeStatus(t *testing.T) {
|
||||
if typeMeta.Kind != "Status" {
|
||||
t.Errorf("Kind is not set to \"Status\". Got %s", encoded)
|
||||
}
|
||||
if typeMeta.APIVersion != "" {
|
||||
if typeMeta.APIVersion != "v1" {
|
||||
t.Errorf("APIVersion is not set to \"\". Got %s", encoded)
|
||||
}
|
||||
decoded, err := runtime.Decode(expCodec, encoded)
|
||||
|
18
pkg/runtime/serializer/protobuf/doc.go
Normal file
18
pkg/runtime/serializer/protobuf/doc.go
Normal file
@ -0,0 +1,18 @@
|
||||
/*
|
||||
Copyright 2015 The Kubernetes Authors 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 protobuf handles serializing API objects to and from wire formats.
|
||||
package protobuf
|
Loading…
Reference in New Issue
Block a user