mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-01 07:47:56 +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 {
|
type Converter struct {
|
||||||
// Map from the conversion pair to a function which can
|
// Map from the conversion pair to a function which can
|
||||||
// do the conversion.
|
// do the conversion.
|
||||||
conversionFuncs map[typePair]reflect.Value
|
conversionFuncs ConversionFuncs
|
||||||
generatedConversionFuncs map[typePair]reflect.Value
|
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
|
// This is a map from a source field type and name, to a list of destination
|
||||||
// field type and name.
|
// field type and name.
|
||||||
@ -76,21 +79,30 @@ type Converter struct {
|
|||||||
// NewConverter creates a new Converter object.
|
// NewConverter creates a new Converter object.
|
||||||
func NewConverter() *Converter {
|
func NewConverter() *Converter {
|
||||||
c := &Converter{
|
c := &Converter{
|
||||||
conversionFuncs: map[typePair]reflect.Value{},
|
conversionFuncs: NewConversionFuncs(),
|
||||||
generatedConversionFuncs: map[typePair]reflect.Value{},
|
generatedConversionFuncs: NewConversionFuncs(),
|
||||||
defaultingFuncs: map[reflect.Type]reflect.Value{},
|
ignoredConversions: make(map[typePair]struct{}),
|
||||||
defaultingInterfaces: map[reflect.Type]interface{}{},
|
defaultingFuncs: make(map[reflect.Type]reflect.Value),
|
||||||
|
defaultingInterfaces: make(map[reflect.Type]interface{}),
|
||||||
nameFunc: func(t reflect.Type) string { return t.Name() },
|
nameFunc: func(t reflect.Type) string { return t.Name() },
|
||||||
structFieldDests: map[typeNamePair][]typeNamePair{},
|
structFieldDests: make(map[typeNamePair][]typeNamePair),
|
||||||
structFieldSources: map[typeNamePair][]typeNamePair{},
|
structFieldSources: make(map[typeNamePair][]typeNamePair),
|
||||||
|
|
||||||
inputFieldMappingFuncs: map[reflect.Type]FieldMappingFunc{},
|
inputFieldMappingFuncs: make(map[reflect.Type]FieldMappingFunc),
|
||||||
inputDefaultFlags: map[reflect.Type]FieldMatchingFlags{},
|
inputDefaultFlags: make(map[reflect.Type]FieldMatchingFlags),
|
||||||
}
|
}
|
||||||
c.RegisterConversionFunc(ByteSliceCopy)
|
c.RegisterConversionFunc(ByteSliceCopy)
|
||||||
return c
|
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
|
// ByteSliceCopy prevents recursing into every byte
|
||||||
func ByteSliceCopy(in *[]byte, out *[]byte, s Scope) error {
|
func ByteSliceCopy(in *[]byte, out *[]byte, s Scope) error {
|
||||||
*out = make([]byte, len(*in))
|
*out = make([]byte, len(*in))
|
||||||
@ -130,6 +142,42 @@ type Scope interface {
|
|||||||
// the value of the source or destination struct tags.
|
// the value of the source or destination struct tags.
|
||||||
type FieldMappingFunc func(key string, sourceTag, destTag reflect.StructTag) (source string, dest string)
|
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.
|
// Meta is supplied by Scheme, when it calls Convert.
|
||||||
type Meta struct {
|
type Meta struct {
|
||||||
SrcVersion string
|
SrcVersion string
|
||||||
@ -296,34 +344,44 @@ func verifyConversionFunctionSignature(ft reflect.Type) error {
|
|||||||
// return nil
|
// return nil
|
||||||
// })
|
// })
|
||||||
func (c *Converter) RegisterConversionFunc(conversionFunc interface{}) error {
|
func (c *Converter) RegisterConversionFunc(conversionFunc interface{}) error {
|
||||||
fv := reflect.ValueOf(conversionFunc)
|
return c.conversionFuncs.Add(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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Similar to RegisterConversionFunc, but registers conversion function that were
|
// Similar to RegisterConversionFunc, but registers conversion function that were
|
||||||
// automatically generated.
|
// automatically generated.
|
||||||
func (c *Converter) RegisterGeneratedConversionFunc(conversionFunc interface{}) error {
|
func (c *Converter) RegisterGeneratedConversionFunc(conversionFunc interface{}) error {
|
||||||
fv := reflect.ValueOf(conversionFunc)
|
return c.generatedConversionFuncs.Add(conversionFunc)
|
||||||
ft := fv.Type()
|
}
|
||||||
if err := verifyConversionFunctionSignature(ft); err != nil {
|
|
||||||
return err
|
// 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
|
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 {
|
func (c *Converter) HasConversionFunc(inType, outType reflect.Type) bool {
|
||||||
_, found := c.conversionFuncs[typePair{inType, outType}]
|
_, found := c.conversionFuncs.fns[typePair{inType, outType}]
|
||||||
return found
|
return found
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Converter) ConversionFuncValue(inType, outType reflect.Type) (reflect.Value, bool) {
|
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
|
return value, found
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -509,16 +567,26 @@ func (c *Converter) convert(sv, dv reflect.Value, scope *scope) error {
|
|||||||
fv.Call(args)
|
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.
|
// 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 {
|
if c.Debug != nil {
|
||||||
c.Debug.Logf("Calling custom conversion of '%v' to '%v'", st, dt)
|
c.Debug.Logf("Calling custom conversion of '%v' to '%v'", st, dt)
|
||||||
}
|
}
|
||||||
return c.callCustom(sv, dv, fv, scope)
|
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 {
|
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)
|
return c.callCustom(sv, dv, fv, scope)
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,8 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/google/gofuzz"
|
"github.com/google/gofuzz"
|
||||||
|
|
||||||
|
"k8s.io/kubernetes/pkg/api/unversioned"
|
||||||
)
|
)
|
||||||
|
|
||||||
func testLogger(t *testing.T) DebugLogger {
|
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) {
|
func TestConverter_GeneratedConversionOverriden(t *testing.T) {
|
||||||
type A struct{}
|
type A struct{}
|
||||||
type B 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) {
|
func TestConverter_MapsStringArrays(t *testing.T) {
|
||||||
type A struct {
|
type A struct {
|
||||||
Foo string
|
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.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Package conversion provides go object versioning and encoding/decoding
|
// Package conversion provides go object versioning.
|
||||||
// mechanisms.
|
|
||||||
//
|
//
|
||||||
// Specifically, conversion provides a way for you to define multiple versions
|
// Specifically, conversion provides a way for you to define multiple versions
|
||||||
// of the same object. You may write functions which implement conversion logic,
|
// 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
|
// 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
|
// 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.
|
// 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
|
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
|
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 {
|
func (k *notRegisteredErr) Error() string {
|
||||||
if k.t != nil {
|
if k.t != nil {
|
||||||
return fmt.Sprintf("no kind is registered for the type %v", k.t)
|
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"
|
"net/url"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"k8s.io/kubernetes/pkg/runtime"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func jsonTag(field reflect.StructField) (string, bool) {
|
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
|
// Convert takes an object and converts it to a url.Values object using JSON tags as
|
||||||
// using JSON tags as parameter names. Only top-level simple values, arrays, and slices
|
// parameter names. Only top-level simple values, arrays, and slices are serialized.
|
||||||
// are serialized. Embedded structs, maps, etc. will not be serialized.
|
// Embedded structs, maps, etc. will not be serialized.
|
||||||
func Convert(obj runtime.Object) (url.Values, error) {
|
func Convert(obj interface{}) (url.Values, error) {
|
||||||
result := url.Values{}
|
result := url.Values{}
|
||||||
if obj == nil {
|
if obj == nil {
|
||||||
return result, nil
|
return result, nil
|
||||||
|
@ -23,7 +23,6 @@ import (
|
|||||||
|
|
||||||
"k8s.io/kubernetes/pkg/api/unversioned"
|
"k8s.io/kubernetes/pkg/api/unversioned"
|
||||||
"k8s.io/kubernetes/pkg/conversion/queryparams"
|
"k8s.io/kubernetes/pkg/conversion/queryparams"
|
||||||
"k8s.io/kubernetes/pkg/runtime"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type namedString string
|
type namedString string
|
||||||
@ -85,7 +84,7 @@ func validateResult(t *testing.T, input interface{}, actual, expected url.Values
|
|||||||
|
|
||||||
func TestConvert(t *testing.T) {
|
func TestConvert(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
input runtime.Object
|
input interface{}
|
||||||
expected url.Values
|
expected url.Values
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
|
@ -33,6 +33,13 @@ type Scheme struct {
|
|||||||
// The reflect.Type we index by should *not* be a pointer.
|
// The reflect.Type we index by should *not* be a pointer.
|
||||||
typeToGVK map[reflect.Type][]unversioned.GroupVersionKind
|
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
|
// converter stores all registered conversion functions. It also has
|
||||||
// default coverting behavior.
|
// default coverting behavior.
|
||||||
converter *Converter
|
converter *Converter
|
||||||
@ -44,34 +51,17 @@ type Scheme struct {
|
|||||||
// Indent will cause the JSON output from Encode to be indented,
|
// Indent will cause the JSON output from Encode to be indented,
|
||||||
// if and only if it is true.
|
// if and only if it is true.
|
||||||
Indent bool
|
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.
|
// NewScheme manufactures a new scheme.
|
||||||
func NewScheme() *Scheme {
|
func NewScheme() *Scheme {
|
||||||
s := &Scheme{
|
s := &Scheme{
|
||||||
gvkToType: map[unversioned.GroupVersionKind]reflect.Type{},
|
gvkToType: map[unversioned.GroupVersionKind]reflect.Type{},
|
||||||
typeToGVK: map[reflect.Type][]unversioned.GroupVersionKind{},
|
typeToGVK: map[reflect.Type][]unversioned.GroupVersionKind{},
|
||||||
converter: NewConverter(),
|
unversionedTypes: map[reflect.Type]unversioned.GroupVersionKind{},
|
||||||
cloner: NewCloner(),
|
unversionedKinds: map[string]reflect.Type{},
|
||||||
// TODO remove this hard coded list. As step one, hardcode it here so this pull doesn't become even bigger
|
converter: NewConverter(),
|
||||||
InternalVersions: map[string]unversioned.GroupVersion{
|
cloner: NewCloner(),
|
||||||
"": {},
|
|
||||||
"componentconfig": {Group: "componentconfig"},
|
|
||||||
"extensions": {Group: "extensions"},
|
|
||||||
"metrics": {Group: "metrics"},
|
|
||||||
},
|
|
||||||
MetaFactory: DefaultMetaFactory,
|
|
||||||
}
|
}
|
||||||
s.converter.nameFunc = s.nameFunc
|
s.converter.nameFunc = s.nameFunc
|
||||||
return s
|
return s
|
||||||
@ -92,11 +82,8 @@ func (s *Scheme) nameFunc(t reflect.Type) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, gvk := range gvks {
|
for _, gvk := range gvks {
|
||||||
internalGV, exists := s.InternalVersions[gvk.Group]
|
internalGV := gvk.GroupVersion()
|
||||||
if !exists {
|
internalGV.Version = "__internal" // this is hacky and maybe should be passed in
|
||||||
internalGV := gvk.GroupVersion()
|
|
||||||
internalGV.Version = ""
|
|
||||||
}
|
|
||||||
internalGVK := internalGV.WithKind(gvk.Kind)
|
internalGVK := internalGV.WithKind(gvk.Kind)
|
||||||
|
|
||||||
if internalType, exists := s.gvkToType[internalGVK]; exists {
|
if internalType, exists := s.gvkToType[internalGVK]; exists {
|
||||||
@ -107,11 +94,30 @@ func (s *Scheme) nameFunc(t reflect.Type) string {
|
|||||||
return gvks[0].Kind
|
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'.
|
// 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
|
// All objects passed to types should be pointers to structs. The name that go reports for
|
||||||
// the struct becomes the "kind" field when encoding.
|
// the struct becomes the "kind" field when encoding.
|
||||||
func (s *Scheme) AddKnownTypes(gv unversioned.GroupVersion, types ...interface{}) {
|
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 {
|
for _, obj := range types {
|
||||||
t := reflect.TypeOf(obj)
|
t := reflect.TypeOf(obj)
|
||||||
if t.Kind() != reflect.Ptr {
|
if t.Kind() != reflect.Ptr {
|
||||||
@ -133,6 +139,9 @@ func (s *Scheme) AddKnownTypes(gv unversioned.GroupVersion, types ...interface{}
|
|||||||
// your structs.
|
// your structs.
|
||||||
func (s *Scheme) AddKnownTypeWithName(gvk unversioned.GroupVersionKind, obj interface{}) {
|
func (s *Scheme) AddKnownTypeWithName(gvk unversioned.GroupVersionKind, obj interface{}) {
|
||||||
t := reflect.TypeOf(obj)
|
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 {
|
if t.Kind() != reflect.Ptr {
|
||||||
panic("All types must be pointers to structs.")
|
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
|
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}
|
return nil, ¬RegisteredErr{gvk: kind}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -221,6 +233,13 @@ func (s *Scheme) AddGeneratedConversionFuncs(conversionFuncs ...interface{}) err
|
|||||||
return nil
|
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.
|
// AddDeepCopyFuncs adds functions to the list of deep copy functions.
|
||||||
// Note that to copy sub-objects, you can use the conversion.Cloner object that
|
// Note that to copy sub-objects, you can use the conversion.Cloner object that
|
||||||
// will be passed to your deep-copy function.
|
// will be passed to your deep-copy function.
|
||||||
@ -282,6 +301,22 @@ func (s *Scheme) Recognizes(gvk unversioned.GroupVersionKind) bool {
|
|||||||
return exists
|
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
|
// 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
|
// 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
|
// 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)
|
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)
|
outVersion, err := unversioned.ParseGroupVersion(outGroupVersionString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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)
|
inKind, err := s.ObjectKind(in)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -355,10 +398,6 @@ func (s *Scheme) ConvertToVersion(in interface{}, outGroupVersionString string)
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.SetVersionAndKind(outVersion.String(), outKind.Kind, out); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -367,6 +406,14 @@ func (s *Scheme) Converter() *Converter {
|
|||||||
return s.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.
|
// generateConvertMeta constructs the meta value we pass to Convert.
|
||||||
func (s *Scheme) generateConvertMeta(srcGroupVersion, destGroupVersion unversioned.GroupVersion, in interface{}) (FieldMatchingFlags, *Meta) {
|
func (s *Scheme) generateConvertMeta(srcGroupVersion, destGroupVersion unversioned.GroupVersion, in interface{}) (FieldMatchingFlags, *Meta) {
|
||||||
t := reflect.TypeOf(in)
|
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,
|
// ObjectKind returns the group,version,kind of the go object,
|
||||||
// or an error if it's not a pointer or is unregistered.
|
// or an error if it's not a pointer or is unregistered.
|
||||||
func (s *Scheme) ObjectKind(obj interface{}) (unversioned.GroupVersionKind, error) {
|
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 {
|
if err != nil {
|
||||||
return unversioned.GroupVersionKind{}, err
|
return unversioned.GroupVersionKind{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return gvks[0], nil
|
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) {
|
func (s *Scheme) ObjectKinds(obj interface{}) ([]unversioned.GroupVersionKind, error) {
|
||||||
v, err := EnforcePtr(obj)
|
v, err := EnforcePtr(obj)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []unversioned.GroupVersionKind{}, err
|
return nil, err
|
||||||
}
|
}
|
||||||
t := v.Type()
|
t := v.Type()
|
||||||
|
|
||||||
gvks, ok := s.typeToGVK[t]
|
gvks, ok := s.typeToGVK[t]
|
||||||
if !ok {
|
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
|
return gvks, nil
|
||||||
// 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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// maybeCopy copies obj if it is not a pointer, to get a settable/addressable
|
// maybeCopy copies obj if it is not a pointer, to get a settable/addressable
|
||||||
|
@ -18,15 +18,11 @@ package conversion
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"k8s.io/kubernetes/pkg/api/unversioned"
|
"k8s.io/kubernetes/pkg/api/unversioned"
|
||||||
"k8s.io/kubernetes/pkg/util"
|
"k8s.io/kubernetes/pkg/util"
|
||||||
|
|
||||||
"github.com/ghodss/yaml"
|
|
||||||
"github.com/google/gofuzz"
|
"github.com/google/gofuzz"
|
||||||
flag "github.com/spf13/pflag"
|
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.
|
// Returns a new Scheme set up with the test objects.
|
||||||
func GetTestScheme() *Scheme {
|
func GetTestScheme() *Scheme {
|
||||||
internalGV := unversioned.GroupVersion{}
|
internalGV := unversioned.GroupVersion{Version: "__internal"}
|
||||||
externalGV := unversioned.GroupVersion{Version: "v1"}
|
externalGV := unversioned.GroupVersion{Version: "v1"}
|
||||||
|
|
||||||
s := NewScheme()
|
s := NewScheme()
|
||||||
@ -122,34 +118,9 @@ func GetTestScheme() *Scheme {
|
|||||||
s.AddKnownTypeWithName(externalGV.WithKind("TestType2"), &ExternalTestType2{})
|
s.AddKnownTypeWithName(externalGV.WithKind("TestType2"), &ExternalTestType2{})
|
||||||
s.AddKnownTypeWithName(internalGV.WithKind("TestType3"), &TestType1{})
|
s.AddKnownTypeWithName(internalGV.WithKind("TestType3"), &TestType1{})
|
||||||
s.AddKnownTypeWithName(externalGV.WithKind("TestType3"), &ExternalTestType1{})
|
s.AddKnownTypeWithName(externalGV.WithKind("TestType3"), &ExternalTestType1{})
|
||||||
s.MetaFactory = testMetaFactory{}
|
|
||||||
return s
|
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 {
|
func objDiff(a, b interface{}) string {
|
||||||
ab, err := json.Marshal(a)
|
ab, err := json.Marshal(a)
|
||||||
if err != nil {
|
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) {
|
func TestKnownTypes(t *testing.T) {
|
||||||
s := GetTestScheme()
|
s := GetTestScheme()
|
||||||
if len(s.KnownTypes(unversioned.GroupVersion{Group: "group", Version: "v2"})) != 0 {
|
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")
|
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.,
|
// TODO: Ideally we should create the necessary package structure in e.g.,
|
||||||
// pkg/conversion/test/... instead of importing pkg/api here.
|
// 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/testapi"
|
||||||
"k8s.io/kubernetes/pkg/api/unversioned"
|
"k8s.io/kubernetes/pkg/api/unversioned"
|
||||||
"k8s.io/kubernetes/pkg/apis/extensions/v1beta1"
|
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||||
"k8s.io/kubernetes/pkg/runtime"
|
"k8s.io/kubernetes/pkg/runtime"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -67,8 +67,8 @@ func TestV1EncodeDecodeStatus(t *testing.T) {
|
|||||||
func TestExperimentalEncodeDecodeStatus(t *testing.T) {
|
func TestExperimentalEncodeDecodeStatus(t *testing.T) {
|
||||||
// TODO: caesarxuchao: use the testapi.Extensions.Codec() once the PR that
|
// TODO: caesarxuchao: use the testapi.Extensions.Codec() once the PR that
|
||||||
// moves experimental from v1 to v1beta1 got merged.
|
// moves experimental from v1 to v1beta1 got merged.
|
||||||
expCodec := runtime.CodecFor(api.Scheme, v1beta1.SchemeGroupVersion)
|
expCodec := latest.Codecs.LegacyCodec(extensions.SchemeGroupVersion)
|
||||||
encoded, err := expCodec.Encode(status)
|
encoded, err := runtime.Encode(expCodec, status)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("unexpected error: %v", err)
|
t.Errorf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
@ -79,7 +79,7 @@ func TestExperimentalEncodeDecodeStatus(t *testing.T) {
|
|||||||
if typeMeta.Kind != "Status" {
|
if typeMeta.Kind != "Status" {
|
||||||
t.Errorf("Kind is not set to \"Status\". Got %s", encoded)
|
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)
|
t.Errorf("APIVersion is not set to \"\". Got %s", encoded)
|
||||||
}
|
}
|
||||||
decoded, err := runtime.Decode(expCodec, 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