mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-22 03:11:40 +00:00
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:
parent
6582b4c2ea
commit
63a7a41ddf
@ -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
|
||||
}
|
||||
|
||||
|
@ -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 ¶meterCodec{
|
||||
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 = ¶meterCodec{}
|
||||
|
||||
// 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(¶meters, into)
|
||||
}
|
||||
input, err := c.creator.New(from.WithKind(targetGVK.Kind))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.convertor.Convert(¶meters, 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)
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
124
pkg/runtime/embedded.go
Normal 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
|
||||
},
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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" {
|
||||
|
@ -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.
|
||||
|
@ -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]
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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])
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user