From 12a5eeea17ba75a84708eb9d47b69fc55970c65d Mon Sep 17 00:00:00 2001 From: Clayton Coleman Date: Sun, 22 May 2016 19:10:42 -0400 Subject: [PATCH] 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. --- pkg/api/meta/restmapper_test.go | 2 +- pkg/api/serialization_test.go | 13 +- pkg/api/testapi/testapi.go | 6 +- pkg/api/unversioned/group_version.go | 38 +++ pkg/apiserver/apiserver.go | 2 +- pkg/apiserver/negotiate_test.go | 4 +- .../clientcmd/api/latest/latest.go | 4 +- pkg/conversion/converter.go | 2 + pkg/genericapiserver/storage_factory.go | 30 +- pkg/registry/thirdpartyresourcedata/codec.go | 12 +- pkg/runtime/codec.go | 80 ++++++ pkg/runtime/helper.go | 2 +- pkg/runtime/interfaces.go | 27 +- pkg/runtime/scheme.go | 170 ++++------- pkg/runtime/scheme_test.go | 267 +++++++++++++++++- pkg/runtime/serializer/codec_factory.go | 40 ++- pkg/runtime/serializer/codec_test.go | 8 +- pkg/runtime/serializer/negotiated_codec.go | 5 +- .../serializer/versioning/versioning.go | 165 +++-------- .../serializer/versioning/versioning_test.go | 29 +- pkg/runtime/unstructured.go | 11 +- pkg/runtime/unversioned_test.go | 19 +- plugin/pkg/scheduler/api/latest/latest.go | 4 +- test/integration/framework/serializer.go | 9 +- 24 files changed, 617 insertions(+), 332 deletions(-) diff --git a/pkg/api/meta/restmapper_test.go b/pkg/api/meta/restmapper_test.go index 4240981300c..c4900d4aa44 100644 --- a/pkg/api/meta/restmapper_test.go +++ b/pkg/api/meta/restmapper_test.go @@ -32,7 +32,7 @@ func (fakeConvertor) Convert(in, out interface{}) error { 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 } diff --git a/pkg/api/serialization_test.go b/pkg/api/serialization_test.go index 4a44937a913..ec2894a8d03 100644 --- a/pkg/api/serialization_test.go +++ b/pkg/api/serialization_test.go @@ -43,7 +43,6 @@ import ( "k8s.io/kubernetes/pkg/conversion" "k8s.io/kubernetes/pkg/runtime" "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/sets" "k8s.io/kubernetes/pkg/watch" @@ -181,10 +180,14 @@ func TestSetControllerConversion(t *testing.T) { t.Fatalf("unexpected encoding error: %v", err) } - decoder := api.Codecs.UniversalDecoder(*extGroup.GroupVersion(), *defaultGroup.GroupVersion()) - if err := versioning.EnableCrossGroupDecoding(decoder, extGroup.GroupVersion().Group, defaultGroup.GroupVersion().Group); err != nil { - t.Fatalf("unexpected error while enabling cross-group decoding: %v", err) - } + decoder := api.Codecs.DecoderToVersion( + api.Codecs.UniversalDeserializer(), + runtime.NewMultiGroupVersioner( + *defaultGroup.GroupVersion(), + unversioned.GroupKind{Group: defaultGroup.GroupVersion().Group}, + unversioned.GroupKind{Group: extGroup.GroupVersion().Group}, + ), + ) t.Logf("rs.v1beta1.extensions -> rc._internal") if err := runtime.DecodeInto(decoder, data, rc); err != nil { diff --git a/pkg/api/testapi/testapi.go b/pkg/api/testapi/testapi.go index a2b9c3b1f7b..0ad8b08c678 100644 --- a/pkg/api/testapi/testapi.go +++ b/pkg/api/testapi/testapi.go @@ -281,7 +281,7 @@ func (g TestGroup) Codec() runtime.Codec { if serializer.Serializer == nil { 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. @@ -309,7 +309,7 @@ func (g TestGroup) StorageCodec() runtime.Codec { } 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 @@ -393,7 +393,7 @@ func (g TestGroup) RESTMapper() meta.RESTMapper { } // ExternalGroupVersions returns all external group versions allowed for the server. -func ExternalGroupVersions() []unversioned.GroupVersion { +func ExternalGroupVersions() unversioned.GroupVersions { versions := []unversioned.GroupVersion{} for _, g := range Groups { gv := g.GroupVersion() diff --git a/pkg/api/unversioned/group_version.go b/pkg/api/unversioned/group_version.go index 8f747892171..73cc1eb0556 100644 --- a/pkg/api/unversioned/group_version.go +++ b/pkg/api/unversioned/group_version.go @@ -179,6 +179,25 @@ func (gv GroupVersion) String() string { 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 // if it cannot parse the string. func ParseGroupVersion(gv string) (GroupVersion, error) { @@ -241,6 +260,25 @@ func (gv *GroupVersion) UnmarshalText(value []byte) error { 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 // do not use TypeMeta. func (gvk *GroupVersionKind) ToAPIVersionAndKind() (string, string) { diff --git a/pkg/apiserver/apiserver.go b/pkg/apiserver/apiserver.go index 1c4a0fcd3f9..1d4b07c1680 100644 --- a/pkg/apiserver/apiserver.go +++ b/pkg/apiserver/apiserver.go @@ -320,7 +320,7 @@ type StripVersionNegotiatedSerializer struct { 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) if !ok { // The stripVersionEncoder needs both an encoder and decoder, but is called from a context that doesn't have access to the diff --git a/pkg/apiserver/negotiate_test.go b/pkg/apiserver/negotiate_test.go index 1735f3eebde..8187a1761a2 100644 --- a/pkg/apiserver/negotiate_test.go +++ b/pkg/apiserver/negotiate_test.go @@ -64,11 +64,11 @@ func (n *fakeNegotiater) StreamingSerializerForMediaType(mediaType string, optio }, 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 } -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 } diff --git a/pkg/client/unversioned/clientcmd/api/latest/latest.go b/pkg/client/unversioned/clientcmd/api/latest/latest.go index b77509cb563..a32b251c77c 100644 --- a/pkg/client/unversioned/clientcmd/api/latest/latest.go +++ b/pkg/client/unversioned/clientcmd/api/latest/latest.go @@ -60,7 +60,7 @@ func init() { Scheme, yamlSerializer, yamlSerializer, - []unversioned.GroupVersion{{Version: Version}}, - []unversioned.GroupVersion{{Version: runtime.APIVersionInternal}}, + unversioned.GroupVersion{Version: Version}, + runtime.InternalGroupVersioner, ) } diff --git a/pkg/conversion/converter.go b/pkg/conversion/converter.go index 7a18d6360f5..8941b18aefd 100644 --- a/pkg/conversion/converter.go +++ b/pkg/conversion/converter.go @@ -213,6 +213,8 @@ type Meta struct { // KeyNameMapping is an optional function which may map the listed key (field name) // into a source and destination value. 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. diff --git a/pkg/genericapiserver/storage_factory.go b/pkg/genericapiserver/storage_factory.go index 05c3b4ff5f1..dd2d55b52f5 100644 --- a/pkg/genericapiserver/storage_factory.go +++ b/pkg/genericapiserver/storage_factory.go @@ -24,7 +24,6 @@ import ( "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/runtime/serializer/recognizer" - "k8s.io/kubernetes/pkg/runtime/serializer/versioning" "k8s.io/kubernetes/pkg/storage/storagebackend" "k8s.io/kubernetes/pkg/util/sets" @@ -262,18 +261,25 @@ func NewStorageCodec(storageMediaType string, ns runtime.StorageSerializer, stor 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()) - encoder := ns.EncoderForVersion(s, storageVersion) - decoder := ns.DecoderToVersion(ds, memoryVersion) - if memoryVersion.Group != storageVersion.Group { - // Allow this codec to translate between groups. - if err := versioning.EnableCrossGroupEncoding(encoder, memoryVersion.Group, storageVersion.Group); err != nil { - return nil, fmt.Errorf("error setting up encoder from %v to %v: %v", memoryVersion, storageVersion, err) - } - 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) - } - } + decoder := ns.DecoderToVersion( + ds, + runtime.NewMultiGroupVersioner( + memoryVersion, + unversioned.GroupKind{Group: memoryVersion.Group}, + unversioned.GroupKind{Group: storageVersion.Group}, + ), + ) + return runtime.NewCodec(encoder, decoder), nil } diff --git a/pkg/registry/thirdpartyresourcedata/codec.go b/pkg/registry/thirdpartyresourcedata/codec.go index b96d789ca57..323b640324d 100644 --- a/pkg/registry/thirdpartyresourcedata/codec.go +++ b/pkg/registry/thirdpartyresourcedata/codec.go @@ -42,7 +42,7 @@ type thirdPartyObjectConverter struct { 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) { // 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. @@ -234,11 +234,11 @@ func (t *thirdPartyResourceDataCodecFactory) StreamingSerializerForMediaType(med } } -func (t *thirdPartyResourceDataCodecFactory) EncoderForVersion(s runtime.Encoder, gv unversioned.GroupVersion) runtime.Encoder { - return &thirdPartyResourceDataEncoder{delegate: t.delegate.EncoderForVersion(s, gv), gvk: gv.WithKind(t.kind)} +func (t *thirdPartyResourceDataCodecFactory) EncoderForVersion(s runtime.Encoder, gv runtime.GroupVersioner) runtime.Encoder { + 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) } @@ -517,6 +517,10 @@ func (t *thirdPartyResourceDataEncoder) Encode(obj runtime.Object, stream io.Wri listItems[ix] = json.RawMessage(buff.Bytes()) } + if t.gvk.IsEmpty() { + return fmt.Errorf("thirdPartyResourceDataEncoder was not given a target version") + } + encMap := struct { Kind string `json:"kind,omitempty"` Items []json.RawMessage `json:"items"` diff --git a/pkg/runtime/codec.go b/pkg/runtime/codec.go index dc70ea55903..58e1d3b6784 100644 --- a/pkg/runtime/codec.go +++ b/pkg/runtime/codec.go @@ -198,3 +198,83 @@ func (s base64Serializer) Decode(data []byte, defaults *unversioned.GroupVersion } 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 +} diff --git a/pkg/runtime/helper.go b/pkg/runtime/helper.go index 827cff1b846..2f8f161dde9 100644 --- a/pkg/runtime/helper.go +++ b/pkg/runtime/helper.go @@ -35,7 +35,7 @@ var _ ObjectConvertor = unsafeObjectConvertor{} // 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. -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) } diff --git a/pkg/runtime/interfaces.go b/pkg/runtime/interfaces.go index e46324608e5..4341f89b3fa 100644 --- a/pkg/runtime/interfaces.go +++ b/pkg/runtime/interfaces.go @@ -30,12 +30,23 @@ const ( 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 { // Encode writes an object to a stream. Implementations may return errors if the versions are // incompatible, or if no conversion is defined. Encode(obj Object, w io.Writer) error } +// Decoders attempt to load an object from data. 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 @@ -117,12 +128,10 @@ type NegotiatedSerializer interface { // EncoderForVersion returns an encoder that ensures objects being written to the provided // serializer are in the provided group version. - // TODO: take multiple group versions - EncoderForVersion(serializer Encoder, gv unversioned.GroupVersion) Encoder + EncoderForVersion(serializer Encoder, gv GroupVersioner) Encoder // DecoderForVersion returns a decoder that ensures objects being read by the provided // serializer are in the provided group version by default. - // TODO: take multiple group versions - DecoderToVersion(serializer Decoder, gv unversioned.GroupVersion) Decoder + DecoderToVersion(serializer Decoder, gv GroupVersioner) Decoder } // 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 // serializer are in the provided group version. - // TODO: take multiple group versions - EncoderForVersion(serializer Encoder, gv unversioned.GroupVersion) Encoder + EncoderForVersion(serializer Encoder, gv GroupVersioner) Encoder // DecoderForVersion returns a decoder that ensures objects being read by the provided // serializer are in the provided group version by default. - // TODO: take multiple group versions - DecoderToVersion(serializer Decoder, gv unversioned.GroupVersion) Decoder + DecoderToVersion(serializer Decoder, gv GroupVersioner) Decoder } /////////////////////////////////////////////////////////////////////////////// // Non-codec interfaces 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. @@ -161,7 +168,7 @@ type ObjectConvertor interface { 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 unversioned.GroupVersion) (out Object, err error) + ConvertToVersion(in Object, gv GroupVersioner) (out Object, err error) ConvertFieldLabel(version, kind, label, value string) (string, string, error) } diff --git a/pkg/runtime/scheme.go b/pkg/runtime/scheme.go index 3da67978b97..f5d3bd08298 100644 --- a/pkg/runtime/scheme.go +++ b/pkg/runtime/scheme.go @@ -237,7 +237,7 @@ func (s *Scheme) ObjectKinds(obj Object) ([]unversioned.GroupVersionKind, bool, gvks, ok := s.typeToGVK[t] if !ok { - return nil, false, ¬RegisteredErr{t: t} + return nil, false, NewNotRegisteredErr(unversioned.GroupVersionKind{}, 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 { return reflect.New(t).Interface().(Object), nil } - return nil, ¬RegisteredErr{gvk: kind} + return nil, NewNotRegisteredErr(kind, nil) } // 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 // 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, -// a to test conversion of types that are nested within registered types), but in -// that case, the conversion.Scope object passed to your conversion functions won't -// have SrcVersion or DestVersion fields set correctly in Meta(). -func (s *Scheme) Convert(in, out interface{}) error { - inVersion := unversioned.GroupVersion{Group: "unknown", Version: "unknown"} - outVersion := unversioned.GroupVersion{Group: "unknown", Version: "unknown"} - if inObj, ok := in.(Object); ok { - 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) +// a to test conversion of types that are nested within registered types). The +// context interface is passed to the convertor. +// TODO: identify whether context should be hidden, or behind a formal context/scope +// interface +func (s *Scheme) Convert(in, out interface{}, context interface{}) error { + flags, meta := s.generateConvertMeta(in) + meta.Context = context if flags == 0 { 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 // contain the inKind (or a mapping by name defined with AddKnownTypeWithName). Will also // return an error if the conversion does not result in a valid Object being -// returned. The serializer handles loading/serializing nested objects. -func (s *Scheme) ConvertToVersion(in Object, outVersion unversioned.GroupVersion) (Object, error) { - switch in.(type) { - 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 +// returned. Passes target down to the conversion methods as the Context on the scope. +func (s *Scheme) ConvertToVersion(in Object, target GroupVersioner) (Object, error) { + return s.convertToVersion(true, in, target) } -// 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 // efficient as possible when doing conversion. -func (s *Scheme) UnsafeConvertToVersion(in Object, outVersion unversioned.GroupVersion) (Object, error) { - switch t := in.(type) { - 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 - } +func (s *Scheme) UnsafeConvertToVersion(in Object, target GroupVersioner) (Object, error) { + return s.convertToVersion(false, in, target) +} +// 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. t := reflect.TypeOf(in) if t.Kind() != reflect.Ptr { @@ -556,64 +493,69 @@ func (s *Scheme) UnsafeConvertToVersion(in Object, outVersion unversioned.GroupV } 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) + return nil, NewNotRegisteredErr(unversioned.GroupVersionKind{}, t) } - // if the Go type is also registered to the destination kind, no conversion is necessary - for i := range kinds { - if kinds[i].Version == outVersion.Version && kinds[i].Group == outVersion.Group { - setTargetKind(in, kinds[i]) - return in, nil + gvk, ok := target.KindForGroupVersionKinds(kinds) + if !ok { + // TODO: should this be a typed error? + return nil, fmt.Errorf("%v is not suitable for converting to %q", t, target) + } + + // 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 - // it should be possible to avoid this allocation if unversionedKind, ok := s.unversionedTypes[t]; ok { - kind := unversionedKind - outKind := outVersion.WithKind(kind.Kind) - setTargetKind(in, outKind) - return in, nil + if gvk, ok := target.KindForGroupVersionKinds([]unversioned.GroupVersionKind{unversionedKind}); ok { + return copyAndSetTargetKind(copy, s, in, gvk) + } + return copyAndSetTargetKind(copy, s, in, unversionedKind) } - // allocate a new object as the target using the target kind - // 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) + out, err := s.New(gvk) if err != nil { return nil, err } - // TODO: try to avoid the allocations here - in fast paths we are not likely to need these flags or meta - flags, meta := s.converter.DefaultMeta(t) + if copy { + 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 { return nil, err } - setTargetKind(out, kind) + setTargetKind(out, gvk) return out, nil } // 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)) } -// setTargetVersion is deprecated and should be replaced by use of setTargetKind -func setTargetVersion(obj Object, raw *Scheme, gv unversioned.GroupVersion) { - if gv.Version == APIVersionInternal { - // internal is a special case - obj.GetObjectKind().SetGroupVersionKind(unversioned.GroupVersionKind{}) - return - } - if gvks, _, _ := raw.ObjectKinds(obj); len(gvks) > 0 { - 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}) +// copyAndSetTargetKind performs a conditional copy before returning the object, or an error if copy was not successful. +func copyAndSetTargetKind(copy bool, copier ObjectCopier, obj Object, kind unversioned.GroupVersionKind) (Object, error) { + if copy { + copied, err := copier.Copy(obj) + if err != nil { + return nil, err + } + obj = copied } + setTargetKind(obj, kind) + return obj, nil } // setTargetKind sets the kind on an object, taking into account whether the target kind is the internal version. diff --git a/pkg/runtime/scheme_test.go b/pkg/runtime/scheme_test.go index b81b9528f57..135d9c2f93c 100644 --- a/pkg/runtime/scheme_test.go +++ b/pkg/runtime/scheme_test.go @@ -18,6 +18,7 @@ package runtime_test import ( "reflect" + "strings" "testing" "github.com/google/gofuzz" @@ -460,6 +461,16 @@ type ExternalInternalSame struct { 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) SetGroupVersionKind(gvk unversioned.GroupVersionKind) { obj.APIVersion, obj.ObjectKind = gvk.ToAPIVersionAndKind() @@ -500,6 +511,8 @@ var TestObjectFuzzer = fuzz.New().NilChance(.5).NumElements(1, 100).Funcs( func GetTestScheme() *runtime.Scheme { internalGV := unversioned.GroupVersion{Version: "__internal"} externalGV := unversioned.GroupVersion{Version: "v1"} + alternateExternalGV := unversioned.GroupVersion{Group: "custom", Version: "v1"} + differentExternalGV := unversioned.GroupVersion{Group: "other", Version: "v2"} s := runtime.NewScheme() // 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(internalGV.WithKind("TestType3"), &TestType1{}) 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 } @@ -528,7 +546,7 @@ func TestKnownTypes(t *testing.T) { } } -func TestConvertToVersion(t *testing.T) { +func TestConvertToVersionBasic(t *testing.T) { s := GetTestScheme() tt := &TestType1{A: "I'm not a pointer object"} other, err := s.ConvertToVersion(tt, unversioned.GroupVersion{Version: "v1"}) @@ -537,13 +555,258 @@ func TestConvertToVersion(t *testing.T) { } converted, ok := other.(*ExternalTestType1) if !ok { - t.Fatalf("Got wrong type") + t.Fatalf("Got wrong type: %T", other) } if tt.A != converted.A { 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) { internalGV := unversioned.GroupVersion{Group: "test.group", Version: "__internal"} externalGV := unversioned.GroupVersion{Group: "test.group", Version: "externalVersion"} diff --git a/pkg/runtime/serializer/codec_factory.go b/pkg/runtime/serializer/codec_factory.go index 66a9ae5dbe2..ed5e8c471c3 100644 --- a/pkg/runtime/serializer/codec_factory.go +++ b/pkg/runtime/serializer/codec_factory.go @@ -188,13 +188,17 @@ func (f CodecFactory) SupportedStreamingMediaTypes() []string { return f.streamingAccepts } -// LegacyCodec encodes output to a given API version, and decodes output into the internal form from -// any recognized source. The returned codec will always encode output to JSON. +// 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. 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 // 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 { - 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 @@ -211,25 +215,39 @@ func (f CodecFactory) UniversalDeserializer() runtime.Decoder { // defaulting. // // 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 { - 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, // it will default to runtime.APIVersionInternal. If encode is not specified for an object's group, the object is not // converted. If encode or decode are nil, no conversion is performed. -func (f CodecFactory) CodecForVersions(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) } // DecoderToVersion returns a decoder that targets the provided group version. -func (f CodecFactory) DecoderToVersion(decoder runtime.Decoder, gv unversioned.GroupVersion) runtime.Decoder { - return f.CodecForVersions(nil, decoder, nil, []unversioned.GroupVersion{gv}) +func (f CodecFactory) DecoderToVersion(decoder runtime.Decoder, gv runtime.GroupVersioner) runtime.Decoder { + return f.CodecForVersions(nil, decoder, nil, gv) } // EncoderForVersion returns an encoder that targets the provided group version. -func (f CodecFactory) EncoderForVersion(encoder runtime.Encoder, gv unversioned.GroupVersion) runtime.Encoder { - return f.CodecForVersions(encoder, nil, []unversioned.GroupVersion{gv}, nil) +func (f CodecFactory) EncoderForVersion(encoder runtime.Encoder, gv runtime.GroupVersioner) runtime.Encoder { + return f.CodecForVersions(encoder, nil, gv, nil) } // 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. -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{ runtime.NewCodec(serializer, nil), 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. -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{ runtime.NewCodec(nil, serializer), nil, diff --git a/pkg/runtime/serializer/codec_test.go b/pkg/runtime/serializer/codec_test.go index 0982fb3aae2..87158a0c23e 100644 --- a/pkg/runtime/serializer/codec_test.go +++ b/pkg/runtime/serializer/codec_test.go @@ -254,7 +254,7 @@ func TestVersionedEncoding(t *testing.T) { cf := newCodecFactory(s, newSerializersForScheme(s, testMetaFactory{})) 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{}) if err != nil { t.Fatal(err) @@ -263,19 +263,19 @@ func TestVersionedEncoding(t *testing.T) { 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{}) if err == nil { t.Fatal(err) } // 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{}) if err != nil { t.Fatal(err) } - if string(out) != `{"myVersionKey":"__internal","myKindKey":"TestType1"}`+"\n" { + if string(out) != `{}`+"\n" { t.Fatal(string(out)) } } diff --git a/pkg/runtime/serializer/negotiated_codec.go b/pkg/runtime/serializer/negotiated_codec.go index 59b078ce8b0..d89177259ff 100644 --- a/pkg/runtime/serializer/negotiated_codec.go +++ b/pkg/runtime/serializer/negotiated_codec.go @@ -17,7 +17,6 @@ limitations under the License. package serializer import ( - "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/runtime" ) @@ -48,10 +47,10 @@ func (n *negotiatedSerializerWrapper) StreamingSerializerForMediaType(mediaType 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 } -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 } diff --git a/pkg/runtime/serializer/versioning/versioning.go b/pkg/runtime/serializer/versioning/versioning.go index 6e67964b163..4d8e69ecf4a 100644 --- a/pkg/runtime/serializer/versioning/versioning.go +++ b/pkg/runtime/serializer/versioning/versioning.go @@ -17,59 +17,20 @@ limitations under the License. package versioning import ( - "fmt" "io" "k8s.io/kubernetes/pkg/api/unversioned" "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. func NewCodecForScheme( // TODO: I should be a scheme interface? scheme *runtime.Scheme, encoder runtime.Encoder, decoder runtime.Decoder, - encodeVersion []unversioned.GroupVersion, - decodeVersion []unversioned.GroupVersion, + encodeVersion runtime.GroupVersioner, + decodeVersion runtime.GroupVersioner, ) runtime.Codec { return NewCodec(encoder, decoder, runtime.UnsafeObjectConvertor(scheme), scheme, scheme, scheme, encodeVersion, decodeVersion) } @@ -84,8 +45,8 @@ func NewCodec( creater runtime.ObjectCreater, copier runtime.ObjectCopier, typer runtime.ObjectTyper, - encodeVersion []unversioned.GroupVersion, - decodeVersion []unversioned.GroupVersion, + encodeVersion runtime.GroupVersioner, + decodeVersion runtime.GroupVersioner, ) runtime.Codec { internal := &codec{ encoder: encoder, @@ -94,33 +55,10 @@ func NewCodec( creater: creater, copier: copier, 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 } @@ -132,10 +70,8 @@ type codec struct { copier runtime.ObjectCopier typer runtime.ObjectTyper - encodeVersion map[string]unversioned.GroupVersion - decodeVersion map[string]unversioned.GroupVersion - - preferredEncodeVersion []unversioned.GroupVersion + encodeVersion runtime.GroupVersioner + decodeVersion runtime.GroupVersioner } // 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 } - // invoke a version conversion - group := gvk.Group - if defaultGVK != nil { - group = defaultGVK.Group - } - var targetGV unversioned.GroupVersion - if c.decodeVersion == nil { - // convert to internal by default - targetGV.Group = group - targetGV.Version = runtime.APIVersionInternal - } else { - gv, ok := c.decodeVersion[group] - if !ok { - // unknown objects are left in their original version - if isVersioned { - versioned.Objects = []runtime.Object{obj} - return versioned, gvk, nil - } - return obj, gvk, nil - } - targetGV = gv - } - - if gvk.GroupVersion() == targetGV { - if isVersioned { - versioned.Objects = []runtime.Object{obj} - return versioned, gvk, nil - } - return obj, gvk, nil - } - + // Convert if needed. if isVersioned { // create a copy, because ConvertToVersion does not guarantee non-mutation of objects 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} } - - // Convert if needed. - out, err := c.convertor.ConvertToVersion(obj, targetGV) + out, err := c.convertor.ConvertToVersion(obj, c.decodeVersion) if err != nil { return nil, gvk, err } if isVersioned { - versioned.Objects = append(versioned.Objects, out) + if versioned.Last() != out { + versioned.Objects = append(versioned.Objects, out) + } return versioned, 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 // conversion if necessary. Unversioned objects (according to the ObjectTyper) are output as is. 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) } + gvks, isUnversioned, err := c.typer.ObjectKinds(obj) if err != nil { return err } - gvk := gvks[0] if c.encodeVersion == nil || isUnversioned { objectKind := obj.GetObjectKind() old := objectKind.GroupVersionKind() - objectKind.SetGroupVersionKind(gvk) + objectKind.SetGroupVersionKind(gvks[0]) err = c.encoder.Encode(obj, w) objectKind.SetGroupVersionKind(old) 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 objectKind := obj.GetObjectKind() old := objectKind.GroupVersionKind() - out, err := c.convertor.ConvertToVersion(obj, targetGV) + out, err := c.convertor.ConvertToVersion(obj, c.encodeVersion) if err != nil { - if ok { - return err - } - } else { - obj = out + return err } // 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 objectKind.SetGroupVersionKind(old) return err diff --git a/pkg/runtime/serializer/versioning/versioning_test.go b/pkg/runtime/serializer/versioning/versioning_test.go index 2dfae4921af..f2600909c6c 100644 --- a/pkg/runtime/serializer/versioning/versioning_test.go +++ b/pkg/runtime/serializer/versioning/versioning_test.go @@ -53,7 +53,7 @@ func TestDecode(t *testing.T) { yaml bool pretty bool - encodes, decodes []unversioned.GroupVersion + encodes, decodes runtime.GroupVersioner defaultGVK *unversioned.GroupVersionKind into runtime.Object @@ -67,12 +67,14 @@ func TestDecode(t *testing.T) { serializer: &mockSerializer{actual: gvk1}, convertor: &checkConvertor{groupVersion: unversioned.GroupVersion{Group: "other", Version: "__internal"}}, expectedGVK: gvk1, + decodes: unversioned.GroupVersion{Group: "other", Version: "__internal"}, }, { serializer: &mockSerializer{actual: gvk1, obj: decodable1}, convertor: &checkConvertor{in: decodable1, obj: decodable2, groupVersion: unversioned.GroupVersion{Group: "other", Version: "__internal"}}, expectedGVK: gvk1, sameObject: decodable2, + decodes: unversioned.GroupVersion{Group: "other", Version: "__internal"}, }, // 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"}}, expectedGVK: gvk1, sameObject: decodable2, + decodes: unversioned.GroupVersion{Group: "force", Version: "__internal"}, }, // 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"}}, expectedGVK: gvk1, expectedObject: &runtime.VersionedObjects{Objects: []runtime.Object{decodable1, decodable2}}, + decodes: unversioned.GroupVersion{Group: "other", Version: "__internal"}, }, { 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"}}, expectedGVK: gvk1, expectedObject: &runtime.VersionedObjects{Objects: []runtime.Object{decodable1, decodable2}}, + decodes: unversioned.GroupVersion{Group: "other", Version: "__internal"}, }, // 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}, + convertor: &checkConvertor{in: decodable1, obj: decodable1, groupVersion: unversioned.GroupVersions{{Group: "other", Version: "blah"}}}, expectedGVK: gvk1, expectedObject: decodable1, }, { into: &runtime.VersionedObjects{Objects: []runtime.Object{}}, - decodes: []unversioned.GroupVersion{gvk1.GroupVersion()}, + decodes: unversioned.GroupVersions{gvk1.GroupVersion()}, 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, expectedObject: &runtime.VersionedObjects{Objects: []runtime.Object{decodable1}}, }, // 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}, + convertor: &checkConvertor{in: decodable1, obj: decodable1, groupVersion: unversioned.GroupVersions{{Group: "something", Version: "else"}}}, expectedGVK: gvk1, expectedObject: decodable1, }, { 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}, + convertor: &checkConvertor{in: decodable1, obj: decodable1, groupVersion: unversioned.GroupVersions{{Group: "something", Version: "else"}}}, + copier: &checkCopy{in: decodable1, obj: decodable1, err: nil}, expectedGVK: gvk1, expectedObject: &runtime.VersionedObjects{Objects: []runtime.Object{decodable1}}, }, @@ -228,7 +239,7 @@ func (c *checkCopy) Copy(obj runtime.Object) (runtime.Object, error) { type checkConvertor struct { err error in, obj runtime.Object - groupVersion unversioned.GroupVersion + groupVersion runtime.GroupVersioner directConvert bool } @@ -244,15 +255,15 @@ func (c *checkConvertor) Convert(in, out interface{}) error { } 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 { return nil, fmt.Errorf("unexpected call to ConvertToVersion") } if c.in != nil && c.in != in { return nil, fmt.Errorf("unexpected in: %s", in) } - if c.groupVersion != outVersion { - return nil, fmt.Errorf("unexpected outversion: %s", outVersion) + if !reflect.DeepEqual(c.groupVersion, outVersion) { + return nil, fmt.Errorf("unexpected outversion: %s (%s)", outVersion, c.groupVersion) } return c.obj, c.err } diff --git a/pkg/runtime/unstructured.go b/pkg/runtime/unstructured.go index 153dccda6c8..0e3488792d2 100644 --- a/pkg/runtime/unstructured.go +++ b/pkg/runtime/unstructured.go @@ -187,9 +187,14 @@ func (UnstructuredObjectConverter) Convert(in, out interface{}) error { return nil } -func (UnstructuredObjectConverter) ConvertToVersion(in Object, outVersion unversioned.GroupVersion) (Object, error) { - if gvk := in.GetObjectKind().GroupVersionKind(); gvk.GroupVersion() != outVersion { - return nil, errors.New("unstructured converter cannot convert versions") +func (UnstructuredObjectConverter) ConvertToVersion(in Object, target GroupVersioner) (Object, error) { + if kind := in.GetObjectKind().GroupVersionKind(); !kind.IsEmpty() { + 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 } diff --git a/pkg/runtime/unversioned_test.go b/pkg/runtime/unversioned_test.go index 5eb525dc944..7ffedfdb5a2 100644 --- a/pkg/runtime/unversioned_test.go +++ b/pkg/runtime/unversioned_test.go @@ -30,14 +30,13 @@ import ( "k8s.io/kubernetes/pkg/runtime" ) -var status = &unversioned.Status{ - Status: unversioned.StatusFailure, - Code: 200, - Reason: unversioned.StatusReasonUnknown, - Message: "", -} - func TestV1EncodeDecodeStatus(t *testing.T) { + status := &unversioned.Status{ + Status: unversioned.StatusFailure, + Code: 200, + Reason: unversioned.StatusReasonUnknown, + Message: "", + } v1Codec := testapi.Default.Codec() @@ -65,6 +64,12 @@ func TestV1EncodeDecodeStatus(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 // moves experimental from v1 to v1beta1 got merged. expCodec := api.Codecs.LegacyCodec(extensions.SchemeGroupVersion) diff --git a/plugin/pkg/scheduler/api/latest/latest.go b/plugin/pkg/scheduler/api/latest/latest.go index dbea2a8af56..9eec37e2e7b 100644 --- a/plugin/pkg/scheduler/api/latest/latest.go +++ b/plugin/pkg/scheduler/api/latest/latest.go @@ -47,7 +47,7 @@ func init() { api.Scheme, jsonSerializer, jsonSerializer, - []unversioned.GroupVersion{{Version: Version}}, - []unversioned.GroupVersion{{Version: runtime.APIVersionInternal}}, + unversioned.GroupVersion{Version: Version}, + runtime.InternalGroupVersioner, ) } diff --git a/test/integration/framework/serializer.go b/test/integration/framework/serializer.go index dc5ac6ba446..3a72ca82d02 100644 --- a/test/integration/framework/serializer.go +++ b/test/integration/framework/serializer.go @@ -17,7 +17,6 @@ limitations under the License. package framework import ( - "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/runtime/serializer/versioning" ) @@ -58,10 +57,10 @@ func (s *wrappedSerializer) UniversalDeserializer() runtime.Decoder { return s.serializer } -func (s *wrappedSerializer) EncoderForVersion(encoder runtime.Encoder, gv unversioned.GroupVersion) runtime.Encoder { - return versioning.NewCodec(encoder, nil, s.scheme, s.scheme, s.scheme, s.scheme, []unversioned.GroupVersion{gv}, nil) +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, gv, nil) } -func (s *wrappedSerializer) DecoderToVersion(decoder runtime.Decoder, gv unversioned.GroupVersion) runtime.Decoder { - return versioning.NewCodec(nil, decoder, s.scheme, s.scheme, s.scheme, s.scheme, nil, []unversioned.GroupVersion{gv}) +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, gv) }