diff --git a/pkg/runtime/interfaces.go b/pkg/runtime/interfaces.go index 4341f89b3fa..117f43a4a50 100644 --- a/pkg/runtime/interfaces.go +++ b/pkg/runtime/interfaces.go @@ -154,6 +154,18 @@ type StorageSerializer interface { 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 diff --git a/pkg/runtime/serializer/codec_factory.go b/pkg/runtime/serializer/codec_factory.go index ed5e8c471c3..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" @@ -336,47 +334,15 @@ type DirectCodecFactory struct { // EncoderForVersion returns an encoder that does not do conversion. gv is ignored. func (f DirectCodecFactory) EncoderForVersion(serializer runtime.Encoder, _ runtime.GroupVersioner) runtime.Encoder { - return DirectCodec{ - runtime.NewCodec(serializer, nil), - f.CodecFactory.scheme, + 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, _ runtime.GroupVersioner) runtime.Decoder { - return DirectCodec{ - runtime.NewCodec(nil, serializer), - nil, + 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/versioning/versioning.go b/pkg/runtime/serializer/versioning/versioning.go index 4d8e69ecf4a..4bd25e83156 100644 --- a/pkg/runtime/serializer/versioning/versioning.go +++ b/pkg/runtime/serializer/versioning/versioning.go @@ -88,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 { @@ -131,21 +137,8 @@ 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 { - 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()) - } + switch obj.(type) { + case *runtime.Unknown, *runtime.Unstructured, *runtime.UnstructuredList: return c.encoder.Encode(obj, w) } @@ -155,6 +148,11 @@ func (c *codec) Encode(obj runtime.Object, w io.Writer) error { } 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(gvks[0]) @@ -170,9 +168,52 @@ func (c *codec) Encode(obj runtime.Object, w io.Writer) error { if err != nil { return err } + + if e, ok := out.(runtime.NestedObjectEncoder); ok { + if err := e.EncodeNestedObjects(DirectEncoder{Encoder: c.encoder, ObjectTyper: c.typer}); err != nil { + return err + } + } + // Conversion is responsible for setting the proper group, version, and kind onto the outgoing object 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 { + 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 f2600909c6c..0ae74b2c9ae 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{} @@ -300,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 }