Simplify Codec and split responsibilities

Break Codec into two general purpose interfaces, Encoder and Decoder,
and move parameter codec responsibilities to ParameterCodec.

Make unversioned types explicit when registering - these types go
through conversion without modification.

Switch to use "__internal" instead of "" to represent the internal
version. Future commits will also add group defaulting (so that "" is
expanded internally into a known group version, and only cleared during
set).

For embedded types like runtime.Object -> runtime.RawExtension, put the
responsibility on the caller of Decode/Encode to handle transformation
into destination serialization. Future commits will expand RawExtension
and Unknown to accept a content encoding as well as bytes.

Make Unknown a bit more powerful and use it to carry unrecognized types.
This commit is contained in:
Clayton Coleman 2015-12-21 00:08:33 -05:00
parent 6582b4c2ea
commit 63a7a41ddf
17 changed files with 798 additions and 723 deletions

View File

@ -104,7 +104,7 @@ func main() {
} else {
pkgname = gv.Group
}
if len(gv.Version) != 0 {
if len(gv.Version) != 0 && gv.Version != kruntime.APIVersionInternal {
pkgname = gv.Version
}

View File

@ -17,99 +17,155 @@ limitations under the License.
package runtime
import (
"bytes"
"fmt"
"io"
"net/url"
"reflect"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/util/yaml"
"k8s.io/kubernetes/pkg/conversion/queryparams"
)
// codec binds an encoder and decoder.
type codec struct {
Encoder
Decoder
}
// NewCodec creates a Codec from an Encoder and Decoder.
func NewCodec(e Encoder, d Decoder) Codec {
return codec{e, d}
}
// Encode is a convenience wrapper for encoding to a []byte from an Encoder
// TODO: these are transitional interfaces to reduce refactor cost as Codec is altered.
func Encode(e Encoder, obj Object) ([]byte, error) {
return e.Encode(obj)
func Encode(e Encoder, obj Object, overrides ...unversioned.GroupVersion) ([]byte, error) {
// TODO: reuse buffer
buf := &bytes.Buffer{}
if err := e.EncodeToStream(obj, buf, overrides...); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
// Decode is a convenience wrapper for decoding data into an Object.
// TODO: these are transitional interfaces to reduce refactor cost as Codec is altered.
func Decode(d Decoder, data []byte) (Object, error) {
return d.Decode(data)
obj, _, err := d.Decode(data, nil, nil)
return obj, err
}
// DecodeInto performs a Decode into the provided object.
// TODO: these are transitional interfaces to reduce refactor cost as Codec is altered.
func DecodeInto(d Decoder, data []byte, into Object) error {
return d.DecodeInto(data, into)
}
// CodecFor returns a Codec that invokes Encode with the provided version.
func CodecFor(codec ObjectCodec, version unversioned.GroupVersion) Codec {
return &codecWrapper{codec, version}
}
// yamlCodec converts YAML passed to the Decoder methods to JSON.
type yamlCodec struct {
// a Codec for JSON
Codec
}
// yamlCodec implements Codec
var _ Codec = yamlCodec{}
var _ Decoder = yamlCodec{}
// YAMLDecoder adds YAML decoding support to a codec that supports JSON.
func YAMLDecoder(codec Codec) Codec {
return &yamlCodec{codec}
}
func (c yamlCodec) Decode(data []byte) (Object, error) {
out, err := yaml.ToJSON(data)
if err != nil {
return nil, err
}
data = out
return c.Codec.Decode(data)
}
func (c yamlCodec) DecodeInto(data []byte, obj Object) error {
out, err := yaml.ToJSON(data)
out, gvk, err := d.Decode(data, nil, into)
if err != nil {
return err
}
data = out
return c.Codec.DecodeInto(data, obj)
if out != into {
return fmt.Errorf("unable to decode %s into %v", gvk, reflect.TypeOf(into))
}
return nil
}
// EncodeOrDie is a version of Encode which will panic instead of returning an error. For tests.
func EncodeOrDie(codec Codec, obj Object) string {
bytes, err := Encode(codec, obj)
func EncodeOrDie(e Encoder, obj Object) string {
bytes, err := Encode(e, obj)
if err != nil {
panic(err)
}
return string(bytes)
}
// codecWrapper implements encoding to an alternative
// default version for a scheme.
type codecWrapper struct {
ObjectCodec
version unversioned.GroupVersion
// UseOrCreateObject returns obj if the canonical ObjectKind returned by the provided typer matches gvk, or
// invokes the ObjectCreator to instantiate a new gvk. Returns an error if the typer cannot find the object.
func UseOrCreateObject(t Typer, c ObjectCreater, gvk unversioned.GroupVersionKind, obj Object) (Object, error) {
if obj != nil {
into, _, err := t.ObjectKind(obj)
if err != nil {
return nil, err
}
if gvk == *into {
return obj, nil
}
}
return c.New(gvk)
}
// codecWrapper implements Decoder
var _ Decoder = &codecWrapper{}
// Encode implements Codec
func (c *codecWrapper) Encode(obj Object) ([]byte, error) {
return c.EncodeToVersion(obj, c.version.String())
// NoopEncoder converts an Decoder to a Serializer or Codec for code that expects them but only uses decoding.
type NoopEncoder struct {
Decoder
}
func (c *codecWrapper) EncodeToStream(obj Object, stream io.Writer) error {
return c.EncodeToVersionStream(obj, c.version.String(), stream)
var _ Serializer = NoopEncoder{}
func (n NoopEncoder) EncodeToStream(obj Object, w io.Writer, overrides ...unversioned.GroupVersion) error {
return fmt.Errorf("encoding is not allowed for this codec: %v", reflect.TypeOf(n.Decoder))
}
// TODO: Make this behaviour default when we move everyone away from
// the unversioned types.
//
// func (c *codecWrapper) Decode(data []byte) (Object, error) {
// return c.DecodeToVersion(data, c.version)
// }
// NoopDecoder converts an Encoder to a Serializer or Codec for code that expects them but only uses encoding.
type NoopDecoder struct {
Encoder
}
var _ Serializer = NoopDecoder{}
func (n NoopDecoder) Decode(data []byte, gvk *unversioned.GroupVersionKind, into Object) (Object, *unversioned.GroupVersionKind, error) {
return nil, nil, fmt.Errorf("decoding is not allowed for this codec: %v", reflect.TypeOf(n.Encoder))
}
// NewParameterCodec creates a ParameterCodec capable of transforming url values into versioned objects and back.
func NewParameterCodec(scheme *Scheme) ParameterCodec {
return &parameterCodec{
typer: ObjectTyperToTyper(scheme),
convertor: scheme,
creator: scheme,
}
}
// parameterCodec implements conversion to and from query parameters and objects.
type parameterCodec struct {
typer Typer
convertor ObjectConvertor
creator ObjectCreater
}
var _ ParameterCodec = &parameterCodec{}
// DecodeParameters converts the provided url.Values into an object of type From with the kind of into, and then
// converts that object to into (if necessary). Returns an error if the operation cannot be completed.
func (c *parameterCodec) DecodeParameters(parameters url.Values, from unversioned.GroupVersion, into Object) error {
if len(parameters) == 0 {
return nil
}
targetGVK, _, err := c.typer.ObjectKind(into)
if err != nil {
return err
}
if targetGVK.GroupVersion() == from {
return c.convertor.Convert(&parameters, into)
}
input, err := c.creator.New(from.WithKind(targetGVK.Kind))
if err != nil {
return err
}
if err := c.convertor.Convert(&parameters, input); err != nil {
return err
}
return c.convertor.Convert(input, into)
}
// EncodeParameters converts the provided object into the to version, then converts that object to url.Values.
// Returns an error if conversion is not possible.
func (c *parameterCodec) EncodeParameters(obj Object, to unversioned.GroupVersion) (url.Values, error) {
gvk, _, err := c.typer.ObjectKind(obj)
if err != nil {
return nil, err
}
if to != gvk.GroupVersion() {
out, err := c.convertor.ConvertToVersion(obj, to.String())
if err != nil {
return nil, err
}
obj = out
}
return queryparams.Convert(obj)
}

View File

@ -102,10 +102,8 @@ func (g *conversionGenerator) AddImport(pkg string) string {
func (g *conversionGenerator) GenerateConversionsForType(gv unversioned.GroupVersion, reflection reflect.Type) error {
kind := reflection.Name()
// TODO this is equivalent to what it did before, but it needs to be fixed for the proper group
internalVersion, exists := g.scheme.InternalVersions[gv.Group]
if !exists {
return fmt.Errorf("no internal version for %v", gv)
}
internalVersion := gv
internalVersion.Version = APIVersionInternal
internalObj, err := g.scheme.NewObject(internalVersion.WithKind(kind))
if err != nil {
@ -775,6 +773,10 @@ func (g *conversionGenerator) writeConversionForStruct(b *buffer, inType, outTyp
continue
}
if g.scheme.Converter().IsConversionIgnored(inField.Type, outField.Type) {
continue
}
existsConversion := g.scheme.Converter().HasConversionFunc(inField.Type, outField.Type)
_, hasPublicConversion := g.publicFuncs[typePair{inField.Type, outField.Type}]
// TODO: This allows a private conversion for a slice to take precedence over a public
@ -895,12 +897,7 @@ type typePair struct {
outType reflect.Type
}
var defaultConversions []typePair = []typePair{
{reflect.TypeOf([]RawExtension{}), reflect.TypeOf([]Object{})},
{reflect.TypeOf([]Object{}), reflect.TypeOf([]RawExtension{})},
{reflect.TypeOf(RawExtension{}), reflect.TypeOf(EmbeddedObject{})},
{reflect.TypeOf(EmbeddedObject{}), reflect.TypeOf(RawExtension{})},
}
var defaultConversions []typePair = []typePair{}
func (g *conversionGenerator) OverwritePackage(pkg, overwrite string) {
g.pkgOverwrites[pkg] = overwrite

View File

@ -46,12 +46,11 @@ func (obj *InternalComplex) GetObjectKind() unversioned.ObjectKind { return &obj
func (obj *ExternalComplex) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
func TestStringMapConversion(t *testing.T) {
internalGV := unversioned.GroupVersion{Group: "test.group", Version: ""}
internalGV := unversioned.GroupVersion{Group: "test.group", Version: runtime.APIVersionInternal}
externalGV := unversioned.GroupVersion{Group: "test.group", Version: "external"}
scheme := runtime.NewScheme()
scheme.Log(t)
scheme.AddInternalGroupVersion(internalGV)
scheme.AddKnownTypeWithName(internalGV.WithKind("Complex"), &InternalComplex{})
scheme.AddKnownTypeWithName(externalGV.WithKind("Complex"), &ExternalComplex{})

124
pkg/runtime/embedded.go Normal file
View File

@ -0,0 +1,124 @@
/*
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 runtime
import (
"errors"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/conversion"
)
type encodable struct {
e Encoder `json:"-"`
obj Object
versions []unversioned.GroupVersion `json:"-"`
}
func (e encodable) GetObjectKind() unversioned.ObjectKind { return e.obj.GetObjectKind() }
// NewEncodable creates an object that will be encoded with the provided codec on demand.
// Provided as a convenience for test cases dealing with internal objects.
func NewEncodable(e Encoder, obj Object, versions ...unversioned.GroupVersion) Object {
if _, ok := obj.(*Unknown); ok {
return obj
}
return encodable{e, obj, versions}
}
func (re encodable) UnmarshalJSON(in []byte) error {
return errors.New("runtime.encodable cannot be unmarshalled from JSON")
}
// Marshal may get called on pointers or values, so implement MarshalJSON on value.
// http://stackoverflow.com/questions/21390979/custom-marshaljson-never-gets-called-in-go
func (re encodable) MarshalJSON() ([]byte, error) {
return Encode(re.e, re.obj)
}
// NewEncodableList creates an object that will be encoded with the provided codec on demand.
// Provided as a convenience for test cases dealing with internal objects.
func NewEncodableList(e Encoder, objects []Object, versions ...unversioned.GroupVersion) []Object {
out := make([]Object, len(objects))
for i := range objects {
if _, ok := objects[i].(*Unknown); ok {
out[i] = objects[i]
continue
}
out[i] = NewEncodable(e, objects[i], versions...)
}
return out
}
func (re *Unknown) UnmarshalJSON(in []byte) error {
if re == nil {
return errors.New("runtime.Unknown: UnmarshalJSON on nil pointer")
}
re.TypeMeta = TypeMeta{}
re.RawJSON = append(re.RawJSON[0:0], in...)
return nil
}
// Marshal may get called on pointers or values, so implement MarshalJSON on value.
// http://stackoverflow.com/questions/21390979/custom-marshaljson-never-gets-called-in-go
func (re Unknown) MarshalJSON() ([]byte, error) {
if re.RawJSON == nil {
return []byte("null"), nil
}
return re.RawJSON, nil
}
func DefaultEmbeddedConversions() []interface{} {
return []interface{}{
func(in *Object, out *RawExtension, s conversion.Scope) error {
if in == nil {
out.RawJSON = []byte("null")
return nil
}
obj := *in
if unk, ok := obj.(*Unknown); ok {
if unk.RawJSON != nil {
out.RawJSON = unk.RawJSON
return nil
}
obj = out.Object
}
if obj == nil {
out.RawJSON = nil
return nil
}
out.Object = obj
return nil
},
func(in *RawExtension, out *Object, s conversion.Scope) error {
if in.Object != nil {
*out = in.Object
return nil
}
data := in.RawJSON
if len(data) == 0 || (len(data) == 4 && string(data) == "null") {
*out = nil
return nil
}
*out = &Unknown{
RawJSON: data,
}
return nil
},
}
}

View File

@ -19,20 +19,21 @@ package runtime_test
import (
"encoding/json"
"reflect"
"strings"
"testing"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/meta"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/runtime/serializer"
"k8s.io/kubernetes/pkg/util"
)
type EmbeddedTest struct {
runtime.TypeMeta
ID string
Object runtime.EmbeddedObject
EmptyObject runtime.EmbeddedObject
Object runtime.Object
EmptyObject runtime.Object
}
type EmbeddedTestExternal struct {
@ -62,16 +63,17 @@ func (obj *EmbeddedTest) GetObjectKind() unversioned.ObjectKind { return
func (obj *EmbeddedTestExternal) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
func TestDecodeEmptyRawExtensionAsObject(t *testing.T) {
internalGV := unversioned.GroupVersion{Group: "test.group", Version: ""}
internalGV := unversioned.GroupVersion{Group: "test.group", Version: runtime.APIVersionInternal}
externalGV := unversioned.GroupVersion{Group: "test.group", Version: "v1test"}
externalGVK := externalGV.WithKind("ObjectTest")
s := runtime.NewScheme()
s.AddInternalGroupVersion(internalGV)
s.AddKnownTypes(internalGV, &ObjectTest{})
s.AddKnownTypeWithName(externalGVK, &ObjectTestExternal{})
obj, err := s.Decode([]byte(`{"kind":"` + externalGVK.Kind + `","apiVersion":"` + externalGV.String() + `","items":[{}]}`))
codec := serializer.NewCodecFactory(s).LegacyCodec(externalGV)
obj, gvk, err := codec.Decode([]byte(`{"kind":"`+externalGVK.Kind+`","apiVersion":"`+externalGV.String()+`","items":[{}]}`), nil, nil)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
@ -79,42 +81,51 @@ func TestDecodeEmptyRawExtensionAsObject(t *testing.T) {
if unk, ok := test.Items[0].(*runtime.Unknown); !ok || unk.Kind != "" || unk.APIVersion != "" || string(unk.RawJSON) != "{}" {
t.Fatalf("unexpected object: %#v", test.Items[0])
}
if *gvk != externalGVK {
t.Fatalf("unexpected kind: %#v", gvk)
}
obj, err = s.Decode([]byte(`{"kind":"` + externalGVK.Kind + `","apiVersion":"` + externalGV.String() + `","items":[{"kind":"Other","apiVersion":"v1"}]}`))
obj, gvk, err = codec.Decode([]byte(`{"kind":"`+externalGVK.Kind+`","apiVersion":"`+externalGV.String()+`","items":[{"kind":"Other","apiVersion":"v1"}]}`), nil, nil)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
test = obj.(*ObjectTest)
if unk, ok := test.Items[0].(*runtime.Unknown); !ok || unk.Kind != "Other" || unk.APIVersion != "v1" || string(unk.RawJSON) != `{"kind":"Other","apiVersion":"v1"}` {
if unk, ok := test.Items[0].(*runtime.Unknown); !ok || unk.Kind != "" || unk.APIVersion != "" || string(unk.RawJSON) != `{"kind":"Other","apiVersion":"v1"}` {
t.Fatalf("unexpected object: %#v", test.Items[0])
}
if *gvk != externalGVK {
t.Fatalf("unexpected kind: %#v", gvk)
}
}
func TestArrayOfRuntimeObject(t *testing.T) {
internalGV := unversioned.GroupVersion{Group: "test.group", Version: ""}
internalGV := unversioned.GroupVersion{Group: "test.group", Version: runtime.APIVersionInternal}
externalGV := unversioned.GroupVersion{Group: "test.group", Version: "v1test"}
s := runtime.NewScheme()
s.AddInternalGroupVersion(internalGV)
s.AddKnownTypes(internalGV, &EmbeddedTest{})
s.AddKnownTypeWithName(externalGV.WithKind("EmbeddedTest"), &EmbeddedTestExternal{})
s.AddKnownTypes(internalGV, &ObjectTest{})
s.AddKnownTypeWithName(externalGV.WithKind("ObjectTest"), &ObjectTestExternal{})
internal := &ObjectTest{
Items: []runtime.Object{
&EmbeddedTest{ID: "foo"},
&EmbeddedTest{ID: "bar"},
// TODO: until YAML is removed, this JSON must be in ascending key order to ensure consistent roundtrip serialization
&runtime.Unknown{RawJSON: []byte(`{"apiVersion":"unknown.group/unknown","foo":"bar","kind":"OtherTest"}`)},
&ObjectTest{
Items: []runtime.Object{
&EmbeddedTest{ID: "baz"},
},
},
codec := serializer.NewCodecFactory(s).LegacyCodec(externalGV)
innerItems := []runtime.Object{
&EmbeddedTest{ID: "baz"},
}
items := []runtime.Object{
&EmbeddedTest{ID: "foo"},
&EmbeddedTest{ID: "bar"},
// TODO: until YAML is removed, this JSON must be in ascending key order to ensure consistent roundtrip serialization
&runtime.Unknown{RawJSON: []byte(`{"apiVersion":"unknown.group/unknown","foo":"bar","kind":"OtherTest"}`)},
&ObjectTest{
Items: runtime.NewEncodableList(codec, innerItems),
},
}
wire, err := s.EncodeToVersion(internal, externalGV.String())
internal := &ObjectTest{
Items: runtime.NewEncodableList(codec, items),
}
wire, err := runtime.Encode(codec, internal)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
@ -126,7 +137,10 @@ func TestArrayOfRuntimeObject(t *testing.T) {
}
t.Logf("exact wire is: %s", string(obj.Items[0].RawJSON))
decoded, err := runtime.Decode(s, wire)
items[3] = &ObjectTest{Items: innerItems}
internal.Items = items
decoded, err := runtime.Decode(codec, wire)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
@ -134,7 +148,7 @@ func TestArrayOfRuntimeObject(t *testing.T) {
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if errs := runtime.DecodeList(list, s); len(errs) > 0 {
if errs := runtime.DecodeList(list, codec); len(errs) > 0 {
t.Fatalf("unexpected error: %v", errs)
}
@ -142,53 +156,65 @@ func TestArrayOfRuntimeObject(t *testing.T) {
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if errs := runtime.DecodeList(list2, s); len(errs) > 0 {
if errs := runtime.DecodeList(list2, codec); len(errs) > 0 {
t.Fatalf("unexpected error: %v", errs)
}
if err := meta.SetList(list[3], list2); err != nil {
t.Fatalf("unexpected error: %v", err)
}
internal.Items[2].(*runtime.Unknown).Kind = "OtherTest"
internal.Items[2].(*runtime.Unknown).APIVersion = "unknown.group/unknown"
// we want DecodeList to set type meta if possible, even on runtime.Unknown objects
internal.Items[2].(*runtime.Unknown).TypeMeta = runtime.TypeMeta{Kind: "OtherTest", APIVersion: "unknown.group/unknown"}
if e, a := internal.Items, list; !reflect.DeepEqual(e, a) {
t.Errorf("mismatched decoded: %s", util.ObjectDiff(e, a))
t.Errorf("mismatched decoded: %s", util.ObjectGoPrintSideBySide(e, a))
}
}
func TestEmbeddedObject(t *testing.T) {
internalGV := unversioned.GroupVersion{Group: "test.group", Version: ""}
func TestNestedObject(t *testing.T) {
internalGV := unversioned.GroupVersion{Group: "test.group", Version: runtime.APIVersionInternal}
externalGV := unversioned.GroupVersion{Group: "test.group", Version: "v1test"}
embeddedTestExternalGVK := externalGV.WithKind("EmbeddedTest")
s := runtime.NewScheme()
s.AddInternalGroupVersion(internalGV)
s.AddKnownTypes(internalGV, &EmbeddedTest{})
s.AddKnownTypeWithName(embeddedTestExternalGVK, &EmbeddedTestExternal{})
codec := serializer.NewCodecFactory(s).LegacyCodec(externalGV)
inner := &EmbeddedTest{
ID: "inner",
}
outer := &EmbeddedTest{
ID: "outer",
Object: runtime.EmbeddedObject{
Object: &EmbeddedTest{
ID: "inner",
},
},
ID: "outer",
Object: runtime.NewEncodable(codec, inner),
}
wire, err := s.EncodeToVersion(outer, externalGV.String())
wire, err := runtime.Encode(codec, outer)
if err != nil {
t.Fatalf("Unexpected encode error '%v'", err)
}
t.Logf("Wire format is:\n%v\n", string(wire))
decoded, err := runtime.Decode(s, wire)
decoded, err := runtime.Decode(codec, wire)
if err != nil {
t.Fatalf("Unexpected decode error %v", err)
}
// for later tests
outer.Object = inner
if e, a := outer, decoded; reflect.DeepEqual(e, a) {
t.Errorf("Expected unequal %#v %#v", e, a)
}
obj, err := runtime.Decode(codec, decoded.(*EmbeddedTest).Object.(*runtime.Unknown).RawJSON)
if err != nil {
t.Fatal(err)
}
decoded.(*EmbeddedTest).Object = obj
if e, a := outer, decoded; !reflect.DeepEqual(e, a) {
t.Errorf("Expected: %#v but got %#v", e, a)
t.Errorf("Expected equal %#v %#v", e, a)
}
// test JSON decoding of the external object, which should preserve
@ -211,46 +237,45 @@ func TestEmbeddedObject(t *testing.T) {
// the external representation
var decodedViaJSON EmbeddedTest
err = json.Unmarshal(wire, &decodedViaJSON)
if err != nil {
if err == nil || !strings.Contains(err.Error(), "unmarshal object into Go value of type runtime.Object") {
t.Fatalf("Unexpected decode error %v", err)
}
if a := decodedViaJSON; a.Object.Object != nil || a.EmptyObject.Object != nil {
if a := decodedViaJSON; a.Object != nil || a.EmptyObject != nil {
t.Errorf("Expected embedded objects to be nil: %#v", a)
}
}
// TestDeepCopyOfEmbeddedObject checks to make sure that EmbeddedObject's can be passed through DeepCopy with fidelity
func TestDeepCopyOfEmbeddedObject(t *testing.T) {
internalGV := unversioned.GroupVersion{Group: "test.group", Version: ""}
// TestDeepCopyOfRuntimeObject checks to make sure that runtime.Objects's can be passed through DeepCopy with fidelity
func TestDeepCopyOfRuntimeObject(t *testing.T) {
internalGV := unversioned.GroupVersion{Group: "test.group", Version: runtime.APIVersionInternal}
externalGV := unversioned.GroupVersion{Group: "test.group", Version: "v1test"}
embeddedTestExternalGVK := externalGV.WithKind("EmbeddedTest")
s := runtime.NewScheme()
s.AddInternalGroupVersion(internalGV)
s.AddKnownTypes(internalGV, &EmbeddedTest{})
s.AddKnownTypeWithName(embeddedTestExternalGVK, &EmbeddedTestExternal{})
original := &EmbeddedTest{
ID: "outer",
Object: runtime.EmbeddedObject{
Object: &EmbeddedTest{
ID: "inner",
},
Object: &EmbeddedTest{
ID: "inner",
},
}
originalData, err := s.EncodeToVersion(original, externalGV.String())
codec := serializer.NewCodecFactory(s).LegacyCodec(externalGV)
originalData, err := runtime.Encode(codec, original)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
t.Logf("originalRole = %v\n", string(originalData))
copyOfOriginal, err := api.Scheme.DeepCopy(original)
copyOfOriginal, err := s.DeepCopy(original)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
copiedData, err := s.EncodeToVersion(copyOfOriginal.(runtime.Object), externalGV.String())
copiedData, err := runtime.Encode(codec, copyOfOriginal.(runtime.Object))
if err != nil {
t.Errorf("unexpected error: %v", err)
}

View File

@ -37,3 +37,11 @@ func IsMissingKind(err error) bool {
func IsMissingVersion(err error) bool {
return conversion.IsMissingVersion(err)
}
func NewMissingKindErr(data string) error {
return conversion.NewMissingKindErr(data)
}
func NewMissingVersionErr(data string) error {
return conversion.NewMissingVersionErr(data)
}

View File

@ -16,7 +16,10 @@ limitations under the License.
package runtime
import "errors"
import (
"encoding/json"
"errors"
)
func (re *RawExtension) UnmarshalJSON(in []byte) error {
if re == nil {
@ -29,5 +32,16 @@ func (re *RawExtension) UnmarshalJSON(in []byte) error {
// Marshal may get called on pointers or values, so implement MarshalJSON on value.
// http://stackoverflow.com/questions/21390979/custom-marshaljson-never-gets-called-in-go
func (re RawExtension) MarshalJSON() ([]byte, error) {
if re.RawJSON == nil {
// TODO: this is to support legacy behavior of JSONPrinter and YAMLPrinter, which
// expect to call json.Marshal on arbitrary versioned objects (even those not in
// the scheme). pkg/kubectl/resource#AsVersionedObjects and its interaction with
// kubectl get on objects not in the scheme needs to be updated to ensure that the
// objects that are not part of the scheme are correctly put into the right form.
if re.Object != nil {
return json.Marshal(re.Object)
}
return []byte("null"), nil
}
return re.RawJSON, nil
}

View File

@ -22,8 +22,30 @@ import (
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/conversion"
"k8s.io/kubernetes/pkg/util/errors"
)
type objectTyperToTyper struct {
typer ObjectTyper
}
func (t objectTyperToTyper) ObjectKind(obj Object) (*unversioned.GroupVersionKind, bool, error) {
gvk, err := t.typer.ObjectKind(obj)
if err != nil {
return nil, false, err
}
unversionedType, ok := t.typer.IsUnversioned(obj)
if !ok {
// ObjectTyper violates its contract
return nil, false, fmt.Errorf("typer returned a kind for %v, but then reported it was not in the scheme with IsUnversioned", reflect.TypeOf(obj))
}
return &gvk, unversionedType, nil
}
func ObjectTyperToTyper(typer ObjectTyper) Typer {
return objectTyperToTyper{typer: typer}
}
// fieldPtr puts the address of fieldName, which must be a member of v,
// into dest, which must be an address of a variable to which this field's
// address can be assigned.
@ -48,32 +70,56 @@ func FieldPtr(v reflect.Value, fieldName string, dest interface{}) error {
return fmt.Errorf("couldn't assign/convert %v to %v", field.Type(), v.Type())
}
// EncodeList ensures that each object in an array is converted to a Unknown{} in serialized form.
// TODO: accept a content type.
func EncodeList(e Encoder, objects []Object, overrides ...unversioned.GroupVersion) error {
var errs []error
for i := range objects {
data, err := Encode(e, objects[i], overrides...)
if err != nil {
errs = append(errs, err)
continue
}
objects[i] = &Unknown{RawJSON: data}
}
return errors.NewAggregate(errs)
}
func decodeListItem(obj *Unknown, decoders []Decoder) (Object, error) {
for _, decoder := range decoders {
obj, err := Decode(decoder, obj.RawJSON)
if err != nil {
if IsNotRegisteredError(err) {
continue
}
return nil, err
}
return obj, nil
}
// could not decode, so leave the object as Unknown, but give the decoders the
// chance to set Unknown.TypeMeta if it is available.
for _, decoder := range decoders {
if err := DecodeInto(decoder, obj.RawJSON, obj); err == nil {
return obj, nil
}
}
return obj, nil
}
// DecodeList alters the list in place, attempting to decode any objects found in
// the list that have the runtime.Unknown type. Any errors that occur are returned
// the list that have the Unknown type. Any errors that occur are returned
// after the entire list is processed. Decoders are tried in order.
func DecodeList(objects []Object, decoders ...ObjectDecoder) []error {
func DecodeList(objects []Object, decoders ...Decoder) []error {
errs := []error(nil)
for i, obj := range objects {
switch t := obj.(type) {
case *Unknown:
for _, decoder := range decoders {
gv, err := unversioned.ParseGroupVersion(t.APIVersion)
if err != nil {
errs = append(errs, err)
break
}
if !decoder.Recognizes(gv.WithKind(t.Kind)) {
continue
}
obj, err := Decode(decoder, t.RawJSON)
if err != nil {
errs = append(errs, err)
break
}
objects[i] = obj
decoded, err := decodeListItem(t, decoders)
if err != nil {
errs = append(errs, err)
break
}
objects[i] = decoded
}
}
return errs
@ -84,16 +130,6 @@ type MultiObjectTyper []ObjectTyper
var _ ObjectTyper = MultiObjectTyper{}
func (m MultiObjectTyper) DataKind(data []byte) (gvk unversioned.GroupVersionKind, err error) {
for _, t := range m {
gvk, err = t.DataKind(data)
if err == nil {
return
}
}
return
}
func (m MultiObjectTyper) ObjectKind(obj Object) (gvk unversioned.GroupVersionKind, err error) {
for _, t := range m {
gvk, err = t.ObjectKind(obj)
@ -122,3 +158,12 @@ func (m MultiObjectTyper) Recognizes(gvk unversioned.GroupVersionKind) bool {
}
return false
}
func (m MultiObjectTyper) IsUnversioned(obj Object) (bool, bool) {
for _, t := range m {
if unversioned, ok := t.IsUnversioned(obj); ok {
return unversioned, true
}
}
return false, false
}

View File

@ -32,7 +32,7 @@ func TestDecodeList(t *testing.T) {
&runtime.Unstructured{TypeMeta: runtime.TypeMeta{Kind: "Foo", APIVersion: "Bar"}, Object: map[string]interface{}{"test": "value"}},
},
}
if errs := runtime.DecodeList(pl.Items, api.Scheme); len(errs) != 0 {
if errs := runtime.DecodeList(pl.Items, testapi.Default.Codec()); len(errs) != 0 {
t.Fatalf("unexpected error %v", errs)
}
if pod, ok := pl.Items[1].(*api.Pod); !ok || pod.Name != "test" {

View File

@ -23,70 +23,84 @@ import (
"k8s.io/kubernetes/pkg/api/unversioned"
)
// Codec defines methods for serializing and deserializing API objects.
type Codec interface {
Decoder
Encoder
const (
APIVersionInternal = "__internal"
APIVersionUnversioned = "__unversioned"
)
// Typer retrieves information about an object's group, version, and kind.
type Typer interface {
// ObjectKind returns the version and kind of the provided object, or an
// error if the object is not recognized (IsNotRegisteredError will return true).
// It returns whether the object is considered unversioned at the same time.
// TODO: align the signature of ObjectTyper with this interface
ObjectKind(Object) (*unversioned.GroupVersionKind, bool, error)
}
// Decoder defines methods for deserializing API objects into a given type
type Decoder interface {
// TODO: change the signature of this method
Decode(data []byte) (Object, error)
// DEPRECATED: This method is being removed
DecodeToVersion(data []byte, groupVersion unversioned.GroupVersion) (Object, error)
// DEPRECATED: This method is being removed
DecodeInto(data []byte, obj Object) error
// DEPRECATED: This method is being removed
DecodeIntoWithSpecifiedVersionKind(data []byte, obj Object, groupVersionKind unversioned.GroupVersionKind) error
DecodeParametersInto(parameters url.Values, obj Object) error
}
// Encoder defines methods for serializing API objects into bytes
type Encoder interface {
// DEPRECATED: This method is being removed
Encode(obj Object) (data []byte, err error)
EncodeToStream(obj Object, stream io.Writer) error
// TODO: Add method for processing url parameters.
// EncodeParameters(obj Object) (url.Values, error)
// EncodeToStream writes an object to a stream. Override versions may be provided for each group
// that enforce a certain versioning. Implementations may return errors if the versions are incompatible,
// or if no conversion is defined.
EncodeToStream(obj Object, stream io.Writer, overrides ...unversioned.GroupVersion) error
}
// ObjectCodec represents the common mechanisms for converting to and from a particular
// binary representation of an object.
// TODO: Remove this interface - it is used only in CodecFor() method.
type ObjectCodec interface {
Decoder
// EncodeToVersion convert and serializes an object in the internal format
// to a specified output version. An error is returned if the object
// cannot be converted for any reason.
EncodeToVersion(obj Object, outVersion string) ([]byte, error)
EncodeToVersionStream(obj Object, outVersion string, stream io.Writer) error
type Decoder interface {
// Decode attempts to deserialize the provided data using either the innate typing of the scheme or the
// default kind, group, and version provided. It returns a decoded object as well as the kind, group, and
// version from the serialized data, or an error. If into is non-nil, it will be used as the target type
// and implementations may choose to use it rather than reallocating an object. However, the object is not
// guaranteed to be populated. The returned object is not guaranteed to match into. If defaults are
// provided, they are applied to the data by default. If no defaults or partial defaults are provided, the
// type of the into may be used to guide conversion decisions.
Decode(data []byte, defaults *unversioned.GroupVersionKind, into Object) (Object, *unversioned.GroupVersionKind, error)
}
// ObjectDecoder is a convenience interface for identifying serialized versions of objects
// and transforming them into Objects. It intentionally overlaps with ObjectTyper and
// Decoder for use in decode only paths.
// TODO: Consider removing this interface?
type ObjectDecoder interface {
// Serializer is the core interface for transforming objects into a serialized format and back.
// Implementations may choose to perform conversion of the object, but no assumptions should be made.
type Serializer interface {
Encoder
Decoder
// DataVersionAndKind returns the group,version,kind of the provided data, or an error
// if another problem is detected. In many cases this method can be as expensive to
// invoke as the Decode method.
DataKind([]byte) (unversioned.GroupVersionKind, error)
// Recognizes returns true if the scheme is able to handle the provided group,version,kind
// of an object.
Recognizes(unversioned.GroupVersionKind) bool
}
// Codec is a Serializer that deals with the details of versioning objects. It offers the same
// interface as Serializer, so this is a marker to consumers that care about the version of the objects
// they receive.
type Codec Serializer
// ParameterCodec defines methods for serializing and deserializing API objects to url.Values and
// performing any necessary conversion. Unlike the normal Codec, query parameters are not self describing
// and the desired version must be specified.
type ParameterCodec interface {
// DecodeParameters takes the given url.Values in the specified group version and decodes them
// into the provided object, or returns an error.
DecodeParameters(parameters url.Values, from unversioned.GroupVersion, into Object) error
// EncodeParameters encodes the provided object as query parameters or returns an error.
EncodeParameters(obj Object, to unversioned.GroupVersion) (url.Values, error)
}
// NegotiatedSerializer is an interface used for obtaining encoders, decoders, and serializers
// for multiple supported media types.
type NegotiatedSerializer interface {
SupportedMediaTypes() []string
SerializerForMediaType(mediaType string, options map[string]string) (Serializer, bool)
EncoderForVersion(serializer Serializer, gv unversioned.GroupVersion) Encoder
DecoderToVersion(serializer Serializer, gv unversioned.GroupVersion) Decoder
}
///////////////////////////////////////////////////////////////////////////////
// Non-codec interfaces
type ObjectVersioner interface {
ConvertToVersion(in Object, outVersion string) (out Object, err error)
}
// ObjectConvertor converts an object to a different version.
type ObjectConvertor interface {
// Convert attempts to convert one object into another, or returns an error. This method does
// not guarantee the in object is not mutated.
Convert(in, out interface{}) error
// ConvertToVersion takes the provided object and converts it the provided version. This
// method does not guarantee that the in object is not mutated.
ConvertToVersion(in Object, outVersion string) (out Object, err error)
ConvertFieldLabel(version, kind, label, value string) (string, string, error)
}
@ -94,10 +108,6 @@ type ObjectConvertor interface {
// ObjectTyper contains methods for extracting the APIVersion and Kind
// of objects.
type ObjectTyper interface {
// DataKind returns the group,version,kind of the provided data, or an error
// if another problem is detected. In many cases this method can be as expensive to
// invoke as the Decode method.
DataKind([]byte) (unversioned.GroupVersionKind, error)
// ObjectKind returns the default group,version,kind of the provided object, or an
// error if the object is not recognized (IsNotRegisteredError will return true).
ObjectKind(Object) (unversioned.GroupVersionKind, error)
@ -108,6 +118,10 @@ type ObjectTyper interface {
// or more precisely that the provided version is a possible conversion or decoding
// target.
Recognizes(gvk unversioned.GroupVersionKind) bool
// IsUnversioned returns true if the provided object is considered unversioned and thus
// should have Version and Group suppressed in the output. If the object is not recognized
// in the scheme, ok is false.
IsUnversioned(Object) (unversioned bool, ok bool)
}
// ObjectCreater contains methods for instantiating an object by kind and version.

View File

@ -20,16 +20,6 @@ import (
"k8s.io/kubernetes/pkg/api/unversioned"
)
// SetGroupVersionKind satisfies the ObjectKind interface for all objects that embed PluginBase
func (obj *PluginBase) SetGroupVersionKind(gvk *unversioned.GroupVersionKind) {
_, obj.Kind = gvk.ToAPIVersionAndKind()
}
// GroupVersionKind satisfies the ObjectKind interface for all objects that embed PluginBase
func (obj *PluginBase) GroupVersionKind() *unversioned.GroupVersionKind {
return unversioned.FromAPIVersionAndKind("", obj.Kind)
}
// SetGroupVersionKind satisfies the ObjectKind interface for all objects that embed TypeMeta
func (obj *TypeMeta) SetGroupVersionKind(gvk *unversioned.GroupVersionKind) {
obj.APIVersion, obj.Kind = gvk.ToAPIVersionAndKind()
@ -42,3 +32,33 @@ func (obj *TypeMeta) GroupVersionKind() *unversioned.GroupVersionKind {
func (obj *Unknown) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
func (obj *Unstructured) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
// GetObjectKind implements Object for VersionedObjects, returning an empty ObjectKind
// interface if no objects are provided, or the ObjectKind interface of the object in the
// highest array position.
func (obj *VersionedObjects) GetObjectKind() unversioned.ObjectKind {
last := obj.Last()
if last == nil {
return unversioned.EmptyObjectKind
}
return last.GetObjectKind()
}
// First returns the leftmost object in the VersionedObjects array, which is usually the
// object as serialized on the wire.
func (obj *VersionedObjects) First() Object {
if len(obj.Objects) == 0 {
return nil
}
return obj.Objects[0]
}
// Last is the rightmost object in the VersionedObjects array, which is the object after
// all transformations have been applied. This is the same object that would be returned
// by Decode in a normal invocation (without VersionedObjects in the into argument).
func (obj *VersionedObjects) Last() Object {
if len(obj.Objects) == 0 {
return nil
}
return obj.Objects[len(obj.Objects)-1]
}

View File

@ -17,9 +17,7 @@ limitations under the License.
package runtime
import (
"encoding/json"
"fmt"
"io"
"net/url"
"reflect"
@ -36,9 +34,6 @@ type Scheme struct {
fieldLabelConversionFuncs map[string]map[string]FieldLabelConversionFunc
}
var _ Decoder = &Scheme{}
var _ ObjectTyper = &Scheme{}
// Function to convert a field selector to internal representation.
type FieldLabelConversionFunc func(label, value string) (internalLabel, internalValue string, err error)
@ -55,205 +50,11 @@ func (self *Scheme) fromScope(s conversion.Scope) (inVersion, outVersion string,
return inVersion, outVersion, scheme
}
// emptyPlugin is used to copy the Kind field to and from plugin objects.
type emptyPlugin struct {
PluginBase `json:",inline"`
}
// embeddedObjectToRawExtension does the conversion you would expect from the name, using the information
// given in conversion.Scope. It's placed in the DefaultScheme as a ConversionFunc to enable plugins;
// see the comment for RawExtension.
func (self *Scheme) embeddedObjectToRawExtension(in *EmbeddedObject, out *RawExtension, s conversion.Scope) error {
if in.Object == nil {
out.RawJSON = []byte("null")
return nil
}
// Figure out the type and kind of the output object.
_, outGroupVersionString, scheme := self.fromScope(s)
objKind, err := scheme.raw.ObjectKind(in.Object)
if err != nil {
return err
}
outVersion, err := unversioned.ParseGroupVersion(outGroupVersionString)
if err != nil {
return err
}
// Manufacture an object of this type and kind.
outObj, err := scheme.New(outVersion.WithKind(objKind.Kind))
if err != nil {
return err
}
// Manually do the conversion.
err = s.Convert(in.Object, outObj, 0)
if err != nil {
return err
}
// Copy the kind field into the output object.
err = s.Convert(
&emptyPlugin{PluginBase: PluginBase{Kind: objKind.Kind}},
outObj,
conversion.SourceToDest|conversion.IgnoreMissingFields|conversion.AllowDifferentFieldTypeNames,
)
if err != nil {
return err
}
// Because we provide the correct version, EncodeToVersion will not attempt a conversion.
raw, err := scheme.EncodeToVersion(outObj, outVersion.String())
if err != nil {
// TODO: if this fails, create an Unknown-- maybe some other
// component will understand it.
return err
}
out.RawJSON = raw
return nil
}
// rawExtensionToEmbeddedObject does the conversion you would expect from the name, using the information
// given in conversion.Scope. It's placed in all schemes as a ConversionFunc to enable plugins;
// see the comment for RawExtension.
func (self *Scheme) rawExtensionToEmbeddedObject(in *RawExtension, out *EmbeddedObject, s conversion.Scope) error {
if len(in.RawJSON) == 0 || (len(in.RawJSON) == 4 && string(in.RawJSON) == "null") {
out.Object = nil
return nil
}
// Figure out the type and kind of the output object.
inGroupVersionString, outGroupVersionString, scheme := self.fromScope(s)
dataKind, err := scheme.raw.DataKind(in.RawJSON)
if err != nil {
return err
}
inVersion, err := unversioned.ParseGroupVersion(inGroupVersionString)
if err != nil {
return err
}
outVersion, err := unversioned.ParseGroupVersion(outGroupVersionString)
if err != nil {
return err
}
// We have to make this object ourselves because we don't store the version field for
// plugin objects.
inObj, err := scheme.New(inVersion.WithKind(dataKind.Kind))
if err != nil {
return err
}
err = DecodeInto(scheme, in.RawJSON, inObj)
if err != nil {
return err
}
// Make the desired internal version, and do the conversion.
outObj, err := scheme.New(outVersion.WithKind(dataKind.Kind))
if err != nil {
return err
}
err = scheme.Convert(inObj, outObj)
if err != nil {
return err
}
// Last step, clear the Kind field; that should always be blank in memory.
err = s.Convert(
&emptyPlugin{PluginBase: PluginBase{Kind: ""}},
outObj,
conversion.SourceToDest|conversion.IgnoreMissingFields|conversion.AllowDifferentFieldTypeNames,
)
if err != nil {
return err
}
out.Object = outObj
return nil
}
// runtimeObjectToRawExtensionArray takes a list of objects and encodes them as RawExtension in the output version
// defined by the conversion.Scope. If objects must be encoded to different schema versions than the default, you
// should encode them yourself with runtime.Unknown, or convert the object prior to invoking conversion. Objects
// outside of the current scheme must be added as runtime.Unknown.
func (self *Scheme) runtimeObjectToRawExtensionArray(in *[]Object, out *[]RawExtension, s conversion.Scope) error {
src := *in
dest := make([]RawExtension, len(src))
_, outVersion, scheme := self.fromScope(s)
for i := range src {
switch t := src[i].(type) {
case *Unknown:
// TODO: this should be decoupled from the scheme (since it is JSON specific)
dest[i].RawJSON = t.RawJSON
case *Unstructured:
// TODO: this should be decoupled from the scheme (since it is JSON specific)
data, err := json.Marshal(t.Object)
if err != nil {
return err
}
dest[i].RawJSON = data
default:
version := outVersion
// if the object exists
// this code is try to set the outputVersion, but only if the object has a non-internal group version
if inGVK, err := scheme.ObjectKind(src[i]); err == nil && !inGVK.GroupVersion().IsEmpty() {
if self.raw.InternalVersions[inGVK.Group] != inGVK.GroupVersion() {
version = inGVK.GroupVersion().String()
}
}
data, err := scheme.EncodeToVersion(src[i], version)
if err != nil {
return err
}
dest[i].RawJSON = data
}
}
*out = dest
return nil
}
// rawExtensionToRuntimeObjectArray attempts to decode objects from the array - if they are unrecognized objects,
// they are added as Unknown.
func (self *Scheme) rawExtensionToRuntimeObjectArray(in *[]RawExtension, out *[]Object, s conversion.Scope) error {
src := *in
dest := make([]Object, len(src))
_, _, scheme := self.fromScope(s)
for i := range src {
data := src[i].RawJSON
dataKind, err := scheme.raw.DataKind(data)
if err != nil {
return err
}
dest[i] = &Unknown{
TypeMeta: TypeMeta{
APIVersion: dataKind.GroupVersion().String(),
Kind: dataKind.Kind,
},
RawJSON: data,
}
}
*out = dest
return nil
}
// NewScheme creates a new Scheme. This scheme is pluggable by default.
func NewScheme(internalGroupVersions ...unversioned.GroupVersion) *Scheme {
func NewScheme() *Scheme {
s := &Scheme{conversion.NewScheme(), map[string]map[string]FieldLabelConversionFunc{}}
s.AddConversionFuncs(DefaultEmbeddedConversions()...)
for _, internalGV := range internalGroupVersions {
s.raw.InternalVersions[internalGV.Group] = internalGV
}
s.raw.MetaFactory = conversion.SimpleMetaFactory{BaseFields: []string{"TypeMeta"}, VersionField: "APIVersion", KindField: "Kind"}
if err := s.raw.AddConversionFuncs(
s.embeddedObjectToRawExtension,
s.rawExtensionToEmbeddedObject,
s.runtimeObjectToRawExtensionArray,
s.rawExtensionToRuntimeObjectArray,
); err != nil {
panic(err)
}
// Enable map[string][]string conversions by default
if err := s.raw.AddConversionFuncs(DefaultStringConversions...); err != nil {
panic(err)
@ -267,14 +68,22 @@ func NewScheme(internalGroupVersions ...unversioned.GroupVersion) *Scheme {
return s
}
// AddInternalGroupVersion registers an internal GroupVersion with the scheme. This can later be
// used to lookup the internal GroupVersion for a given Group
func (s *Scheme) AddInternalGroupVersion(gv unversioned.GroupVersion) {
s.raw.InternalVersions[gv.Group] = gv
// AddUnversionedTypes registers the provided types as "unversioned", which means that they follow special rules.
// Whenever an object of this type is serialized, it is serialized with the provided group version and is not
// converted. Thus unversioned objects are expected to remain backwards compatible forever, as if they were in an
// API group and version that would never be updated.
//
// TODO: there is discussion about removing unversioned and replacing it with objects that are manifest into
// every version with particular schemas. Resolve tihs method at that point.
func (s *Scheme) AddUnversionedTypes(gv unversioned.GroupVersion, types ...Object) {
interfaces := make([]interface{}, len(types))
for i := range types {
interfaces[i] = types[i]
}
s.raw.AddUnversionedTypes(gv, interfaces...)
}
// AddKnownTypes registers the types of the arguments to the marshaller of the package api.
// Encode() refuses the object unless its type is registered with AddKnownTypes.
func (s *Scheme) AddKnownTypes(gv unversioned.GroupVersion, types ...Object) {
interfaces := make([]interface{}, len(types))
for i := range types {
@ -283,6 +92,12 @@ func (s *Scheme) AddKnownTypes(gv unversioned.GroupVersion, types ...Object) {
s.raw.AddKnownTypes(gv, interfaces...)
}
// AddIgnoredConversionType declares a particular conversion that should be ignored - during conversion
// this method is not invoked.
func (s *Scheme) AddIgnoredConversionType(from, to interface{}) error {
return s.raw.AddIgnoredConversionType(from, to)
}
// AddKnownTypeWithName is like AddKnownTypes, but it lets you specify what this type should
// be encoded as. Useful for testing when you don't want to make multiple packages to define
// your structs.
@ -296,12 +111,6 @@ func (s *Scheme) KnownTypes(gv unversioned.GroupVersion) map[string]reflect.Type
return s.raw.KnownTypes(gv)
}
// 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.raw.DataKind(data)
}
// ObjectKind returns the default group,version,kind of the given Object.
func (s *Scheme) ObjectKind(obj Object) (unversioned.GroupVersionKind, error) {
return s.raw.ObjectKind(obj)
@ -318,7 +127,12 @@ func (s *Scheme) Recognizes(gvk unversioned.GroupVersionKind) bool {
return s.raw.Recognizes(gvk)
}
// New returns a new API object of the given kind, or an error if it hasn't been registered.
func (s *Scheme) IsUnversioned(obj Object) (bool, bool) {
return s.raw.IsUnversioned(obj)
}
// New returns a new API object of the given version ("" for internal
// representation) and name, or an error if it hasn't been registered.
func (s *Scheme) New(kind unversioned.GroupVersionKind) (Object, error) {
obj, err := s.raw.NewObject(kind)
if err != nil {
@ -394,11 +208,31 @@ func (s *Scheme) AddDefaultingFuncs(defaultingFuncs ...interface{}) error {
return s.raw.AddDefaultingFuncs(defaultingFuncs...)
}
// Copy does a deep copy of an API object.
func (s *Scheme) Copy(src Object) (Object, error) {
dst, err := s.raw.DeepCopy(src)
if err != nil {
return nil, err
}
return dst.(Object), nil
}
// Performs a deep copy of the given object.
func (s *Scheme) DeepCopy(src interface{}) (interface{}, error) {
return s.raw.DeepCopy(src)
}
// WithConversions returns an ObjectConvertor that has the additional conversion functions
// defined in fns. The current scheme is not altered.
func (s *Scheme) WithConversions(fns *conversion.ConversionFuncs) ObjectConvertor {
if fns == nil {
return s
}
copied := *s
copied.raw = s.raw.WithConversions(*fns)
return &copied
}
// Convert will attempt to convert in into out. Both must be pointers.
// For easy testing of conversion functions. Returns an error if the conversion isn't
// possible.
@ -423,8 +257,19 @@ func (s *Scheme) ConvertFieldLabel(version, kind, label, value string) (string,
// version within this scheme. Will return an error if the provided version does not
// contain the inKind (or a mapping by name defined with AddKnownTypeWithName). Will also
// return an error if the conversion does not result in a valid Object being
// returned.
// returned. The serializer handles loading/serializing nested objects.
func (s *Scheme) ConvertToVersion(in Object, outVersion string) (Object, error) {
gv, err := unversioned.ParseGroupVersion(outVersion)
if err != nil {
return nil, err
}
switch in.(type) {
case *Unknown, *Unstructured:
old := in.GetObjectKind().GroupVersionKind()
defer in.GetObjectKind().SetGroupVersionKind(old)
setTargetVersion(in, s.raw, gv)
return in, nil
}
unknown, err := s.raw.ConvertToVersion(in, outVersion)
if err != nil {
return nil, err
@ -433,105 +278,16 @@ func (s *Scheme) ConvertToVersion(in Object, outVersion string) (Object, error)
if !ok {
return nil, fmt.Errorf("the provided object cannot be converted to a runtime.Object: %#v", unknown)
}
setTargetVersion(obj, s.raw, gv)
return obj, nil
}
// EncodeToVersion turns the given api object into an appropriate JSON string.
// Will return an error if the object doesn't have an embedded TypeMeta.
// Obj may be a pointer to a struct, or a struct. If a struct, a copy
// must be made. If a pointer, the object may be modified before encoding,
// but will be put back into its original state before returning.
//
// Memory/wire format differences:
// * Having to keep track of the Kind and APIVersion fields makes tests
// very annoying, so the rule is that they are set only in wire format
// (json), not when in native (memory) format. This is possible because
// both pieces of information are implicit in the go typed object.
// * An exception: note that, if there are embedded API objects of known
// type, for example, PodList{... Items []Pod ...}, these embedded
// objects must be of the same version of the object they are embedded
// within, and their APIVersion and Kind must both be empty.
// * Note that the exception does not apply to the APIObject type, which
// recursively does Encode()/Decode(), and is capable of expressing any
// API object.
// * 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 Object, destVersion string) (data []byte, err error) {
return s.raw.EncodeToVersion(obj, destVersion)
}
func (s *Scheme) EncodeToVersionStream(obj Object, destVersion string, stream io.Writer) error {
return s.raw.EncodeToVersionStream(obj, destVersion, stream)
}
// Decode converts a YAML or JSON string back into a pointer to an api object.
// Deduces the type based upon the APIVersion and Kind fields, which are set
// by Encode. Only versioned objects (APIVersion != "") are accepted. The object
// will be converted into the in-memory unversioned type before being returned.
func (s *Scheme) Decode(data []byte) (Object, error) {
obj, err := s.raw.Decode(data)
if err != nil {
return nil, err
func setTargetVersion(obj Object, raw *conversion.Scheme, gv unversioned.GroupVersion) {
if gv.Version == APIVersionInternal {
// internal is a special case
obj.GetObjectKind().SetGroupVersionKind(nil)
} else {
gvk, _ := raw.ObjectKind(obj)
obj.GetObjectKind().SetGroupVersionKind(&unversioned.GroupVersionKind{Group: gv.Group, Version: gv.Version, Kind: gvk.Kind})
}
return obj.(Object), nil
}
// DecodeToVersion converts a YAML or JSON string back into a pointer to an api
// object. Deduces the type based upon the APIVersion and Kind fields, which
// are set by Encode. Only versioned objects (APIVersion != "") are
// accepted. The object will be converted into the in-memory versioned type
// requested before being returned.
func (s *Scheme) DecodeToVersion(data []byte, gv unversioned.GroupVersion) (Object, error) {
obj, err := s.raw.DecodeToVersion(data, gv)
if err != nil {
return nil, err
}
return obj.(Object), nil
}
// DecodeInto parses a YAML or JSON string and stores it in obj. Returns an error
// if data.Kind is set and doesn't match the type of obj. Obj should be a
// pointer to an api type.
// If obj's APIVersion doesn't match that in data, an attempt will be made to convert
// data into obj's version.
// TODO: allow Decode/DecodeInto to take a default apiVersion and a default kind, to
// be applied if the provided object does not have either field (integrate external
// apis into the decoding scheme).
func (s *Scheme) DecodeInto(data []byte, obj Object) error {
return s.raw.DecodeInto(data, obj)
}
// DecodeIntoWithSpecifiedVersionKind coerces the data into the obj, assuming that the data is
// of type GroupVersionKind
func (s *Scheme) DecodeIntoWithSpecifiedVersionKind(data []byte, obj Object, gvk unversioned.GroupVersionKind) error {
return s.raw.DecodeIntoWithSpecifiedVersionKind(data, obj, gvk)
}
func (s *Scheme) DecodeParametersInto(parameters url.Values, obj Object) error {
return s.raw.DecodeParametersInto(parameters, obj)
}
// Copy does a deep copy of an API object. Useful mostly for tests.
func (s *Scheme) Copy(src Object) (Object, error) {
dst, err := s.raw.DeepCopy(src)
if err != nil {
return nil, err
}
return dst.(Object), nil
}
func (s *Scheme) CopyOrDie(obj Object) Object {
newObj, err := s.Copy(obj)
if err != nil {
panic(err)
}
return newObj
}

View File

@ -23,6 +23,8 @@ import (
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/conversion"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/runtime/serializer"
"k8s.io/kubernetes/pkg/util"
)
type TypeMeta struct {
@ -51,23 +53,19 @@ type ExternalSimple struct {
}
func (obj *InternalSimple) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
func (obj *InternalSimple) SetGroupVersionKind(gvk *unversioned.GroupVersionKind) {
obj.TypeMeta.APIVersion, obj.TypeMeta.Kind = gvk.ToAPIVersionAndKind()
}
func (obj *ExternalSimple) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
func (obj *ExternalSimple) SetGroupVersionKind(gvk *unversioned.GroupVersionKind) {
obj.TypeMeta.APIVersion, obj.TypeMeta.Kind = gvk.ToAPIVersionAndKind()
}
func TestScheme(t *testing.T) {
internalGV := unversioned.GroupVersion{Group: "test.group", Version: ""}
internalGV := unversioned.GroupVersion{Group: "test.group", Version: runtime.APIVersionInternal}
externalGV := unversioned.GroupVersion{Group: "test.group", Version: "testExternal"}
scheme := runtime.NewScheme()
scheme.AddInternalGroupVersion(internalGV)
scheme.AddKnownTypeWithName(internalGV.WithKind("Simple"), &InternalSimple{})
scheme.AddKnownTypeWithName(externalGV.WithKind("Simple"), &ExternalSimple{})
// If set, would clear TypeMeta during conversion.
//scheme.AddIgnoredConversionType(&TypeMeta{}, &TypeMeta{})
// test that scheme is an ObjectTyper
var _ runtime.ObjectTyper = scheme
@ -102,21 +100,27 @@ func TestScheme(t *testing.T) {
},
)
if err != nil {
t.Errorf("unexpected error: %v", err)
t.Fatalf("unexpected error: %v", err)
}
codecs := serializer.NewCodecFactory(scheme)
codec := codecs.LegacyCodec(externalGV)
jsonserializer, _ := codecs.SerializerForFileExtension("json")
simple := &InternalSimple{
TestString: "foo",
}
// Test Encode, Decode, DecodeInto, and DecodeToVersion
obj := runtime.Object(simple)
data, err := scheme.EncodeToVersion(obj, externalGV.String())
obj2, err2 := runtime.Decode(scheme, data)
obj3 := &InternalSimple{}
err3 := runtime.DecodeInto(scheme, data, obj3)
obj4, err4 := scheme.DecodeToVersion(data, externalGV)
if err != nil || err2 != nil || err3 != nil || err4 != nil {
t.Fatalf("Failure: '%v' '%v' '%v' '%v'", err, err2, err3, err4)
data, err := runtime.Encode(codec, obj)
if err != nil {
t.Fatal(err)
}
obj2, err := runtime.Decode(codec, data)
if err != nil {
t.Fatal(err)
}
if _, ok := obj2.(*InternalSimple); !ok {
t.Fatalf("Got wrong type")
@ -124,9 +128,22 @@ func TestScheme(t *testing.T) {
if e, a := simple, obj2; !reflect.DeepEqual(e, a) {
t.Errorf("Expected:\n %#v,\n Got:\n %#v", e, a)
}
obj3 := &InternalSimple{}
if err := runtime.DecodeInto(codec, data, obj3); err != nil {
t.Fatal(err)
}
// clearing TypeMeta is a function of the scheme, which we do not test here (ConvertToVersion
// does not automatically clear TypeMeta anymore).
simple.TypeMeta = TypeMeta{Kind: "Simple", APIVersion: externalGV.String()}
if e, a := simple, obj3; !reflect.DeepEqual(e, a) {
t.Errorf("Expected:\n %#v,\n Got:\n %#v", e, a)
}
obj4, err := runtime.Decode(jsonserializer, data)
if err != nil {
t.Fatal(err)
}
if _, ok := obj4.(*ExternalSimple); !ok {
t.Fatalf("Got wrong type")
}
@ -135,7 +152,7 @@ func TestScheme(t *testing.T) {
external := &ExternalSimple{}
err = scheme.Convert(simple, external)
if err != nil {
t.Errorf("Unexpected error: %v", err)
t.Fatalf("Unexpected error: %v", err)
}
if e, a := simple.TestString, external.TestString; e != a {
t.Errorf("Expected %v, got %v", e, a)
@ -145,36 +162,23 @@ func TestScheme(t *testing.T) {
if e, a := 2, internalToExternalCalls; e != a {
t.Errorf("Expected %v, got %v", e, a)
}
// Decode and DecodeInto should each have caused an increment.
// DecodeInto and Decode should each have caused an increment because of a conversion
if e, a := 2, externalToInternalCalls; e != a {
t.Errorf("Expected %v, got %v", e, a)
}
}
func TestInvalidObjectValueKind(t *testing.T) {
internalGV := unversioned.GroupVersion{Group: "", Version: ""}
scheme := runtime.NewScheme()
scheme.AddKnownTypeWithName(internalGV.WithKind("Simple"), &InternalSimple{})
embedded := &runtime.EmbeddedObject{}
switch obj := embedded.Object.(type) {
default:
_, err := scheme.ObjectKind(obj)
if err == nil {
t.Errorf("Expected error on invalid kind")
}
}
}
func TestBadJSONRejection(t *testing.T) {
scheme := runtime.NewScheme()
codecs := serializer.NewCodecFactory(scheme)
jsonserializer, _ := codecs.SerializerForFileExtension("json")
badJSONMissingKind := []byte(`{ }`)
if _, err := runtime.Decode(scheme, badJSONMissingKind); err == nil {
if _, err := runtime.Decode(jsonserializer, badJSONMissingKind); err == nil {
t.Errorf("Did not reject despite lack of kind field: %s", badJSONMissingKind)
}
badJSONUnknownType := []byte(`{"kind": "bar"}`)
if _, err1 := runtime.Decode(scheme, badJSONUnknownType); err1 == nil {
if _, err1 := runtime.Decode(jsonserializer, badJSONUnknownType); err1 == nil {
t.Errorf("Did not reject despite use of unknown type: %s", badJSONUnknownType)
}
/*badJSONKindMismatch := []byte(`{"kind": "Pod"}`)
@ -184,13 +188,13 @@ func TestBadJSONRejection(t *testing.T) {
}
type ExtensionA struct {
runtime.PluginBase `json:",inline"`
TestString string `json:"testString"`
TypeMeta `json:",inline"`
TestString string `json:"testString"`
}
type ExtensionB struct {
runtime.PluginBase `json:",inline"`
TestString string `json:"testString"`
TypeMeta `json:",inline"`
TestString string `json:"testString"`
}
type ExternalExtensionType struct {
@ -200,7 +204,7 @@ type ExternalExtensionType struct {
type InternalExtensionType struct {
TypeMeta `json:",inline"`
Extension runtime.EmbeddedObject `json:"extension"`
Extension runtime.Object `json:"extension"`
}
type ExternalOptionalExtensionType struct {
@ -210,143 +214,135 @@ type ExternalOptionalExtensionType struct {
type InternalOptionalExtensionType struct {
TypeMeta `json:",inline"`
Extension runtime.EmbeddedObject `json:"extension,omitempty"`
Extension runtime.Object `json:"extension,omitempty"`
}
func (obj *ExtensionA) GetObjectKind() unversioned.ObjectKind { return &obj.PluginBase }
func (obj *ExtensionA) SetGroupVersionKind(gvk *unversioned.GroupVersionKind) {
_, obj.PluginBase.Kind = gvk.ToAPIVersionAndKind()
}
func (obj *ExtensionB) GetObjectKind() unversioned.ObjectKind { return &obj.PluginBase }
func (obj *ExtensionB) SetGroupVersionKind(gvk *unversioned.GroupVersionKind) {
_, obj.PluginBase.Kind = gvk.ToAPIVersionAndKind()
}
func (obj *ExternalExtensionType) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
func (obj *ExternalExtensionType) SetGroupVersionKind(gvk *unversioned.GroupVersionKind) {
obj.TypeMeta.APIVersion, obj.TypeMeta.Kind = gvk.ToAPIVersionAndKind()
}
func (obj *InternalExtensionType) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
func (obj *InternalExtensionType) SetGroupVersionKind(gvk *unversioned.GroupVersionKind) {
obj.TypeMeta.APIVersion, obj.TypeMeta.Kind = gvk.ToAPIVersionAndKind()
}
func (obj *ExtensionA) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
func (obj *ExtensionB) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
func (obj *ExternalExtensionType) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
func (obj *InternalExtensionType) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
func (obj *ExternalOptionalExtensionType) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
func (obj *ExternalOptionalExtensionType) SetGroupVersionKind(gvk *unversioned.GroupVersionKind) {
obj.TypeMeta.APIVersion, obj.TypeMeta.Kind = gvk.ToAPIVersionAndKind()
}
func (obj *InternalOptionalExtensionType) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
func (obj *InternalOptionalExtensionType) SetGroupVersionKind(gvk *unversioned.GroupVersionKind) {
obj.TypeMeta.APIVersion, obj.TypeMeta.Kind = gvk.ToAPIVersionAndKind()
}
func TestExternalToInternalMapping(t *testing.T) {
internalGV := unversioned.GroupVersion{Group: "test.group", Version: ""}
internalGV := unversioned.GroupVersion{Group: "test.group", Version: runtime.APIVersionInternal}
externalGV := unversioned.GroupVersion{Group: "test.group", Version: "testExternal"}
scheme := runtime.NewScheme()
scheme.AddInternalGroupVersion(internalGV)
scheme.AddKnownTypeWithName(internalGV.WithKind("OptionalExtensionType"), &InternalOptionalExtensionType{})
scheme.AddKnownTypeWithName(externalGV.WithKind("OptionalExtensionType"), &ExternalOptionalExtensionType{})
codec := serializer.NewCodecFactory(scheme).LegacyCodec(externalGV)
table := []struct {
obj runtime.Object
encoded string
}{
{
&InternalOptionalExtensionType{Extension: runtime.EmbeddedObject{Object: nil}},
&InternalOptionalExtensionType{Extension: nil},
`{"kind":"OptionalExtensionType","apiVersion":"` + externalGV.String() + `"}`,
},
}
for _, item := range table {
gotDecoded, err := runtime.Decode(scheme, []byte(item.encoded))
for i, item := range table {
gotDecoded, err := runtime.Decode(codec, []byte(item.encoded))
if err != nil {
t.Errorf("unexpected error '%v' (%v)", err, item.encoded)
} else if e, a := item.obj, gotDecoded; !reflect.DeepEqual(e, a) {
var eEx, aEx runtime.Object
if obj, ok := e.(*InternalOptionalExtensionType); ok {
eEx = obj.Extension.Object
}
if obj, ok := a.(*InternalOptionalExtensionType); ok {
aEx = obj.Extension.Object
}
t.Errorf("expected %#v, got %#v (%#v, %#v)", e, a, eEx, aEx)
t.Errorf("%d: unexpected objects:\n%s", i, util.ObjectGoPrintSideBySide(e, a))
}
}
}
func TestExtensionMapping(t *testing.T) {
internalGV := unversioned.GroupVersion{Group: "test.group", Version: ""}
internalGV := unversioned.GroupVersion{Group: "test.group", Version: runtime.APIVersionInternal}
externalGV := unversioned.GroupVersion{Group: "test.group", Version: "testExternal"}
scheme := runtime.NewScheme()
scheme.AddInternalGroupVersion(internalGV)
scheme.AddKnownTypeWithName(internalGV.WithKind("ExtensionType"), &InternalExtensionType{})
scheme.AddKnownTypeWithName(internalGV.WithKind("OptionalExtensionType"), &InternalOptionalExtensionType{})
scheme.AddKnownTypeWithName(internalGV.WithKind("A"), &ExtensionA{})
scheme.AddKnownTypeWithName(internalGV.WithKind("B"), &ExtensionB{})
scheme.AddKnownTypeWithName(externalGV.WithKind("ExtensionType"), &ExternalExtensionType{})
scheme.AddKnownTypeWithName(externalGV.WithKind("OptionalExtensionType"), &ExternalOptionalExtensionType{})
// register external first when the object is the same in both schemes, so ObjectVersionAndKind reports the
// external version.
scheme.AddKnownTypeWithName(externalGV.WithKind("A"), &ExtensionA{})
scheme.AddKnownTypeWithName(externalGV.WithKind("B"), &ExtensionB{})
scheme.AddKnownTypeWithName(internalGV.WithKind("A"), &ExtensionA{})
scheme.AddKnownTypeWithName(internalGV.WithKind("B"), &ExtensionB{})
codec := serializer.NewCodecFactory(scheme).LegacyCodec(externalGV)
table := []struct {
obj runtime.Object
encoded string
obj runtime.Object
expected runtime.Object
encoded string
}{
{
&InternalExtensionType{Extension: runtime.EmbeddedObject{Object: &ExtensionA{TestString: "foo"}}},
`{"kind":"ExtensionType","apiVersion":"` + externalGV.String() + `","extension":{"kind":"A","testString":"foo"}}
&InternalExtensionType{
Extension: runtime.NewEncodable(codec, &ExtensionA{TestString: "foo"}),
},
&InternalExtensionType{
Extension: &runtime.Unknown{
RawJSON: []byte(`{"kind":"A","apiVersion":"test.group/testExternal","testString":"foo"}`),
},
},
// apiVersion is set in the serialized object for easier consumption by clients
`{"kind":"ExtensionType","apiVersion":"` + externalGV.String() + `","extension":{"kind":"A","apiVersion":"test.group/testExternal","testString":"foo"}}
`,
}, {
&InternalExtensionType{Extension: runtime.EmbeddedObject{Object: &ExtensionB{TestString: "bar"}}},
`{"kind":"ExtensionType","apiVersion":"` + externalGV.String() + `","extension":{"kind":"B","testString":"bar"}}
&InternalExtensionType{Extension: runtime.NewEncodable(codec, &ExtensionB{TestString: "bar"})},
&InternalExtensionType{
Extension: &runtime.Unknown{
RawJSON: []byte(`{"kind":"B","apiVersion":"test.group/testExternal","testString":"bar"}`),
},
},
// apiVersion is set in the serialized object for easier consumption by clients
`{"kind":"ExtensionType","apiVersion":"` + externalGV.String() + `","extension":{"kind":"B","apiVersion":"test.group/testExternal","testString":"bar"}}
`,
}, {
&InternalExtensionType{Extension: runtime.EmbeddedObject{Object: nil}},
&InternalExtensionType{Extension: nil},
&InternalExtensionType{
Extension: nil,
},
`{"kind":"ExtensionType","apiVersion":"` + externalGV.String() + `","extension":null}
`,
},
}
for _, item := range table {
gotEncoded, err := scheme.EncodeToVersion(item.obj, externalGV.String())
for i, item := range table {
gotEncoded, err := runtime.Encode(codec, item.obj)
if err != nil {
t.Errorf("unexpected error '%v' (%#v)", err, item.obj)
} else if e, a := item.encoded, string(gotEncoded); e != a {
t.Errorf("expected\n%#v\ngot\n%#v\n", e, a)
}
gotDecoded, err := runtime.Decode(scheme, []byte(item.encoded))
gotDecoded, err := runtime.Decode(codec, []byte(item.encoded))
if err != nil {
t.Errorf("unexpected error '%v' (%v)", err, item.encoded)
} else if e, a := item.obj, gotDecoded; !reflect.DeepEqual(e, a) {
var eEx, aEx runtime.Object
if obj, ok := e.(*InternalExtensionType); ok {
eEx = obj.Extension.Object
}
if obj, ok := a.(*InternalExtensionType); ok {
aEx = obj.Extension.Object
}
t.Errorf("expected %#v, got %#v (%#v, %#v)", e, a, eEx, aEx)
} else if e, a := item.expected, gotDecoded; !reflect.DeepEqual(e, a) {
t.Errorf("%d: unexpected objects:\n%s", i, util.ObjectGoPrintSideBySide(e, a))
}
}
}
func TestEncode(t *testing.T) {
internalGV := unversioned.GroupVersion{Group: "test.group", Version: ""}
internalGV := unversioned.GroupVersion{Group: "test.group", Version: runtime.APIVersionInternal}
externalGV := unversioned.GroupVersion{Group: "test.group", Version: "testExternal"}
scheme := runtime.NewScheme()
scheme.AddInternalGroupVersion(internalGV)
scheme.AddKnownTypeWithName(internalGV.WithKind("Simple"), &InternalSimple{})
scheme.AddKnownTypeWithName(externalGV.WithKind("Simple"), &ExternalSimple{})
codec := runtime.CodecFor(scheme, externalGV)
codec := serializer.NewCodecFactory(scheme).LegacyCodec(externalGV)
test := &InternalSimple{
TestString: "I'm the same",
}
obj := runtime.Object(test)
data, err := runtime.Encode(codec, obj)
obj2, err2 := runtime.Decode(codec, data)
obj2, gvk, err2 := codec.Decode(data, nil, nil)
if err != nil || err2 != nil {
t.Fatalf("Failure: '%v' '%v'", err, err2)
}
@ -354,6 +350,68 @@ func TestEncode(t *testing.T) {
t.Fatalf("Got wrong type")
}
if !reflect.DeepEqual(obj2, test) {
t.Errorf("Expected:\n %#v,\n Got:\n %#v", &test, obj2)
t.Errorf("Expected:\n %#v,\n Got:\n %#v", test, obj2)
}
if !reflect.DeepEqual(gvk, &unversioned.GroupVersionKind{Group: "test.group", Version: "testExternal", Kind: "Simple"}) {
t.Errorf("unexpected gvk returned by decode: %#v", gvk)
}
}
func TestUnversionedTypes(t *testing.T) {
internalGV := unversioned.GroupVersion{Group: "test.group", Version: runtime.APIVersionInternal}
externalGV := unversioned.GroupVersion{Group: "test.group", Version: "testExternal"}
otherGV := unversioned.GroupVersion{Group: "group", Version: "other"}
scheme := runtime.NewScheme()
scheme.AddUnversionedTypes(externalGV, &InternalSimple{})
scheme.AddKnownTypeWithName(internalGV.WithKind("Simple"), &InternalSimple{})
scheme.AddKnownTypeWithName(externalGV.WithKind("Simple"), &ExternalSimple{})
scheme.AddKnownTypeWithName(otherGV.WithKind("Simple"), &ExternalSimple{})
codec := serializer.NewCodecFactory(scheme).LegacyCodec(externalGV)
if unv, ok := scheme.IsUnversioned(&InternalSimple{}); !unv || !ok {
t.Fatal("type not unversioned and in scheme: %t %t", unv, ok)
}
kind, err := scheme.ObjectKind(&InternalSimple{})
if err != nil {
t.Fatal(err)
}
if kind != externalGV.WithKind("InternalSimple") {
t.Fatalf("unexpected: %#v", kind)
}
test := &InternalSimple{
TestString: "I'm the same",
}
obj := runtime.Object(test)
data, err := runtime.Encode(codec, obj)
if err != nil {
t.Fatal(err)
}
obj2, gvk, err := codec.Decode(data, nil, nil)
if err != nil {
t.Fatal(err)
}
if _, ok := obj2.(*InternalSimple); !ok {
t.Fatalf("Got wrong type")
}
if !reflect.DeepEqual(obj2, test) {
t.Errorf("Expected:\n %#v,\n Got:\n %#v", test, obj2)
}
// object is serialized as an unversioned object (in the group and version it was defined in)
if !reflect.DeepEqual(gvk, &unversioned.GroupVersionKind{Group: "test.group", Version: "testExternal", Kind: "InternalSimple"}) {
t.Errorf("unexpected gvk returned by decode: %#v", gvk)
}
// when serialized to a different group, the object is kept in its preferred name
codec = serializer.NewCodecFactory(scheme).LegacyCodec(otherGV)
data, err = runtime.Encode(codec, obj)
if err != nil {
t.Fatal(err)
}
if string(data) != `{"kind":"InternalSimple","apiVersion":"test.group/testExternal","testString":"I'm the same"}`+"\n" {
t.Errorf("unexpected data: %s", data)
}
}

View File

@ -36,39 +36,18 @@ type TypeMeta struct {
Kind string `json:"kind,omitempty" yaml:"kind,omitempty"`
}
// PluginBase is like TypeMeta, but it's intended for plugin objects that won't ever be encoded
// except while embedded in other objects.
type PluginBase struct {
Kind string `json:"kind,omitempty"`
}
// EmbeddedObject has appropriate encoder and decoder functions, such that on the wire, it's
// stored as a []byte, but in memory, the contained object is accessible as an Object
// via the Get() function. Only valid API objects may be stored via EmbeddedObject.
// The purpose of this is to allow an API object of type known only at runtime to be
// embedded within other API objects.
//
// Note that object assumes that you've registered all of your api types with the api package.
//
// EmbeddedObject and RawExtension can be used together to allow for API object extensions:
// see the comment for RawExtension.
type EmbeddedObject struct {
Object
}
// RawExtension is used with EmbeddedObject to do a two-phase encoding of extension objects.
// RawExtension is used to hold extensions in external versions.
//
// To use this, make a field which has RawExtension as its type in your external, versioned
// struct, and EmbeddedObject in your internal struct. You also need to register your
// struct, and Object in your internal struct. You also need to register your
// various plugin types.
//
// // Internal package:
// type MyAPIObject struct {
// runtime.TypeMeta `json:",inline"`
// MyPlugin runtime.EmbeddedObject `json:"myPlugin"`
// MyPlugin runtime.Object `json:"myPlugin"`
// }
// type PluginA struct {
// runtime.PluginBase `json:",inline"`
// AOption string `json:"aOption"`
// }
//
@ -78,7 +57,6 @@ type EmbeddedObject struct {
// MyPlugin runtime.RawExtension `json:"myPlugin"`
// }
// type PluginA struct {
// runtime.PluginBase `json:",inline"`
// AOption string `json:"aOption"`
// }
//
@ -97,12 +75,16 @@ type EmbeddedObject struct {
// The next step is to copy (using pkg/conversion) into the internal struct. The runtime
// package's DefaultScheme has conversion functions installed which will unpack the
// JSON stored in RawExtension, turning it into the correct object type, and storing it
// in the EmbeddedObject. (TODO: In the case where the object is of an unknown type, a
// in the Object. (TODO: In the case where the object is of an unknown type, a
// runtime.Unknown object will be created and stored.)
//
// +protobuf=true
type RawExtension struct {
// RawJSON is the underlying serialization of this object.
RawJSON []byte
// Object can hold a representation of this extension - useful for working with versioned
// structs.
Object Object `json:"-"`
}
// Unknown allows api objects with unknown types to be passed-through. This can be used
@ -131,3 +113,13 @@ type Unstructured struct {
// children.
Object map[string]interface{}
}
// VersionedObjects is used by Decoders to give callers a way to access all versions
// of an object during the decoding process.
type VersionedObjects struct {
// Objects is the set of objects retrieved during decoding, in order of conversion.
// The 0 index is the object as serialized on the wire. If conversion has occured,
// other objects may be present. The right most object is the same as would be returned
// by a normal Decode call.
Objects []Object
}

View File

@ -18,9 +18,7 @@ package runtime
import (
"encoding/json"
"fmt"
"net/url"
"reflect"
"io"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/conversion"
@ -28,36 +26,19 @@ import (
// UnstructuredJSONScheme is capable of converting JSON data into the Unstructured
// type, which can be used for generic access to objects without a predefined scheme.
var UnstructuredJSONScheme ObjectDecoder = unstructuredJSONScheme{}
// TODO: move into serializer/json.
var UnstructuredJSONScheme Decoder = unstructuredJSONScheme{}
type unstructuredJSONScheme struct{}
var _ Decoder = unstructuredJSONScheme{}
var _ ObjectDecoder = unstructuredJSONScheme{}
var _ Codec = unstructuredJSONScheme{}
// Recognizes returns true for any version or kind that is specified (internal
// versions are specifically excluded).
func (unstructuredJSONScheme) Recognizes(gvk unversioned.GroupVersionKind) bool {
return !gvk.GroupVersion().IsEmpty() && len(gvk.Kind) > 0
}
func (s unstructuredJSONScheme) Decode(data []byte) (Object, error) {
func (s unstructuredJSONScheme) Decode(data []byte, _ *unversioned.GroupVersionKind, _ Object) (Object, *unversioned.GroupVersionKind, error) {
unstruct := &Unstructured{}
if err := DecodeInto(s, data, unstruct); err != nil {
return nil, err
}
return unstruct, nil
}
func (unstructuredJSONScheme) DecodeInto(data []byte, obj Object) error {
unstruct, ok := obj.(*Unstructured)
if !ok {
return fmt.Errorf("the unstructured JSON scheme does not recognize %v", reflect.TypeOf(obj))
}
m := make(map[string]interface{})
if err := json.Unmarshal(data, &m); err != nil {
return err
return nil, nil, err
}
if v, ok := m["kind"]; ok {
if s, ok := v.(string); ok {
@ -69,44 +50,30 @@ func (unstructuredJSONScheme) DecodeInto(data []byte, obj Object) error {
unstruct.APIVersion = s
}
}
if len(unstruct.APIVersion) == 0 {
return conversion.NewMissingVersionErr(string(data))
return nil, nil, conversion.NewMissingVersionErr(string(data))
}
gv, err := unversioned.ParseGroupVersion(unstruct.APIVersion)
if err != nil {
return nil, nil, err
}
gvk := gv.WithKind(unstruct.Kind)
if len(unstruct.Kind) == 0 {
return conversion.NewMissingKindErr(string(data))
return nil, &gvk, conversion.NewMissingKindErr(string(data))
}
unstruct.Object = m
return nil
return unstruct, &gvk, nil
}
func (unstructuredJSONScheme) DecodeIntoWithSpecifiedVersionKind(data []byte, obj Object, gvk unversioned.GroupVersionKind) error {
return nil
}
func (unstructuredJSONScheme) DecodeToVersion(data []byte, gv unversioned.GroupVersion) (Object, error) {
return nil, nil
}
func (unstructuredJSONScheme) DecodeParametersInto(paramaters url.Values, obj Object) error {
return nil
}
func (unstructuredJSONScheme) DataKind(data []byte) (unversioned.GroupVersionKind, error) {
obj := TypeMeta{}
if err := json.Unmarshal(data, &obj); err != nil {
return unversioned.GroupVersionKind{}, err
func (s unstructuredJSONScheme) EncodeToStream(obj Object, w io.Writer, overrides ...unversioned.GroupVersion) error {
switch t := obj.(type) {
case *Unstructured:
return json.NewEncoder(w).Encode(t.Object)
case *Unknown:
_, err := w.Write(t.RawJSON)
return err
default:
return json.NewEncoder(w).Encode(t)
}
if len(obj.APIVersion) == 0 {
return unversioned.GroupVersionKind{}, conversion.NewMissingVersionErr(string(data))
}
if len(obj.Kind) == 0 {
return unversioned.GroupVersionKind{}, conversion.NewMissingKindErr(string(data))
}
gv, err := unversioned.ParseGroupVersion(obj.APIVersion)
if err != nil {
return unversioned.GroupVersionKind{}, err
}
return gv.WithKind(obj.Kind), nil
}

View File

@ -42,7 +42,7 @@ func TestDecodeUnstructured(t *testing.T) {
if pod, ok := pl.Items[1].(*runtime.Unstructured); !ok || pod.Object["kind"] != "Pod" || pod.Object["metadata"].(map[string]interface{})["name"] != "test" {
t.Errorf("object not converted: %#v", pl.Items[1])
}
if _, ok := pl.Items[2].(*runtime.Unknown); !ok {
t.Errorf("object should not have been converted: %#v", pl.Items[2])
if pod, ok := pl.Items[2].(*runtime.Unstructured); !ok || pod.Object["kind"] != "Pod" || pod.Object["metadata"].(map[string]interface{})["name"] != "test" {
t.Errorf("object not converted: %#v", pl.Items[2])
}
}