Provide a JSON and YAML serializer, and a versioning wrapper

Add a recognizer that is capable of sniffing content type from data by
asking each serializer to try to decode - this is for a "universal
decoder/deserializer" which can be used by client logic.

Add codec factory, which provides the core primitives for content type
negotiation. Codec factories depend only on schemes, serializers, and
groupversion pairs.
This commit is contained in:
Clayton Coleman 2015-12-21 00:12:51 -05:00
parent 63a7a41ddf
commit 6b2f70d553
13 changed files with 1870 additions and 21 deletions

View File

@ -0,0 +1,176 @@
/*
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 serializer
import (
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/runtime/serializer/json"
"k8s.io/kubernetes/pkg/runtime/serializer/recognizer"
"k8s.io/kubernetes/pkg/runtime/serializer/versioning"
)
type serializerType struct {
AcceptContentTypes []string
ContentType string
FileExtensions []string
Serializer runtime.Serializer
PrettySerializer runtime.Serializer
}
// NewCodecFactory provides methods for retrieving serializers for the supported wire formats
// and conversion wrappers to define preferred internal and external versions. In the future,
// as the internal version is used less, callers may instead use a defaulting serializer and
// only convert objects which are shared internally (Status, common API machinery).
// TODO: allow other codecs to be compiled in?
// TODO: accept a scheme interface
func NewCodecFactory(scheme *runtime.Scheme) CodecFactory {
return newCodecFactory(scheme, json.DefaultMetaFactory)
}
// newCodecFactory is a helper for testing that allows a different metafactory to be specified.
func newCodecFactory(scheme *runtime.Scheme, mf json.MetaFactory) CodecFactory {
jsonSerializer := json.NewSerializer(mf, scheme, runtime.ObjectTyperToTyper(scheme), false)
jsonPrettySerializer := json.NewSerializer(mf, scheme, runtime.ObjectTyperToTyper(scheme), true)
yamlSerializer := json.NewYAMLSerializer(mf, scheme, runtime.ObjectTyperToTyper(scheme))
serializers := []serializerType{
{
AcceptContentTypes: []string{"application/json"},
ContentType: "application/json",
FileExtensions: []string{"json"},
Serializer: jsonSerializer,
PrettySerializer: jsonPrettySerializer,
},
{
AcceptContentTypes: []string{"application/yaml"},
ContentType: "application/yaml",
FileExtensions: []string{"yaml"},
Serializer: yamlSerializer,
},
}
decoders := make([]runtime.Decoder, 0, len(serializers))
accepts := []string{}
alreadyAccepted := make(map[string]struct{})
for _, d := range serializers {
decoders = append(decoders, d.Serializer)
for _, mediaType := range d.AcceptContentTypes {
if _, ok := alreadyAccepted[mediaType]; ok {
continue
}
alreadyAccepted[mediaType] = struct{}{}
accepts = append(accepts, mediaType)
}
}
return CodecFactory{
scheme: scheme,
serializers: serializers,
universal: recognizer.NewDecoder(decoders...),
accepts: accepts,
legacySerializer: jsonSerializer,
}
}
// CodecFactory provides methods for retrieving codecs and serializers for specific
// versions and content types.
type CodecFactory struct {
scheme *runtime.Scheme
serializers []serializerType
universal runtime.Decoder
accepts []string
legacySerializer runtime.Serializer
}
var _ runtime.NegotiatedSerializer = &CodecFactory{}
// SupportedMediaTypes returns the RFC2046 media types that this factory has serializers for.
func (f CodecFactory) SupportedMediaTypes() []string {
return f.accepts
}
// LegacyCodec encodes output to a given API version, and decodes output into the internal form from
// any recognized source. The returned codec will always encode output to JSON.
//
// This method is deprecated - clients and servers should negotiate a serializer by mime-type and
// invoke CodecForVersions. Callers that need only to read data should use UniversalDecoder().
func (f CodecFactory) LegacyCodec(version ...unversioned.GroupVersion) runtime.Codec {
return f.CodecForVersions(runtime.NewCodec(f.legacySerializer, f.universal), version, nil)
}
// UniversalDeserializer can convert any stored data recognized by this factory into a Go object that satisfies
// runtime.Object. It does not perform conversion. It does not perform defaulting.
func (f CodecFactory) UniversalDeserializer() runtime.Decoder {
return f.universal
}
// UniversalDecoder returns a runtime.Decoder capable of decoding all known API objects in all known formats. Used
// by clients that do not need to encode objects but want to deserialize API objects stored on disk. Only decodes
// objects in groups registered with the scheme. The GroupVersions passed may be used to select alternate
// versions of objects to return - by default, runtime.APIVersionInternal is used. If any versions are specified,
// unrecognized groups will be returned in the version they are encoded as (no conversion). This decoder performs
// defaulting.
//
// TODO: the decoder will eventually be removed in favor of dealing with objects in their versioned form
func (f CodecFactory) UniversalDecoder(versions ...unversioned.GroupVersion) runtime.Decoder {
return f.CodecForVersions(runtime.NoopEncoder{f.universal}, nil, versions)
}
// CodecFor creates a codec with the provided serializer. If an object is decoded and its group is not in the list,
// it will default to runtime.APIVersionInternal. If encode is not specified for an object's group, the object is not
// converted. If encode or decode are nil, no conversion is performed.
func (f CodecFactory) CodecForVersions(serializer runtime.Serializer, encode []unversioned.GroupVersion, decode []unversioned.GroupVersion) runtime.Codec {
return versioning.NewCodecForScheme(f.scheme, serializer, encode, decode)
}
// DecoderToVersion returns a decoder that targets the provided group version.
func (f CodecFactory) DecoderToVersion(serializer runtime.Serializer, gv unversioned.GroupVersion) runtime.Decoder {
return f.CodecForVersions(serializer, nil, []unversioned.GroupVersion{gv})
}
// EncoderForVersion returns an encoder that targets the provided group version.
func (f CodecFactory) EncoderForVersion(serializer runtime.Serializer, gv unversioned.GroupVersion) runtime.Encoder {
return f.CodecForVersions(serializer, []unversioned.GroupVersion{gv}, nil)
}
// SerializerForMediaType returns a serializer that matches the provided RFC2046 mediaType, or false if no such
// serializer exists
func (f CodecFactory) SerializerForMediaType(mediaType string, options map[string]string) (runtime.Serializer, bool) {
for _, s := range f.serializers {
for _, accepted := range s.AcceptContentTypes {
if accepted == mediaType {
if v, ok := options["pretty"]; ok && v == "1" && s.PrettySerializer != nil {
return s.PrettySerializer, true
}
return s.Serializer, true
}
}
}
return nil, false
}
// SerializerForFileExtension returns a serializer for the provided extension, or false if no serializer matches.
func (f CodecFactory) SerializerForFileExtension(extension string) (runtime.Serializer, bool) {
for _, s := range f.serializers {
for _, ext := range s.FileExtensions {
if extension == ext {
return s.Serializer, true
}
}
}
return nil, false
}

View File

@ -0,0 +1,400 @@
/*
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 serializer
import (
"encoding/json"
"fmt"
"log"
"os"
"reflect"
"strings"
"testing"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/conversion"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/util"
"github.com/ghodss/yaml"
"github.com/google/gofuzz"
flag "github.com/spf13/pflag"
)
var fuzzIters = flag.Int("fuzz-iters", 50, "How many fuzzing iterations to do.")
type testMetaFactory struct{}
func (testMetaFactory) Interpret(data []byte) (*unversioned.GroupVersionKind, error) {
findKind := struct {
APIVersion string `json:"myVersionKey,omitempty"`
ObjectKind string `json:"myKindKey,omitempty"`
}{}
// yaml is a superset of json, so we use it to decode here. That way,
// we understand both.
if err := yaml.Unmarshal(data, &findKind); err != nil {
return nil, fmt.Errorf("couldn't get version/kind: %v", err)
}
gv, err := unversioned.ParseGroupVersion(findKind.APIVersion)
if err != nil {
return nil, err
}
return &unversioned.GroupVersionKind{Group: gv.Group, Version: gv.Version, Kind: findKind.ObjectKind}, nil
}
// Test a weird version/kind embedding format.
type MyWeirdCustomEmbeddedVersionKindField struct {
ID string `json:"ID,omitempty"`
APIVersion string `json:"myVersionKey,omitempty"`
ObjectKind string `json:"myKindKey,omitempty"`
Z string `json:"Z,omitempty"`
Y uint64 `json:"Y,omitempty"`
}
type TestType1 struct {
MyWeirdCustomEmbeddedVersionKindField `json:",inline"`
A string `json:"A,omitempty"`
B int `json:"B,omitempty"`
C int8 `json:"C,omitempty"`
D int16 `json:"D,omitempty"`
E int32 `json:"E,omitempty"`
F int64 `json:"F,omitempty"`
G uint `json:"G,omitempty"`
H uint8 `json:"H,omitempty"`
I uint16 `json:"I,omitempty"`
J uint32 `json:"J,omitempty"`
K uint64 `json:"K,omitempty"`
L bool `json:"L,omitempty"`
M map[string]int `json:"M,omitempty"`
N map[string]TestType2 `json:"N,omitempty"`
O *TestType2 `json:"O,omitempty"`
P []TestType2 `json:"Q,omitempty"`
}
type TestType2 struct {
A string `json:"A,omitempty"`
B int `json:"B,omitempty"`
}
type ExternalTestType2 struct {
A string `json:"A,omitempty"`
B int `json:"B,omitempty"`
}
type ExternalTestType1 struct {
MyWeirdCustomEmbeddedVersionKindField `json:",inline"`
A string `json:"A,omitempty"`
B int `json:"B,omitempty"`
C int8 `json:"C,omitempty"`
D int16 `json:"D,omitempty"`
E int32 `json:"E,omitempty"`
F int64 `json:"F,omitempty"`
G uint `json:"G,omitempty"`
H uint8 `json:"H,omitempty"`
I uint16 `json:"I,omitempty"`
J uint32 `json:"J,omitempty"`
K uint64 `json:"K,omitempty"`
L bool `json:"L,omitempty"`
M map[string]int `json:"M,omitempty"`
N map[string]ExternalTestType2 `json:"N,omitempty"`
O *ExternalTestType2 `json:"O,omitempty"`
P []ExternalTestType2 `json:"Q,omitempty"`
}
type ExternalInternalSame struct {
MyWeirdCustomEmbeddedVersionKindField `json:",inline"`
A TestType2 `json:"A,omitempty"`
}
// TestObjectFuzzer can randomly populate all the above objects.
var TestObjectFuzzer = fuzz.New().NilChance(.5).NumElements(1, 100).Funcs(
func(j *MyWeirdCustomEmbeddedVersionKindField, c fuzz.Continue) {
c.FuzzNoCustom(j)
j.APIVersion = ""
j.ObjectKind = ""
},
)
func (obj *MyWeirdCustomEmbeddedVersionKindField) GetObjectKind() unversioned.ObjectKind { return obj }
func (obj *MyWeirdCustomEmbeddedVersionKindField) SetGroupVersionKind(gvk *unversioned.GroupVersionKind) {
obj.APIVersion, obj.ObjectKind = gvk.ToAPIVersionAndKind()
}
func (obj *MyWeirdCustomEmbeddedVersionKindField) GroupVersionKind() *unversioned.GroupVersionKind {
return unversioned.FromAPIVersionAndKind(obj.APIVersion, obj.ObjectKind)
}
func (obj *ExternalInternalSame) GetObjectKind() unversioned.ObjectKind {
return &obj.MyWeirdCustomEmbeddedVersionKindField
}
func (obj *TestType1) GetObjectKind() unversioned.ObjectKind {
return &obj.MyWeirdCustomEmbeddedVersionKindField
}
func (obj *ExternalTestType1) GetObjectKind() unversioned.ObjectKind {
return &obj.MyWeirdCustomEmbeddedVersionKindField
}
func (obj *TestType2) GetObjectKind() unversioned.ObjectKind { return unversioned.EmptyObjectKind }
func (obj *ExternalTestType2) GetObjectKind() unversioned.ObjectKind {
return unversioned.EmptyObjectKind
}
// Returns a new Scheme set up with the test objects.
func GetTestScheme() (*runtime.Scheme, runtime.Codec) {
internalGV := unversioned.GroupVersion{Version: runtime.APIVersionInternal}
externalGV := unversioned.GroupVersion{Version: "v1"}
externalGV2 := unversioned.GroupVersion{Version: "v2"}
s := runtime.NewScheme()
// Ordinarily, we wouldn't add TestType2, but because this is a test and
// both types are from the same package, we need to get it into the system
// so that converter will match it with ExternalType2.
s.AddKnownTypes(internalGV, &TestType1{}, &TestType2{}, &ExternalInternalSame{})
s.AddKnownTypes(externalGV, &ExternalInternalSame{})
s.AddKnownTypeWithName(externalGV.WithKind("TestType1"), &ExternalTestType1{})
s.AddKnownTypeWithName(externalGV.WithKind("TestType2"), &ExternalTestType2{})
s.AddKnownTypeWithName(internalGV.WithKind("TestType3"), &TestType1{})
s.AddKnownTypeWithName(externalGV.WithKind("TestType3"), &ExternalTestType1{})
s.AddKnownTypeWithName(externalGV2.WithKind("TestType1"), &ExternalTestType1{})
s.AddUnversionedTypes(externalGV, &unversioned.Status{})
cf := newCodecFactory(s, testMetaFactory{})
codec := cf.LegacyCodec(unversioned.GroupVersion{Version: "v1"})
return s, codec
}
func objDiff(a, b interface{}) string {
ab, err := json.Marshal(a)
if err != nil {
panic("a")
}
bb, err := json.Marshal(b)
if err != nil {
panic("b")
}
return util.StringDiff(string(ab), string(bb))
// An alternate diff attempt, in case json isn't showing you
// the difference. (reflect.DeepEqual makes a distinction between
// nil and empty slices, for example.)
//return util.StringDiff(
// fmt.Sprintf("%#v", a),
// fmt.Sprintf("%#v", b),
//)
}
var semantic = conversion.EqualitiesOrDie(
func(a, b MyWeirdCustomEmbeddedVersionKindField) bool {
a.APIVersion, a.ObjectKind = "", ""
b.APIVersion, b.ObjectKind = "", ""
return a == b
},
)
func runTest(t *testing.T, source interface{}) {
name := reflect.TypeOf(source).Elem().Name()
TestObjectFuzzer.Fuzz(source)
_, codec := GetTestScheme()
data, err := runtime.Encode(codec, source.(runtime.Object))
if err != nil {
t.Errorf("%v: %v (%#v)", name, err, source)
return
}
obj2, err := runtime.Decode(codec, data)
if err != nil {
t.Errorf("%v: %v (%v)", name, err, string(data))
return
}
if !semantic.DeepEqual(source, obj2) {
t.Errorf("1: %v: diff: %v", name, util.ObjectGoPrintSideBySide(source, obj2))
return
}
obj3 := reflect.New(reflect.TypeOf(source).Elem()).Interface()
if err := runtime.DecodeInto(codec, data, obj3.(runtime.Object)); err != nil {
t.Errorf("2: %v: %v", name, err)
return
}
if !semantic.DeepEqual(source, obj3) {
t.Errorf("3: %v: diff: %v", name, objDiff(source, obj3))
return
}
}
func TestTypes(t *testing.T) {
table := []interface{}{
&TestType1{},
&ExternalInternalSame{},
}
for _, item := range table {
// Try a few times, since runTest uses random values.
for i := 0; i < *fuzzIters; i++ {
runTest(t, item)
}
}
}
func TestVersionedEncoding(t *testing.T) {
s, codec := GetTestScheme()
out, err := runtime.Encode(codec, &TestType1{}, unversioned.GroupVersion{Version: "v2"})
if err != nil {
t.Fatal(err)
}
if string(out) != `{"myVersionKey":"v2","myKindKey":"TestType1"}`+"\n" {
t.Fatal(string(out))
}
_, err = runtime.Encode(codec, &TestType1{}, unversioned.GroupVersion{Version: "v3"})
if err == nil {
t.Fatal(err)
}
cf := newCodecFactory(s, testMetaFactory{})
encoder, _ := cf.SerializerForFileExtension("json")
// codec that is unversioned uses the target version
unversionedCodec := cf.CodecForVersions(encoder, nil, nil)
_, err = runtime.Encode(unversionedCodec, &TestType1{}, unversioned.GroupVersion{Version: "v3"})
if err == nil || !runtime.IsNotRegisteredError(err) {
t.Fatal(err)
}
// unversioned encode with no versions is written directly to wire
out, err = runtime.Encode(unversionedCodec, &TestType1{})
if err != nil {
t.Fatal(err)
}
if string(out) != `{"myVersionKey":"__internal","myKindKey":"TestType1"}`+"\n" {
t.Fatal(string(out))
}
}
func TestMultipleNames(t *testing.T) {
_, codec := GetTestScheme()
obj, _, err := codec.Decode([]byte(`{"myKindKey":"TestType3","myVersionKey":"v1","A":"value"}`), nil, nil)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
internal := obj.(*TestType1)
if internal.A != "value" {
t.Fatalf("unexpected decoded object: %#v", internal)
}
out, err := runtime.Encode(codec, internal)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !strings.Contains(string(out), `"myKindKey":"TestType1"`) {
t.Errorf("unexpected encoded output: %s", string(out))
}
}
func TestConvertTypesWhenDefaultNamesMatch(t *testing.T) {
internalGV := unversioned.GroupVersion{Version: runtime.APIVersionInternal}
externalGV := unversioned.GroupVersion{Version: "v1"}
s := runtime.NewScheme()
// create two names internally, with TestType1 being preferred
s.AddKnownTypeWithName(internalGV.WithKind("TestType1"), &TestType1{})
s.AddKnownTypeWithName(internalGV.WithKind("OtherType1"), &TestType1{})
// create two names externally, with TestType1 being preferred
s.AddKnownTypeWithName(externalGV.WithKind("TestType1"), &ExternalTestType1{})
s.AddKnownTypeWithName(externalGV.WithKind("OtherType1"), &ExternalTestType1{})
ext := &ExternalTestType1{}
ext.APIVersion = "v1"
ext.ObjectKind = "OtherType1"
ext.A = "test"
data, err := json.Marshal(ext)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
expect := &TestType1{A: "test"}
codec := newCodecFactory(s, testMetaFactory{}).LegacyCodec(unversioned.GroupVersion{Version: "v1"})
obj, err := runtime.Decode(codec, data)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !semantic.DeepEqual(expect, obj) {
t.Errorf("unexpected object: %#v", obj)
}
into := &TestType1{}
if err := runtime.DecodeInto(codec, data, into); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !semantic.DeepEqual(expect, into) {
t.Errorf("unexpected object: %#v", obj)
}
}
func TestEncode_Ptr(t *testing.T) {
_, codec := GetTestScheme()
tt := &TestType1{A: "I am a pointer object"}
data, err := runtime.Encode(codec, tt)
obj2, err2 := runtime.Decode(codec, data)
if err != nil || err2 != nil {
t.Fatalf("Failure: '%v' '%v'\n%s", err, err2, data)
}
if _, ok := obj2.(*TestType1); !ok {
t.Fatalf("Got wrong type")
}
if !semantic.DeepEqual(obj2, tt) {
t.Errorf("Expected:\n %#v,\n Got:\n %#v", tt, obj2)
}
}
func TestBadJSONRejection(t *testing.T) {
log.SetOutput(os.Stderr)
_, codec := GetTestScheme()
badJSONs := [][]byte{
[]byte(`{"myVersionKey":"v1"}`), // Missing kind
[]byte(`{"myVersionKey":"v1","myKindKey":"bar"}`), // Unknown kind
[]byte(`{"myVersionKey":"bar","myKindKey":"TestType1"}`), // Unknown version
[]byte(`{"myKindKey":"TestType1"}`), // Missing version
}
for _, b := range badJSONs {
if _, err := runtime.Decode(codec, b); err == nil {
t.Errorf("Did not reject bad json: %s", string(b))
}
}
badJSONKindMismatch := []byte(`{"myVersionKey":"v1","myKindKey":"ExternalInternalSame"}`)
if err := runtime.DecodeInto(codec, badJSONKindMismatch, &TestType1{}); err == nil {
t.Errorf("Kind is set but doesn't match the object type: %s", badJSONKindMismatch)
}
if err := runtime.DecodeInto(codec, []byte(``), &TestType1{}); err != nil {
t.Errorf("Should allow empty decode: %v", err)
}
if _, _, err := codec.Decode([]byte(``), &unversioned.GroupVersionKind{Kind: "ExternalInternalSame"}, nil); err == nil {
t.Errorf("Did not give error for empty data with only kind default")
}
if _, _, err := codec.Decode([]byte(`{"myVersionKey":"v1"}`), &unversioned.GroupVersionKind{Kind: "ExternalInternalSame"}, nil); err != nil {
t.Errorf("Gave error for version and kind default")
}
if _, _, err := codec.Decode([]byte(`{"myKindKey":"ExternalInternalSame"}`), &unversioned.GroupVersionKind{Version: "v1"}, nil); err != nil {
t.Errorf("Gave error for version and kind default")
}
if _, _, err := codec.Decode([]byte(``), &unversioned.GroupVersionKind{Kind: "ExternalInternalSame", Version: "v1"}, nil); err != nil {
t.Errorf("Gave error for version and kind defaulted: %v", err)
}
if _, err := runtime.Decode(codec, []byte(``)); err == nil {
t.Errorf("Did not give error for empty data")
}
}

View File

@ -0,0 +1,191 @@
/*
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 json
import (
"encoding/json"
"io"
"github.com/ghodss/yaml"
"github.com/ugorji/go/codec"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/runtime"
utilyaml "k8s.io/kubernetes/pkg/util/yaml"
)
// NewSerializer creates a JSON serializer that handles encoding versioned objects into the proper JSON form. If typer
// is not nil, the object has the group, version, and kind fields set.
func NewSerializer(meta MetaFactory, creater runtime.ObjectCreater, typer runtime.Typer, pretty bool) runtime.Serializer {
return &Serializer{
meta: meta,
creater: creater,
typer: typer,
yaml: false,
pretty: pretty,
}
}
// NewYAMLSerializer creates a YAML serializer that handles encoding versioned objects into the proper YAML form. If typer
// is not nil, the object has the group, version, and kind fields set. This serializer supports only the subset of YAML that
// matches JSON, and will error if constructs are used that do not serialize to JSON.
func NewYAMLSerializer(meta MetaFactory, creater runtime.ObjectCreater, typer runtime.Typer) runtime.Serializer {
return &Serializer{
meta: meta,
creater: creater,
typer: typer,
yaml: true,
}
}
type Serializer struct {
meta MetaFactory
creater runtime.ObjectCreater
typer runtime.Typer
yaml bool
pretty bool
}
// Decode attempts to convert the provided data into YAML or JSON, extract the stored schema kind, apply the provided default gvk, and then
// load that data into an object matching the desired schema kind or the provided into. If into is *runtime.Unknown, the raw data will be
// extracted and no decoding will be performed. If into is not registered with the typer, then the object will be straight decoded using
// normal JSON/YAML unmarshalling. If into is provided and the original data is not fully qualified with kind/version/group, the type of
// the into will be used to alter the returned gvk. On success or most errors, the method will return the calculated schema kind.
func (s *Serializer) Decode(originalData []byte, gvk *unversioned.GroupVersionKind, into runtime.Object) (runtime.Object, *unversioned.GroupVersionKind, error) {
if versioned, ok := into.(*runtime.VersionedObjects); ok {
into = versioned.Last()
obj, actual, err := s.Decode(originalData, gvk, into)
if err != nil {
return nil, actual, err
}
versioned.Objects = []runtime.Object{obj}
return versioned, actual, nil
}
data := originalData
if s.yaml {
altered, err := yaml.YAMLToJSON(data)
if err != nil {
return nil, nil, err
}
data = altered
}
actual, err := s.meta.Interpret(data)
if err != nil {
return nil, nil, err
}
if gvk != nil {
// apply kind and version defaulting from provided default
if len(actual.Kind) == 0 {
actual.Kind = gvk.Kind
}
if len(actual.Version) == 0 && len(actual.Group) == 0 {
actual.Group = gvk.Group
actual.Version = gvk.Version
}
if len(actual.Version) == 0 && actual.Group == gvk.Group {
actual.Version = gvk.Version
}
}
if unk, ok := into.(*runtime.Unknown); ok && unk != nil {
unk.RawJSON = originalData
// TODO: set content type here
unk.GetObjectKind().SetGroupVersionKind(actual)
return unk, actual, nil
}
if into != nil {
typed, _, err := s.typer.ObjectKind(into)
switch {
case runtime.IsNotRegisteredError(err):
if err := codec.NewDecoderBytes(data, new(codec.JsonHandle)).Decode(into); err != nil {
return nil, actual, err
}
return into, actual, nil
case err != nil:
return nil, actual, err
default:
if len(actual.Kind) == 0 {
actual.Kind = typed.Kind
}
if len(actual.Version) == 0 && len(actual.Group) == 0 {
actual.Group = typed.Group
actual.Version = typed.Version
}
if len(actual.Version) == 0 && actual.Group == typed.Group {
actual.Version = typed.Version
}
}
}
if len(actual.Kind) == 0 {
return nil, actual, runtime.NewMissingKindErr(string(originalData))
}
if len(actual.Version) == 0 {
return nil, actual, runtime.NewMissingVersionErr(string(originalData))
}
// use the target if necessary
obj, err := runtime.UseOrCreateObject(s.typer, s.creater, *actual, into)
if err != nil {
return nil, actual, err
}
if err := codec.NewDecoderBytes(data, new(codec.JsonHandle)).Decode(obj); err != nil {
return nil, actual, err
}
return obj, actual, nil
}
// EncodeToStream serializes the provided object to the given writer. Overrides is ignored.
func (s *Serializer) EncodeToStream(obj runtime.Object, w io.Writer, overrides ...unversioned.GroupVersion) error {
if s.yaml {
json, err := json.Marshal(obj)
if err != nil {
return err
}
data, err := yaml.JSONToYAML(json)
if err != nil {
return err
}
_, err = w.Write(data)
return err
}
if s.pretty {
data, err := json.MarshalIndent(obj, "", " ")
if err != nil {
return err
}
_, err = w.Write(data)
return err
}
encoder := json.NewEncoder(w)
return encoder.Encode(obj)
}
// RecognizesData implements the RecognizingDecoder interface.
func (s *Serializer) RecognizesData(peek io.Reader) (bool, error) {
_, ok := utilyaml.GuessJSONStream(peek, 2048)
if s.yaml {
return !ok, nil
}
return ok, nil
}

View File

@ -0,0 +1,269 @@
/*
Copyright 2015 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package json_test
import (
"fmt"
"reflect"
"strings"
"testing"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/conversion"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/runtime/serializer/json"
"k8s.io/kubernetes/pkg/util"
)
type testDecodable struct {
Other string
Value int `json:"value"`
gvk *unversioned.GroupVersionKind
}
func (d *testDecodable) GetObjectKind() unversioned.ObjectKind { return d }
func (d *testDecodable) SetGroupVersionKind(gvk *unversioned.GroupVersionKind) { d.gvk = gvk }
func (d *testDecodable) GroupVersionKind() *unversioned.GroupVersionKind { return d.gvk }
func TestDecode(t *testing.T) {
testCases := []struct {
creater runtime.ObjectCreater
typer runtime.Typer
yaml bool
pretty bool
data []byte
defaultGVK *unversioned.GroupVersionKind
into runtime.Object
errFn func(error) bool
expectedObject runtime.Object
expectedGVK *unversioned.GroupVersionKind
}{
{
data: []byte("{}"),
expectedGVK: &unversioned.GroupVersionKind{},
errFn: func(err error) bool { return strings.Contains(err.Error(), "Object 'Kind' is missing in") },
},
{
data: []byte("{}"),
defaultGVK: &unversioned.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"},
creater: &mockCreater{err: fmt.Errorf("fake error")},
expectedGVK: &unversioned.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"},
errFn: func(err error) bool { return err.Error() == "fake error" },
},
{
data: []byte("{}"),
defaultGVK: &unversioned.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"},
creater: &mockCreater{err: fmt.Errorf("fake error")},
expectedGVK: &unversioned.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"},
errFn: func(err error) bool { return err.Error() == "fake error" },
},
{
data: []byte("{}"),
defaultGVK: &unversioned.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"},
creater: &mockCreater{obj: &testDecodable{}},
expectedObject: &testDecodable{
gvk: nil, // json serializer does NOT set GVK
},
expectedGVK: &unversioned.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"},
},
// version without group is not defaulted
{
data: []byte(`{"apiVersion":"blah"}`),
defaultGVK: &unversioned.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"},
creater: &mockCreater{obj: &testDecodable{}},
expectedObject: &testDecodable{
gvk: nil, // json serializer does NOT set GVK
},
expectedGVK: &unversioned.GroupVersionKind{Kind: "Test", Group: "", Version: "blah"},
},
// group without version is defaulted
{
data: []byte(`{"apiVersion":"other/"}`),
defaultGVK: &unversioned.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"},
creater: &mockCreater{obj: &testDecodable{}},
expectedObject: &testDecodable{
gvk: nil, // json serializer does NOT set GVK
},
expectedGVK: &unversioned.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"},
},
// accept runtime.Unknown as into and bypass creator
{
data: []byte(`{}`),
into: &runtime.Unknown{},
expectedGVK: &unversioned.GroupVersionKind{},
expectedObject: &runtime.Unknown{
RawJSON: []byte(`{}`),
},
},
{
data: []byte(`{"test":"object"}`),
into: &runtime.Unknown{},
expectedGVK: &unversioned.GroupVersionKind{},
expectedObject: &runtime.Unknown{
RawJSON: []byte(`{"test":"object"}`),
},
},
{
data: []byte(`{"test":"object"}`),
into: &runtime.Unknown{},
defaultGVK: &unversioned.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"},
expectedGVK: &unversioned.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"},
expectedObject: &runtime.Unknown{
TypeMeta: runtime.TypeMeta{APIVersion: "other/blah", Kind: "Test"},
RawJSON: []byte(`{"test":"object"}`),
},
},
// unregistered objects can be decoded into directly
{
data: []byte(`{"kind":"Test","apiVersion":"other/blah","value":1,"Other":"test"}`),
into: &testDecodable{},
typer: &mockTyper{err: conversion.NewNotRegisteredErr(unversioned.GroupVersionKind{}, nil)},
expectedGVK: &unversioned.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"},
expectedObject: &testDecodable{
Other: "test",
Value: 1,
},
},
// registered types get defaulted by the into object kind
{
data: []byte(`{"value":1,"Other":"test"}`),
into: &testDecodable{},
typer: &mockTyper{gvk: &unversioned.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}},
expectedGVK: &unversioned.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"},
expectedObject: &testDecodable{
Other: "test",
Value: 1,
},
},
// registered types get defaulted by the into object kind even without version, but return an error
{
data: []byte(`{"value":1,"Other":"test"}`),
into: &testDecodable{},
typer: &mockTyper{gvk: &unversioned.GroupVersionKind{Kind: "Test", Group: "other", Version: ""}},
expectedGVK: &unversioned.GroupVersionKind{Kind: "Test", Group: "other", Version: ""},
errFn: func(err error) bool { return strings.Contains(err.Error(), "Object 'apiVersion' is missing in") },
expectedObject: &testDecodable{
Other: "test",
Value: 1,
},
},
// runtime.VersionedObjects are decoded
{
data: []byte(`{"value":1,"Other":"test"}`),
into: &runtime.VersionedObjects{Objects: []runtime.Object{}},
creater: &mockCreater{obj: &testDecodable{}},
typer: &mockTyper{gvk: &unversioned.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}},
defaultGVK: &unversioned.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"},
expectedGVK: &unversioned.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"},
expectedObject: &runtime.VersionedObjects{
Objects: []runtime.Object{
&testDecodable{
Other: "test",
Value: 1,
},
},
},
},
// runtime.VersionedObjects with an object are decoded into
{
data: []byte(`{"Other":"test"}`),
into: &runtime.VersionedObjects{Objects: []runtime.Object{&testDecodable{Value: 2}}},
typer: &mockTyper{gvk: &unversioned.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}},
expectedGVK: &unversioned.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"},
expectedObject: &runtime.VersionedObjects{
Objects: []runtime.Object{
&testDecodable{
Other: "test",
Value: 2,
},
},
},
},
}
for i, test := range testCases {
var s runtime.Serializer
if test.yaml {
s = json.NewYAMLSerializer(json.DefaultMetaFactory, test.creater, test.typer)
} else {
s = json.NewSerializer(json.DefaultMetaFactory, test.creater, test.typer, test.pretty)
}
obj, gvk, err := s.Decode([]byte(test.data), test.defaultGVK, test.into)
if !reflect.DeepEqual(test.expectedGVK, gvk) {
t.Errorf("%d: unexpected GVK: %v", i, gvk)
}
switch {
case err == nil && test.errFn != nil:
t.Errorf("%d: failed: %v", i, err)
continue
case err != nil && test.errFn == nil:
t.Errorf("%d: failed: %v", i, err)
continue
case err != nil:
if !test.errFn(err) {
t.Errorf("%d: failed: %v", i, err)
}
if obj != nil {
t.Errorf("%d: should have returned nil object", i)
}
continue
}
if test.into != nil && test.into != obj {
t.Errorf("%d: expected into to be returned: %v", i, obj)
continue
}
if !reflect.DeepEqual(test.expectedObject, obj) {
t.Errorf("%d: unexpected object:\n%s", i, util.ObjectGoPrintSideBySide(test.expectedObject, obj))
}
}
}
type mockCreater struct {
apiVersion string
kind string
err error
obj runtime.Object
}
func (c *mockCreater) New(kind unversioned.GroupVersionKind) (runtime.Object, error) {
c.apiVersion, c.kind = kind.GroupVersion().String(), kind.Kind
return c.obj, c.err
}
type mockTyper struct {
gvk *unversioned.GroupVersionKind
err error
}
func (t *mockTyper) ObjectKind(obj runtime.Object) (*unversioned.GroupVersionKind, bool, error) {
return t.gvk, false, t.err
}

View File

@ -0,0 +1,61 @@
/*
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 json
import (
"encoding/json"
"fmt"
"k8s.io/kubernetes/pkg/api/unversioned"
)
// MetaFactory is used to store and retrieve the version and kind
// information for JSON objects in a serializer.
type MetaFactory interface {
// Interpret should return the version and kind of the wire-format of
// the object.
Interpret(data []byte) (*unversioned.GroupVersionKind, error)
}
// DefaultMetaFactory is a default factory for versioning objects in JSON. The object
// in memory and in the default JSON serialization will use the "kind" and "apiVersion"
// fields.
var DefaultMetaFactory = SimpleMetaFactory{}
// SimpleMetaFactory provides default methods for retrieving the type and version of objects
// that are identified with an "apiVersion" and "kind" fields in their JSON
// serialization. It may be parameterized with the names of the fields in memory, or an
// optional list of base structs to search for those fields in memory.
type SimpleMetaFactory struct {
}
// Interpret will return the APIVersion and Kind of the JSON wire-format
// encoding of an object, or an error.
func (SimpleMetaFactory) Interpret(data []byte) (*unversioned.GroupVersionKind, error) {
findKind := struct {
APIVersion string `json:"apiVersion,omitempty"`
Kind string `json:"kind,omitempty"`
}{}
if err := json.Unmarshal(data, &findKind); err != nil {
return nil, fmt.Errorf("couldn't get version/kind; json parse error: %v", err)
}
gv, err := unversioned.ParseGroupVersion(findKind.APIVersion)
if err != nil {
return nil, err
}
return &unversioned.GroupVersionKind{Group: gv.Group, Version: gv.Version, Kind: findKind.Kind}, nil
}

View File

@ -0,0 +1,45 @@
/*
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 json
import "testing"
func TestSimpleMetaFactoryInterpret(t *testing.T) {
factory := SimpleMetaFactory{}
gvk, err := factory.Interpret([]byte(`{"apiVersion":"1","kind":"object"}`))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if gvk.Version != "1" || gvk.Kind != "object" {
t.Errorf("unexpected interpret: %#v", gvk)
}
// no kind or version
gvk, err = factory.Interpret([]byte(`{}`))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if gvk.Version != "" || gvk.Kind != "" {
t.Errorf("unexpected interpret: %#v", gvk)
}
// unparsable
gvk, err = factory.Interpret([]byte(`{`))
if err == nil {
t.Errorf("unexpected non-error")
}
}

View File

@ -1,18 +0,0 @@
/*
Copyright 2015 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package protobuf handles serializing API objects to and from wire formats.
package protobuf

View File

@ -0,0 +1,79 @@
/*
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 recognizer
import (
"bytes"
"fmt"
"io"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/runtime"
)
type RecognizingDecoder interface {
runtime.Decoder
RecognizesData(peek io.Reader) (bool, error)
}
func NewDecoder(decoders ...runtime.Decoder) runtime.Decoder {
recognizing, blind := []RecognizingDecoder{}, []runtime.Decoder{}
for _, d := range decoders {
if r, ok := d.(RecognizingDecoder); ok {
recognizing = append(recognizing, r)
} else {
blind = append(blind, d)
}
}
return &decoder{
recognizing: recognizing,
blind: blind,
}
}
type decoder struct {
recognizing []RecognizingDecoder
blind []runtime.Decoder
}
func (d *decoder) Decode(data []byte, gvk *unversioned.GroupVersionKind, into runtime.Object) (runtime.Object, *unversioned.GroupVersionKind, error) {
var lastErr error
for _, r := range d.recognizing {
buf := bytes.NewBuffer(data)
ok, err := r.RecognizesData(buf)
if err != nil {
lastErr = err
continue
}
if !ok {
continue
}
return r.Decode(data, gvk, into)
}
for _, d := range d.blind {
out, actual, err := d.Decode(data, gvk, into)
if err != nil {
lastErr = err
continue
}
return out, actual, nil
}
if lastErr == nil {
lastErr = fmt.Errorf("no serialization format matched the provided data")
}
return nil, nil, lastErr
}

View File

@ -0,0 +1,57 @@
/*
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 recognizer
import (
"testing"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/runtime/serializer/json"
)
type A struct{}
func (A) GetObjectKind() unversioned.ObjectKind { return unversioned.EmptyObjectKind }
func TestRecognizer(t *testing.T) {
s := runtime.NewScheme()
s.AddKnownTypes(unversioned.GroupVersion{Version: "v1"}, &A{})
d := NewDecoder(
json.NewSerializer(json.DefaultMetaFactory, s, runtime.ObjectTyperToTyper(s), false),
json.NewYAMLSerializer(json.DefaultMetaFactory, s, runtime.ObjectTyperToTyper(s)),
)
out, _, err := d.Decode([]byte(`
kind: A
apiVersion: v1
`), nil, nil)
if err != nil {
t.Fatal(err)
}
t.Logf("%#v", out)
out, _, err = d.Decode([]byte(`
{
"kind":"A",
"apiVersion":"v1"
}
`), nil, nil)
if err != nil {
t.Fatal(err)
}
t.Logf("%#v", out)
}

View File

@ -0,0 +1,243 @@
/*
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 versioning
import (
"fmt"
"io"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/runtime"
)
// NewCodecForScheme is a convenience method for callers that are using a scheme.
func NewCodecForScheme(
// TODO: I should be a scheme interface?
scheme *runtime.Scheme,
serializer runtime.Serializer,
encodeVersion []unversioned.GroupVersion,
decodeVersion []unversioned.GroupVersion,
) runtime.Codec {
return NewCodec(serializer, scheme, scheme, scheme, runtime.ObjectTyperToTyper(scheme), encodeVersion, decodeVersion)
}
// NewCodec takes objects in their internal versions and converts them to external versions before
// serializing them. It assumes the serializer provided to it only deals with external versions.
// This class is also a serializer, but is generally used with a specific version.
func NewCodec(
serializer runtime.Serializer,
convertor runtime.ObjectConvertor,
creater runtime.ObjectCreater,
copier runtime.ObjectCopier,
typer runtime.Typer,
encodeVersion []unversioned.GroupVersion,
decodeVersion []unversioned.GroupVersion,
) runtime.Codec {
internal := &codec{
serializer: serializer,
convertor: convertor,
creater: creater,
copier: copier,
typer: typer,
}
if encodeVersion != nil {
internal.encodeVersion = make(map[string]unversioned.GroupVersion)
for _, v := range encodeVersion {
internal.encodeVersion[v.Group] = v
}
}
if decodeVersion != nil {
internal.decodeVersion = make(map[string]unversioned.GroupVersion)
for _, v := range decodeVersion {
internal.decodeVersion[v.Group] = v
}
}
return internal
}
type codec struct {
serializer runtime.Serializer
convertor runtime.ObjectConvertor
creater runtime.ObjectCreater
copier runtime.ObjectCopier
typer runtime.Typer
encodeVersion map[string]unversioned.GroupVersion
decodeVersion map[string]unversioned.GroupVersion
}
// Decode attempts a decode of the object, then tries to convert it to the internal version. If into is provided and the decoding is
// successful, the returned runtime.Object will be the value passed as into. Note that this may bypass conversion if you pass an
// into that matches the serialized version.
func (c *codec) Decode(data []byte, defaultGVK *unversioned.GroupVersionKind, into runtime.Object) (runtime.Object, *unversioned.GroupVersionKind, error) {
versioned, isVersioned := into.(*runtime.VersionedObjects)
if isVersioned {
into = versioned.Last()
}
obj, gvk, err := c.serializer.Decode(data, defaultGVK, into)
if err != nil {
return nil, gvk, err
}
// if we specify a target, use generic conversion.
if into != nil {
if into == obj {
if isVersioned {
return versioned, gvk, nil
}
return into, gvk, nil
}
if err := c.convertor.Convert(obj, into); err != nil {
return nil, gvk, err
}
if isVersioned {
versioned.Objects = []runtime.Object{obj, into}
return versioned, gvk, nil
}
return into, gvk, nil
}
// invoke a version conversion
group := gvk.Group
if defaultGVK != nil {
group = defaultGVK.Group
}
var targetGV unversioned.GroupVersion
if c.decodeVersion == nil {
// convert to internal by default
targetGV.Group = group
targetGV.Version = runtime.APIVersionInternal
} else {
gv, ok := c.decodeVersion[group]
if !ok {
// unknown objects are left in their original version
if isVersioned {
versioned.Objects = []runtime.Object{obj}
return versioned, gvk, nil
}
return obj, gvk, nil
}
targetGV = gv
}
if gvk.GroupVersion() == targetGV {
if isVersioned {
versioned.Objects = []runtime.Object{obj}
return versioned, gvk, nil
}
return obj, gvk, nil
}
if isVersioned {
// create a copy, because ConvertToVersion does not guarantee non-mutation of objects
copied, err := c.copier.Copy(obj)
if err != nil {
copied = obj
}
versioned.Objects = []runtime.Object{copied}
}
// Convert if needed.
out, err := c.convertor.ConvertToVersion(obj, targetGV.String())
if err != nil {
return nil, gvk, err
}
if isVersioned {
versioned.Objects = append(versioned.Objects, out)
return versioned, gvk, nil
}
return out, gvk, nil
}
// EncodeToStream ensures the provided object is output in the right scheme. If overrides are specified, when
// encoding the object the first override that matches the object's group is used. Other overrides are ignored.
func (c *codec) EncodeToStream(obj runtime.Object, w io.Writer, overrides ...unversioned.GroupVersion) error {
if _, ok := obj.(*runtime.Unknown); ok {
return c.serializer.EncodeToStream(obj, w, overrides...)
}
gvk, isUnversioned, err := c.typer.ObjectKind(obj)
if err != nil {
return err
}
if (c.encodeVersion == nil && len(overrides) == 0) || isUnversioned {
old := obj.GetObjectKind().GroupVersionKind()
obj.GetObjectKind().SetGroupVersionKind(gvk)
defer obj.GetObjectKind().SetGroupVersionKind(old)
return c.serializer.EncodeToStream(obj, w, overrides...)
}
targetGV, ok := c.encodeVersion[gvk.Group]
// use override if provided
for i, override := range overrides {
if override.Group == gvk.Group {
ok = true
targetGV = override
// swap the position of the override
overrides[0], overrides[i] = targetGV, overrides[0]
break
}
}
// attempt a conversion to the sole encode version
if !ok && len(c.encodeVersion) == 1 {
ok = true
for _, v := range c.encodeVersion {
targetGV = v
}
// ensure the target override is first
overrides = promoteOrPrependGroupVersion(targetGV, overrides)
}
// if no fallback is available, error
if !ok {
return fmt.Errorf("the codec does not recognize group %q for kind %q and cannot encode it", gvk.Group, gvk.Kind)
}
// Perform a conversion if necessary
if gvk.GroupVersion() != targetGV {
out, err := c.convertor.ConvertToVersion(obj, targetGV.String())
if err != nil {
if ok {
return err
}
} else {
obj = out
}
} else {
old := obj.GetObjectKind().GroupVersionKind()
defer obj.GetObjectKind().SetGroupVersionKind(old)
obj.GetObjectKind().SetGroupVersionKind(&unversioned.GroupVersionKind{Group: targetGV.Group, Version: targetGV.Version, Kind: gvk.Kind})
}
return c.serializer.EncodeToStream(obj, w, overrides...)
}
// promoteOrPrependGroupVersion finds the group version in the provided group versions that has the same group as target.
// If the group is found the returned array will have that group version in the first position - if the group is not found
// the returned array will have target in the first position.
func promoteOrPrependGroupVersion(target unversioned.GroupVersion, gvs []unversioned.GroupVersion) []unversioned.GroupVersion {
for i, gv := range gvs {
if gv.Group == target.Group {
gvs[0], gvs[i] = gvs[i], gvs[0]
return gvs
}
}
return append([]unversioned.GroupVersion{target}, gvs...)
}

View File

@ -0,0 +1,300 @@
/*
Copyright 2015 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package versioning
import (
"fmt"
"io"
"reflect"
"testing"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/util"
)
type testDecodable struct {
Other string
Value int `json:"value"`
gvk *unversioned.GroupVersionKind
}
func (d *testDecodable) GetObjectKind() unversioned.ObjectKind { return d }
func (d *testDecodable) SetGroupVersionKind(gvk *unversioned.GroupVersionKind) { d.gvk = gvk }
func (d *testDecodable) GroupVersionKind() *unversioned.GroupVersionKind { return d.gvk }
func TestDecode(t *testing.T) {
gvk1 := &unversioned.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}
decodable1 := &testDecodable{}
decodable2 := &testDecodable{}
decodable3 := &testDecodable{}
versionedDecodable1 := &runtime.VersionedObjects{Objects: []runtime.Object{decodable1}}
testCases := []struct {
serializer runtime.Serializer
convertor runtime.ObjectConvertor
creater runtime.ObjectCreater
copier runtime.ObjectCopier
typer runtime.Typer
yaml bool
pretty bool
encodes, decodes []unversioned.GroupVersion
defaultGVK *unversioned.GroupVersionKind
into runtime.Object
errFn func(error) bool
expectedObject runtime.Object
sameObject runtime.Object
expectedGVK *unversioned.GroupVersionKind
}{
{
serializer: &mockSerializer{actual: gvk1},
convertor: &checkConvertor{groupVersion: "other/__internal"},
expectedGVK: gvk1,
},
{
serializer: &mockSerializer{actual: gvk1, obj: decodable1},
convertor: &checkConvertor{in: decodable1, obj: decodable2, groupVersion: "other/__internal"},
expectedGVK: gvk1,
sameObject: decodable2,
},
// defaultGVK.Group is allowed to force a conversion to the destination group
{
serializer: &mockSerializer{actual: gvk1, obj: decodable1},
defaultGVK: &unversioned.GroupVersionKind{Group: "force"},
convertor: &checkConvertor{in: decodable1, obj: decodable2, groupVersion: "force/__internal"},
expectedGVK: gvk1,
sameObject: decodable2,
},
// uses direct conversion for into when objects differ
{
into: decodable3,
serializer: &mockSerializer{actual: gvk1, obj: decodable1},
convertor: &checkConvertor{in: decodable1, obj: decodable3, directConvert: true},
expectedGVK: gvk1,
sameObject: decodable3,
},
{
into: versionedDecodable1,
serializer: &mockSerializer{actual: gvk1, obj: decodable3},
convertor: &checkConvertor{in: decodable3, obj: decodable1, directConvert: true},
expectedGVK: gvk1,
sameObject: versionedDecodable1,
},
// returns directly when serializer returns into
{
into: decodable3,
serializer: &mockSerializer{actual: gvk1, obj: decodable3},
expectedGVK: gvk1,
sameObject: decodable3,
},
// returns directly when serializer returns into
{
into: versionedDecodable1,
serializer: &mockSerializer{actual: gvk1, obj: decodable1},
expectedGVK: gvk1,
sameObject: versionedDecodable1,
},
// runtime.VersionedObjects are decoded
{
into: &runtime.VersionedObjects{Objects: []runtime.Object{}},
serializer: &mockSerializer{actual: gvk1, obj: decodable1},
copier: &checkCopy{in: decodable1, obj: decodable1},
convertor: &checkConvertor{in: decodable1, obj: decodable2, groupVersion: "other/__internal"},
expectedGVK: gvk1,
expectedObject: &runtime.VersionedObjects{Objects: []runtime.Object{decodable1, decodable2}},
},
{
into: &runtime.VersionedObjects{Objects: []runtime.Object{}},
serializer: &mockSerializer{actual: gvk1, obj: decodable1},
copier: &checkCopy{in: decodable1, obj: nil, err: fmt.Errorf("error on copy")},
convertor: &checkConvertor{in: decodable1, obj: decodable2, groupVersion: "other/__internal"},
expectedGVK: gvk1,
expectedObject: &runtime.VersionedObjects{Objects: []runtime.Object{decodable1, decodable2}},
},
// decode into the same version as the serialized object
{
decodes: []unversioned.GroupVersion{gvk1.GroupVersion()},
serializer: &mockSerializer{actual: gvk1, obj: decodable1},
expectedGVK: gvk1,
expectedObject: decodable1,
},
{
into: &runtime.VersionedObjects{Objects: []runtime.Object{}},
decodes: []unversioned.GroupVersion{gvk1.GroupVersion()},
serializer: &mockSerializer{actual: gvk1, obj: decodable1},
expectedGVK: gvk1,
expectedObject: &runtime.VersionedObjects{Objects: []runtime.Object{decodable1}},
},
// codec with non matching version skips conversion altogether
{
decodes: []unversioned.GroupVersion{{Group: "something", Version: "else"}},
serializer: &mockSerializer{actual: gvk1, obj: decodable1},
expectedGVK: gvk1,
expectedObject: decodable1,
},
{
into: &runtime.VersionedObjects{Objects: []runtime.Object{}},
decodes: []unversioned.GroupVersion{{Group: "something", Version: "else"}},
serializer: &mockSerializer{actual: gvk1, obj: decodable1},
expectedGVK: gvk1,
expectedObject: &runtime.VersionedObjects{Objects: []runtime.Object{decodable1}},
},
}
for i, test := range testCases {
t.Logf("%d", i)
s := NewCodec(test.serializer, test.convertor, test.creater, test.copier, test.typer, test.encodes, test.decodes)
obj, gvk, err := s.Decode([]byte(`{}`), test.defaultGVK, test.into)
if !reflect.DeepEqual(test.expectedGVK, gvk) {
t.Errorf("%d: unexpected GVK: %v", i, gvk)
}
switch {
case err == nil && test.errFn != nil:
t.Errorf("%d: failed: %v", i, err)
continue
case err != nil && test.errFn == nil:
t.Errorf("%d: failed: %v", i, err)
continue
case err != nil:
if !test.errFn(err) {
t.Errorf("%d: failed: %v", i, err)
}
if obj != nil {
t.Errorf("%d: should have returned nil object", i)
}
continue
}
if test.into != nil && test.into != obj {
t.Errorf("%d: expected into to be returned: %v", i, obj)
continue
}
switch {
case test.expectedObject != nil:
if !reflect.DeepEqual(test.expectedObject, obj) {
t.Errorf("%d: unexpected object:\n%s", i, util.ObjectGoPrintSideBySide(test.expectedObject, obj))
}
case test.sameObject != nil:
if test.sameObject != obj {
t.Errorf("%d: unexpected object:\n%s", i, util.ObjectGoPrintSideBySide(test.sameObject, obj))
}
case obj != nil:
t.Errorf("%d: unexpected object: %#v", i, obj)
}
}
}
type checkCopy struct {
in, obj runtime.Object
err error
}
func (c *checkCopy) Copy(obj runtime.Object) (runtime.Object, error) {
if c.in != nil && c.in != obj {
return nil, fmt.Errorf("unexpected input to copy: %#v", obj)
}
return c.obj, c.err
}
type checkConvertor struct {
err error
in, obj runtime.Object
groupVersion string
directConvert bool
}
func (c *checkConvertor) Convert(in, out interface{}) error {
if !c.directConvert {
return fmt.Errorf("unexpected call to Convert")
}
if c.in != nil && c.in != in {
return fmt.Errorf("unexpected in: %s", in)
}
if c.obj != nil && c.obj != out {
return fmt.Errorf("unexpected out: %s", out)
}
return c.err
}
func (c *checkConvertor) ConvertToVersion(in runtime.Object, outVersion string) (out runtime.Object, err error) {
if c.directConvert {
return nil, fmt.Errorf("unexpected call to ConvertToVersion")
}
if c.in != nil && c.in != in {
return nil, fmt.Errorf("unexpected in: %s", in)
}
if c.groupVersion != outVersion {
return nil, fmt.Errorf("unexpected outversion: %s", outVersion)
}
return c.obj, c.err
}
func (c *checkConvertor) ConvertFieldLabel(version, kind, label, value string) (string, string, error) {
return "", "", fmt.Errorf("unexpected call to ConvertFieldLabel")
}
type mockSerializer struct {
err error
obj runtime.Object
versions []unversioned.GroupVersion
defaults, actual *unversioned.GroupVersionKind
into runtime.Object
}
func (s *mockSerializer) Decode(data []byte, defaults *unversioned.GroupVersionKind, into runtime.Object) (runtime.Object, *unversioned.GroupVersionKind, error) {
s.defaults = defaults
s.into = into
return s.obj, s.actual, s.err
}
func (s *mockSerializer) EncodeToStream(obj runtime.Object, w io.Writer, versions ...unversioned.GroupVersion) error {
s.obj = obj
s.versions = versions
return s.err
}
type mockCreater struct {
err error
obj runtime.Object
}
func (c *mockCreater) New(kind unversioned.GroupVersionKind) (runtime.Object, error) {
return c.obj, c.err
}
type mockTyper struct {
gvk *unversioned.GroupVersionKind
err error
}
func (t *mockTyper) ObjectKind(obj runtime.Object) (*unversioned.GroupVersionKind, bool, error) {
return t.gvk, false, t.err
}

View File

@ -0,0 +1,46 @@
/*
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 yaml
import (
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/util/yaml"
)
// yamlSerializer converts YAML passed to the Decoder methods to JSON.
type yamlSerializer struct {
// the nested serializer
runtime.Serializer
}
// yamlSerializer implements Serializer
var _ runtime.Serializer = yamlSerializer{}
// NewDecodingSerializer adds YAML decoding support to a serializer that supports JSON.
func NewDecodingSerializer(jsonSerializer runtime.Serializer) runtime.Serializer {
return &yamlSerializer{jsonSerializer}
}
func (c yamlSerializer) Decode(data []byte, gvk *unversioned.GroupVersionKind, into runtime.Object) (runtime.Object, *unversioned.GroupVersionKind, error) {
out, err := yaml.ToJSON(data)
if err != nil {
return nil, nil, err
}
data = out
return c.Serializer.Decode(data, gvk, into)
}

View File

@ -136,7 +136,7 @@ func NewYAMLOrJSONDecoder(r io.Reader, bufferSize int) *YAMLOrJSONDecoder {
// provide object, or returns an error.
func (d *YAMLOrJSONDecoder) Decode(into interface{}) error {
if d.decoder == nil {
buffer, isJSON := guessJSONStream(d.r, d.bufferSize)
buffer, isJSON := GuessJSONStream(d.r, d.bufferSize)
if isJSON {
glog.V(4).Infof("decoding stream as JSON")
d.decoder = json.NewDecoder(buffer)
@ -148,10 +148,10 @@ func (d *YAMLOrJSONDecoder) Decode(into interface{}) error {
return d.decoder.Decode(into)
}
// guessJSONStream scans the provided reader up to size, looking
// GuessJSONStream scans the provided reader up to size, looking
// for an open brace indicating this is JSON. It will return the
// bufio.Reader it creates for the consumer.
func guessJSONStream(r io.Reader, size int) (io.Reader, bool) {
func GuessJSONStream(r io.Reader, size int) (io.Reader, bool) {
buffer := bufio.NewReaderSize(r, size)
b, _ := buffer.Peek(size)
return buffer, hasJSONPrefix(b)