Make defaulting part of versioning codec

Most normal codec use should perform defaulting. DirectCodecs should not
perform defaulting. Update the defaulting_test to fuzz the list of known
defaulters. Use the new versioning.NewDefaultingCodec() method.
This commit is contained in:
Clayton Coleman
2016-09-25 12:09:52 -04:00
parent 742fb698d4
commit 1694cfb72d
9 changed files with 191 additions and 48 deletions

View File

@@ -76,6 +76,24 @@ func EncodeOrDie(e Encoder, obj Object) string {
return string(bytes)
}
// DefaultingSerializer invokes defaulting after decoding.
type DefaultingSerializer struct {
Defaulter ObjectDefaulter
Decoder Decoder
// Encoder is optional to allow this type to be used as both a Decoder and an Encoder
Encoder
}
// Decode performs a decode and then allows the defaulter to act on the provided object.
func (d DefaultingSerializer) Decode(data []byte, defaultGVK *unversioned.GroupVersionKind, into Object) (Object, *unversioned.GroupVersionKind, error) {
obj, gvk, err := d.Decoder.Decode(data, defaultGVK, into)
if err != nil {
return obj, gvk, err
}
d.Defaulter.Default(obj)
return obj, gvk, nil
}
// UseOrCreateObject returns obj if the canonical ObjectKind returned by the provided typer matches gvk, or
// invokes the ObjectCreator to instantiate a new gvk. Returns an error if the typer cannot find the object.
func UseOrCreateObject(t ObjectTyper, c ObjectCreater, gvk unversioned.GroupVersionKind, obj Object) (Object, error) {

View File

@@ -169,6 +169,12 @@ type NestedObjectDecoder interface {
///////////////////////////////////////////////////////////////////////////////
// Non-codec interfaces
type ObjectDefaulter interface {
// Default takes an object (must be a pointer) and applies any default values.
// Defaulters may not error.
Default(in Object)
}
type ObjectVersioner interface {
ConvertToVersion(in Object, gv GroupVersioner) (out Object, err error)
}

View File

@@ -196,7 +196,7 @@ func (f CodecFactory) SupportedStreamingMediaTypes() []string {
// 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, unversioned.GroupVersions(version), runtime.InternalGroupVersioner)
return versioning.NewDefaultingCodecForScheme(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
@@ -235,7 +235,7 @@ func (f CodecFactory) CodecForVersions(encoder runtime.Encoder, decoder runtime.
if decode == nil {
decode = runtime.InternalGroupVersioner
}
return versioning.NewCodecForScheme(f.scheme, encoder, decoder, encode, decode)
return versioning.NewDefaultingCodecForScheme(f.scheme, encoder, decoder, encode, decode)
}
// DecoderToVersion returns a decoder that targets the provided group version.

View File

@@ -21,6 +21,7 @@ import (
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/runtime"
utilruntime "k8s.io/kubernetes/pkg/util/runtime"
)
// NewCodecForScheme is a convenience method for callers that are using a scheme.
@@ -32,7 +33,19 @@ func NewCodecForScheme(
encodeVersion runtime.GroupVersioner,
decodeVersion runtime.GroupVersioner,
) runtime.Codec {
return NewCodec(encoder, decoder, runtime.UnsafeObjectConvertor(scheme), scheme, scheme, scheme, encodeVersion, decodeVersion)
return NewCodec(encoder, decoder, runtime.UnsafeObjectConvertor(scheme), scheme, scheme, scheme, nil, encodeVersion, decodeVersion)
}
// NewDefaultingCodecForScheme is a convenience method for callers that are using a scheme.
func NewDefaultingCodecForScheme(
// TODO: I should be a scheme interface?
scheme *runtime.Scheme,
encoder runtime.Encoder,
decoder runtime.Decoder,
encodeVersion runtime.GroupVersioner,
decodeVersion runtime.GroupVersioner,
) runtime.Codec {
return NewCodec(encoder, decoder, runtime.UnsafeObjectConvertor(scheme), scheme, scheme, scheme, scheme, encodeVersion, decodeVersion)
}
// NewCodec takes objects in their internal versions and converts them to external versions before
@@ -45,6 +58,7 @@ func NewCodec(
creater runtime.ObjectCreater,
copier runtime.ObjectCopier,
typer runtime.ObjectTyper,
defaulter runtime.ObjectDefaulter,
encodeVersion runtime.GroupVersioner,
decodeVersion runtime.GroupVersioner,
) runtime.Codec {
@@ -55,6 +69,7 @@ func NewCodec(
creater: creater,
copier: copier,
typer: typer,
defaulter: defaulter,
encodeVersion: encodeVersion,
decodeVersion: decodeVersion,
@@ -69,6 +84,7 @@ type codec struct {
creater runtime.ObjectCreater
copier runtime.ObjectCopier
typer runtime.ObjectTyper
defaulter runtime.ObjectDefaulter
encodeVersion runtime.GroupVersioner
decodeVersion runtime.GroupVersioner
@@ -102,11 +118,31 @@ func (c *codec) Decode(data []byte, defaultGVK *unversioned.GroupVersionKind, in
}
return into, gvk, nil
}
// perform defaulting if requested
if c.defaulter != nil {
// create a copy to ensure defaulting is not applied to the original versioned objects
if isVersioned {
copied, err := c.copier.Copy(obj)
if err != nil {
utilruntime.HandleError(err)
copied = obj
}
versioned.Objects = []runtime.Object{copied}
}
c.defaulter.Default(obj)
} else {
if isVersioned {
versioned.Objects = []runtime.Object{obj}
}
}
if err := c.convertor.Convert(obj, into, c.decodeVersion); err != nil {
return nil, gvk, err
}
if isVersioned {
versioned.Objects = []runtime.Object{obj, into}
versioned.Objects = append(versioned.Objects, into)
return versioned, gvk, nil
}
return into, gvk, nil
@@ -117,10 +153,17 @@ func (c *codec) Decode(data []byte, defaultGVK *unversioned.GroupVersionKind, in
// create a copy, because ConvertToVersion does not guarantee non-mutation of objects
copied, err := c.copier.Copy(obj)
if err != nil {
utilruntime.HandleError(err)
copied = obj
}
versioned.Objects = []runtime.Object{copied}
}
// perform defaulting if requested
if c.defaulter != nil {
c.defaulter.Default(obj)
}
out, err := c.convertor.ConvertToVersion(obj, c.decodeVersion)
if err != nil {
return nil, gvk, err

View File

@@ -64,7 +64,7 @@ func (d *testNestedDecodable) DecodeNestedObjects(_ runtime.Decoder) error {
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)
codec := NewCodec(nil, decoder, nil, nil, nil, nil, nil, nil, nil)
if _, _, err := codec.Decode([]byte(`{}`), nil, n); err != n.nestedErr {
t.Errorf("unexpected error: %v", err)
}
@@ -82,6 +82,7 @@ func TestNestedEncode(t *testing.T) {
&checkConvertor{obj: n2, groupVersion: unversioned.GroupVersion{Group: "other"}},
nil, nil,
&mockTyper{gvks: []unversioned.GroupVersionKind{{Kind: "test"}}},
nil,
unversioned.GroupVersion{Group: "other"}, nil,
)
if err := codec.Encode(n, ioutil.Discard); err != n2.nestedErr {
@@ -105,6 +106,7 @@ func TestDecode(t *testing.T) {
creater runtime.ObjectCreater
copier runtime.ObjectCopier
typer runtime.ObjectTyper
defaulter runtime.ObjectDefaulter
yaml bool
pretty bool
@@ -235,7 +237,7 @@ func TestDecode(t *testing.T) {
for i, test := range testCases {
t.Logf("%d", i)
s := NewCodec(test.serializer, test.serializer, test.convertor, test.creater, test.copier, test.typer, test.encodes, test.decodes)
s := NewCodec(test.serializer, test.serializer, test.convertor, test.creater, test.copier, test.typer, test.defaulter, test.encodes, test.decodes)
obj, gvk, err := s.Decode([]byte(`{}`), test.defaultGVK, test.into)
if !reflect.DeepEqual(test.expectedGVK, gvk) {