diff --git a/cmd/kube-proxy/app/options/options.go b/cmd/kube-proxy/app/options/options.go index 0f492cd9f67..27382dd0054 100644 --- a/cmd/kube-proxy/app/options/options.go +++ b/cmd/kube-proxy/app/options/options.go @@ -50,7 +50,7 @@ type ProxyServerConfig struct { func NewProxyConfig() *ProxyServerConfig { config := componentconfig.KubeProxyConfiguration{} - api.Scheme.Convert(&v1alpha1.KubeProxyConfiguration{}, &config) + api.Scheme.Convert(&v1alpha1.KubeProxyConfiguration{}, &config, nil) return &ProxyServerConfig{ KubeProxyConfiguration: config, ContentType: "application/vnd.kubernetes.protobuf", diff --git a/cmd/kubelet/app/options/options.go b/cmd/kubelet/app/options/options.go index 57e43b67f80..9fdf239968b 100644 --- a/cmd/kubelet/app/options/options.go +++ b/cmd/kubelet/app/options/options.go @@ -58,7 +58,7 @@ type KubeletServer struct { // NewKubeletServer will create a new KubeletServer with default values. func NewKubeletServer() *KubeletServer { config := componentconfig.KubeletConfiguration{} - api.Scheme.Convert(&v1alpha1.KubeletConfiguration{}, &config) + api.Scheme.Convert(&v1alpha1.KubeletConfiguration{}, &config, nil) return &KubeletServer{ AuthPath: util.NewStringFlag("/var/lib/kubelet/kubernetes_auth"), // deprecated KubeConfig: util.NewStringFlag("/var/lib/kubelet/kubeconfig"), diff --git a/pkg/api/meta/restmapper_test.go b/pkg/api/meta/restmapper_test.go index 4240981300c..11d7c11be0a 100644 --- a/pkg/api/meta/restmapper_test.go +++ b/pkg/api/meta/restmapper_test.go @@ -28,11 +28,11 @@ import ( type fakeConvertor struct{} -func (fakeConvertor) Convert(in, out interface{}) error { +func (fakeConvertor) Convert(in, out, context 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_proto_test.go b/pkg/api/serialization_proto_test.go index b899a71314b..5a75071e0dd 100644 --- a/pkg/api/serialization_proto_test.go +++ b/pkg/api/serialization_proto_test.go @@ -106,7 +106,7 @@ func BenchmarkEncodeCodecFromInternalProtobuf(b *testing.B) { width := len(items) encodable := make([]api.Pod, width) for i := range items { - if err := api.Scheme.Convert(&items[i], &encodable[i]); err != nil { + if err := api.Scheme.Convert(&items[i], &encodable[i], nil); err != nil { b.Fatal(err) } } diff --git a/pkg/api/serialization_test.go b/pkg/api/serialization_test.go index 4a44937a913..dcaf1880f3f 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" @@ -93,7 +92,11 @@ func roundTrip(t *testing.T, codec runtime.Codec, item runtime.Object) { name := reflect.TypeOf(item).Elem().Name() data, err := runtime.Encode(codec, item) if err != nil { - t.Errorf("%v: %v (%s)", name, err, printer.Sprintf("%#v", item)) + if runtime.IsNotRegisteredError(err) { + t.Logf("%v: not registered: %v (%s)", name, err, printer.Sprintf("%#v", item)) + } else { + t.Errorf("%v: %v (%s)", name, err, printer.Sprintf("%#v", item)) + } return } @@ -181,10 +184,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 { @@ -475,7 +482,7 @@ func BenchmarkEncodeCodecFromInternal(b *testing.B) { width := len(items) encodable := make([]api.Pod, width) for i := range items { - if err := api.Scheme.Convert(&items[i], &encodable[i]); err != nil { + if err := api.Scheme.Convert(&items[i], &encodable[i], nil); err != nil { b.Fatal(err) } } 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/api/v1/conversion_test.go b/pkg/api/v1/conversion_test.go index 27c215f3cfe..1384de341f7 100644 --- a/pkg/api/v1/conversion_test.go +++ b/pkg/api/v1/conversion_test.go @@ -127,7 +127,7 @@ func TestPodSpecConversion(t *testing.T) { ServiceAccountName: name, } v := versioned.PodSpec{} - if err := api.Scheme.Convert(i, &v); err != nil { + if err := api.Scheme.Convert(i, &v, nil); err != nil { t.Fatalf("unexpected error: %v", err) } if v.ServiceAccountName != name { @@ -152,7 +152,7 @@ func TestPodSpecConversion(t *testing.T) { } for k, v := range testCases { got := api.PodSpec{} - err := api.Scheme.Convert(v, &got) + err := api.Scheme.Convert(v, &got, nil) if err != nil { t.Fatalf("unexpected error for case %d: %v", k, err) } @@ -206,7 +206,7 @@ func TestResourceListConversion(t *testing.T) { for i, test := range tests { output := api.ResourceList{} - err := api.Scheme.Convert(&test.input, &output) + err := api.Scheme.Convert(&test.input, &output, nil) if err != nil { t.Fatalf("unexpected error for case %d: %v", i, err) } diff --git a/pkg/api/v1/defaults_test.go b/pkg/api/v1/defaults_test.go index 6987d0f301c..e7c802f6166 100644 --- a/pkg/api/v1/defaults_test.go +++ b/pkg/api/v1/defaults_test.go @@ -40,7 +40,7 @@ func roundTrip(t *testing.T, obj runtime.Object) runtime.Object { return nil } obj3 := reflect.New(reflect.TypeOf(obj).Elem()).Interface().(runtime.Object) - err = api.Scheme.Convert(obj2, obj3) + err = api.Scheme.Convert(obj2, obj3, nil) if err != nil { t.Errorf("%v\nSource: %#v", err, obj2) return nil diff --git a/pkg/apis/abac/v0/conversion_test.go b/pkg/apis/abac/v0/conversion_test.go index 827e8ce3681..30a433b5e35 100644 --- a/pkg/apis/abac/v0/conversion_test.go +++ b/pkg/apis/abac/v0/conversion_test.go @@ -67,7 +67,7 @@ func TestConversion(t *testing.T) { } for k, tc := range testcases { internal := &api.Policy{} - if err := api.Scheme.Convert(tc.old, internal); err != nil { + if err := api.Scheme.Convert(tc.old, internal, nil); err != nil { t.Errorf("%s: unexpected error: %v", k, err) } if !reflect.DeepEqual(internal, tc.expected) { diff --git a/pkg/apis/extensions/register.go b/pkg/apis/extensions/register.go index f12335b97c2..d4168eefae8 100644 --- a/pkg/apis/extensions/register.go +++ b/pkg/apis/extensions/register.go @@ -68,6 +68,7 @@ func addKnownTypes(scheme *runtime.Scheme) error { &Ingress{}, &IngressList{}, &api.ListOptions{}, + &api.DeleteOptions{}, &ReplicaSet{}, &ReplicaSetList{}, &api.ExportOptions{}, diff --git a/pkg/apis/extensions/v1beta1/conversion_test.go b/pkg/apis/extensions/v1beta1/conversion_test.go index 5fab6b2f463..82fd46009e6 100644 --- a/pkg/apis/extensions/v1beta1/conversion_test.go +++ b/pkg/apis/extensions/v1beta1/conversion_test.go @@ -59,7 +59,7 @@ func TestJobSpecConversion(t *testing.T) { ManualSelector: test.in, } v := versioned.JobSpec{} - if err := api.Scheme.Convert(i, &v); err != nil { + if err := api.Scheme.Convert(i, &v, nil); err != nil { t.Fatalf("unexpected error: %v", err) } if !reflect.DeepEqual(test.expectOut, v.AutoSelector) { @@ -73,7 +73,7 @@ func TestJobSpecConversion(t *testing.T) { AutoSelector: test.in, } e := batch.JobSpec{} - if err := api.Scheme.Convert(i, &e); err != nil { + if err := api.Scheme.Convert(i, &e, nil); err != nil { t.Fatalf("unexpected error: %v", err) } if !reflect.DeepEqual(test.expectOut, e.ManualSelector) { diff --git a/pkg/apis/extensions/v1beta1/defaults_test.go b/pkg/apis/extensions/v1beta1/defaults_test.go index 092cbe4b2e4..4a90f178215 100644 --- a/pkg/apis/extensions/v1beta1/defaults_test.go +++ b/pkg/apis/extensions/v1beta1/defaults_test.go @@ -728,7 +728,7 @@ func roundTrip(t *testing.T, obj runtime.Object) runtime.Object { return nil } obj3 := reflect.New(reflect.TypeOf(obj).Elem()).Interface().(runtime.Object) - err = api.Scheme.Convert(obj2, obj3) + err = api.Scheme.Convert(obj2, obj3, nil) if err != nil { t.Errorf("%v\nSource: %#v", err, obj2) return nil diff --git a/pkg/apis/policy/register.go b/pkg/apis/policy/register.go index 0b219dff702..d9c26d2e82e 100644 --- a/pkg/apis/policy/register.go +++ b/pkg/apis/policy/register.go @@ -17,6 +17,7 @@ limitations under the License. package policy import ( + "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/runtime" ) @@ -48,6 +49,7 @@ func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, &PodDisruptionBudget{}, &PodDisruptionBudgetList{}, + &api.ListOptions{}, ) return nil } 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/auth/authorizer/abac/abac.go b/pkg/auth/authorizer/abac/abac.go index 75db334e819..ef4b74d694a 100644 --- a/pkg/auth/authorizer/abac/abac.go +++ b/pkg/auth/authorizer/abac/abac.go @@ -89,7 +89,7 @@ func NewFromFile(path string) (policyList, error) { if err := runtime.DecodeInto(decoder, b, oldPolicy); err != nil { return nil, policyLoadError{path, i, b, err} } - if err := api.Scheme.Convert(oldPolicy, p); err != nil { + if err := api.Scheme.Convert(oldPolicy, p, nil); err != nil { return nil, policyLoadError{path, i, b, err} } pl = append(pl, p) diff --git a/pkg/auth/authorizer/abac/abac_test.go b/pkg/auth/authorizer/abac/abac_test.go index 0070fbdea32..05d09aa51c8 100644 --- a/pkg/auth/authorizer/abac/abac_test.go +++ b/pkg/auth/authorizer/abac/abac_test.go @@ -562,7 +562,7 @@ func TestSubjectMatches(t *testing.T) { for k, tc := range testCases { policy := &api.Policy{} - if err := api.Scheme.Convert(tc.Policy, policy); err != nil { + if err := api.Scheme.Convert(tc.Policy, policy, nil); err != nil { t.Errorf("%s: error converting: %v", k, err) continue } @@ -950,7 +950,7 @@ func TestPolicy(t *testing.T) { } for _, test := range tests { policy := &api.Policy{} - if err := api.Scheme.Convert(test.policy, policy); err != nil { + if err := api.Scheme.Convert(test.policy, policy, nil); err != nil { t.Errorf("%s: error converting: %v", test.name, err) continue } 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/controller/controller_utils.go b/pkg/controller/controller_utils.go index 81930422eba..0a851c23f81 100644 --- a/pkg/controller/controller_utils.go +++ b/pkg/controller/controller_utils.go @@ -458,7 +458,7 @@ func GetPodFromTemplate(template *api.PodTemplateSpec, parentObject runtime.Obje if controllerRef != nil { pod.OwnerReferences = append(pod.OwnerReferences, *controllerRef) } - if err := api.Scheme.Convert(&template.Spec, &pod.Spec); err != nil { + if err := api.Scheme.Convert(&template.Spec, &pod.Spec, nil); err != nil { return nil, fmt.Errorf("unable to convert pod template: %v", err) } return pod, nil diff --git a/pkg/controller/scheduledjob/utils.go b/pkg/controller/scheduledjob/utils.go index a5d716baf0e..f65b2a2d136 100644 --- a/pkg/controller/scheduledjob/utils.go +++ b/pkg/controller/scheduledjob/utils.go @@ -209,7 +209,7 @@ func getJobFromTemplate(sj *batch.ScheduledJob, scheduledTime time.Time) (*batch Name: name, }, } - if err := api.Scheme.Convert(&sj.Spec.JobTemplate.Spec, &job.Spec); err != nil { + if err := api.Scheme.Convert(&sj.Spec.JobTemplate.Spec, &job.Spec, nil); err != nil { return nil, fmt.Errorf("unable to convert job template: %v", err) } return job, nil 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/kubectl/cmd/patch.go b/pkg/kubectl/cmd/patch.go index ec3aba2aad4..0e830908bc5 100644 --- a/pkg/kubectl/cmd/patch.go +++ b/pkg/kubectl/cmd/patch.go @@ -197,7 +197,7 @@ func RunPatch(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []stri if err != nil { return err } - originalObjJS, err := runtime.Encode(api.Codecs.LegacyCodec(), info.VersionedObject.(runtime.Object)) + originalObjJS, err := runtime.Encode(api.Codecs.LegacyCodec(mapping.GroupVersionKind.GroupVersion()), info.VersionedObject.(runtime.Object)) if err != nil { return err } diff --git a/pkg/kubectl/resource_printer.go b/pkg/kubectl/resource_printer.go index f4ee016daef..dcc958a33f2 100644 --- a/pkg/kubectl/resource_printer.go +++ b/pkg/kubectl/resource_printer.go @@ -195,20 +195,11 @@ func (p *VersionedPrinter) PrintObj(obj runtime.Object, w io.Writer) error { if len(p.versions) == 0 { return fmt.Errorf("no version specified, object cannot be converted") } - for _, version := range p.versions { - if version.IsEmpty() { - continue - } - converted, err := p.converter.ConvertToVersion(obj, version) - if runtime.IsNotRegisteredError(err) { - continue - } - if err != nil { - return err - } - return p.printer.PrintObj(converted, w) + converted, err := p.converter.ConvertToVersion(obj, unversioned.GroupVersions(p.versions)) + if err != nil { + return err } - return fmt.Errorf("the object cannot be converted to any of the versions: %v", p.versions) + return p.printer.PrintObj(converted, w) } // TODO: implement HandledResources() diff --git a/pkg/kubelet/config/file_test.go b/pkg/kubelet/config/file_test.go index ce79d88f4cf..f6bb67e81bb 100644 --- a/pkg/kubelet/config/file_test.go +++ b/pkg/kubelet/config/file_test.go @@ -129,7 +129,7 @@ func TestReadPodsFromFile(t *testing.T) { for _, testCase := range testCases { func() { var versionedPod runtime.Object - err := testapi.Default.Converter().Convert(&testCase.pod, &versionedPod) + err := testapi.Default.Converter().Convert(&testCase.pod, &versionedPod, nil) if err != nil { t.Fatalf("%s: error in versioning the pod: %v", testCase.desc, err) } diff --git a/pkg/kubelet/config/http_test.go b/pkg/kubelet/config/http_test.go index 20b4dc39e1c..9e70d9eecad 100644 --- a/pkg/kubelet/config/http_test.go +++ b/pkg/kubelet/config/http_test.go @@ -276,7 +276,7 @@ func TestExtractPodsFromHTTP(t *testing.T) { for _, testCase := range testCases { var versionedPods runtime.Object - err := testapi.Default.Converter().Convert(&testCase.pods, &versionedPods) + err := testapi.Default.Converter().Convert(&testCase.pods, &versionedPods, nil) if err != nil { t.Fatalf("%s: error in versioning the pods: %s", testCase.desc, err) } diff --git a/pkg/registry/thirdpartyresourcedata/codec.go b/pkg/registry/thirdpartyresourcedata/codec.go index b96d789ca57..64308565e77 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. @@ -53,8 +53,8 @@ func (t *thirdPartyObjectConverter) ConvertToVersion(in runtime.Object, outVersi } } -func (t *thirdPartyObjectConverter) Convert(in, out interface{}) error { - return t.converter.Convert(in, out) +func (t *thirdPartyObjectConverter) Convert(in, out, context interface{}) error { + return t.converter.Convert(in, out, context) } func (t *thirdPartyObjectConverter) ConvertFieldLabel(version, kind, label, value string) (string, string, error) { @@ -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..9077f0fcd73 100644 --- a/pkg/runtime/codec.go +++ b/pkg/runtime/codec.go @@ -145,16 +145,16 @@ func (c *parameterCodec) DecodeParameters(parameters url.Values, from unversione } targetGVK := targetGVKs[0] if targetGVK.GroupVersion() == from { - return c.convertor.Convert(¶meters, into) + return c.convertor.Convert(¶meters, into, nil) } input, err := c.creator.New(from.WithKind(targetGVK.Kind)) if err != nil { return err } - if err := c.convertor.Convert(¶meters, input); err != nil { + if err := c.convertor.Convert(¶meters, input, nil); err != nil { return err } - return c.convertor.Convert(input, into) + return c.convertor.Convert(input, into, nil) } // EncodeParameters converts the provided object into the to version, then converts that object to url.Values. @@ -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/conversion_test.go b/pkg/runtime/conversion_test.go index c364d8fd2d4..9c2e7f9f81a 100644 --- a/pkg/runtime/conversion_test.go +++ b/pkg/runtime/conversion_test.go @@ -122,7 +122,7 @@ func TestStringMapConversion(t *testing.T) { for k, tc := range testCases { out := &ExternalComplex{} - if err := scheme.Convert(&tc.input, out); (tc.errFn == nil && err != nil) || (tc.errFn != nil && !tc.errFn(err)) { + if err := scheme.Convert(&tc.input, out, nil); (tc.errFn == nil && err != nil) || (tc.errFn != nil && !tc.errFn(err)) { t.Errorf("%s: unexpected error: %v", k, err) continue } else if err != nil { 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..07f77ca1b9f 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,29 +148,41 @@ 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 +} + +// NestedObjectEncoder is an optional interface that objects may implement to be given +// an opportunity to encode any nested Objects / RawExtensions during serialization. +type NestedObjectEncoder interface { + EncodeNestedObjects(e Encoder) error +} + +// NestedObjectDecoder is an optional interface that objects may implement to be given +// an opportunity to decode any nested Objects / RawExtensions during serialization. +type NestedObjectDecoder interface { + DecodeNestedObjects(d Decoder) error } /////////////////////////////////////////////////////////////////////////////// // 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. type ObjectConvertor interface { // Convert attempts to convert one object into another, or returns an error. This method does - // not guarantee the in object is not mutated. - Convert(in, out interface{}) error + // not guarantee the in object is not mutated. The context argument will be passed to + // all nested conversions. + Convert(in, out, context 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) + // method does not guarantee that the in object is not mutated. This method is similar to + // Convert() but handles specific details of choosing the correct output version. + 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..beeafe72176 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" @@ -128,7 +129,7 @@ func TestScheme(t *testing.T) { // Test Convert external := &ExternalSimple{} - err = scheme.Convert(simple, external) + err = scheme.Convert(simple, external, nil) if err != nil { t.Fatalf("Unexpected error: %v", err) } @@ -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"} @@ -631,7 +894,7 @@ func TestMetaValuesUnregisteredConvert(t *testing.T) { simple := &InternalSimple{TestString: "foo"} external := &ExternalSimple{} - err = s.Convert(simple, external) + err = s.Convert(simple, external, nil) if err != nil { t.Fatalf("Unexpected error: %v", err) } diff --git a/pkg/runtime/serializer/codec_factory.go b/pkg/runtime/serializer/codec_factory.go index 66a9ae5dbe2..afccae5a6ea 100644 --- a/pkg/runtime/serializer/codec_factory.go +++ b/pkg/runtime/serializer/codec_factory.go @@ -17,8 +17,6 @@ limitations under the License. package serializer import ( - "io" - "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/runtime/serializer/json" @@ -188,13 +186,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 +213,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,48 +333,16 @@ 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 { - return DirectCodec{ - runtime.NewCodec(serializer, nil), - f.CodecFactory.scheme, +func (f DirectCodecFactory) EncoderForVersion(serializer runtime.Encoder, _ runtime.GroupVersioner) runtime.Encoder { + return versioning.DirectEncoder{ + Encoder: serializer, + ObjectTyper: f.CodecFactory.scheme, } } // DecoderToVersion returns an decoder that does not do conversion. gv is ignored. -func (f DirectCodecFactory) DecoderToVersion(serializer runtime.Decoder, gv unversioned.GroupVersion) runtime.Decoder { - return DirectCodec{ - runtime.NewCodec(nil, serializer), - nil, +func (f DirectCodecFactory) DecoderToVersion(serializer runtime.Decoder, _ runtime.GroupVersioner) runtime.Decoder { + return versioning.DirectDecoder{ + Decoder: serializer, } } - -// DirectCodec is a codec that does not do conversion. It sets the gvk during serialization, and removes the gvk during deserialization. -type DirectCodec struct { - runtime.Serializer - runtime.ObjectTyper -} - -// EncodeToStream does not do conversion. It sets the gvk during serialization. overrides are ignored. -func (c DirectCodec) Encode(obj runtime.Object, stream io.Writer) error { - gvks, _, err := c.ObjectTyper.ObjectKinds(obj) - if err != nil { - return err - } - kind := obj.GetObjectKind() - oldGVK := kind.GroupVersionKind() - kind.SetGroupVersionKind(gvks[0]) - err = c.Serializer.Encode(obj, stream) - kind.SetGroupVersionKind(oldGVK) - return err -} - -// Decode does not do conversion. It removes the gvk during deserialization. -func (c DirectCodec) Decode(data []byte, defaults *unversioned.GroupVersionKind, into runtime.Object) (runtime.Object, *unversioned.GroupVersionKind, error) { - obj, gvk, err := c.Serializer.Decode(data, defaults, into) - if obj != nil { - kind := obj.GetObjectKind() - // clearing the gvk is just a convention of a codec - kind.SetGroupVersionKind(unversioned.GroupVersionKind{}) - } - return obj, gvk, err -} 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..b3a165a5379 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 @@ -152,6 +88,12 @@ func (c *codec) Decode(data []byte, defaultGVK *unversioned.GroupVersionKind, in return nil, gvk, err } + if d, ok := obj.(runtime.NestedObjectDecoder); ok { + if err := d.DecodeNestedObjects(DirectDecoder{c.decoder}); err != nil { + return nil, gvk, err + } + } + // if we specify a target, use generic conversion. if into != nil { if into == obj { @@ -160,7 +102,7 @@ func (c *codec) Decode(data []byte, defaultGVK *unversioned.GroupVersionKind, in } return into, gvk, nil } - if err := c.convertor.Convert(obj, into); err != nil { + if err := c.convertor.Convert(obj, into, c.decodeVersion); err != nil { return nil, gvk, err } if isVersioned { @@ -170,37 +112,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 +121,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,51 +137,86 @@ 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 obj.(type) { + case *runtime.Unknown, *runtime.Unstructured, *runtime.UnstructuredList: 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 { + if e, ok := obj.(runtime.NestedObjectEncoder); ok { + if err := e.EncodeNestedObjects(DirectEncoder{Encoder: c.encoder, ObjectTyper: c.typer}); err != nil { + return err + } + } 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 + } + + if e, ok := out.(runtime.NestedObjectEncoder); ok { + if err := e.EncodeNestedObjects(DirectEncoder{Encoder: c.encoder, ObjectTyper: c.typer}); err != nil { return err } - } else { - obj = out } + // 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 } + +// DirectEncoder serializes an object and ensures the GVK is set. +type DirectEncoder struct { + runtime.Encoder + runtime.ObjectTyper +} + +// Encode does not do conversion. It sets the gvk during serialization. +func (e DirectEncoder) Encode(obj runtime.Object, stream io.Writer) error { + gvks, _, err := e.ObjectTyper.ObjectKinds(obj) + if err != nil { + if runtime.IsNotRegisteredError(err) { + return e.Encoder.Encode(obj, stream) + } + return err + } + kind := obj.GetObjectKind() + oldGVK := kind.GroupVersionKind() + kind.SetGroupVersionKind(gvks[0]) + err = e.Encoder.Encode(obj, stream) + kind.SetGroupVersionKind(oldGVK) + return err +} + +// DirectDecoder clears the group version kind of a deserialized object. +type DirectDecoder struct { + runtime.Decoder +} + +// Decode does not do conversion. It removes the gvk during deserialization. +func (d DirectDecoder) Decode(data []byte, defaults *unversioned.GroupVersionKind, into runtime.Object) (runtime.Object, *unversioned.GroupVersionKind, error) { + obj, gvk, err := d.Decoder.Decode(data, defaults, into) + if obj != nil { + kind := obj.GetObjectKind() + // clearing the gvk is just a convention of a codec + kind.SetGroupVersionKind(unversioned.GroupVersionKind{}) + } + return obj, gvk, err +} diff --git a/pkg/runtime/serializer/versioning/versioning_test.go b/pkg/runtime/serializer/versioning/versioning_test.go index 2dfae4921af..ce5d944d1e8 100644 --- a/pkg/runtime/serializer/versioning/versioning_test.go +++ b/pkg/runtime/serializer/versioning/versioning_test.go @@ -19,6 +19,7 @@ package versioning import ( "fmt" "io" + "io/ioutil" "reflect" "testing" @@ -37,6 +38,60 @@ func (d *testDecodable) GetObjectKind() unversioned.ObjectKind { func (d *testDecodable) SetGroupVersionKind(gvk unversioned.GroupVersionKind) { d.gvk = gvk } func (d *testDecodable) GroupVersionKind() unversioned.GroupVersionKind { return d.gvk } +type testNestedDecodable struct { + Other string + Value int `json:"value"` + + gvk unversioned.GroupVersionKind + nestedCalled bool + nestedErr error +} + +func (d *testNestedDecodable) GetObjectKind() unversioned.ObjectKind { return d } +func (d *testNestedDecodable) SetGroupVersionKind(gvk unversioned.GroupVersionKind) { d.gvk = gvk } +func (d *testNestedDecodable) GroupVersionKind() unversioned.GroupVersionKind { return d.gvk } + +func (d *testNestedDecodable) EncodeNestedObjects(e runtime.Encoder) error { + d.nestedCalled = true + return d.nestedErr +} + +func (d *testNestedDecodable) DecodeNestedObjects(_ runtime.Decoder) error { + d.nestedCalled = true + return d.nestedErr +} + +func TestNestedDecode(t *testing.T) { + n := &testNestedDecodable{nestedErr: fmt.Errorf("unable to decode")} + decoder := &mockSerializer{obj: n} + codec := NewCodec(nil, decoder, nil, nil, nil, nil, nil, nil) + if _, _, err := codec.Decode([]byte(`{}`), nil, n); err != n.nestedErr { + t.Errorf("unexpected error: %v", err) + } + if !n.nestedCalled { + t.Errorf("did not invoke nested decoder") + } +} + +func TestNestedEncode(t *testing.T) { + n := &testNestedDecodable{nestedErr: fmt.Errorf("unable to decode")} + n2 := &testNestedDecodable{nestedErr: fmt.Errorf("unable to decode 2")} + encoder := &mockSerializer{obj: n} + codec := NewCodec( + encoder, nil, + &checkConvertor{obj: n2, groupVersion: unversioned.GroupVersion{Group: "other"}}, + nil, nil, + &mockTyper{gvks: []unversioned.GroupVersionKind{{Kind: "test"}}}, + unversioned.GroupVersion{Group: "other"}, nil, + ) + if err := codec.Encode(n, ioutil.Discard); err != n2.nestedErr { + t.Errorf("unexpected error: %v", err) + } + if n.nestedCalled || !n2.nestedCalled { + t.Errorf("did not invoke correct nested decoder") + } +} + func TestDecode(t *testing.T) { gvk1 := &unversioned.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"} decodable1 := &testDecodable{} @@ -53,7 +108,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 +122,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 +138,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 +179,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 +189,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,11 +294,11 @@ 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 } -func (c *checkConvertor) Convert(in, out interface{}) error { +func (c *checkConvertor) Convert(in, out, context interface{}) error { if !c.directConvert { return fmt.Errorf("unexpected call to Convert") } @@ -244,15 +310,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 } @@ -289,10 +355,15 @@ func (c *mockCreater) New(kind unversioned.GroupVersionKind) (runtime.Object, er } type mockTyper struct { - gvk *unversioned.GroupVersionKind - err error + gvks []unversioned.GroupVersionKind + unversioned bool + err error } -func (t *mockTyper) ObjectKind(obj runtime.Object) (*unversioned.GroupVersionKind, bool, error) { - return t.gvk, false, t.err +func (t *mockTyper) ObjectKinds(obj runtime.Object) ([]unversioned.GroupVersionKind, bool, error) { + return t.gvks, t.unversioned, t.err +} + +func (t *mockTyper) Recognizes(_ unversioned.GroupVersionKind) bool { + return true } diff --git a/pkg/runtime/unstructured.go b/pkg/runtime/unstructured.go index 153dccda6c8..7f275ef1a9a 100644 --- a/pkg/runtime/unstructured.go +++ b/pkg/runtime/unstructured.go @@ -168,7 +168,7 @@ func (s unstructuredJSONScheme) decodeToList(data []byte, list *UnstructuredList // sane implementation for APIs that require an object converter. type UnstructuredObjectConverter struct{} -func (UnstructuredObjectConverter) Convert(in, out interface{}) error { +func (UnstructuredObjectConverter) Convert(in, out, context interface{}) error { unstructIn, ok := in.(*Unstructured) if !ok { return fmt.Errorf("input type %T in not valid for unstructured conversion", in) @@ -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/cmd/kube-scheduler/app/options/options.go b/plugin/cmd/kube-scheduler/app/options/options.go index 4d84dcd225f..69ec1db955f 100644 --- a/plugin/cmd/kube-scheduler/app/options/options.go +++ b/plugin/cmd/kube-scheduler/app/options/options.go @@ -41,7 +41,7 @@ type SchedulerServer struct { // NewSchedulerServer creates a new SchedulerServer with default parameters func NewSchedulerServer() *SchedulerServer { config := componentconfig.KubeSchedulerConfiguration{} - api.Scheme.Convert(&v1alpha1.KubeSchedulerConfiguration{}, &config) + api.Scheme.Convert(&v1alpha1.KubeSchedulerConfiguration{}, &config, nil) config.LeaderElection.LeaderElect = true s := SchedulerServer{ KubeSchedulerConfiguration: config, 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) }