Introduce GroupVersioner for capturing desired target version

Convert single GV and lists of GVs into an interface that can handle
more complex scenarios (everything internal, nothing supported). Pass
the interface down into conversion.
pull/6/head
Clayton Coleman 2016-05-22 19:10:42 -04:00
parent 9d2a5fe5e8
commit 12a5eeea17
No known key found for this signature in database
GPG Key ID: 3D16906B4F1C5CB3
24 changed files with 617 additions and 332 deletions

View File

@ -32,7 +32,7 @@ func (fakeConvertor) Convert(in, out interface{}) error {
return nil return nil
} }
func (fakeConvertor) ConvertToVersion(in runtime.Object, _ unversioned.GroupVersion) (runtime.Object, error) { func (fakeConvertor) ConvertToVersion(in runtime.Object, _ runtime.GroupVersioner) (runtime.Object, error) {
return in, nil return in, nil
} }

View File

@ -43,7 +43,6 @@ import (
"k8s.io/kubernetes/pkg/conversion" "k8s.io/kubernetes/pkg/conversion"
"k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/runtime/serializer/streaming" "k8s.io/kubernetes/pkg/runtime/serializer/streaming"
"k8s.io/kubernetes/pkg/runtime/serializer/versioning"
"k8s.io/kubernetes/pkg/util/diff" "k8s.io/kubernetes/pkg/util/diff"
"k8s.io/kubernetes/pkg/util/sets" "k8s.io/kubernetes/pkg/util/sets"
"k8s.io/kubernetes/pkg/watch" "k8s.io/kubernetes/pkg/watch"
@ -181,10 +180,14 @@ func TestSetControllerConversion(t *testing.T) {
t.Fatalf("unexpected encoding error: %v", err) t.Fatalf("unexpected encoding error: %v", err)
} }
decoder := api.Codecs.UniversalDecoder(*extGroup.GroupVersion(), *defaultGroup.GroupVersion()) decoder := api.Codecs.DecoderToVersion(
if err := versioning.EnableCrossGroupDecoding(decoder, extGroup.GroupVersion().Group, defaultGroup.GroupVersion().Group); err != nil { api.Codecs.UniversalDeserializer(),
t.Fatalf("unexpected error while enabling cross-group decoding: %v", err) runtime.NewMultiGroupVersioner(
} *defaultGroup.GroupVersion(),
unversioned.GroupKind{Group: defaultGroup.GroupVersion().Group},
unversioned.GroupKind{Group: extGroup.GroupVersion().Group},
),
)
t.Logf("rs.v1beta1.extensions -> rc._internal") t.Logf("rs.v1beta1.extensions -> rc._internal")
if err := runtime.DecodeInto(decoder, data, rc); err != nil { if err := runtime.DecodeInto(decoder, data, rc); err != nil {

View File

@ -281,7 +281,7 @@ func (g TestGroup) Codec() runtime.Codec {
if serializer.Serializer == nil { if serializer.Serializer == nil {
return api.Codecs.LegacyCodec(g.externalGroupVersion) return api.Codecs.LegacyCodec(g.externalGroupVersion)
} }
return api.Codecs.CodecForVersions(serializer, api.Codecs.UniversalDeserializer(), []unversioned.GroupVersion{g.externalGroupVersion}, nil) return api.Codecs.CodecForVersions(serializer, api.Codecs.UniversalDeserializer(), unversioned.GroupVersions{g.externalGroupVersion}, nil)
} }
// NegotiatedSerializer returns the negotiated serializer for the server. // NegotiatedSerializer returns the negotiated serializer for the server.
@ -309,7 +309,7 @@ func (g TestGroup) StorageCodec() runtime.Codec {
} }
ds := recognizer.NewDecoder(s, api.Codecs.UniversalDeserializer()) ds := recognizer.NewDecoder(s, api.Codecs.UniversalDeserializer())
return api.Codecs.CodecForVersions(s, ds, []unversioned.GroupVersion{g.externalGroupVersion}, nil) return api.Codecs.CodecForVersions(s, ds, unversioned.GroupVersions{g.externalGroupVersion}, nil)
} }
// Converter returns the api.Scheme for the API version to test against, as set by the // Converter returns the api.Scheme for the API version to test against, as set by the
@ -393,7 +393,7 @@ func (g TestGroup) RESTMapper() meta.RESTMapper {
} }
// ExternalGroupVersions returns all external group versions allowed for the server. // ExternalGroupVersions returns all external group versions allowed for the server.
func ExternalGroupVersions() []unversioned.GroupVersion { func ExternalGroupVersions() unversioned.GroupVersions {
versions := []unversioned.GroupVersion{} versions := []unversioned.GroupVersion{}
for _, g := range Groups { for _, g := range Groups {
gv := g.GroupVersion() gv := g.GroupVersion()

View File

@ -179,6 +179,25 @@ func (gv GroupVersion) String() string {
return gv.Version return gv.Version
} }
// KindForGroupVersionKinds identifies the preferred GroupVersionKind out of a list. It returns ok false
// if none of the options match the group. It prefers a match to group and version over just group.
// TODO: Move GroupVersion to a package under pkg/runtime, since it's used by scheme.
// TODO: Introduce an adapter type between GroupVersion and runtime.GroupVersioner, and use LegacyCodec(GroupVersion)
// in fewer places.
func (gv GroupVersion) KindForGroupVersionKinds(kinds []GroupVersionKind) (target GroupVersionKind, ok bool) {
for _, gvk := range kinds {
if gvk.Group == gv.Group && gvk.Version == gv.Version {
return gvk, true
}
}
for _, gvk := range kinds {
if gvk.Group == gv.Group {
return gv.WithKind(gvk.Kind), true
}
}
return GroupVersionKind{}, false
}
// ParseGroupVersion turns "group/version" string into a GroupVersion struct. It reports error // ParseGroupVersion turns "group/version" string into a GroupVersion struct. It reports error
// if it cannot parse the string. // if it cannot parse the string.
func ParseGroupVersion(gv string) (GroupVersion, error) { func ParseGroupVersion(gv string) (GroupVersion, error) {
@ -241,6 +260,25 @@ func (gv *GroupVersion) UnmarshalText(value []byte) error {
return gv.unmarshal(value) return gv.unmarshal(value)
} }
// GroupVersions can be used to represent a set of desired group versions.
// TODO: Move GroupVersions to a package under pkg/runtime, since it's used by scheme.
// TODO: Introduce an adapter type between GroupVersions and runtime.GroupVersioner, and use LegacyCodec(GroupVersion)
// in fewer places.
type GroupVersions []GroupVersion
// KindForGroupVersionKinds identifies the preferred GroupVersionKind out of a list. It returns ok false
// if none of the options match the group.
func (gvs GroupVersions) KindForGroupVersionKinds(kinds []GroupVersionKind) (target GroupVersionKind, ok bool) {
for _, gv := range gvs {
target, ok := gv.KindForGroupVersionKinds(kinds)
if !ok {
continue
}
return target, true
}
return GroupVersionKind{}, false
}
// ToAPIVersionAndKind is a convenience method for satisfying runtime.Object on types that // ToAPIVersionAndKind is a convenience method for satisfying runtime.Object on types that
// do not use TypeMeta. // do not use TypeMeta.
func (gvk *GroupVersionKind) ToAPIVersionAndKind() (string, string) { func (gvk *GroupVersionKind) ToAPIVersionAndKind() (string, string) {

View File

@ -320,7 +320,7 @@ type StripVersionNegotiatedSerializer struct {
runtime.NegotiatedSerializer runtime.NegotiatedSerializer
} }
func (n StripVersionNegotiatedSerializer) EncoderForVersion(encoder runtime.Encoder, gv unversioned.GroupVersion) runtime.Encoder { func (n StripVersionNegotiatedSerializer) EncoderForVersion(encoder runtime.Encoder, gv runtime.GroupVersioner) runtime.Encoder {
serializer, ok := encoder.(runtime.Serializer) serializer, ok := encoder.(runtime.Serializer)
if !ok { if !ok {
// The stripVersionEncoder needs both an encoder and decoder, but is called from a context that doesn't have access to the // The stripVersionEncoder needs both an encoder and decoder, but is called from a context that doesn't have access to the

View File

@ -64,11 +64,11 @@ func (n *fakeNegotiater) StreamingSerializerForMediaType(mediaType string, optio
}, n.streamSerializer != nil }, n.streamSerializer != nil
} }
func (n *fakeNegotiater) EncoderForVersion(serializer runtime.Encoder, gv unversioned.GroupVersion) runtime.Encoder { func (n *fakeNegotiater) EncoderForVersion(serializer runtime.Encoder, gv runtime.GroupVersioner) runtime.Encoder {
return n.serializer return n.serializer
} }
func (n *fakeNegotiater) DecoderToVersion(serializer runtime.Decoder, gv unversioned.GroupVersion) runtime.Decoder { func (n *fakeNegotiater) DecoderToVersion(serializer runtime.Decoder, gv runtime.GroupVersioner) runtime.Decoder {
return n.serializer return n.serializer
} }

View File

@ -60,7 +60,7 @@ func init() {
Scheme, Scheme,
yamlSerializer, yamlSerializer,
yamlSerializer, yamlSerializer,
[]unversioned.GroupVersion{{Version: Version}}, unversioned.GroupVersion{Version: Version},
[]unversioned.GroupVersion{{Version: runtime.APIVersionInternal}}, runtime.InternalGroupVersioner,
) )
} }

View File

@ -213,6 +213,8 @@ type Meta struct {
// KeyNameMapping is an optional function which may map the listed key (field name) // KeyNameMapping is an optional function which may map the listed key (field name)
// into a source and destination value. // into a source and destination value.
KeyNameMapping FieldMappingFunc KeyNameMapping FieldMappingFunc
// Context is an optional field that callers may use to pass info to conversion functions.
Context interface{}
} }
// scope contains information about an ongoing conversion. // scope contains information about an ongoing conversion.

View File

@ -24,7 +24,6 @@ import (
"k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/runtime/serializer/recognizer" "k8s.io/kubernetes/pkg/runtime/serializer/recognizer"
"k8s.io/kubernetes/pkg/runtime/serializer/versioning"
"k8s.io/kubernetes/pkg/storage/storagebackend" "k8s.io/kubernetes/pkg/storage/storagebackend"
"k8s.io/kubernetes/pkg/util/sets" "k8s.io/kubernetes/pkg/util/sets"
@ -262,18 +261,25 @@ func NewStorageCodec(storageMediaType string, ns runtime.StorageSerializer, stor
s = runtime.NewBase64Serializer(s) s = runtime.NewBase64Serializer(s)
} }
encoder := ns.EncoderForVersion(
s,
runtime.NewMultiGroupVersioner(
storageVersion,
unversioned.GroupKind{Group: storageVersion.Group},
unversioned.GroupKind{Group: memoryVersion.Group},
),
)
ds := recognizer.NewDecoder(s, ns.UniversalDeserializer()) ds := recognizer.NewDecoder(s, ns.UniversalDeserializer())
encoder := ns.EncoderForVersion(s, storageVersion) decoder := ns.DecoderToVersion(
decoder := ns.DecoderToVersion(ds, memoryVersion) ds,
if memoryVersion.Group != storageVersion.Group { runtime.NewMultiGroupVersioner(
// Allow this codec to translate between groups. memoryVersion,
if err := versioning.EnableCrossGroupEncoding(encoder, memoryVersion.Group, storageVersion.Group); err != nil { unversioned.GroupKind{Group: memoryVersion.Group},
return nil, fmt.Errorf("error setting up encoder from %v to %v: %v", memoryVersion, storageVersion, err) unversioned.GroupKind{Group: storageVersion.Group},
} ),
if err := versioning.EnableCrossGroupDecoding(decoder, storageVersion.Group, memoryVersion.Group); err != nil { )
return nil, fmt.Errorf("error setting up decoder from %v to %v: %v", storageVersion, memoryVersion, err)
}
}
return runtime.NewCodec(encoder, decoder), nil return runtime.NewCodec(encoder, decoder), nil
} }

View File

@ -42,7 +42,7 @@ type thirdPartyObjectConverter struct {
converter runtime.ObjectConvertor converter runtime.ObjectConvertor
} }
func (t *thirdPartyObjectConverter) ConvertToVersion(in runtime.Object, outVersion unversioned.GroupVersion) (out runtime.Object, err error) { func (t *thirdPartyObjectConverter) ConvertToVersion(in runtime.Object, outVersion runtime.GroupVersioner) (out runtime.Object, err error) {
switch in.(type) { switch in.(type) {
// This seems weird, but in this case the ThirdPartyResourceData is really just a wrapper on the raw 3rd party data. // This seems weird, but in this case the ThirdPartyResourceData is really just a wrapper on the raw 3rd party data.
// The actual thing printed/sent to server is the actual raw third party resource data, which only has one version. // The actual thing printed/sent to server is the actual raw third party resource data, which only has one version.
@ -234,11 +234,11 @@ func (t *thirdPartyResourceDataCodecFactory) StreamingSerializerForMediaType(med
} }
} }
func (t *thirdPartyResourceDataCodecFactory) EncoderForVersion(s runtime.Encoder, gv unversioned.GroupVersion) runtime.Encoder { func (t *thirdPartyResourceDataCodecFactory) EncoderForVersion(s runtime.Encoder, gv runtime.GroupVersioner) runtime.Encoder {
return &thirdPartyResourceDataEncoder{delegate: t.delegate.EncoderForVersion(s, gv), gvk: gv.WithKind(t.kind)} return &thirdPartyResourceDataEncoder{delegate: t.delegate.EncoderForVersion(s, gv), gvk: t.encodeGV.WithKind(t.kind)}
} }
func (t *thirdPartyResourceDataCodecFactory) DecoderToVersion(s runtime.Decoder, gv unversioned.GroupVersion) runtime.Decoder { func (t *thirdPartyResourceDataCodecFactory) DecoderToVersion(s runtime.Decoder, gv runtime.GroupVersioner) runtime.Decoder {
return NewDecoder(t.delegate.DecoderToVersion(s, gv), t.kind) return NewDecoder(t.delegate.DecoderToVersion(s, gv), t.kind)
} }
@ -517,6 +517,10 @@ func (t *thirdPartyResourceDataEncoder) Encode(obj runtime.Object, stream io.Wri
listItems[ix] = json.RawMessage(buff.Bytes()) listItems[ix] = json.RawMessage(buff.Bytes())
} }
if t.gvk.IsEmpty() {
return fmt.Errorf("thirdPartyResourceDataEncoder was not given a target version")
}
encMap := struct { encMap := struct {
Kind string `json:"kind,omitempty"` Kind string `json:"kind,omitempty"`
Items []json.RawMessage `json:"items"` Items []json.RawMessage `json:"items"`

View File

@ -198,3 +198,83 @@ func (s base64Serializer) Decode(data []byte, defaults *unversioned.GroupVersion
} }
return s.Serializer.Decode(out[:n], defaults, into) return s.Serializer.Decode(out[:n], defaults, into)
} }
var (
// InternalGroupVersioner will always prefer the internal version for a given group version kind.
InternalGroupVersioner GroupVersioner = internalGroupVersioner{}
// DisabledGroupVersioner will reject all kinds passed to it.
DisabledGroupVersioner GroupVersioner = disabledGroupVersioner{}
)
type internalGroupVersioner struct{}
// KindForGroupVersionKinds returns an internal Kind if one is found, or converts the first provided kind to the internal version.
func (internalGroupVersioner) KindForGroupVersionKinds(kinds []unversioned.GroupVersionKind) (unversioned.GroupVersionKind, bool) {
for _, kind := range kinds {
if kind.Version == APIVersionInternal {
return kind, true
}
}
for _, kind := range kinds {
return unversioned.GroupVersionKind{Group: kind.Group, Version: APIVersionInternal, Kind: kind.Kind}, true
}
return unversioned.GroupVersionKind{}, false
}
type disabledGroupVersioner struct{}
// KindForGroupVersionKinds returns false for any input.
func (disabledGroupVersioner) KindForGroupVersionKinds(kinds []unversioned.GroupVersionKind) (unversioned.GroupVersionKind, bool) {
return unversioned.GroupVersionKind{}, false
}
// GroupVersioners implements GroupVersioner and resolves to the first exact match for any kind.
type GroupVersioners []GroupVersioner
// KindForGroupVersionKinds returns the first match of any of the group versioners, or false if no match occured.
func (gvs GroupVersioners) KindForGroupVersionKinds(kinds []unversioned.GroupVersionKind) (unversioned.GroupVersionKind, bool) {
for _, gv := range gvs {
target, ok := gv.KindForGroupVersionKinds(kinds)
if !ok {
continue
}
return target, true
}
return unversioned.GroupVersionKind{}, false
}
// Assert that unversioned.GroupVersion and GroupVersions implement GroupVersioner
var _ GroupVersioner = unversioned.GroupVersion{}
var _ GroupVersioner = unversioned.GroupVersions{}
var _ GroupVersioner = multiGroupVersioner{}
type multiGroupVersioner struct {
target unversioned.GroupVersion
acceptedGroupKinds []unversioned.GroupKind
}
// NewMultiGroupVersioner returns the provided group version for any kind that matches one of the provided group kinds.
// Kind may be empty in the provided group kind, in which case any kind will match.
func NewMultiGroupVersioner(gv unversioned.GroupVersion, groupKinds ...unversioned.GroupKind) GroupVersioner {
if len(groupKinds) == 0 || (len(groupKinds) == 1 && groupKinds[0].Group == gv.Group) {
return gv
}
return multiGroupVersioner{target: gv, acceptedGroupKinds: groupKinds}
}
// KindForGroupVersionKinds returns the target group version if any kind matches any of the original group kinds. It will
// use the originating kind where possible.
func (v multiGroupVersioner) KindForGroupVersionKinds(kinds []unversioned.GroupVersionKind) (unversioned.GroupVersionKind, bool) {
for _, src := range kinds {
for _, kind := range v.acceptedGroupKinds {
if kind.Group != src.Group {
continue
}
if len(kind.Kind) > 0 && kind.Kind != src.Kind {
continue
}
return v.target.WithKind(src.Kind), true
}
}
return unversioned.GroupVersionKind{}, false
}

View File

@ -35,7 +35,7 @@ var _ ObjectConvertor = unsafeObjectConvertor{}
// ConvertToVersion converts in to the provided outVersion without copying the input first, which // ConvertToVersion converts in to the provided outVersion without copying the input first, which
// is only safe if the output object is not mutated or reused. // is only safe if the output object is not mutated or reused.
func (c unsafeObjectConvertor) ConvertToVersion(in Object, outVersion unversioned.GroupVersion) (Object, error) { func (c unsafeObjectConvertor) ConvertToVersion(in Object, outVersion GroupVersioner) (Object, error) {
return c.Scheme.UnsafeConvertToVersion(in, outVersion) return c.Scheme.UnsafeConvertToVersion(in, outVersion)
} }

View File

@ -30,12 +30,23 @@ const (
APIVersionInternal = "__internal" APIVersionInternal = "__internal"
) )
// GroupVersioner refines a set of possible conversion targets into a single option.
type GroupVersioner interface {
// KindForGroupVersionKinds returns a desired target group version kind for the given input, or returns ok false if no
// target is known. In general, if the return target is not in the input list, the caller is expected to invoke
// Scheme.New(target) and then perform a conversion between the current Go type and the destination Go type.
// Sophisticated implementations may use additional information about the input kinds to pick a destination kind.
KindForGroupVersionKinds(kinds []unversioned.GroupVersionKind) (target unversioned.GroupVersionKind, ok bool)
}
// Encoders write objects to a serialized form
type Encoder interface { type Encoder interface {
// Encode writes an object to a stream. Implementations may return errors if the versions are // Encode writes an object to a stream. Implementations may return errors if the versions are
// incompatible, or if no conversion is defined. // incompatible, or if no conversion is defined.
Encode(obj Object, w io.Writer) error Encode(obj Object, w io.Writer) error
} }
// Decoders attempt to load an object from data.
type Decoder interface { type Decoder interface {
// Decode attempts to deserialize the provided data using either the innate typing of the scheme or the // 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 // default kind, group, and version provided. It returns a decoded object as well as the kind, group, and
@ -117,12 +128,10 @@ type NegotiatedSerializer interface {
// EncoderForVersion returns an encoder that ensures objects being written to the provided // EncoderForVersion returns an encoder that ensures objects being written to the provided
// serializer are in the provided group version. // serializer are in the provided group version.
// TODO: take multiple group versions EncoderForVersion(serializer Encoder, gv GroupVersioner) Encoder
EncoderForVersion(serializer Encoder, gv unversioned.GroupVersion) Encoder
// DecoderForVersion returns a decoder that ensures objects being read by the provided // DecoderForVersion returns a decoder that ensures objects being read by the provided
// serializer are in the provided group version by default. // serializer are in the provided group version by default.
// TODO: take multiple group versions DecoderToVersion(serializer Decoder, gv GroupVersioner) Decoder
DecoderToVersion(serializer Decoder, gv unversioned.GroupVersion) Decoder
} }
// StorageSerializer is an interface used for obtaining encoders, decoders, and serializers // StorageSerializer is an interface used for obtaining encoders, decoders, and serializers
@ -139,19 +148,17 @@ type StorageSerializer interface {
// EncoderForVersion returns an encoder that ensures objects being written to the provided // EncoderForVersion returns an encoder that ensures objects being written to the provided
// serializer are in the provided group version. // serializer are in the provided group version.
// TODO: take multiple group versions EncoderForVersion(serializer Encoder, gv GroupVersioner) Encoder
EncoderForVersion(serializer Encoder, gv unversioned.GroupVersion) Encoder
// DecoderForVersion returns a decoder that ensures objects being read by the provided // DecoderForVersion returns a decoder that ensures objects being read by the provided
// serializer are in the provided group version by default. // serializer are in the provided group version by default.
// TODO: take multiple group versions DecoderToVersion(serializer Decoder, gv GroupVersioner) Decoder
DecoderToVersion(serializer Decoder, gv unversioned.GroupVersion) Decoder
} }
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
// Non-codec interfaces // Non-codec interfaces
type ObjectVersioner interface { type ObjectVersioner interface {
ConvertToVersion(in Object, outVersion unversioned.GroupVersion) (out Object, err error) ConvertToVersion(in Object, gv GroupVersioner) (out Object, err error)
} }
// ObjectConvertor converts an object to a different version. // ObjectConvertor converts an object to a different version.
@ -161,7 +168,7 @@ type ObjectConvertor interface {
Convert(in, out interface{}) error Convert(in, out interface{}) error
// ConvertToVersion takes the provided object and converts it the provided version. This // ConvertToVersion takes the provided object and converts it the provided version. This
// method does not guarantee that the in object is not mutated. // method does not guarantee that the in object is not mutated.
ConvertToVersion(in Object, outVersion unversioned.GroupVersion) (out Object, err error) ConvertToVersion(in Object, gv GroupVersioner) (out Object, err error)
ConvertFieldLabel(version, kind, label, value string) (string, string, error) ConvertFieldLabel(version, kind, label, value string) (string, string, error)
} }

View File

@ -237,7 +237,7 @@ func (s *Scheme) ObjectKinds(obj Object) ([]unversioned.GroupVersionKind, bool,
gvks, ok := s.typeToGVK[t] gvks, ok := s.typeToGVK[t]
if !ok { if !ok {
return nil, false, &notRegisteredErr{t: t} return nil, false, NewNotRegisteredErr(unversioned.GroupVersionKind{}, t)
} }
_, unversionedType := s.unversionedTypes[t] _, unversionedType := s.unversionedTypes[t]
@ -275,7 +275,7 @@ func (s *Scheme) New(kind unversioned.GroupVersionKind) (Object, error) {
if t, exists := s.unversionedKinds[kind.Kind]; exists { if t, exists := s.unversionedKinds[kind.Kind]; exists {
return reflect.New(t).Interface().(Object), nil return reflect.New(t).Interface().(Object), nil
} }
return nil, &notRegisteredErr{gvk: kind} return nil, NewNotRegisteredErr(kind, nil)
} }
// AddGenericConversionFunc adds a function that accepts the ConversionFunc call pattern // AddGenericConversionFunc adds a function that accepts the ConversionFunc call pattern
@ -438,23 +438,13 @@ func (s *Scheme) DeepCopy(src interface{}) (interface{}, error) {
// Convert will attempt to convert in into out. Both must be pointers. For easy // Convert will attempt to convert in into out. Both must be pointers. For easy
// testing of conversion functions. Returns an error if the conversion isn't // testing of conversion functions. Returns an error if the conversion isn't
// possible. You can call this with types that haven't been registered (for example, // possible. You can call this with types that haven't been registered (for example,
// a to test conversion of types that are nested within registered types), but in // a to test conversion of types that are nested within registered types). The
// that case, the conversion.Scope object passed to your conversion functions won't // context interface is passed to the convertor.
// have SrcVersion or DestVersion fields set correctly in Meta(). // TODO: identify whether context should be hidden, or behind a formal context/scope
func (s *Scheme) Convert(in, out interface{}) error { // interface
inVersion := unversioned.GroupVersion{Group: "unknown", Version: "unknown"} func (s *Scheme) Convert(in, out interface{}, context interface{}) error {
outVersion := unversioned.GroupVersion{Group: "unknown", Version: "unknown"} flags, meta := s.generateConvertMeta(in)
if inObj, ok := in.(Object); ok { meta.Context = context
if gvks, _, err := s.ObjectKinds(inObj); err == nil {
inVersion = gvks[0].GroupVersion()
}
}
if outObj, ok := out.(Object); ok {
if gvks, _, err := s.ObjectKinds(outObj); err == nil {
outVersion = gvks[0].GroupVersion()
}
}
flags, meta := s.generateConvertMeta(inVersion, outVersion, in)
if flags == 0 { if flags == 0 {
flags = conversion.AllowDifferentFieldTypeNames flags = conversion.AllowDifferentFieldTypeNames
} }
@ -478,73 +468,20 @@ func (s *Scheme) ConvertFieldLabel(version, kind, label, value string) (string,
// version within this scheme. Will return an error if the provided version does not // version within this scheme. Will return an error if the provided version does not
// contain the inKind (or a mapping by name defined with AddKnownTypeWithName). Will also // contain the inKind (or a mapping by name defined with AddKnownTypeWithName). Will also
// return an error if the conversion does not result in a valid Object being // return an error if the conversion does not result in a valid Object being
// returned. The serializer handles loading/serializing nested objects. // returned. Passes target down to the conversion methods as the Context on the scope.
func (s *Scheme) ConvertToVersion(in Object, outVersion unversioned.GroupVersion) (Object, error) { func (s *Scheme) ConvertToVersion(in Object, target GroupVersioner) (Object, error) {
switch in.(type) { return s.convertToVersion(true, in, target)
case *Unknown, *Unstructured, *UnstructuredList:
old := in.GetObjectKind().GroupVersionKind()
defer in.GetObjectKind().SetGroupVersionKind(old)
setTargetVersion(in, s, outVersion)
return in, nil
}
t := reflect.TypeOf(in)
if t.Kind() != reflect.Ptr {
return nil, fmt.Errorf("only pointer types may be converted: %v", t)
}
t = t.Elem()
if t.Kind() != reflect.Struct {
return nil, fmt.Errorf("only pointers to struct types may be converted: %v", t)
}
var kind unversioned.GroupVersionKind
if unversionedKind, ok := s.unversionedTypes[t]; ok {
kind = unversionedKind
} else {
kinds, ok := s.typeToGVK[t]
if !ok || len(kinds) == 0 {
return nil, fmt.Errorf("%v is not a registered type and cannot be converted into version %q", t, outVersion)
}
kind = kinds[0]
}
outKind := outVersion.WithKind(kind.Kind)
inKinds, _, err := s.ObjectKinds(in)
if err != nil {
return nil, err
}
out, err := s.New(outKind)
if err != nil {
return nil, err
}
flags, meta := s.generateConvertMeta(inKinds[0].GroupVersion(), outVersion, in)
if err := s.converter.Convert(in, out, flags, meta); err != nil {
return nil, err
}
setTargetVersion(out, s, outVersion)
return out, nil
} }
// UnsafeConvertToVersion will convert in to the provided outVersion if such a conversion is possible, // UnsafeConvertToVersion will convert in to the provided target if such a conversion is possible,
// but does not guarantee the output object does not share fields with the input object. It attempts to be as // but does not guarantee the output object does not share fields with the input object. It attempts to be as
// efficient as possible when doing conversion. // efficient as possible when doing conversion.
func (s *Scheme) UnsafeConvertToVersion(in Object, outVersion unversioned.GroupVersion) (Object, error) { func (s *Scheme) UnsafeConvertToVersion(in Object, target GroupVersioner) (Object, error) {
switch t := in.(type) { return s.convertToVersion(false, in, target)
case *Unknown: }
t.APIVersion = outVersion.String()
return t, nil
case *Unstructured:
t.SetAPIVersion(outVersion.String())
return t, nil
case *UnstructuredList:
t.SetAPIVersion(outVersion.String())
return t, nil
}
// convertToVersion handles conversion with an optional copy.
func (s *Scheme) convertToVersion(copy bool, in Object, target GroupVersioner) (Object, error) {
// determine the incoming kinds with as few allocations as possible. // determine the incoming kinds with as few allocations as possible.
t := reflect.TypeOf(in) t := reflect.TypeOf(in)
if t.Kind() != reflect.Ptr { if t.Kind() != reflect.Ptr {
@ -556,64 +493,69 @@ func (s *Scheme) UnsafeConvertToVersion(in Object, outVersion unversioned.GroupV
} }
kinds, ok := s.typeToGVK[t] kinds, ok := s.typeToGVK[t]
if !ok || len(kinds) == 0 { if !ok || len(kinds) == 0 {
return nil, fmt.Errorf("%v is not a registered type and cannot be converted into version %q", t, outVersion) return nil, NewNotRegisteredErr(unversioned.GroupVersionKind{}, t)
} }
// if the Go type is also registered to the destination kind, no conversion is necessary gvk, ok := target.KindForGroupVersionKinds(kinds)
for i := range kinds { if !ok {
if kinds[i].Version == outVersion.Version && kinds[i].Group == outVersion.Group { // TODO: should this be a typed error?
setTargetKind(in, kinds[i]) return nil, fmt.Errorf("%v is not suitable for converting to %q", t, target)
return in, nil }
// target wants to use the existing type, set kind and return (no conversion necessary)
for _, kind := range kinds {
if gvk == kind {
return copyAndSetTargetKind(copy, s, in, gvk)
} }
} }
// type is unversioned, no conversion necessary // type is unversioned, no conversion necessary
// it should be possible to avoid this allocation
if unversionedKind, ok := s.unversionedTypes[t]; ok { if unversionedKind, ok := s.unversionedTypes[t]; ok {
kind := unversionedKind if gvk, ok := target.KindForGroupVersionKinds([]unversioned.GroupVersionKind{unversionedKind}); ok {
outKind := outVersion.WithKind(kind.Kind) return copyAndSetTargetKind(copy, s, in, gvk)
setTargetKind(in, outKind) }
return in, nil return copyAndSetTargetKind(copy, s, in, unversionedKind)
} }
// allocate a new object as the target using the target kind out, err := s.New(gvk)
// TODO: this should look in the target group version and find the first kind that matches, rather than the
// first kind registered in typeToGVK
kind := kinds[0]
kind.Version = outVersion.Version
kind.Group = outVersion.Group
out, err := s.New(kind)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// TODO: try to avoid the allocations here - in fast paths we are not likely to need these flags or meta if copy {
flags, meta := s.converter.DefaultMeta(t) copied, err := s.Copy(in)
if err != nil {
return nil, err
}
in = copied
}
flags, meta := s.generateConvertMeta(in)
meta.Context = target
if err := s.converter.Convert(in, out, flags, meta); err != nil { if err := s.converter.Convert(in, out, flags, meta); err != nil {
return nil, err return nil, err
} }
setTargetKind(out, kind) setTargetKind(out, gvk)
return out, nil return out, nil
} }
// generateConvertMeta constructs the meta value we pass to Convert. // generateConvertMeta constructs the meta value we pass to Convert.
func (s *Scheme) generateConvertMeta(srcGroupVersion, destGroupVersion unversioned.GroupVersion, in interface{}) (conversion.FieldMatchingFlags, *conversion.Meta) { func (s *Scheme) generateConvertMeta(in interface{}) (conversion.FieldMatchingFlags, *conversion.Meta) {
return s.converter.DefaultMeta(reflect.TypeOf(in)) return s.converter.DefaultMeta(reflect.TypeOf(in))
} }
// setTargetVersion is deprecated and should be replaced by use of setTargetKind // copyAndSetTargetKind performs a conditional copy before returning the object, or an error if copy was not successful.
func setTargetVersion(obj Object, raw *Scheme, gv unversioned.GroupVersion) { func copyAndSetTargetKind(copy bool, copier ObjectCopier, obj Object, kind unversioned.GroupVersionKind) (Object, error) {
if gv.Version == APIVersionInternal { if copy {
// internal is a special case copied, err := copier.Copy(obj)
obj.GetObjectKind().SetGroupVersionKind(unversioned.GroupVersionKind{}) if err != nil {
return return nil, err
} }
if gvks, _, _ := raw.ObjectKinds(obj); len(gvks) > 0 { obj = copied
obj.GetObjectKind().SetGroupVersionKind(unversioned.GroupVersionKind{Group: gv.Group, Version: gv.Version, Kind: gvks[0].Kind})
} else {
obj.GetObjectKind().SetGroupVersionKind(unversioned.GroupVersionKind{Group: gv.Group, Version: gv.Version})
} }
setTargetKind(obj, kind)
return obj, nil
} }
// setTargetKind sets the kind on an object, taking into account whether the target kind is the internal version. // setTargetKind sets the kind on an object, taking into account whether the target kind is the internal version.

View File

@ -18,6 +18,7 @@ package runtime_test
import ( import (
"reflect" "reflect"
"strings"
"testing" "testing"
"github.com/google/gofuzz" "github.com/google/gofuzz"
@ -460,6 +461,16 @@ type ExternalInternalSame struct {
A TestType2 `json:"A,omitempty"` A TestType2 `json:"A,omitempty"`
} }
type UnversionedType struct {
MyWeirdCustomEmbeddedVersionKindField `json:",inline"`
A string `json:"A,omitempty"`
}
type UnknownType struct {
MyWeirdCustomEmbeddedVersionKindField `json:",inline"`
A string `json:"A,omitempty"`
}
func (obj *MyWeirdCustomEmbeddedVersionKindField) GetObjectKind() unversioned.ObjectKind { return obj } func (obj *MyWeirdCustomEmbeddedVersionKindField) GetObjectKind() unversioned.ObjectKind { return obj }
func (obj *MyWeirdCustomEmbeddedVersionKindField) SetGroupVersionKind(gvk unversioned.GroupVersionKind) { func (obj *MyWeirdCustomEmbeddedVersionKindField) SetGroupVersionKind(gvk unversioned.GroupVersionKind) {
obj.APIVersion, obj.ObjectKind = gvk.ToAPIVersionAndKind() obj.APIVersion, obj.ObjectKind = gvk.ToAPIVersionAndKind()
@ -500,6 +511,8 @@ var TestObjectFuzzer = fuzz.New().NilChance(.5).NumElements(1, 100).Funcs(
func GetTestScheme() *runtime.Scheme { func GetTestScheme() *runtime.Scheme {
internalGV := unversioned.GroupVersion{Version: "__internal"} internalGV := unversioned.GroupVersion{Version: "__internal"}
externalGV := unversioned.GroupVersion{Version: "v1"} externalGV := unversioned.GroupVersion{Version: "v1"}
alternateExternalGV := unversioned.GroupVersion{Group: "custom", Version: "v1"}
differentExternalGV := unversioned.GroupVersion{Group: "other", Version: "v2"}
s := runtime.NewScheme() s := runtime.NewScheme()
// Ordinarily, we wouldn't add TestType2, but because this is a test and // Ordinarily, we wouldn't add TestType2, but because this is a test and
@ -511,6 +524,11 @@ func GetTestScheme() *runtime.Scheme {
s.AddKnownTypeWithName(externalGV.WithKind("TestType2"), &ExternalTestType2{}) s.AddKnownTypeWithName(externalGV.WithKind("TestType2"), &ExternalTestType2{})
s.AddKnownTypeWithName(internalGV.WithKind("TestType3"), &TestType1{}) s.AddKnownTypeWithName(internalGV.WithKind("TestType3"), &TestType1{})
s.AddKnownTypeWithName(externalGV.WithKind("TestType3"), &ExternalTestType1{}) s.AddKnownTypeWithName(externalGV.WithKind("TestType3"), &ExternalTestType1{})
s.AddKnownTypeWithName(externalGV.WithKind("TestType4"), &ExternalTestType1{})
s.AddKnownTypeWithName(alternateExternalGV.WithKind("TestType3"), &ExternalTestType1{})
s.AddKnownTypeWithName(alternateExternalGV.WithKind("TestType5"), &ExternalTestType1{})
s.AddKnownTypeWithName(differentExternalGV.WithKind("TestType1"), &ExternalTestType1{})
s.AddUnversionedTypes(externalGV, &UnversionedType{})
return s return s
} }
@ -528,7 +546,7 @@ func TestKnownTypes(t *testing.T) {
} }
} }
func TestConvertToVersion(t *testing.T) { func TestConvertToVersionBasic(t *testing.T) {
s := GetTestScheme() s := GetTestScheme()
tt := &TestType1{A: "I'm not a pointer object"} tt := &TestType1{A: "I'm not a pointer object"}
other, err := s.ConvertToVersion(tt, unversioned.GroupVersion{Version: "v1"}) other, err := s.ConvertToVersion(tt, unversioned.GroupVersion{Version: "v1"})
@ -537,13 +555,258 @@ func TestConvertToVersion(t *testing.T) {
} }
converted, ok := other.(*ExternalTestType1) converted, ok := other.(*ExternalTestType1)
if !ok { if !ok {
t.Fatalf("Got wrong type") t.Fatalf("Got wrong type: %T", other)
} }
if tt.A != converted.A { if tt.A != converted.A {
t.Fatalf("Failed to convert object correctly: %#v", converted) t.Fatalf("Failed to convert object correctly: %#v", converted)
} }
} }
type testGroupVersioner struct {
target unversioned.GroupVersionKind
ok bool
}
func (m testGroupVersioner) KindForGroupVersionKinds(kinds []unversioned.GroupVersionKind) (unversioned.GroupVersionKind, bool) {
return m.target, m.ok
}
func TestConvertToVersion(t *testing.T) {
testCases := []struct {
scheme *runtime.Scheme
in runtime.Object
gv runtime.GroupVersioner
same bool
out runtime.Object
errFn func(error) bool
}{
// errors if the type is not registered in the scheme
{
scheme: GetTestScheme(),
in: &UnknownType{},
errFn: func(err error) bool { return err != nil && runtime.IsNotRegisteredError(err) },
},
// errors if the group versioner returns no target
{
scheme: GetTestScheme(),
in: &ExternalTestType1{A: "test"},
gv: testGroupVersioner{},
errFn: func(err error) bool {
return err != nil && strings.Contains(err.Error(), "is not suitable for converting")
},
},
// converts to internal
{
scheme: GetTestScheme(),
in: &ExternalTestType1{A: "test"},
gv: unversioned.GroupVersion{Version: "__internal"},
out: &TestType1{A: "test"},
},
// prefers the first group version in the list
{
scheme: GetTestScheme(),
in: &ExternalTestType1{A: "test"},
gv: unversioned.GroupVersions{{Version: "__internal"}, {Version: "v1"}},
out: &TestType1{A: "test"},
},
// unversioned type returned as-is
{
scheme: GetTestScheme(),
in: &UnversionedType{A: "test"},
gv: unversioned.GroupVersions{{Version: "v1"}},
same: true,
out: &UnversionedType{
MyWeirdCustomEmbeddedVersionKindField: MyWeirdCustomEmbeddedVersionKindField{APIVersion: "v1", ObjectKind: "UnversionedType"},
A: "test",
},
},
// detected as already being in the target version
{
scheme: GetTestScheme(),
in: &ExternalTestType1{A: "test"},
gv: unversioned.GroupVersions{{Version: "v1"}},
same: true,
out: &ExternalTestType1{
MyWeirdCustomEmbeddedVersionKindField: MyWeirdCustomEmbeddedVersionKindField{APIVersion: "v1", ObjectKind: "TestType1"},
A: "test",
},
},
// detected as already being in the first target version
{
scheme: GetTestScheme(),
in: &ExternalTestType1{A: "test"},
gv: unversioned.GroupVersions{{Version: "v1"}, {Version: "__internal"}},
same: true,
out: &ExternalTestType1{
MyWeirdCustomEmbeddedVersionKindField: MyWeirdCustomEmbeddedVersionKindField{APIVersion: "v1", ObjectKind: "TestType1"},
A: "test",
},
},
// detected as already being in the first target version
{
scheme: GetTestScheme(),
in: &ExternalTestType1{A: "test"},
gv: unversioned.GroupVersions{{Version: "v1"}, {Version: "__internal"}},
same: true,
out: &ExternalTestType1{
MyWeirdCustomEmbeddedVersionKindField: MyWeirdCustomEmbeddedVersionKindField{APIVersion: "v1", ObjectKind: "TestType1"},
A: "test",
},
},
// the external type is registered in multiple groups, versions, and kinds, and can be targeted to all of them (1/3): different kind
{
scheme: GetTestScheme(),
in: &ExternalTestType1{A: "test"},
gv: testGroupVersioner{ok: true, target: unversioned.GroupVersionKind{Kind: "TestType3", Version: "v1"}},
same: true,
out: &ExternalTestType1{
MyWeirdCustomEmbeddedVersionKindField: MyWeirdCustomEmbeddedVersionKindField{APIVersion: "v1", ObjectKind: "TestType3"},
A: "test",
},
},
// the external type is registered in multiple groups, versions, and kinds, and can be targeted to all of them (2/3): different gv
{
scheme: GetTestScheme(),
in: &ExternalTestType1{A: "test"},
gv: testGroupVersioner{ok: true, target: unversioned.GroupVersionKind{Kind: "TestType3", Group: "custom", Version: "v1"}},
same: true,
out: &ExternalTestType1{
MyWeirdCustomEmbeddedVersionKindField: MyWeirdCustomEmbeddedVersionKindField{APIVersion: "custom/v1", ObjectKind: "TestType3"},
A: "test",
},
},
// the external type is registered in multiple groups, versions, and kinds, and can be targeted to all of them (3/3): different gvk
{
scheme: GetTestScheme(),
in: &ExternalTestType1{A: "test"},
gv: testGroupVersioner{ok: true, target: unversioned.GroupVersionKind{Group: "custom", Version: "v1", Kind: "TestType5"}},
same: true,
out: &ExternalTestType1{
MyWeirdCustomEmbeddedVersionKindField: MyWeirdCustomEmbeddedVersionKindField{APIVersion: "custom/v1", ObjectKind: "TestType5"},
A: "test",
},
},
// multi group versioner recognizes multiple groups and forces the output to a particular version, copies because version differs
{
scheme: GetTestScheme(),
in: &ExternalTestType1{A: "test"},
gv: runtime.NewMultiGroupVersioner(unversioned.GroupVersion{Group: "other", Version: "v2"}, unversioned.GroupKind{Group: "custom", Kind: "TestType3"}, unversioned.GroupKind{Kind: "TestType1"}),
out: &ExternalTestType1{
MyWeirdCustomEmbeddedVersionKindField: MyWeirdCustomEmbeddedVersionKindField{APIVersion: "other/v2", ObjectKind: "TestType1"},
A: "test",
},
},
// multi group versioner recognizes multiple groups and forces the output to a particular version, copies because version differs
{
scheme: GetTestScheme(),
in: &ExternalTestType1{A: "test"},
gv: runtime.NewMultiGroupVersioner(unversioned.GroupVersion{Group: "other", Version: "v2"}, unversioned.GroupKind{Kind: "TestType1"}, unversioned.GroupKind{Group: "custom", Kind: "TestType3"}),
out: &ExternalTestType1{
MyWeirdCustomEmbeddedVersionKindField: MyWeirdCustomEmbeddedVersionKindField{APIVersion: "other/v2", ObjectKind: "TestType1"},
A: "test",
},
},
// multi group versioner is unable to find a match when kind AND group don't match (there is no TestType1 kind in group "other", and no kind "TestType5" in the default group)
{
scheme: GetTestScheme(),
in: &TestType1{A: "test"},
gv: runtime.NewMultiGroupVersioner(unversioned.GroupVersion{Group: "custom", Version: "v1"}, unversioned.GroupKind{Group: "other"}, unversioned.GroupKind{Kind: "TestType5"}),
errFn: func(err error) bool {
return err != nil && strings.Contains(err.Error(), "is not suitable for converting")
},
},
// multi group versioner recognizes multiple groups and forces the output to a particular version, performs no copy
{
scheme: GetTestScheme(),
in: &ExternalTestType1{A: "test"},
gv: runtime.NewMultiGroupVersioner(unversioned.GroupVersion{Group: "", Version: "v1"}, unversioned.GroupKind{Group: "custom", Kind: "TestType3"}, unversioned.GroupKind{Kind: "TestType1"}),
same: true,
out: &ExternalTestType1{
MyWeirdCustomEmbeddedVersionKindField: MyWeirdCustomEmbeddedVersionKindField{APIVersion: "v1", ObjectKind: "TestType1"},
A: "test",
},
},
// multi group versioner recognizes multiple groups and forces the output to a particular version, performs no copy
{
scheme: GetTestScheme(),
in: &ExternalTestType1{A: "test"},
gv: runtime.NewMultiGroupVersioner(unversioned.GroupVersion{Group: "", Version: "v1"}, unversioned.GroupKind{Kind: "TestType1"}, unversioned.GroupKind{Group: "custom", Kind: "TestType3"}),
same: true,
out: &ExternalTestType1{
MyWeirdCustomEmbeddedVersionKindField: MyWeirdCustomEmbeddedVersionKindField{APIVersion: "v1", ObjectKind: "TestType1"},
A: "test",
},
},
// group versioner can choose a particular target kind for a given input when kind is the same across group versions
{
scheme: GetTestScheme(),
in: &TestType1{A: "test"},
gv: testGroupVersioner{ok: true, target: unversioned.GroupVersionKind{Version: "v1", Kind: "TestType3"}},
out: &ExternalTestType1{
MyWeirdCustomEmbeddedVersionKindField: MyWeirdCustomEmbeddedVersionKindField{APIVersion: "v1", ObjectKind: "TestType3"},
A: "test",
},
},
// group versioner can choose a different kind
{
scheme: GetTestScheme(),
in: &TestType1{A: "test"},
gv: testGroupVersioner{ok: true, target: unversioned.GroupVersionKind{Kind: "TestType5", Group: "custom", Version: "v1"}},
out: &ExternalTestType1{
MyWeirdCustomEmbeddedVersionKindField: MyWeirdCustomEmbeddedVersionKindField{APIVersion: "custom/v1", ObjectKind: "TestType5"},
A: "test",
},
},
}
for i, test := range testCases {
original, _ := test.scheme.DeepCopy(test.in)
out, err := test.scheme.ConvertToVersion(test.in, test.gv)
switch {
case test.errFn != nil:
if !test.errFn(err) {
t.Errorf("%d: unexpected error: %v", i, err)
}
continue
case err != nil:
t.Errorf("%d: unexpected error: %v", i, err)
continue
}
if out == test.in {
t.Errorf("%d: ConvertToVersion should always copy out: %#v", i, out)
continue
}
if test.same {
if !reflect.DeepEqual(original, test.in) {
t.Errorf("%d: unexpected mutation of input: %s", i, diff.ObjectReflectDiff(original, test.in))
continue
}
if !reflect.DeepEqual(out, test.out) {
t.Errorf("%d: unexpected out: %s", i, diff.ObjectReflectDiff(out, test.out))
continue
}
unsafe, err := test.scheme.UnsafeConvertToVersion(test.in, test.gv)
if err != nil {
t.Errorf("%d: unexpected error: %v", i, err)
continue
}
if !reflect.DeepEqual(unsafe, test.out) {
t.Errorf("%d: unexpected unsafe: %s", i, diff.ObjectReflectDiff(unsafe, test.out))
continue
}
if unsafe != test.in {
t.Errorf("%d: UnsafeConvertToVersion should return same object: %#v", i, unsafe)
continue
}
continue
}
if !reflect.DeepEqual(out, test.out) {
t.Errorf("%d: unexpected out: %s", i, diff.ObjectReflectDiff(out, test.out))
continue
}
}
}
func TestMetaValues(t *testing.T) { func TestMetaValues(t *testing.T) {
internalGV := unversioned.GroupVersion{Group: "test.group", Version: "__internal"} internalGV := unversioned.GroupVersion{Group: "test.group", Version: "__internal"}
externalGV := unversioned.GroupVersion{Group: "test.group", Version: "externalVersion"} externalGV := unversioned.GroupVersion{Group: "test.group", Version: "externalVersion"}

View File

@ -188,13 +188,17 @@ func (f CodecFactory) SupportedStreamingMediaTypes() []string {
return f.streamingAccepts return f.streamingAccepts
} }
// LegacyCodec encodes output to a given API version, and decodes output into the internal form from // LegacyCodec encodes output to a given API versions, and decodes output into the internal form from
// any recognized source. The returned codec will always encode output to JSON. // any recognized source. The returned codec will always encode output to JSON. If a type is not
// found in the list of versions an error will be returned.
// //
// This method is deprecated - clients and servers should negotiate a serializer by mime-type and // 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(). // invoke CodecForVersions. Callers that need only to read data should use UniversalDecoder().
//
// TODO: make this call exist only in pkg/api, and initialize it with the set of default versions.
// All other callers will be forced to request a Codec directly.
func (f CodecFactory) LegacyCodec(version ...unversioned.GroupVersion) runtime.Codec { func (f CodecFactory) LegacyCodec(version ...unversioned.GroupVersion) runtime.Codec {
return versioning.NewCodecForScheme(f.scheme, f.legacySerializer, f.universal, version, nil) return versioning.NewCodecForScheme(f.scheme, f.legacySerializer, f.universal, unversioned.GroupVersions(version), runtime.InternalGroupVersioner)
} }
// UniversalDeserializer can convert any stored data recognized by this factory into a Go object that satisfies // UniversalDeserializer can convert any stored data recognized by this factory into a Go object that satisfies
@ -211,25 +215,39 @@ func (f CodecFactory) UniversalDeserializer() runtime.Decoder {
// defaulting. // defaulting.
// //
// TODO: the decoder will eventually be removed in favor of dealing with objects in their versioned form // TODO: the decoder will eventually be removed in favor of dealing with objects in their versioned form
// TODO: only accept a group versioner
func (f CodecFactory) UniversalDecoder(versions ...unversioned.GroupVersion) runtime.Decoder { func (f CodecFactory) UniversalDecoder(versions ...unversioned.GroupVersion) runtime.Decoder {
return f.CodecForVersions(nil, f.universal, nil, versions) var versioner runtime.GroupVersioner
if len(versions) == 0 {
versioner = runtime.InternalGroupVersioner
} else {
versioner = unversioned.GroupVersions(versions)
}
return f.CodecForVersions(nil, f.universal, nil, versioner)
} }
// CodecForVersions creates a codec with the provided serializer. If an object is decoded and its group is not in the list, // CodecForVersions 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 // 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. // converted. If encode or decode are nil, no conversion is performed.
func (f CodecFactory) CodecForVersions(encoder runtime.Encoder, decoder runtime.Decoder, encode []unversioned.GroupVersion, decode []unversioned.GroupVersion) runtime.Codec { func (f CodecFactory) CodecForVersions(encoder runtime.Encoder, decoder runtime.Decoder, encode runtime.GroupVersioner, decode runtime.GroupVersioner) runtime.Codec {
// TODO: these are for backcompat, remove them in the future
if encode == nil {
encode = runtime.DisabledGroupVersioner
}
if decode == nil {
decode = runtime.InternalGroupVersioner
}
return versioning.NewCodecForScheme(f.scheme, encoder, decoder, encode, decode) return versioning.NewCodecForScheme(f.scheme, encoder, decoder, encode, decode)
} }
// DecoderToVersion returns a decoder that targets the provided group version. // DecoderToVersion returns a decoder that targets the provided group version.
func (f CodecFactory) DecoderToVersion(decoder runtime.Decoder, gv unversioned.GroupVersion) runtime.Decoder { func (f CodecFactory) DecoderToVersion(decoder runtime.Decoder, gv runtime.GroupVersioner) runtime.Decoder {
return f.CodecForVersions(nil, decoder, nil, []unversioned.GroupVersion{gv}) return f.CodecForVersions(nil, decoder, nil, gv)
} }
// EncoderForVersion returns an encoder that targets the provided group version. // EncoderForVersion returns an encoder that targets the provided group version.
func (f CodecFactory) EncoderForVersion(encoder runtime.Encoder, gv unversioned.GroupVersion) runtime.Encoder { func (f CodecFactory) EncoderForVersion(encoder runtime.Encoder, gv runtime.GroupVersioner) runtime.Encoder {
return f.CodecForVersions(encoder, nil, []unversioned.GroupVersion{gv}, nil) return f.CodecForVersions(encoder, nil, gv, nil)
} }
// SerializerForMediaType returns a serializer that matches the provided RFC2046 mediaType, or false if no such // SerializerForMediaType returns a serializer that matches the provided RFC2046 mediaType, or false if no such
@ -317,7 +335,7 @@ type DirectCodecFactory struct {
} }
// EncoderForVersion returns an encoder that does not do conversion. gv is ignored. // EncoderForVersion returns an encoder that does not do conversion. gv is ignored.
func (f DirectCodecFactory) EncoderForVersion(serializer runtime.Encoder, gv unversioned.GroupVersion) runtime.Encoder { func (f DirectCodecFactory) EncoderForVersion(serializer runtime.Encoder, _ runtime.GroupVersioner) runtime.Encoder {
return DirectCodec{ return DirectCodec{
runtime.NewCodec(serializer, nil), runtime.NewCodec(serializer, nil),
f.CodecFactory.scheme, f.CodecFactory.scheme,
@ -325,7 +343,7 @@ func (f DirectCodecFactory) EncoderForVersion(serializer runtime.Encoder, gv unv
} }
// DecoderToVersion returns an decoder that does not do conversion. gv is ignored. // DecoderToVersion returns an decoder that does not do conversion. gv is ignored.
func (f DirectCodecFactory) DecoderToVersion(serializer runtime.Decoder, gv unversioned.GroupVersion) runtime.Decoder { func (f DirectCodecFactory) DecoderToVersion(serializer runtime.Decoder, _ runtime.GroupVersioner) runtime.Decoder {
return DirectCodec{ return DirectCodec{
runtime.NewCodec(nil, serializer), runtime.NewCodec(nil, serializer),
nil, nil,

View File

@ -254,7 +254,7 @@ func TestVersionedEncoding(t *testing.T) {
cf := newCodecFactory(s, newSerializersForScheme(s, testMetaFactory{})) cf := newCodecFactory(s, newSerializersForScheme(s, testMetaFactory{}))
encoder, _ := cf.SerializerForFileExtension("json") encoder, _ := cf.SerializerForFileExtension("json")
codec := cf.CodecForVersions(encoder, nil, []unversioned.GroupVersion{{Version: "v2"}}, nil) codec := cf.CodecForVersions(encoder, nil, unversioned.GroupVersion{Version: "v2"}, nil)
out, err := runtime.Encode(codec, &TestType1{}) out, err := runtime.Encode(codec, &TestType1{})
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -263,19 +263,19 @@ func TestVersionedEncoding(t *testing.T) {
t.Fatal(string(out)) t.Fatal(string(out))
} }
codec = cf.CodecForVersions(encoder, nil, []unversioned.GroupVersion{{Version: "v3"}}, nil) codec = cf.CodecForVersions(encoder, nil, unversioned.GroupVersion{Version: "v3"}, nil)
_, err = runtime.Encode(codec, &TestType1{}) _, err = runtime.Encode(codec, &TestType1{})
if err == nil { if err == nil {
t.Fatal(err) t.Fatal(err)
} }
// unversioned encode with no versions is written directly to wire // unversioned encode with no versions is written directly to wire
codec = cf.CodecForVersions(encoder, nil, nil, nil) codec = cf.CodecForVersions(encoder, nil, runtime.InternalGroupVersioner, nil)
out, err = runtime.Encode(codec, &TestType1{}) out, err = runtime.Encode(codec, &TestType1{})
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if string(out) != `{"myVersionKey":"__internal","myKindKey":"TestType1"}`+"\n" { if string(out) != `{}`+"\n" {
t.Fatal(string(out)) t.Fatal(string(out))
} }
} }

View File

@ -17,7 +17,6 @@ limitations under the License.
package serializer package serializer
import ( import (
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/runtime"
) )
@ -48,10 +47,10 @@ func (n *negotiatedSerializerWrapper) StreamingSerializerForMediaType(mediaType
return n.streamInfo, true return n.streamInfo, true
} }
func (n *negotiatedSerializerWrapper) EncoderForVersion(e runtime.Encoder, _ unversioned.GroupVersion) runtime.Encoder { func (n *negotiatedSerializerWrapper) EncoderForVersion(e runtime.Encoder, _ runtime.GroupVersioner) runtime.Encoder {
return e return e
} }
func (n *negotiatedSerializerWrapper) DecoderToVersion(d runtime.Decoder, _gv unversioned.GroupVersion) runtime.Decoder { func (n *negotiatedSerializerWrapper) DecoderToVersion(d runtime.Decoder, _gv runtime.GroupVersioner) runtime.Decoder {
return d return d
} }

View File

@ -17,59 +17,20 @@ limitations under the License.
package versioning package versioning
import ( import (
"fmt"
"io" "io"
"k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/runtime"
) )
// EnableCrossGroupDecoding modifies the given decoder in place, if it is a codec
// from this package. It allows objects from one group to be auto-decoded into
// another group. 'destGroup' must already exist in the codec.
// TODO: this is an encapsulation violation and should be refactored
func EnableCrossGroupDecoding(d runtime.Decoder, sourceGroup, destGroup string) error {
internal, ok := d.(*codec)
if !ok {
return fmt.Errorf("unsupported decoder type")
}
dest, ok := internal.decodeVersion[destGroup]
if !ok {
return fmt.Errorf("group %q is not a possible destination group in the given codec", destGroup)
}
internal.decodeVersion[sourceGroup] = dest
return nil
}
// EnableCrossGroupEncoding modifies the given encoder in place, if it is a codec
// from this package. It allows objects from one group to be auto-decoded into
// another group. 'destGroup' must already exist in the codec.
// TODO: this is an encapsulation violation and should be refactored
func EnableCrossGroupEncoding(e runtime.Encoder, sourceGroup, destGroup string) error {
internal, ok := e.(*codec)
if !ok {
return fmt.Errorf("unsupported encoder type")
}
dest, ok := internal.encodeVersion[destGroup]
if !ok {
return fmt.Errorf("group %q is not a possible destination group in the given codec", destGroup)
}
internal.encodeVersion[sourceGroup] = dest
return nil
}
// NewCodecForScheme is a convenience method for callers that are using a scheme. // NewCodecForScheme is a convenience method for callers that are using a scheme.
func NewCodecForScheme( func NewCodecForScheme(
// TODO: I should be a scheme interface? // TODO: I should be a scheme interface?
scheme *runtime.Scheme, scheme *runtime.Scheme,
encoder runtime.Encoder, encoder runtime.Encoder,
decoder runtime.Decoder, decoder runtime.Decoder,
encodeVersion []unversioned.GroupVersion, encodeVersion runtime.GroupVersioner,
decodeVersion []unversioned.GroupVersion, decodeVersion runtime.GroupVersioner,
) runtime.Codec { ) runtime.Codec {
return NewCodec(encoder, decoder, runtime.UnsafeObjectConvertor(scheme), scheme, scheme, scheme, encodeVersion, decodeVersion) return NewCodec(encoder, decoder, runtime.UnsafeObjectConvertor(scheme), scheme, scheme, scheme, encodeVersion, decodeVersion)
} }
@ -84,8 +45,8 @@ func NewCodec(
creater runtime.ObjectCreater, creater runtime.ObjectCreater,
copier runtime.ObjectCopier, copier runtime.ObjectCopier,
typer runtime.ObjectTyper, typer runtime.ObjectTyper,
encodeVersion []unversioned.GroupVersion, encodeVersion runtime.GroupVersioner,
decodeVersion []unversioned.GroupVersion, decodeVersion runtime.GroupVersioner,
) runtime.Codec { ) runtime.Codec {
internal := &codec{ internal := &codec{
encoder: encoder, encoder: encoder,
@ -94,33 +55,10 @@ func NewCodec(
creater: creater, creater: creater,
copier: copier, copier: copier,
typer: typer, typer: typer,
}
if encodeVersion != nil {
internal.encodeVersion = make(map[string]unversioned.GroupVersion)
for _, v := range encodeVersion {
// first one for a group wins. This is consistent with best to worst order throughout the codebase
if _, ok := internal.encodeVersion[v.Group]; ok {
continue
}
internal.encodeVersion[v.Group] = v
}
if len(internal.encodeVersion) == 1 {
for _, v := range internal.encodeVersion {
internal.preferredEncodeVersion = []unversioned.GroupVersion{v}
}
}
}
if decodeVersion != nil {
internal.decodeVersion = make(map[string]unversioned.GroupVersion)
for _, v := range decodeVersion {
// first one for a group wins. This is consistent with best to worst order throughout the codebase
if _, ok := internal.decodeVersion[v.Group]; ok {
continue
}
internal.decodeVersion[v.Group] = v
}
}
encodeVersion: encodeVersion,
decodeVersion: decodeVersion,
}
return internal return internal
} }
@ -132,10 +70,8 @@ type codec struct {
copier runtime.ObjectCopier copier runtime.ObjectCopier
typer runtime.ObjectTyper typer runtime.ObjectTyper
encodeVersion map[string]unversioned.GroupVersion encodeVersion runtime.GroupVersioner
decodeVersion map[string]unversioned.GroupVersion decodeVersion runtime.GroupVersioner
preferredEncodeVersion []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 // Decode attempts a decode of the object, then tries to convert it to the internal version. If into is provided and the decoding is
@ -170,37 +106,7 @@ func (c *codec) Decode(data []byte, defaultGVK *unversioned.GroupVersionKind, in
return into, gvk, nil return into, gvk, nil
} }
// invoke a version conversion // Convert if needed.
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 { if isVersioned {
// create a copy, because ConvertToVersion does not guarantee non-mutation of objects // create a copy, because ConvertToVersion does not guarantee non-mutation of objects
copied, err := c.copier.Copy(obj) copied, err := c.copier.Copy(obj)
@ -209,14 +115,14 @@ func (c *codec) Decode(data []byte, defaultGVK *unversioned.GroupVersionKind, in
} }
versioned.Objects = []runtime.Object{copied} versioned.Objects = []runtime.Object{copied}
} }
out, err := c.convertor.ConvertToVersion(obj, c.decodeVersion)
// Convert if needed.
out, err := c.convertor.ConvertToVersion(obj, targetGV)
if err != nil { if err != nil {
return nil, gvk, err return nil, gvk, err
} }
if isVersioned { if isVersioned {
versioned.Objects = append(versioned.Objects, out) if versioned.Last() != out {
versioned.Objects = append(versioned.Objects, out)
}
return versioned, gvk, nil return versioned, gvk, nil
} }
return out, gvk, nil return out, gvk, nil
@ -225,50 +131,47 @@ func (c *codec) Decode(data []byte, defaultGVK *unversioned.GroupVersionKind, in
// Encode ensures the provided object is output in the appropriate group and version, invoking // Encode ensures the provided object is output in the appropriate group and version, invoking
// conversion if necessary. Unversioned objects (according to the ObjectTyper) are output as is. // conversion if necessary. Unversioned objects (according to the ObjectTyper) are output as is.
func (c *codec) Encode(obj runtime.Object, w io.Writer) error { func (c *codec) Encode(obj runtime.Object, w io.Writer) error {
if _, ok := obj.(*runtime.Unknown); ok { switch t := obj.(type) {
case *runtime.Unknown:
if gv, ok := runtime.PreferredGroupVersion(c.encodeVersion); ok {
t.APIVersion = gv.String()
}
return c.encoder.Encode(obj, w)
case *runtime.Unstructured:
if gv, ok := runtime.PreferredGroupVersion(c.encodeVersion); ok {
t.SetAPIVersion(gv.String())
}
return c.encoder.Encode(obj, w)
case *runtime.UnstructuredList:
if gv, ok := runtime.PreferredGroupVersion(c.encodeVersion); ok {
t.SetAPIVersion(gv.String())
}
return c.encoder.Encode(obj, w) return c.encoder.Encode(obj, w)
} }
gvks, isUnversioned, err := c.typer.ObjectKinds(obj) gvks, isUnversioned, err := c.typer.ObjectKinds(obj)
if err != nil { if err != nil {
return err return err
} }
gvk := gvks[0]
if c.encodeVersion == nil || isUnversioned { if c.encodeVersion == nil || isUnversioned {
objectKind := obj.GetObjectKind() objectKind := obj.GetObjectKind()
old := objectKind.GroupVersionKind() old := objectKind.GroupVersionKind()
objectKind.SetGroupVersionKind(gvk) objectKind.SetGroupVersionKind(gvks[0])
err = c.encoder.Encode(obj, w) err = c.encoder.Encode(obj, w)
objectKind.SetGroupVersionKind(old) objectKind.SetGroupVersionKind(old)
return err return err
} }
targetGV, ok := c.encodeVersion[gvk.Group]
// attempt a conversion to the sole encode version
if !ok && c.preferredEncodeVersion != nil {
ok = true
targetGV = c.preferredEncodeVersion[0]
}
// 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 // Perform a conversion if necessary
objectKind := obj.GetObjectKind() objectKind := obj.GetObjectKind()
old := objectKind.GroupVersionKind() old := objectKind.GroupVersionKind()
out, err := c.convertor.ConvertToVersion(obj, targetGV) out, err := c.convertor.ConvertToVersion(obj, c.encodeVersion)
if err != nil { if err != nil {
if ok { return err
return err
}
} else {
obj = out
} }
// Conversion is responsible for setting the proper group, version, and kind onto the outgoing object // Conversion is responsible for setting the proper group, version, and kind onto the outgoing object
err = c.encoder.Encode(obj, w) err = c.encoder.Encode(out, w)
// restore the old GVK, in case conversion returned the same object // restore the old GVK, in case conversion returned the same object
objectKind.SetGroupVersionKind(old) objectKind.SetGroupVersionKind(old)
return err return err

View File

@ -53,7 +53,7 @@ func TestDecode(t *testing.T) {
yaml bool yaml bool
pretty bool pretty bool
encodes, decodes []unversioned.GroupVersion encodes, decodes runtime.GroupVersioner
defaultGVK *unversioned.GroupVersionKind defaultGVK *unversioned.GroupVersionKind
into runtime.Object into runtime.Object
@ -67,12 +67,14 @@ func TestDecode(t *testing.T) {
serializer: &mockSerializer{actual: gvk1}, serializer: &mockSerializer{actual: gvk1},
convertor: &checkConvertor{groupVersion: unversioned.GroupVersion{Group: "other", Version: "__internal"}}, convertor: &checkConvertor{groupVersion: unversioned.GroupVersion{Group: "other", Version: "__internal"}},
expectedGVK: gvk1, expectedGVK: gvk1,
decodes: unversioned.GroupVersion{Group: "other", Version: "__internal"},
}, },
{ {
serializer: &mockSerializer{actual: gvk1, obj: decodable1}, serializer: &mockSerializer{actual: gvk1, obj: decodable1},
convertor: &checkConvertor{in: decodable1, obj: decodable2, groupVersion: unversioned.GroupVersion{Group: "other", Version: "__internal"}}, convertor: &checkConvertor{in: decodable1, obj: decodable2, groupVersion: unversioned.GroupVersion{Group: "other", Version: "__internal"}},
expectedGVK: gvk1, expectedGVK: gvk1,
sameObject: decodable2, sameObject: decodable2,
decodes: unversioned.GroupVersion{Group: "other", Version: "__internal"},
}, },
// defaultGVK.Group is allowed to force a conversion to the destination group // defaultGVK.Group is allowed to force a conversion to the destination group
{ {
@ -81,6 +83,7 @@ func TestDecode(t *testing.T) {
convertor: &checkConvertor{in: decodable1, obj: decodable2, groupVersion: unversioned.GroupVersion{Group: "force", Version: "__internal"}}, convertor: &checkConvertor{in: decodable1, obj: decodable2, groupVersion: unversioned.GroupVersion{Group: "force", Version: "__internal"}},
expectedGVK: gvk1, expectedGVK: gvk1,
sameObject: decodable2, sameObject: decodable2,
decodes: unversioned.GroupVersion{Group: "force", Version: "__internal"},
}, },
// uses direct conversion for into when objects differ // uses direct conversion for into when objects differ
{ {
@ -121,6 +124,7 @@ func TestDecode(t *testing.T) {
convertor: &checkConvertor{in: decodable1, obj: decodable2, groupVersion: unversioned.GroupVersion{Group: "other", Version: "__internal"}}, convertor: &checkConvertor{in: decodable1, obj: decodable2, groupVersion: unversioned.GroupVersion{Group: "other", Version: "__internal"}},
expectedGVK: gvk1, expectedGVK: gvk1,
expectedObject: &runtime.VersionedObjects{Objects: []runtime.Object{decodable1, decodable2}}, expectedObject: &runtime.VersionedObjects{Objects: []runtime.Object{decodable1, decodable2}},
decodes: unversioned.GroupVersion{Group: "other", Version: "__internal"},
}, },
{ {
into: &runtime.VersionedObjects{Objects: []runtime.Object{}}, into: &runtime.VersionedObjects{Objects: []runtime.Object{}},
@ -130,38 +134,45 @@ func TestDecode(t *testing.T) {
convertor: &checkConvertor{in: decodable1, obj: decodable2, groupVersion: unversioned.GroupVersion{Group: "other", Version: "__internal"}}, convertor: &checkConvertor{in: decodable1, obj: decodable2, groupVersion: unversioned.GroupVersion{Group: "other", Version: "__internal"}},
expectedGVK: gvk1, expectedGVK: gvk1,
expectedObject: &runtime.VersionedObjects{Objects: []runtime.Object{decodable1, decodable2}}, expectedObject: &runtime.VersionedObjects{Objects: []runtime.Object{decodable1, decodable2}},
decodes: unversioned.GroupVersion{Group: "other", Version: "__internal"},
}, },
// decode into the same version as the serialized object // decode into the same version as the serialized object
{ {
decodes: []unversioned.GroupVersion{gvk1.GroupVersion()}, decodes: unversioned.GroupVersions{gvk1.GroupVersion()},
serializer: &mockSerializer{actual: gvk1, obj: decodable1}, serializer: &mockSerializer{actual: gvk1, obj: decodable1},
convertor: &checkConvertor{in: decodable1, obj: decodable1, groupVersion: unversioned.GroupVersions{{Group: "other", Version: "blah"}}},
expectedGVK: gvk1, expectedGVK: gvk1,
expectedObject: decodable1, expectedObject: decodable1,
}, },
{ {
into: &runtime.VersionedObjects{Objects: []runtime.Object{}}, into: &runtime.VersionedObjects{Objects: []runtime.Object{}},
decodes: []unversioned.GroupVersion{gvk1.GroupVersion()}, decodes: unversioned.GroupVersions{gvk1.GroupVersion()},
serializer: &mockSerializer{actual: gvk1, obj: decodable1}, serializer: &mockSerializer{actual: gvk1, obj: decodable1},
convertor: &checkConvertor{in: decodable1, obj: decodable1, groupVersion: unversioned.GroupVersions{{Group: "other", Version: "blah"}}},
copier: &checkCopy{in: decodable1, obj: decodable1, err: nil},
expectedGVK: gvk1, expectedGVK: gvk1,
expectedObject: &runtime.VersionedObjects{Objects: []runtime.Object{decodable1}}, expectedObject: &runtime.VersionedObjects{Objects: []runtime.Object{decodable1}},
}, },
// codec with non matching version skips conversion altogether // codec with non matching version skips conversion altogether
{ {
decodes: []unversioned.GroupVersion{{Group: "something", Version: "else"}}, decodes: unversioned.GroupVersions{{Group: "something", Version: "else"}},
serializer: &mockSerializer{actual: gvk1, obj: decodable1}, serializer: &mockSerializer{actual: gvk1, obj: decodable1},
convertor: &checkConvertor{in: decodable1, obj: decodable1, groupVersion: unversioned.GroupVersions{{Group: "something", Version: "else"}}},
expectedGVK: gvk1, expectedGVK: gvk1,
expectedObject: decodable1, expectedObject: decodable1,
}, },
{ {
into: &runtime.VersionedObjects{Objects: []runtime.Object{}}, into: &runtime.VersionedObjects{Objects: []runtime.Object{}},
decodes: []unversioned.GroupVersion{{Group: "something", Version: "else"}}, decodes: unversioned.GroupVersions{{Group: "something", Version: "else"}},
serializer: &mockSerializer{actual: gvk1, obj: decodable1}, serializer: &mockSerializer{actual: gvk1, obj: decodable1},
convertor: &checkConvertor{in: decodable1, obj: decodable1, groupVersion: unversioned.GroupVersions{{Group: "something", Version: "else"}}},
copier: &checkCopy{in: decodable1, obj: decodable1, err: nil},
expectedGVK: gvk1, expectedGVK: gvk1,
expectedObject: &runtime.VersionedObjects{Objects: []runtime.Object{decodable1}}, expectedObject: &runtime.VersionedObjects{Objects: []runtime.Object{decodable1}},
}, },
@ -228,7 +239,7 @@ func (c *checkCopy) Copy(obj runtime.Object) (runtime.Object, error) {
type checkConvertor struct { type checkConvertor struct {
err error err error
in, obj runtime.Object in, obj runtime.Object
groupVersion unversioned.GroupVersion groupVersion runtime.GroupVersioner
directConvert bool directConvert bool
} }
@ -244,15 +255,15 @@ func (c *checkConvertor) Convert(in, out interface{}) error {
} }
return c.err return c.err
} }
func (c *checkConvertor) ConvertToVersion(in runtime.Object, outVersion unversioned.GroupVersion) (out runtime.Object, err error) { func (c *checkConvertor) ConvertToVersion(in runtime.Object, outVersion runtime.GroupVersioner) (out runtime.Object, err error) {
if c.directConvert { if c.directConvert {
return nil, fmt.Errorf("unexpected call to ConvertToVersion") return nil, fmt.Errorf("unexpected call to ConvertToVersion")
} }
if c.in != nil && c.in != in { if c.in != nil && c.in != in {
return nil, fmt.Errorf("unexpected in: %s", in) return nil, fmt.Errorf("unexpected in: %s", in)
} }
if c.groupVersion != outVersion { if !reflect.DeepEqual(c.groupVersion, outVersion) {
return nil, fmt.Errorf("unexpected outversion: %s", outVersion) return nil, fmt.Errorf("unexpected outversion: %s (%s)", outVersion, c.groupVersion)
} }
return c.obj, c.err return c.obj, c.err
} }

View File

@ -187,9 +187,14 @@ func (UnstructuredObjectConverter) Convert(in, out interface{}) error {
return nil return nil
} }
func (UnstructuredObjectConverter) ConvertToVersion(in Object, outVersion unversioned.GroupVersion) (Object, error) { func (UnstructuredObjectConverter) ConvertToVersion(in Object, target GroupVersioner) (Object, error) {
if gvk := in.GetObjectKind().GroupVersionKind(); gvk.GroupVersion() != outVersion { if kind := in.GetObjectKind().GroupVersionKind(); !kind.IsEmpty() {
return nil, errors.New("unstructured converter cannot convert versions") gvk, ok := target.KindForGroupVersionKinds([]unversioned.GroupVersionKind{kind})
if !ok {
// TODO: should this be a typed error?
return nil, fmt.Errorf("%v is unstructured and is not suitable for converting to %q", kind, target)
}
in.GetObjectKind().SetGroupVersionKind(gvk)
} }
return in, nil return in, nil
} }

View File

@ -30,14 +30,13 @@ import (
"k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/runtime"
) )
var status = &unversioned.Status{
Status: unversioned.StatusFailure,
Code: 200,
Reason: unversioned.StatusReasonUnknown,
Message: "",
}
func TestV1EncodeDecodeStatus(t *testing.T) { func TestV1EncodeDecodeStatus(t *testing.T) {
status := &unversioned.Status{
Status: unversioned.StatusFailure,
Code: 200,
Reason: unversioned.StatusReasonUnknown,
Message: "",
}
v1Codec := testapi.Default.Codec() v1Codec := testapi.Default.Codec()
@ -65,6 +64,12 @@ func TestV1EncodeDecodeStatus(t *testing.T) {
} }
func TestExperimentalEncodeDecodeStatus(t *testing.T) { func TestExperimentalEncodeDecodeStatus(t *testing.T) {
status := &unversioned.Status{
Status: unversioned.StatusFailure,
Code: 200,
Reason: unversioned.StatusReasonUnknown,
Message: "",
}
// TODO: caesarxuchao: use the testapi.Extensions.Codec() once the PR that // TODO: caesarxuchao: use the testapi.Extensions.Codec() once the PR that
// moves experimental from v1 to v1beta1 got merged. // moves experimental from v1 to v1beta1 got merged.
expCodec := api.Codecs.LegacyCodec(extensions.SchemeGroupVersion) expCodec := api.Codecs.LegacyCodec(extensions.SchemeGroupVersion)

View File

@ -47,7 +47,7 @@ func init() {
api.Scheme, api.Scheme,
jsonSerializer, jsonSerializer,
jsonSerializer, jsonSerializer,
[]unversioned.GroupVersion{{Version: Version}}, unversioned.GroupVersion{Version: Version},
[]unversioned.GroupVersion{{Version: runtime.APIVersionInternal}}, runtime.InternalGroupVersioner,
) )
} }

View File

@ -17,7 +17,6 @@ limitations under the License.
package framework package framework
import ( import (
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/runtime/serializer/versioning" "k8s.io/kubernetes/pkg/runtime/serializer/versioning"
) )
@ -58,10 +57,10 @@ func (s *wrappedSerializer) UniversalDeserializer() runtime.Decoder {
return s.serializer return s.serializer
} }
func (s *wrappedSerializer) EncoderForVersion(encoder runtime.Encoder, gv unversioned.GroupVersion) runtime.Encoder { func (s *wrappedSerializer) EncoderForVersion(encoder runtime.Encoder, gv runtime.GroupVersioner) runtime.Encoder {
return versioning.NewCodec(encoder, nil, s.scheme, s.scheme, s.scheme, s.scheme, []unversioned.GroupVersion{gv}, nil) return versioning.NewCodec(encoder, nil, s.scheme, s.scheme, s.scheme, s.scheme, gv, nil)
} }
func (s *wrappedSerializer) DecoderToVersion(decoder runtime.Decoder, gv unversioned.GroupVersion) runtime.Decoder { func (s *wrappedSerializer) DecoderToVersion(decoder runtime.Decoder, gv runtime.GroupVersioner) runtime.Decoder {
return versioning.NewCodec(nil, decoder, s.scheme, s.scheme, s.scheme, s.scheme, nil, []unversioned.GroupVersion{gv}) return versioning.NewCodec(nil, decoder, s.scheme, s.scheme, s.scheme, s.scheme, nil, gv)
} }