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() {
} else {
pkgname = gv.Group
}
if len(gv.Version) != 0 {
if len(gv.Version) != 0 && gv.Version != kruntime.APIVersionInternal {
pkgname = gv.Version
}
......
......@@ -17,99 +17,155 @@ limitations under the License.
package runtime
import (
"bytes"
"fmt"
"io"
"net/url"
"reflect"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/util/yaml"
"k8s.io/kubernetes/pkg/conversion/queryparams"
)
// codec binds an encoder and decoder.
type codec struct {
Encoder
Decoder
}
// NewCodec creates a Codec from an Encoder and Decoder.
func NewCodec(e Encoder, d Decoder) Codec {
return codec{e, d}
}
// Encode is a convenience wrapper for encoding to a []byte from an Encoder
// TODO: these are transitional interfaces to reduce refactor cost as Codec is altered.
func Encode(e Encoder, obj Object) ([]byte, error) {
return e.Encode(obj)
func Encode(e Encoder, obj Object, overrides ...unversioned.GroupVersion) ([]byte, error) {
// TODO: reuse buffer
buf := &bytes.Buffer{}
if err := e.EncodeToStream(obj, buf, overrides...); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
// Decode is a convenience wrapper for decoding data into an Object.
// TODO: these are transitional interfaces to reduce refactor cost as Codec is altered.
func Decode(d Decoder, data []byte) (Object, error) {
return d.Decode(data)
obj, _, err := d.Decode(data, nil, nil)
return obj, err
}
// DecodeInto performs a Decode into the provided object.
// TODO: these are transitional interfaces to reduce refactor cost as Codec is altered.
func DecodeInto(d Decoder, data []byte, into Object) error {
return d.DecodeInto(data, into)
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.
func CodecFor(codec ObjectCodec, version unversioned.GroupVersion) Codec {
return &codecWrapper{codec, version}
// UseOrCreateObject returns obj if the canonical ObjectKind returned by the provided typer matches gvk, or
// invokes the ObjectCreator to instantiate a new gvk. Returns an error if the typer cannot find the object.
func UseOrCreateObject(t Typer, c ObjectCreater, gvk unversioned.GroupVersionKind, obj Object) (Object, error) {
if obj != nil {
into, _, err := t.ObjectKind(obj)
if err != nil {
return nil, err
}
if gvk == *into {
return obj, nil
}
}
return c.New(gvk)
}
// yamlCodec converts YAML passed to the Decoder methods to JSON.
type yamlCodec struct {
// a Codec for JSON
Codec
// NoopEncoder converts an Decoder to a Serializer or Codec for code that expects them but only uses decoding.
type NoopEncoder struct {
Decoder
}
// yamlCodec implements Codec
var _ Codec = yamlCodec{}
var _ Decoder = yamlCodec{}
var _ Serializer = NoopEncoder{}
// YAMLDecoder adds YAML decoding support to a codec that supports JSON.
func YAMLDecoder(codec Codec) Codec {
return &yamlCodec{codec}
func (n NoopEncoder) EncodeToStream(obj Object, w io.Writer, overrides ...unversioned.GroupVersion) error {
return fmt.Errorf("encoding is not allowed for this codec: %v", reflect.TypeOf(n.Decoder))
}
func (c yamlCodec) Decode(data []byte) (Object, error) {
out, err := yaml.ToJSON(data)
if err != nil {
return nil, err
}
data = out
return c.Codec.Decode(data)
// NoopDecoder converts an Encoder to a Serializer or Codec for code that expects them but only uses encoding.
type NoopDecoder struct {
Encoder
}
func (c yamlCodec) DecodeInto(data []byte, obj Object) error {
out, err := yaml.ToJSON(data)
if err != nil {
return err
}
data = out
return c.Codec.DecodeInto(data, obj)
var _ Serializer = NoopDecoder{}
func (n NoopDecoder) Decode(data []byte, gvk *unversioned.GroupVersionKind, into Object) (Object, *unversioned.GroupVersionKind, error) {
return nil, nil, fmt.Errorf("decoding is not allowed for this codec: %v", reflect.TypeOf(n.Encoder))
}
// EncodeOrDie is a version of Encode which will panic instead of returning an error. For tests.
func EncodeOrDie(codec Codec, obj Object) string {
bytes, err := Encode(codec, obj)
if err != nil {
panic(err)
// NewParameterCodec creates a ParameterCodec capable of transforming url values into versioned objects and back.
func NewParameterCodec(scheme *Scheme) ParameterCodec {
return &parameterCodec{
typer: ObjectTyperToTyper(scheme),
convertor: scheme,
creator: scheme,
}
return string(bytes)
}
// codecWrapper implements encoding to an alternative
// default version for a scheme.
type codecWrapper struct {
ObjectCodec
version unversioned.GroupVersion
// parameterCodec implements conversion to and from query parameters and objects.
type parameterCodec struct {
typer Typer
convertor ObjectConvertor
creator ObjectCreater
}
// codecWrapper implements Decoder
var _ Decoder = &codecWrapper{}
var _ ParameterCodec = &parameterCodec{}
// Encode implements Codec
func (c *codecWrapper) Encode(obj Object) ([]byte, error) {
return c.EncodeToVersion(obj, c.version.String())
// DecodeParameters converts the provided url.Values into an object of type From with the kind of into, and then
// converts that object to into (if necessary). Returns an error if the operation cannot be completed.
func (c *parameterCodec) DecodeParameters(parameters url.Values, from unversioned.GroupVersion, into Object) error {
if len(parameters) == 0 {
return nil
}
targetGVK, _, err := c.typer.ObjectKind(into)
if err != nil {
return err
}
if targetGVK.GroupVersion() == from {
return c.convertor.Convert(&parameters, into)
}
input, err := c.creator.New(from.WithKind(targetGVK.Kind))
if err != nil {
return err
}
if err := c.convertor.Convert(&parameters, input); err != nil {
return err
}
return c.convertor.Convert(input, into)
}
func (c *codecWrapper) EncodeToStream(obj Object, stream io.Writer) error {
return c.EncodeToVersionStream(obj, c.version.String(), stream)
// EncodeParameters converts the provided object into the to version, then converts that object to url.Values.
// Returns an error if conversion is not possible.
func (c *parameterCodec) EncodeParameters(obj Object, to unversioned.GroupVersion) (url.Values, error) {
gvk, _, err := c.typer.ObjectKind(obj)
if err != nil {
return nil, err
}
if to != gvk.GroupVersion() {
out, err := c.convertor.ConvertToVersion(obj, to.String())
if err != nil {
return nil, err
}
obj = out
}
return queryparams.Convert(obj)
}
// 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 {
func (g *conversionGenerator) GenerateConversionsForType(gv unversioned.GroupVersion, reflection reflect.Type) error {
kind := reflection.Name()
// TODO this is equivalent to what it did before, but it needs to be fixed for the proper group
internalVersion, exists := g.scheme.InternalVersions[gv.Group]
if !exists {
return fmt.Errorf("no internal version for %v", gv)
}
internalVersion := gv
internalVersion.Version = APIVersionInternal
internalObj, err := g.scheme.NewObject(internalVersion.WithKind(kind))
if err != nil {
......@@ -775,6 +773,10 @@ func (g *conversionGenerator) writeConversionForStruct(b *buffer, inType, outTyp
continue
}
if g.scheme.Converter().IsConversionIgnored(inField.Type, outField.Type) {
continue
}
existsConversion := g.scheme.Converter().HasConversionFunc(inField.Type, outField.Type)
_, hasPublicConversion := g.publicFuncs[typePair{inField.Type, outField.Type}]
// TODO: This allows a private conversion for a slice to take precedence over a public
......@@ -895,12 +897,7 @@ type typePair struct {
outType reflect.Type
}
var defaultConversions []typePair = []typePair{
{reflect.TypeOf([]RawExtension{}), reflect.TypeOf([]Object{})},
{reflect.TypeOf([]Object{}), reflect.TypeOf([]RawExtension{})},
{reflect.TypeOf(RawExtension{}), reflect.TypeOf(EmbeddedObject{})},
{reflect.TypeOf(EmbeddedObject{}), reflect.TypeOf(RawExtension{})},
}
var defaultConversions []typePair = []typePair{}
func (g *conversionGenerator) OverwritePackage(pkg, overwrite string) {
g.pkgOverwrites[pkg] = overwrite
......
......@@ -46,12 +46,11 @@ func (obj *InternalComplex) GetObjectKind() unversioned.ObjectKind { return &obj
func (obj *ExternalComplex) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
func TestStringMapConversion(t *testing.T) {
internalGV := unversioned.GroupVersion{Group: "test.group", Version: ""}
internalGV := unversioned.GroupVersion{Group: "test.group", Version: runtime.APIVersionInternal}
externalGV := unversioned.GroupVersion{Group: "test.group", Version: "external"}
scheme := runtime.NewScheme()
scheme.Log(t)
scheme.AddInternalGroupVersion(internalGV)
scheme.AddKnownTypeWithName(internalGV.WithKind("Complex"), &InternalComplex{})
scheme.AddKnownTypeWithName(externalGV.WithKind("Complex"), &ExternalComplex{})
......
/*
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 {
func IsMissingVersion(err error) bool {
return conversion.IsMissingVersion(err)
}
func NewMissingKindErr(data string) error {
return conversion.NewMissingKindErr(data)
}
func NewMissingVersionErr(data string) error {
return conversion.NewMissingVersionErr(data)
}
......@@ -16,7 +16,10 @@ limitations under the License.
package runtime
import "errors"
import (
"encoding/json"
"errors"
)
func (re *RawExtension) UnmarshalJSON(in []byte) error {
if re == nil {
......@@ -29,5 +32,16 @@ func (re *RawExtension) UnmarshalJSON(in []byte) error {
// Marshal may get called on pointers or values, so implement MarshalJSON on value.
// http://stackoverflow.com/questions/21390979/custom-marshaljson-never-gets-called-in-go
func (re RawExtension) MarshalJSON() ([]byte, error) {
if re.RawJSON == nil {
// TODO: this is to support legacy behavior of JSONPrinter and YAMLPrinter, which
// expect to call json.Marshal on arbitrary versioned objects (even those not in
// the scheme). pkg/kubectl/resource#AsVersionedObjects and its interaction with
// kubectl get on objects not in the scheme needs to be updated to ensure that the
// objects that are not part of the scheme are correctly put into the right form.
if re.Object != nil {
return json.Marshal(re.Object)
}
return []byte("null"), nil
}
return re.RawJSON, nil
}
......@@ -22,8 +22,30 @@ import (
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/conversion"
"k8s.io/kubernetes/pkg/util/errors"
)
type objectTyperToTyper struct {
typer ObjectTyper
}
func (t objectTyperToTyper) ObjectKind(obj Object) (*unversioned.GroupVersionKind, bool, error) {
gvk, err := t.typer.ObjectKind(obj)
if err != nil {
return nil, false, err
}
unversionedType, ok := t.typer.IsUnversioned(obj)
if !ok {
// ObjectTyper violates its contract
return nil, false, fmt.Errorf("typer returned a kind for %v, but then reported it was not in the scheme with IsUnversioned", reflect.TypeOf(obj))
}
return &gvk, unversionedType, nil
}
func ObjectTyperToTyper(typer ObjectTyper) Typer {
return objectTyperToTyper{typer: typer}
}
// fieldPtr puts the address of fieldName, which must be a member of v,
// into dest, which must be an address of a variable to which this field's
// address can be assigned.
......@@ -48,32 +70,56 @@ func FieldPtr(v reflect.Value, fieldName string, dest interface{}) error {
return fmt.Errorf("couldn't assign/convert %v to %v", field.Type(), v.Type())
}
// EncodeList ensures that each object in an array is converted to a Unknown{} in serialized form.
// TODO: accept a content type.
func EncodeList(e Encoder, objects []Object, overrides ...unversioned.GroupVersion) error {
var errs []error
for i := range objects {
data, err := Encode(e, objects[i], overrides...)
if err != nil {
errs = append(errs, err)
continue
}
objects[i] = &Unknown{RawJSON: data}
}
return errors.NewAggregate(errs)
}
func decodeListItem(obj *Unknown, decoders []Decoder) (Object, error) {
for _, decoder := range decoders {
obj, err := Decode(decoder, obj.RawJSON)
if err != nil {
if IsNotRegisteredError(err) {
continue
}
return nil, err
}
return obj, nil
}
// could not decode, so leave the object as Unknown, but give the decoders the
// chance to set Unknown.TypeMeta if it is available.
for _, decoder := range decoders {
if err := DecodeInto(decoder, obj.RawJSON, obj); err == nil {
return obj, nil
}
}
return obj, nil
}
// DecodeList alters the list in place, attempting to decode any objects found in
// the list that have the runtime.Unknown type. Any errors that occur are returned
// the list that have the Unknown type. Any errors that occur are returned
// after the entire list is processed. Decoders are tried in order.
func DecodeList(objects []Object, decoders ...ObjectDecoder) []error {
func DecodeList(objects []Object, decoders ...Decoder) []error {
errs := []error(nil)
for i, obj := range objects {
switch t := obj.(type) {
case *Unknown:
for _, decoder := range decoders {
gv, err := unversioned.ParseGroupVersion(t.APIVersion)
if err != nil {
errs = append(errs, err)
break
}
if !decoder.Recognizes(gv.WithKind(t.Kind)) {
continue
}
obj, err := Decode(decoder, t.RawJSON)
if err != nil {
errs = append(errs, err)
break
}
objects[i] = obj
decoded, err := decodeListItem(t, decoders)
if err != nil {
errs = append(errs, err)
break
}
objects[i] = decoded
}
}
return errs
......@@ -84,16 +130,6 @@ type MultiObjectTyper []ObjectTyper
var _ ObjectTyper = MultiObjectTyper{}
func (m MultiObjectTyper) DataKind(data []byte) (gvk unversioned.GroupVersionKind, err error) {
for _, t := range m {
gvk, err = t.DataKind(data)
if err == nil {
return
}
}
return
}
func (m MultiObjectTyper) ObjectKind(obj Object) (gvk unversioned.GroupVersionKind, err error) {
for _, t := range m {
gvk, err = t.ObjectKind(obj)
......@@ -122,3 +158,12 @@ func (m MultiObjectTyper) Recognizes(gvk unversioned.GroupVersionKind) bool {
}
return false
}
func (m MultiObjectTyper) IsUnversioned(obj Object) (bool, bool) {
for _, t := range m {
if unversioned, ok := t.IsUnversioned(obj); ok {
return unversioned, true
}
}
return false, false
}
......@@ -32,7 +32,7 @@ func TestDecodeList(t *testing.T) {
&runtime.Unstructured{TypeMeta: runtime.TypeMeta{Kind: "Foo", APIVersion: "Bar"}, Object: map[string]interface{}{"test": "value"}},
},
}
if errs := runtime.DecodeList(pl.Items, api.Scheme); len(errs) != 0 {
if errs := runtime.DecodeList(pl.Items, testapi.Default.Codec()); len(errs) != 0 {
t.Fatalf("unexpected error %v", errs)
}
if pod, ok := pl.Items[1].(*api.Pod); !ok || pod.Name != "test" {
......
......@@ -23,70 +23,84 @@ import (
"k8s.io/kubernetes/pkg/api/unversioned"
)
// Codec defines methods for serializing and deserializing API objects.
type Codec interface {
Decoder
Encoder
}
const (
APIVersionInternal = "__internal"
APIVersionUnversioned = "__unversioned"
)
// Decoder defines methods for deserializing API objects into a given type
type Decoder interface {
// TODO: change the signature of this method
Decode(data []byte) (Object, error)
// DEPRECATED: This method is being removed
DecodeToVersion(data []byte, groupVersion unversioned.GroupVersion) (Object, error)
// DEPRECATED: This method is being removed
DecodeInto(data []byte, obj Object) error
// DEPRECATED: This method is being removed
DecodeIntoWithSpecifiedVersionKind(data []byte, obj Object, groupVersionKind unversioned.GroupVersionKind) error
DecodeParametersInto(parameters url.Values, obj Object) error
// Typer retrieves information about an object's group, version, and kind.
type Typer interface {
// ObjectKind returns the version and kind of the provided object, or an
// error if the object is not recognized (IsNotRegisteredError will return true).
// It returns whether the object is considered unversioned at the same time.
// TODO: align the signature of ObjectTyper with this interface
ObjectKind(Object) (*unversioned.GroupVersionKind, bool, error)
}
// Encoder defines methods for serializing API objects into bytes
type Encoder interface {
// DEPRECATED: This method is being removed
Encode(obj Object) (data []byte, err error)
EncodeToStream(obj Object, stream io.Writer) error
// EncodeToStream writes an object to a stream. Override versions may be provided for each group
// that enforce a certain versioning. Implementations may return errors if the versions are incompatible,
// or if no conversion is defined.
EncodeToStream(obj Object, stream io.Writer, overrides ...unversioned.GroupVersion) error
}
// TODO: Add method for processing url parameters.
// EncodeParameters(obj Object) (url.Values, error)
type Decoder interface {
// Decode attempts to deserialize the provided data using either the innate typing of the scheme or the
// default kind, group, and version provided. It returns a decoded object as well as the kind, group, and
// version from the serialized data, or an error. If into is non-nil, it will be used as the target type
// and implementations may choose to use it rather than reallocating an object. However, the object is not
// guaranteed to be populated. The returned object is not guaranteed to match into. If defaults are
// provided, they are applied to the data by default. If no defaults or partial defaults are provided, the
// type of the into may be used to guide conversion decisions.
Decode(data []byte, defaults *unversioned.GroupVersionKind, into Object) (Object, *unversioned.GroupVersionKind, error)
}
// ObjectCodec represents the common mechanisms for converting to and from a particular
// binary representation of an object.
// TODO: Remove this interface - it is used only in CodecFor() method.
type ObjectCodec interface {
// Serializer is the core interface for transforming objects into a serialized format and back.
// Implementations may choose to perform conversion of the object, but no assumptions should be made.
type Serializer interface {
Encoder
Decoder
}
// EncodeToVersion convert and serializes an object in the internal format
// to a specified output version. An error is returned if the object
// cannot be converted for any reason.
EncodeToVersion(obj Object, outVersion string) ([]byte, error)
EncodeToVersionStream(obj Object, outVersion string, stream io.Writer) error
// Codec is a Serializer that deals with the details of versioning objects. It offers the same
// interface as Serializer, so this is a marker to consumers that care about the version of the objects
// they receive.
type Codec Serializer
// ParameterCodec defines methods for serializing and deserializing API objects to url.Values and
// performing any necessary conversion. Unlike the normal Codec, query parameters are not self describing
// and the desired version must be specified.
type ParameterCodec interface {
// DecodeParameters takes the given url.Values in the specified group version and decodes them
// into the provided object, or returns an error.
DecodeParameters(parameters url.Values, from unversioned.GroupVersion, into Object) error
// EncodeParameters encodes the provided object as query parameters or returns an error.
EncodeParameters(obj Object, to unversioned.GroupVersion) (url.Values, error)
}
// ObjectDecoder is a convenience interface for identifying serialized versions of objects
// and transforming them into Objects. It intentionally overlaps with ObjectTyper and
// Decoder for use in decode only paths.
// TODO: Consider removing this interface?
type ObjectDecoder interface {
Decoder
// DataVersionAndKind returns the group,version,kind of the provided data, or an error
// if another problem is detected. In many cases this method can be as expensive to
// invoke as the Decode method.
DataKind([]byte) (unversioned.GroupVersionKind, error)
// Recognizes returns true if the scheme is able to handle the provided group,version,kind
// of an object.
Recognizes(unversioned.GroupVersionKind) bool
// NegotiatedSerializer is an interface used for obtaining encoders, decoders, and serializers
// for multiple supported media types.
type NegotiatedSerializer interface {
SupportedMediaTypes() []string
SerializerForMediaType(mediaType string, options map[string]string) (Serializer, bool)
EncoderForVersion(serializer Serializer, gv unversioned.GroupVersion) Encoder
DecoderToVersion(serializer Serializer, gv unversioned.GroupVersion) Decoder
}
///////////////////////////////////////////////////////////////////////////////
// Non-codec interfaces
type ObjectVersioner interface {
ConvertToVersion(in Object, outVersion string) (out Object, err error)
}
// ObjectConvertor converts an object to a different version.
type ObjectConvertor interface {
// Convert attempts to convert one object into another, or returns an error. This method does
// not guarantee the in object is not mutated.
Convert(in, out interface{}) error
// ConvertToVersion takes the provided object and converts it the provided version. This
// method does not guarantee that the in object is not mutated.
ConvertToVersion(in Object, outVersion string) (out Object, err error)
ConvertFieldLabel(version, kind, label, value string) (string, string, error)
}
......@@ -94,10 +108,6 @@ type ObjectConvertor interface {
// ObjectTyper contains methods for extracting the APIVersion and Kind
// of objects.
type ObjectTyper interface {
// DataKind returns the group,version,kind of the provided data, or an error
// if another problem is detected. In many cases this method can be as expensive to
// invoke as the Decode method.
DataKind([]byte) (unversioned.GroupVersionKind, error)
// ObjectKind returns the default group,version,kind of the provided object, or an
// error if the object is not recognized (IsNotRegisteredError will return true).
ObjectKind(Object) (unversioned.GroupVersionKind, error)
......@@ -108,6 +118,10 @@ type ObjectTyper interface {
// or more precisely that the provided version is a possible conversion or decoding
// target.
Recognizes(gvk unversioned.GroupVersionKind) bool
// IsUnversioned returns true if the provided object is considered unversioned and thus
// should have Version and Group suppressed in the output. If the object is not recognized
// in the scheme, ok is false.
IsUnversioned(Object) (unversioned bool, ok bool)
}
// ObjectCreater contains methods for instantiating an object by kind and version.
......
......@@ -20,16 +20,6 @@ import (
"k8s.io/kubernetes/pkg/api/unversioned"
)
// SetGroupVersionKind satisfies the ObjectKind interface for all objects that embed PluginBase
func (obj *PluginBase) SetGroupVersionKind(gvk *unversioned.GroupVersionKind) {
_, obj.Kind = gvk.ToAPIVersionAndKind()
}
// GroupVersionKind satisfies the ObjectKind interface for all objects that embed PluginBase
func (obj *PluginBase) GroupVersionKind() *unversioned.GroupVersionKind {
return unversioned.FromAPIVersionAndKind("", obj.Kind)
}
// SetGroupVersionKind satisfies the ObjectKind interface for all objects that embed TypeMeta
func (obj *TypeMeta) SetGroupVersionKind(gvk *unversioned.GroupVersionKind) {
obj.APIVersion, obj.Kind = gvk.ToAPIVersionAndKind()
......@@ -42,3 +32,33 @@ func (obj *TypeMeta) GroupVersionKind() *unversioned.GroupVersionKind {
func (obj *Unknown) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
func (obj *Unstructured) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
// GetObjectKind implements Object for VersionedObjects, returning an empty ObjectKind
// interface if no objects are provided, or the ObjectKind interface of the object in the
// highest array position.
func (obj *VersionedObjects) GetObjectKind() unversioned.ObjectKind {
last := obj.Last()
if last == nil {
return unversioned.EmptyObjectKind
}
return last.GetObjectKind()
}
// First returns the leftmost object in the VersionedObjects array, which is usually the
// object as serialized on the wire.
func (obj *VersionedObjects) First() Object {
if len(obj.Objects) == 0 {
return nil
}
return obj.Objects[0]
}
// Last is the rightmost object in the VersionedObjects array, which is the object after
// all transformations have been applied. This is the same object that would be returned
// by Decode in a normal invocation (without VersionedObjects in the into argument).
func (obj *VersionedObjects) Last() Object {
if len(obj.Objects) == 0 {
return nil
}
return obj.Objects[len(obj.Objects)-1]
}
......@@ -36,39 +36,18 @@ type TypeMeta struct {
Kind string `json:"kind,omitempty" yaml:"kind,omitempty"`
}
// PluginBase is like TypeMeta, but it's intended for plugin objects that won't ever be encoded
// except while embedded in other objects.
type PluginBase struct {
Kind string `json:"kind,omitempty"`
}
// EmbeddedObject has appropriate encoder and decoder functions, such that on the wire, it's
// stored as a []byte, but in memory, the contained object is accessible as an Object
// via the Get() function. Only valid API objects may be stored via EmbeddedObject.
// The purpose of this is to allow an API object of type known only at runtime to be
// embedded within other API objects.
//
// Note that object assumes that you've registered all of your api types with the api package.
//
// EmbeddedObject and RawExtension can be used together to allow for API object extensions:
// see the comment for RawExtension.
type EmbeddedObject struct {
Object
}
// RawExtension is used with EmbeddedObject to do a two-phase encoding of extension objects.
// RawExtension is used to hold extensions in external versions.
//
// To use this, make a field which has RawExtension as its type in your external, versioned
// struct, and EmbeddedObject in your internal struct. You also need to register your
// struct, and Object in your internal struct. You also need to register your
// various plugin types.
//
// // Internal package:
// type MyAPIObject struct {
// runtime.TypeMeta `json:",inline"`
// MyPlugin runtime.EmbeddedObject `json:"myPlugin"`
// MyPlugin runtime.Object `json:"myPlugin"`
// }
// type PluginA struct {
// runtime.PluginBase `json:",inline"`
// AOption string `json:"aOption"`
// }
//
......@@ -78,7 +57,6 @@ type EmbeddedObject struct {
// MyPlugin runtime.RawExtension `json:"myPlugin"`
// }
// type PluginA struct {
// runtime.PluginBase `json:",inline"`
// AOption string `json:"aOption"`
// }
//
......@@ -97,12 +75,16 @@ type EmbeddedObject struct {
// The next step is to copy (using pkg/conversion) into the internal struct. The runtime
// package's DefaultScheme has conversion functions installed which will unpack the
// JSON stored in RawExtension, turning it into the correct object type, and storing it
// in the EmbeddedObject. (TODO: In the case where the object is of an unknown type, a
// in the Object. (TODO: In the case where the object is of an unknown type, a
// runtime.Unknown object will be created and stored.)
//
// +protobuf=true
type RawExtension struct {
// RawJSON is the underlying serialization of this object.
RawJSON []byte
// Object can hold a representation of this extension - useful for working with versioned
// structs.
Object Object `json:"-"`
}
// Unknown allows api objects with unknown types to be passed-through. This can be used
......@@ -131,3 +113,13 @@ type Unstructured struct {
// children.
Object map[string]interface{}
}
// VersionedObjects is used by Decoders to give callers a way to access all versions
// of an object during the decoding process.
type VersionedObjects struct {
// Objects is the set of objects retrieved during decoding, in order of conversion.
// The 0 index is the object as serialized on the wire. If conversion has occured,
// other objects may be present. The right most object is the same as would be returned
// by a normal Decode call.
Objects []Object
}
......@@ -18,9 +18,7 @@ package runtime
import (
"encoding/json"
"fmt"
"net/url"
"reflect"
"io"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/conversion"
......@@ -28,36 +26,19 @@ import (
// UnstructuredJSONScheme is capable of converting JSON data into the Unstructured
// type, which can be used for generic access to objects without a predefined scheme.
var UnstructuredJSONScheme ObjectDecoder = unstructuredJSONScheme{}
// TODO: move into serializer/json.
var UnstructuredJSONScheme Decoder = unstructuredJSONScheme{}
type unstructuredJSONScheme struct{}
var _ Decoder = unstructuredJSONScheme{}
var _ ObjectDecoder = unstructuredJSONScheme{}
var _ Codec = unstructuredJSONScheme{}
// Recognizes returns true for any version or kind that is specified (internal
// versions are specifically excluded).
func (unstructuredJSONScheme) Recognizes(gvk unversioned.GroupVersionKind) bool {
return !gvk.GroupVersion().IsEmpty() && len(gvk.Kind) > 0
}
func (s unstructuredJSONScheme) Decode(data []byte) (Object, error) {
func (s unstructuredJSONScheme) Decode(data []byte, _ *unversioned.GroupVersionKind, _ Object) (Object, *unversioned.GroupVersionKind, error) {
unstruct := &Unstructured{}
if err := DecodeInto(s, data, unstruct); err != nil {
return nil, err
}
return unstruct, nil
}
func (unstructuredJSONScheme) DecodeInto(data []byte, obj Object) error {
unstruct, ok := obj.(*Unstructured)
if !ok {
return fmt.Errorf("the unstructured JSON scheme does not recognize %v", reflect.TypeOf(obj))
}
m := make(map[string]interface{})
if err := json.Unmarshal(data, &m); err != nil {
return err
return nil, nil, err
}
if v, ok := m["kind"]; ok {
if s, ok := v.(string); ok {
......@@ -69,44 +50,30 @@ func (unstructuredJSONScheme) DecodeInto(data []byte, obj Object) error {
unstruct.APIVersion = s
}
}
if len(unstruct.APIVersion) == 0 {
return conversion.NewMissingVersionErr(string(data))
return nil, nil, conversion.NewMissingVersionErr(string(data))
}
gv, err := unversioned.ParseGroupVersion(unstruct.APIVersion)
if err != nil {
return nil, nil, err
}
gvk := gv.WithKind(unstruct.Kind)
if len(unstruct.Kind) == 0 {
return conversion.NewMissingKindErr(string(data))
return nil, &gvk, conversion.NewMissingKindErr(string(data))
}
unstruct.Object = m
return nil
}
func (unstructuredJSONScheme) DecodeIntoWithSpecifiedVersionKind(data []byte, obj Object, gvk unversioned.GroupVersionKind) error {
return nil
return unstruct, &gvk, nil
}
func (unstructuredJSONScheme) DecodeToVersion(data []byte, gv unversioned.GroupVersion) (Object, error) {
return nil, nil
}
func (unstructuredJSONScheme) DecodeParametersInto(paramaters url.Values, obj Object) error {
return nil
}
func (unstructuredJSONScheme) DataKind(data []byte) (unversioned.GroupVersionKind, error) {
obj := TypeMeta{}
if err := json.Unmarshal(data, &obj); err != nil {
return unversioned.GroupVersionKind{}, err
}
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
func (s unstructuredJSONScheme) EncodeToStream(obj Object, w io.Writer, overrides ...unversioned.GroupVersion) error {
switch t := obj.(type) {
case *Unstructured:
return json.NewEncoder(w).Encode(t.Object)
case *Unknown:
_, err := w.Write(t.RawJSON)
return err
default:
return json.NewEncoder(w).Encode(t)
}
return gv.WithKind(obj.Kind), nil
}
......@@ -42,7 +42,7 @@ func TestDecodeUnstructured(t *testing.T) {
if pod, ok := pl.Items[1].(*runtime.Unstructured); !ok || pod.Object["kind"] != "Pod" || pod.Object["metadata"].(map[string]interface{})["name"] != "test" {
t.Errorf("object not converted: %#v", pl.Items[1])
}
if _, ok := pl.Items[2].(*runtime.Unknown); !ok {
t.Errorf("object should not have been converted: %#v", pl.Items[2])
if pod, ok := pl.Items[2].(*runtime.Unstructured); !ok || pod.Object["kind"] != "Pod" || pod.Object["metadata"].(map[string]interface{})["name"] != "test" {
t.Errorf("object not converted: %#v", pl.Items[2])
}
}
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