Commit 63a7a41d authored by Clayton Coleman's avatar Clayton Coleman

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.
parent 6582b4c2
...@@ -104,7 +104,7 @@ func main() { ...@@ -104,7 +104,7 @@ func main() {
} else { } else {
pkgname = gv.Group pkgname = gv.Group
} }
if len(gv.Version) != 0 { if len(gv.Version) != 0 && gv.Version != kruntime.APIVersionInternal {
pkgname = gv.Version pkgname = gv.Version
} }
......
...@@ -17,99 +17,155 @@ limitations under the License. ...@@ -17,99 +17,155 @@ limitations under the License.
package runtime package runtime
import ( import (
"bytes"
"fmt"
"io" "io"
"net/url"
"reflect"
"k8s.io/kubernetes/pkg/api/unversioned" "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 // 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, overrides ...unversioned.GroupVersion) ([]byte, error) {
func Encode(e Encoder, obj Object) ([]byte, error) { // TODO: reuse buffer
return e.Encode(obj) 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. // 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) { 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. // 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 { func DecodeInto(d Decoder, data []byte, into Object) error {
return d.DecodeInto(data, into) out, gvk, err := d.Decode(data, nil, into)
if err != nil {
return err
}
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(e Encoder, obj Object) string {
bytes, err := Encode(e, obj)
if err != nil {
panic(err)
}
return string(bytes)
} }
// CodecFor returns a Codec that invokes Encode with the provided version. // UseOrCreateObject returns obj if the canonical ObjectKind returned by the provided typer matches gvk, or
func CodecFor(codec ObjectCodec, version unversioned.GroupVersion) Codec { // invokes the ObjectCreator to instantiate a new gvk. Returns an error if the typer cannot find the object.
return &codecWrapper{codec, version} 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)
} }
// yamlCodec converts YAML passed to the Decoder methods to JSON. // NoopEncoder converts an Decoder to a Serializer or Codec for code that expects them but only uses decoding.
type yamlCodec struct { type NoopEncoder struct {
// a Codec for JSON Decoder
Codec
} }
// yamlCodec implements Codec var _ Serializer = NoopEncoder{}
var _ Codec = yamlCodec{}
var _ Decoder = yamlCodec{}
// YAMLDecoder adds YAML decoding support to a codec that supports JSON. func (n NoopEncoder) EncodeToStream(obj Object, w io.Writer, overrides ...unversioned.GroupVersion) error {
func YAMLDecoder(codec Codec) Codec { return fmt.Errorf("encoding is not allowed for this codec: %v", reflect.TypeOf(n.Decoder))
return &yamlCodec{codec}
} }
func (c yamlCodec) Decode(data []byte) (Object, error) { // NoopDecoder converts an Encoder to a Serializer or Codec for code that expects them but only uses encoding.
out, err := yaml.ToJSON(data) type NoopDecoder struct {
if err != nil { Encoder
return nil, err
}
data = out
return c.Codec.Decode(data)
} }
func (c yamlCodec) DecodeInto(data []byte, obj Object) error { var _ Serializer = NoopDecoder{}
out, err := yaml.ToJSON(data)
if err != nil { func (n NoopDecoder) Decode(data []byte, gvk *unversioned.GroupVersionKind, into Object) (Object, *unversioned.GroupVersionKind, error) {
return err return nil, nil, fmt.Errorf("decoding is not allowed for this codec: %v", reflect.TypeOf(n.Encoder))
}
data = out
return c.Codec.DecodeInto(data, obj)
} }
// EncodeOrDie is a version of Encode which will panic instead of returning an error. For tests. // NewParameterCodec creates a ParameterCodec capable of transforming url values into versioned objects and back.
func EncodeOrDie(codec Codec, obj Object) string { func NewParameterCodec(scheme *Scheme) ParameterCodec {
bytes, err := Encode(codec, obj) return &parameterCodec{
if err != nil { typer: ObjectTyperToTyper(scheme),
panic(err) convertor: scheme,
creator: scheme,
} }
return string(bytes)
} }
// codecWrapper implements encoding to an alternative // parameterCodec implements conversion to and from query parameters and objects.
// default version for a scheme. type parameterCodec struct {
type codecWrapper struct { typer Typer
ObjectCodec convertor ObjectConvertor
version unversioned.GroupVersion creator ObjectCreater
} }
// codecWrapper implements Decoder var _ ParameterCodec = &parameterCodec{}
var _ Decoder = &codecWrapper{}
// Encode implements Codec // DecodeParameters converts the provided url.Values into an object of type From with the kind of into, and then
func (c *codecWrapper) Encode(obj Object) ([]byte, error) { // converts that object to into (if necessary). Returns an error if the operation cannot be completed.
return c.EncodeToVersion(obj, c.version.String()) func (c *parameterCodec) DecodeParameters(parameters url.Values, from unversioned.GroupVersion, into Object) error {
if len(parameters) == 0 {
return nil
}
targetGVK, _, err := c.typer.ObjectKind(into)
if err != nil {
return err
}
if targetGVK.GroupVersion() == from {
return c.convertor.Convert(&parameters, into)
}
input, err := c.creator.New(from.WithKind(targetGVK.Kind))
if err != nil {
return err
}
if err := c.convertor.Convert(&parameters, input); err != nil {
return err
}
return c.convertor.Convert(input, into)
} }
func (c *codecWrapper) EncodeToStream(obj Object, stream io.Writer) error { // EncodeParameters converts the provided object into the to version, then converts that object to url.Values.
return c.EncodeToVersionStream(obj, c.version.String(), stream) // 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)
} }
// 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)
// }
...@@ -102,10 +102,8 @@ func (g *conversionGenerator) AddImport(pkg string) string { ...@@ -102,10 +102,8 @@ func (g *conversionGenerator) AddImport(pkg string) string {
func (g *conversionGenerator) GenerateConversionsForType(gv unversioned.GroupVersion, reflection reflect.Type) error { func (g *conversionGenerator) GenerateConversionsForType(gv unversioned.GroupVersion, reflection reflect.Type) error {
kind := reflection.Name() kind := reflection.Name()
// TODO this is equivalent to what it did before, but it needs to be fixed for the proper group // 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] internalVersion := gv
if !exists { internalVersion.Version = APIVersionInternal
return fmt.Errorf("no internal version for %v", gv)
}
internalObj, err := g.scheme.NewObject(internalVersion.WithKind(kind)) internalObj, err := g.scheme.NewObject(internalVersion.WithKind(kind))
if err != nil { if err != nil {
...@@ -775,6 +773,10 @@ func (g *conversionGenerator) writeConversionForStruct(b *buffer, inType, outTyp ...@@ -775,6 +773,10 @@ func (g *conversionGenerator) writeConversionForStruct(b *buffer, inType, outTyp
continue continue
} }
if g.scheme.Converter().IsConversionIgnored(inField.Type, outField.Type) {
continue
}
existsConversion := g.scheme.Converter().HasConversionFunc(inField.Type, outField.Type) existsConversion := g.scheme.Converter().HasConversionFunc(inField.Type, outField.Type)
_, hasPublicConversion := g.publicFuncs[typePair{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 // TODO: This allows a private conversion for a slice to take precedence over a public
...@@ -895,12 +897,7 @@ type typePair struct { ...@@ -895,12 +897,7 @@ type typePair struct {
outType reflect.Type outType reflect.Type
} }
var defaultConversions []typePair = []typePair{ 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{})},
}
func (g *conversionGenerator) OverwritePackage(pkg, overwrite string) { func (g *conversionGenerator) OverwritePackage(pkg, overwrite string) {
g.pkgOverwrites[pkg] = overwrite g.pkgOverwrites[pkg] = overwrite
......
...@@ -46,12 +46,11 @@ func (obj *InternalComplex) GetObjectKind() unversioned.ObjectKind { return &obj ...@@ -46,12 +46,11 @@ func (obj *InternalComplex) GetObjectKind() unversioned.ObjectKind { return &obj
func (obj *ExternalComplex) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } func (obj *ExternalComplex) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
func TestStringMapConversion(t *testing.T) { 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"} externalGV := unversioned.GroupVersion{Group: "test.group", Version: "external"}
scheme := runtime.NewScheme() scheme := runtime.NewScheme()
scheme.Log(t) scheme.Log(t)
scheme.AddInternalGroupVersion(internalGV)
scheme.AddKnownTypeWithName(internalGV.WithKind("Complex"), &InternalComplex{}) scheme.AddKnownTypeWithName(internalGV.WithKind("Complex"), &InternalComplex{})
scheme.AddKnownTypeWithName(externalGV.WithKind("Complex"), &ExternalComplex{}) scheme.AddKnownTypeWithName(externalGV.WithKind("Complex"), &ExternalComplex{})
......
/*
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
},
}
}
...@@ -37,3 +37,11 @@ func IsMissingKind(err error) bool { ...@@ -37,3 +37,11 @@ func IsMissingKind(err error) bool {
func IsMissingVersion(err error) bool { func IsMissingVersion(err error) bool {
return conversion.IsMissingVersion(err) 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. ...@@ -16,7 +16,10 @@ limitations under the License.
package runtime package runtime
import "errors" import (
"encoding/json"
"errors"
)
func (re *RawExtension) UnmarshalJSON(in []byte) error { func (re *RawExtension) UnmarshalJSON(in []byte) error {
if re == nil { if re == nil {
...@@ -29,5 +32,16 @@ func (re *RawExtension) UnmarshalJSON(in []byte) error { ...@@ -29,5 +32,16 @@ func (re *RawExtension) UnmarshalJSON(in []byte) error {
// Marshal may get called on pointers or values, so implement MarshalJSON on value. // 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 // http://stackoverflow.com/questions/21390979/custom-marshaljson-never-gets-called-in-go
func (re RawExtension) MarshalJSON() ([]byte, error) { 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 return re.RawJSON, nil
} }
...@@ -22,8 +22,30 @@ import ( ...@@ -22,8 +22,30 @@ import (
"k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/conversion" "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, // 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 // into dest, which must be an address of a variable to which this field's
// address can be assigned. // address can be assigned.
...@@ -48,32 +70,56 @@ func FieldPtr(v reflect.Value, fieldName string, dest interface{}) error { ...@@ -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()) 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 // 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. // 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) errs := []error(nil)
for i, obj := range objects { for i, obj := range objects {
switch t := obj.(type) { switch t := obj.(type) {
case *Unknown: case *Unknown:
for _, decoder := range decoders { decoded, err := decodeListItem(t, decoders)
gv, err := unversioned.ParseGroupVersion(t.APIVersion) if err != nil {
if err != nil { errs = append(errs, err)
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
break break
} }
objects[i] = decoded
} }
} }
return errs return errs
...@@ -84,16 +130,6 @@ type MultiObjectTyper []ObjectTyper ...@@ -84,16 +130,6 @@ type MultiObjectTyper []ObjectTyper
var _ ObjectTyper = MultiObjectTyper{} 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) { func (m MultiObjectTyper) ObjectKind(obj Object) (gvk unversioned.GroupVersionKind, err error) {
for _, t := range m { for _, t := range m {
gvk, err = t.ObjectKind(obj) gvk, err = t.ObjectKind(obj)
...@@ -122,3 +158,12 @@ func (m MultiObjectTyper) Recognizes(gvk unversioned.GroupVersionKind) bool { ...@@ -122,3 +158,12 @@ func (m MultiObjectTyper) Recognizes(gvk unversioned.GroupVersionKind) bool {
} }
return false 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) { ...@@ -32,7 +32,7 @@ func TestDecodeList(t *testing.T) {
&runtime.Unstructured{TypeMeta: runtime.TypeMeta{Kind: "Foo", APIVersion: "Bar"}, Object: map[string]interface{}{"test": "value"}}, &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) t.Fatalf("unexpected error %v", errs)
} }
if pod, ok := pl.Items[1].(*api.Pod); !ok || pod.Name != "test" { if pod, ok := pl.Items[1].(*api.Pod); !ok || pod.Name != "test" {
......
...@@ -23,70 +23,84 @@ import ( ...@@ -23,70 +23,84 @@ import (
"k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/api/unversioned"
) )
// Codec defines methods for serializing and deserializing API objects. const (
type Codec interface { APIVersionInternal = "__internal"
Decoder APIVersionUnversioned = "__unversioned"
Encoder )
}
// Decoder defines methods for deserializing API objects into a given type // Typer retrieves information about an object's group, version, and kind.
type Decoder interface { type Typer interface {
// TODO: change the signature of this method // ObjectKind returns the version and kind of the provided object, or an
Decode(data []byte) (Object, error) // error if the object is not recognized (IsNotRegisteredError will return true).
// DEPRECATED: This method is being removed // It returns whether the object is considered unversioned at the same time.
DecodeToVersion(data []byte, groupVersion unversioned.GroupVersion) (Object, error) // TODO: align the signature of ObjectTyper with this interface
// DEPRECATED: This method is being removed ObjectKind(Object) (*unversioned.GroupVersionKind, bool, error)
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 { type Encoder interface {
// DEPRECATED: This method is being removed // EncodeToStream writes an object to a stream. Override versions may be provided for each group
Encode(obj Object) (data []byte, err error) // that enforce a certain versioning. Implementations may return errors if the versions are incompatible,
EncodeToStream(obj Object, stream io.Writer) error // or if no conversion is defined.
EncodeToStream(obj Object, stream io.Writer, overrides ...unversioned.GroupVersion) error
}
// TODO: Add method for processing url parameters. type Decoder interface {
// EncodeParameters(obj Object) (url.Values, error) // 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)
} }
// ObjectCodec represents the common mechanisms for converting to and from a particular // Serializer is the core interface for transforming objects into a serialized format and back.
// binary representation of an object. // Implementations may choose to perform conversion of the object, but no assumptions should be made.
// TODO: Remove this interface - it is used only in CodecFor() method. type Serializer interface {
type ObjectCodec interface { Encoder
Decoder Decoder
}
// EncodeToVersion convert and serializes an object in the internal format // Codec is a Serializer that deals with the details of versioning objects. It offers the same
// to a specified output version. An error is returned if the object // interface as Serializer, so this is a marker to consumers that care about the version of the objects
// cannot be converted for any reason. // they receive.
EncodeToVersion(obj Object, outVersion string) ([]byte, error) type Codec Serializer
EncodeToVersionStream(obj Object, outVersion string, stream io.Writer) error
// 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)
} }
// ObjectDecoder is a convenience interface for identifying serialized versions of objects // NegotiatedSerializer is an interface used for obtaining encoders, decoders, and serializers
// and transforming them into Objects. It intentionally overlaps with ObjectTyper and // for multiple supported media types.
// Decoder for use in decode only paths. type NegotiatedSerializer interface {
// TODO: Consider removing this interface? SupportedMediaTypes() []string
type ObjectDecoder interface { SerializerForMediaType(mediaType string, options map[string]string) (Serializer, bool)
Decoder EncoderForVersion(serializer Serializer, gv unversioned.GroupVersion) Encoder
// DataVersionAndKind returns the group,version,kind of the provided data, or an error DecoderToVersion(serializer Serializer, gv unversioned.GroupVersion) Decoder
// 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
} }
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
// Non-codec interfaces // Non-codec interfaces
type ObjectVersioner interface {
ConvertToVersion(in Object, outVersion string) (out Object, err error)
}
// ObjectConvertor converts an object to a different version. // ObjectConvertor converts an object to a different version.
type ObjectConvertor interface { 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 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) ConvertToVersion(in Object, outVersion string) (out Object, err error)
ConvertFieldLabel(version, kind, label, value string) (string, string, error) ConvertFieldLabel(version, kind, label, value string) (string, string, error)
} }
...@@ -94,10 +108,6 @@ type ObjectConvertor interface { ...@@ -94,10 +108,6 @@ type ObjectConvertor interface {
// ObjectTyper contains methods for extracting the APIVersion and Kind // ObjectTyper contains methods for extracting the APIVersion and Kind
// of objects. // of objects.
type ObjectTyper interface { 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 // ObjectKind returns the default group,version,kind of the provided object, or an
// error if the object is not recognized (IsNotRegisteredError will return true). // error if the object is not recognized (IsNotRegisteredError will return true).
ObjectKind(Object) (unversioned.GroupVersionKind, error) ObjectKind(Object) (unversioned.GroupVersionKind, error)
...@@ -108,6 +118,10 @@ type ObjectTyper interface { ...@@ -108,6 +118,10 @@ type ObjectTyper interface {
// or more precisely that the provided version is a possible conversion or decoding // or more precisely that the provided version is a possible conversion or decoding
// target. // target.
Recognizes(gvk unversioned.GroupVersionKind) bool 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. // ObjectCreater contains methods for instantiating an object by kind and version.
......
...@@ -20,16 +20,6 @@ import ( ...@@ -20,16 +20,6 @@ import (
"k8s.io/kubernetes/pkg/api/unversioned" "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 // SetGroupVersionKind satisfies the ObjectKind interface for all objects that embed TypeMeta
func (obj *TypeMeta) SetGroupVersionKind(gvk *unversioned.GroupVersionKind) { func (obj *TypeMeta) SetGroupVersionKind(gvk *unversioned.GroupVersionKind) {
obj.APIVersion, obj.Kind = gvk.ToAPIVersionAndKind() obj.APIVersion, obj.Kind = gvk.ToAPIVersionAndKind()
...@@ -42,3 +32,33 @@ func (obj *TypeMeta) GroupVersionKind() *unversioned.GroupVersionKind { ...@@ -42,3 +32,33 @@ func (obj *TypeMeta) GroupVersionKind() *unversioned.GroupVersionKind {
func (obj *Unknown) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } func (obj *Unknown) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
func (obj *Unstructured) 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]
}
...@@ -36,39 +36,18 @@ type TypeMeta struct { ...@@ -36,39 +36,18 @@ type TypeMeta struct {
Kind string `json:"kind,omitempty" yaml:"kind,omitempty"` 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 // RawExtension is used to hold extensions in external versions.
// 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.
// //
// To use this, make a field which has RawExtension as its type in your external, versioned // 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. // various plugin types.
// //
// // Internal package: // // Internal package:
// type MyAPIObject struct { // type MyAPIObject struct {
// runtime.TypeMeta `json:",inline"` // runtime.TypeMeta `json:",inline"`
// MyPlugin runtime.EmbeddedObject `json:"myPlugin"` // MyPlugin runtime.Object `json:"myPlugin"`
// } // }
// type PluginA struct { // type PluginA struct {
// runtime.PluginBase `json:",inline"`
// AOption string `json:"aOption"` // AOption string `json:"aOption"`
// } // }
// //
...@@ -78,7 +57,6 @@ type EmbeddedObject struct { ...@@ -78,7 +57,6 @@ type EmbeddedObject struct {
// MyPlugin runtime.RawExtension `json:"myPlugin"` // MyPlugin runtime.RawExtension `json:"myPlugin"`
// } // }
// type PluginA struct { // type PluginA struct {
// runtime.PluginBase `json:",inline"`
// AOption string `json:"aOption"` // AOption string `json:"aOption"`
// } // }
// //
...@@ -97,12 +75,16 @@ type EmbeddedObject struct { ...@@ -97,12 +75,16 @@ type EmbeddedObject struct {
// The next step is to copy (using pkg/conversion) into the internal struct. The runtime // 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 // 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 // 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.) // runtime.Unknown object will be created and stored.)
// //
// +protobuf=true // +protobuf=true
type RawExtension struct { type RawExtension struct {
// RawJSON is the underlying serialization of this object.
RawJSON []byte 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 // Unknown allows api objects with unknown types to be passed-through. This can be used
...@@ -131,3 +113,13 @@ type Unstructured struct { ...@@ -131,3 +113,13 @@ type Unstructured struct {
// children. // children.
Object map[string]interface{} 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 ...@@ -18,9 +18,7 @@ package runtime
import ( import (
"encoding/json" "encoding/json"
"fmt" "io"
"net/url"
"reflect"
"k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/conversion" "k8s.io/kubernetes/pkg/conversion"
...@@ -28,36 +26,19 @@ import ( ...@@ -28,36 +26,19 @@ import (
// UnstructuredJSONScheme is capable of converting JSON data into the Unstructured // UnstructuredJSONScheme is capable of converting JSON data into the Unstructured
// type, which can be used for generic access to objects without a predefined scheme. // 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{} type unstructuredJSONScheme struct{}
var _ Decoder = unstructuredJSONScheme{} var _ Codec = unstructuredJSONScheme{}
var _ ObjectDecoder = unstructuredJSONScheme{}
// Recognizes returns true for any version or kind that is specified (internal func (s unstructuredJSONScheme) Decode(data []byte, _ *unversioned.GroupVersionKind, _ Object) (Object, *unversioned.GroupVersionKind, error) {
// 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) {
unstruct := &Unstructured{} 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{}) m := make(map[string]interface{})
if err := json.Unmarshal(data, &m); err != nil { if err := json.Unmarshal(data, &m); err != nil {
return err return nil, nil, err
} }
if v, ok := m["kind"]; ok { if v, ok := m["kind"]; ok {
if s, ok := v.(string); ok { if s, ok := v.(string); ok {
...@@ -69,44 +50,30 @@ func (unstructuredJSONScheme) DecodeInto(data []byte, obj Object) error { ...@@ -69,44 +50,30 @@ func (unstructuredJSONScheme) DecodeInto(data []byte, obj Object) error {
unstruct.APIVersion = s unstruct.APIVersion = s
} }
} }
if len(unstruct.APIVersion) == 0 { 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 { if len(unstruct.Kind) == 0 {
return conversion.NewMissingKindErr(string(data)) return nil, &gvk, conversion.NewMissingKindErr(string(data))
} }
unstruct.Object = m 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) { func (s unstructuredJSONScheme) EncodeToStream(obj Object, w io.Writer, overrides ...unversioned.GroupVersion) error {
return nil, nil switch t := obj.(type) {
} case *Unstructured:
return json.NewEncoder(w).Encode(t.Object)
func (unstructuredJSONScheme) DecodeParametersInto(paramaters url.Values, obj Object) error { case *Unknown:
return nil _, err := w.Write(t.RawJSON)
} return err
default:
func (unstructuredJSONScheme) DataKind(data []byte) (unversioned.GroupVersionKind, error) { return json.NewEncoder(w).Encode(t)
obj := TypeMeta{}
if err := json.Unmarshal(data, &obj); err != nil {
return unversioned.GroupVersionKind{}, err
}
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) { ...@@ -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" { 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]) t.Errorf("object not converted: %#v", pl.Items[1])
} }
if _, ok := pl.Items[2].(*runtime.Unknown); !ok { if pod, ok := pl.Items[2].(*runtime.Unstructured); !ok || pod.Object["kind"] != "Pod" || pod.Object["metadata"].(map[string]interface{})["name"] != "test" {
t.Errorf("object should not have been converted: %#v", pl.Items[2]) t.Errorf("object not converted: %#v", pl.Items[2])
} }
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment