mirror of
https://github.com/k3s-io/kubernetes.git
synced 2026-01-04 23:17:50 +00:00
Simplify Codec and split responsibilities
Break Codec into two general purpose interfaces, Encoder and Decoder, and move parameter codec responsibilities to ParameterCodec. Make unversioned types explicit when registering - these types go through conversion without modification. Switch to use "__internal" instead of "" to represent the internal version. Future commits will also add group defaulting (so that "" is expanded internally into a known group version, and only cleared during set). For embedded types like runtime.Object -> runtime.RawExtension, put the responsibility on the caller of Decode/Encode to handle transformation into destination serialization. Future commits will expand RawExtension and Unknown to accept a content encoding as well as bytes. Make Unknown a bit more powerful and use it to carry unrecognized types.
This commit is contained in:
@@ -17,9 +17,7 @@ limitations under the License.
|
||||
package runtime
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"reflect"
|
||||
|
||||
@@ -36,9 +34,6 @@ type Scheme struct {
|
||||
fieldLabelConversionFuncs map[string]map[string]FieldLabelConversionFunc
|
||||
}
|
||||
|
||||
var _ Decoder = &Scheme{}
|
||||
var _ ObjectTyper = &Scheme{}
|
||||
|
||||
// Function to convert a field selector to internal representation.
|
||||
type FieldLabelConversionFunc func(label, value string) (internalLabel, internalValue string, err error)
|
||||
|
||||
@@ -55,205 +50,11 @@ func (self *Scheme) fromScope(s conversion.Scope) (inVersion, outVersion string,
|
||||
return inVersion, outVersion, scheme
|
||||
}
|
||||
|
||||
// emptyPlugin is used to copy the Kind field to and from plugin objects.
|
||||
type emptyPlugin struct {
|
||||
PluginBase `json:",inline"`
|
||||
}
|
||||
|
||||
// embeddedObjectToRawExtension does the conversion you would expect from the name, using the information
|
||||
// given in conversion.Scope. It's placed in the DefaultScheme as a ConversionFunc to enable plugins;
|
||||
// see the comment for RawExtension.
|
||||
func (self *Scheme) embeddedObjectToRawExtension(in *EmbeddedObject, out *RawExtension, s conversion.Scope) error {
|
||||
if in.Object == nil {
|
||||
out.RawJSON = []byte("null")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Figure out the type and kind of the output object.
|
||||
_, outGroupVersionString, scheme := self.fromScope(s)
|
||||
objKind, err := scheme.raw.ObjectKind(in.Object)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
outVersion, err := unversioned.ParseGroupVersion(outGroupVersionString)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Manufacture an object of this type and kind.
|
||||
outObj, err := scheme.New(outVersion.WithKind(objKind.Kind))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Manually do the conversion.
|
||||
err = s.Convert(in.Object, outObj, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Copy the kind field into the output object.
|
||||
err = s.Convert(
|
||||
&emptyPlugin{PluginBase: PluginBase{Kind: objKind.Kind}},
|
||||
outObj,
|
||||
conversion.SourceToDest|conversion.IgnoreMissingFields|conversion.AllowDifferentFieldTypeNames,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Because we provide the correct version, EncodeToVersion will not attempt a conversion.
|
||||
raw, err := scheme.EncodeToVersion(outObj, outVersion.String())
|
||||
if err != nil {
|
||||
// TODO: if this fails, create an Unknown-- maybe some other
|
||||
// component will understand it.
|
||||
return err
|
||||
}
|
||||
out.RawJSON = raw
|
||||
return nil
|
||||
}
|
||||
|
||||
// rawExtensionToEmbeddedObject does the conversion you would expect from the name, using the information
|
||||
// given in conversion.Scope. It's placed in all schemes as a ConversionFunc to enable plugins;
|
||||
// see the comment for RawExtension.
|
||||
func (self *Scheme) rawExtensionToEmbeddedObject(in *RawExtension, out *EmbeddedObject, s conversion.Scope) error {
|
||||
if len(in.RawJSON) == 0 || (len(in.RawJSON) == 4 && string(in.RawJSON) == "null") {
|
||||
out.Object = nil
|
||||
return nil
|
||||
}
|
||||
// Figure out the type and kind of the output object.
|
||||
inGroupVersionString, outGroupVersionString, scheme := self.fromScope(s)
|
||||
dataKind, err := scheme.raw.DataKind(in.RawJSON)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
inVersion, err := unversioned.ParseGroupVersion(inGroupVersionString)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
outVersion, err := unversioned.ParseGroupVersion(outGroupVersionString)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// We have to make this object ourselves because we don't store the version field for
|
||||
// plugin objects.
|
||||
inObj, err := scheme.New(inVersion.WithKind(dataKind.Kind))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = DecodeInto(scheme, in.RawJSON, inObj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Make the desired internal version, and do the conversion.
|
||||
outObj, err := scheme.New(outVersion.WithKind(dataKind.Kind))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = scheme.Convert(inObj, outObj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Last step, clear the Kind field; that should always be blank in memory.
|
||||
err = s.Convert(
|
||||
&emptyPlugin{PluginBase: PluginBase{Kind: ""}},
|
||||
outObj,
|
||||
conversion.SourceToDest|conversion.IgnoreMissingFields|conversion.AllowDifferentFieldTypeNames,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
out.Object = outObj
|
||||
return nil
|
||||
}
|
||||
|
||||
// runtimeObjectToRawExtensionArray takes a list of objects and encodes them as RawExtension in the output version
|
||||
// defined by the conversion.Scope. If objects must be encoded to different schema versions than the default, you
|
||||
// should encode them yourself with runtime.Unknown, or convert the object prior to invoking conversion. Objects
|
||||
// outside of the current scheme must be added as runtime.Unknown.
|
||||
func (self *Scheme) runtimeObjectToRawExtensionArray(in *[]Object, out *[]RawExtension, s conversion.Scope) error {
|
||||
src := *in
|
||||
dest := make([]RawExtension, len(src))
|
||||
|
||||
_, outVersion, scheme := self.fromScope(s)
|
||||
|
||||
for i := range src {
|
||||
switch t := src[i].(type) {
|
||||
case *Unknown:
|
||||
// TODO: this should be decoupled from the scheme (since it is JSON specific)
|
||||
dest[i].RawJSON = t.RawJSON
|
||||
case *Unstructured:
|
||||
// TODO: this should be decoupled from the scheme (since it is JSON specific)
|
||||
data, err := json.Marshal(t.Object)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dest[i].RawJSON = data
|
||||
default:
|
||||
version := outVersion
|
||||
// if the object exists
|
||||
// this code is try to set the outputVersion, but only if the object has a non-internal group version
|
||||
if inGVK, err := scheme.ObjectKind(src[i]); err == nil && !inGVK.GroupVersion().IsEmpty() {
|
||||
if self.raw.InternalVersions[inGVK.Group] != inGVK.GroupVersion() {
|
||||
version = inGVK.GroupVersion().String()
|
||||
}
|
||||
}
|
||||
data, err := scheme.EncodeToVersion(src[i], version)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dest[i].RawJSON = data
|
||||
}
|
||||
}
|
||||
*out = dest
|
||||
return nil
|
||||
}
|
||||
|
||||
// rawExtensionToRuntimeObjectArray attempts to decode objects from the array - if they are unrecognized objects,
|
||||
// they are added as Unknown.
|
||||
func (self *Scheme) rawExtensionToRuntimeObjectArray(in *[]RawExtension, out *[]Object, s conversion.Scope) error {
|
||||
src := *in
|
||||
dest := make([]Object, len(src))
|
||||
|
||||
_, _, scheme := self.fromScope(s)
|
||||
|
||||
for i := range src {
|
||||
data := src[i].RawJSON
|
||||
dataKind, err := scheme.raw.DataKind(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dest[i] = &Unknown{
|
||||
TypeMeta: TypeMeta{
|
||||
APIVersion: dataKind.GroupVersion().String(),
|
||||
Kind: dataKind.Kind,
|
||||
},
|
||||
RawJSON: data,
|
||||
}
|
||||
}
|
||||
*out = dest
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewScheme creates a new Scheme. This scheme is pluggable by default.
|
||||
func NewScheme(internalGroupVersions ...unversioned.GroupVersion) *Scheme {
|
||||
func NewScheme() *Scheme {
|
||||
s := &Scheme{conversion.NewScheme(), map[string]map[string]FieldLabelConversionFunc{}}
|
||||
s.AddConversionFuncs(DefaultEmbeddedConversions()...)
|
||||
|
||||
for _, internalGV := range internalGroupVersions {
|
||||
s.raw.InternalVersions[internalGV.Group] = internalGV
|
||||
}
|
||||
|
||||
s.raw.MetaFactory = conversion.SimpleMetaFactory{BaseFields: []string{"TypeMeta"}, VersionField: "APIVersion", KindField: "Kind"}
|
||||
if err := s.raw.AddConversionFuncs(
|
||||
s.embeddedObjectToRawExtension,
|
||||
s.rawExtensionToEmbeddedObject,
|
||||
s.runtimeObjectToRawExtensionArray,
|
||||
s.rawExtensionToRuntimeObjectArray,
|
||||
); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// Enable map[string][]string conversions by default
|
||||
if err := s.raw.AddConversionFuncs(DefaultStringConversions...); err != nil {
|
||||
panic(err)
|
||||
@@ -267,14 +68,22 @@ func NewScheme(internalGroupVersions ...unversioned.GroupVersion) *Scheme {
|
||||
return s
|
||||
}
|
||||
|
||||
// AddInternalGroupVersion registers an internal GroupVersion with the scheme. This can later be
|
||||
// used to lookup the internal GroupVersion for a given Group
|
||||
func (s *Scheme) AddInternalGroupVersion(gv unversioned.GroupVersion) {
|
||||
s.raw.InternalVersions[gv.Group] = gv
|
||||
// AddUnversionedTypes registers the provided types as "unversioned", which means that they follow special rules.
|
||||
// Whenever an object of this type is serialized, it is serialized with the provided group version and is not
|
||||
// converted. Thus unversioned objects are expected to remain backwards compatible forever, as if they were in an
|
||||
// API group and version that would never be updated.
|
||||
//
|
||||
// TODO: there is discussion about removing unversioned and replacing it with objects that are manifest into
|
||||
// every version with particular schemas. Resolve tihs method at that point.
|
||||
func (s *Scheme) AddUnversionedTypes(gv unversioned.GroupVersion, types ...Object) {
|
||||
interfaces := make([]interface{}, len(types))
|
||||
for i := range types {
|
||||
interfaces[i] = types[i]
|
||||
}
|
||||
s.raw.AddUnversionedTypes(gv, interfaces...)
|
||||
}
|
||||
|
||||
// AddKnownTypes registers the types of the arguments to the marshaller of the package api.
|
||||
// Encode() refuses the object unless its type is registered with AddKnownTypes.
|
||||
func (s *Scheme) AddKnownTypes(gv unversioned.GroupVersion, types ...Object) {
|
||||
interfaces := make([]interface{}, len(types))
|
||||
for i := range types {
|
||||
@@ -283,6 +92,12 @@ func (s *Scheme) AddKnownTypes(gv unversioned.GroupVersion, types ...Object) {
|
||||
s.raw.AddKnownTypes(gv, interfaces...)
|
||||
}
|
||||
|
||||
// AddIgnoredConversionType declares a particular conversion that should be ignored - during conversion
|
||||
// this method is not invoked.
|
||||
func (s *Scheme) AddIgnoredConversionType(from, to interface{}) error {
|
||||
return s.raw.AddIgnoredConversionType(from, to)
|
||||
}
|
||||
|
||||
// AddKnownTypeWithName is like AddKnownTypes, but it lets you specify what this type should
|
||||
// be encoded as. Useful for testing when you don't want to make multiple packages to define
|
||||
// your structs.
|
||||
@@ -296,12 +111,6 @@ func (s *Scheme) KnownTypes(gv unversioned.GroupVersion) map[string]reflect.Type
|
||||
return s.raw.KnownTypes(gv)
|
||||
}
|
||||
|
||||
// DataKind will return the group,version,kind of the given wire-format
|
||||
// encoding of an API Object, or an error.
|
||||
func (s *Scheme) DataKind(data []byte) (unversioned.GroupVersionKind, error) {
|
||||
return s.raw.DataKind(data)
|
||||
}
|
||||
|
||||
// ObjectKind returns the default group,version,kind of the given Object.
|
||||
func (s *Scheme) ObjectKind(obj Object) (unversioned.GroupVersionKind, error) {
|
||||
return s.raw.ObjectKind(obj)
|
||||
@@ -318,7 +127,12 @@ func (s *Scheme) Recognizes(gvk unversioned.GroupVersionKind) bool {
|
||||
return s.raw.Recognizes(gvk)
|
||||
}
|
||||
|
||||
// New returns a new API object of the given kind, or an error if it hasn't been registered.
|
||||
func (s *Scheme) IsUnversioned(obj Object) (bool, bool) {
|
||||
return s.raw.IsUnversioned(obj)
|
||||
}
|
||||
|
||||
// New returns a new API object of the given version ("" for internal
|
||||
// representation) and name, or an error if it hasn't been registered.
|
||||
func (s *Scheme) New(kind unversioned.GroupVersionKind) (Object, error) {
|
||||
obj, err := s.raw.NewObject(kind)
|
||||
if err != nil {
|
||||
@@ -394,11 +208,31 @@ func (s *Scheme) AddDefaultingFuncs(defaultingFuncs ...interface{}) error {
|
||||
return s.raw.AddDefaultingFuncs(defaultingFuncs...)
|
||||
}
|
||||
|
||||
// Copy does a deep copy of an API object.
|
||||
func (s *Scheme) Copy(src Object) (Object, error) {
|
||||
dst, err := s.raw.DeepCopy(src)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dst.(Object), nil
|
||||
}
|
||||
|
||||
// Performs a deep copy of the given object.
|
||||
func (s *Scheme) DeepCopy(src interface{}) (interface{}, error) {
|
||||
return s.raw.DeepCopy(src)
|
||||
}
|
||||
|
||||
// WithConversions returns an ObjectConvertor that has the additional conversion functions
|
||||
// defined in fns. The current scheme is not altered.
|
||||
func (s *Scheme) WithConversions(fns *conversion.ConversionFuncs) ObjectConvertor {
|
||||
if fns == nil {
|
||||
return s
|
||||
}
|
||||
copied := *s
|
||||
copied.raw = s.raw.WithConversions(*fns)
|
||||
return &copied
|
||||
}
|
||||
|
||||
// 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.
|
||||
@@ -423,8 +257,19 @@ 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.
|
||||
// returned. The serializer handles loading/serializing nested objects.
|
||||
func (s *Scheme) ConvertToVersion(in Object, outVersion string) (Object, error) {
|
||||
gv, err := unversioned.ParseGroupVersion(outVersion)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch in.(type) {
|
||||
case *Unknown, *Unstructured:
|
||||
old := in.GetObjectKind().GroupVersionKind()
|
||||
defer in.GetObjectKind().SetGroupVersionKind(old)
|
||||
setTargetVersion(in, s.raw, gv)
|
||||
return in, nil
|
||||
}
|
||||
unknown, err := s.raw.ConvertToVersion(in, outVersion)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -433,105 +278,16 @@ func (s *Scheme) ConvertToVersion(in Object, outVersion string) (Object, error)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("the provided object cannot be converted to a runtime.Object: %#v", unknown)
|
||||
}
|
||||
setTargetVersion(obj, s.raw, gv)
|
||||
return obj, nil
|
||||
}
|
||||
|
||||
// EncodeToVersion turns the given api object into an appropriate JSON string.
|
||||
// Will return an error if the object doesn't have an embedded TypeMeta.
|
||||
// Obj may be a pointer to a struct, or a struct. If a struct, a copy
|
||||
// must be made. If a pointer, the object may be modified before encoding,
|
||||
// but will be put back into its original state before returning.
|
||||
//
|
||||
// Memory/wire format differences:
|
||||
// * Having to keep track of the Kind and APIVersion fields makes tests
|
||||
// very annoying, so the rule is that they are set only in wire format
|
||||
// (json), not when in native (memory) format. This is possible because
|
||||
// both pieces of information are implicit in the go typed object.
|
||||
// * An exception: note that, if there are embedded API objects of known
|
||||
// type, for example, PodList{... Items []Pod ...}, these embedded
|
||||
// objects must be of the same version of the object they are embedded
|
||||
// within, and their APIVersion and Kind must both be empty.
|
||||
// * Note that the exception does not apply to the APIObject type, which
|
||||
// recursively does Encode()/Decode(), and is capable of expressing any
|
||||
// API object.
|
||||
// * Only versioned objects should be encoded. This means that, if you pass
|
||||
// a native object, Encode will convert it to a versioned object. For
|
||||
// example, an api.Pod will get converted to a v1.Pod. However, if
|
||||
// you pass in an object that's already versioned (v1.Pod), Encode
|
||||
// will not modify it.
|
||||
//
|
||||
// The purpose of the above complex conversion behavior is to allow us to
|
||||
// change the memory format yet not break compatibility with any stored
|
||||
// objects, whether they be in our storage layer (e.g., etcd), or in user's
|
||||
// config files.
|
||||
func (s *Scheme) EncodeToVersion(obj Object, destVersion string) (data []byte, err error) {
|
||||
return s.raw.EncodeToVersion(obj, destVersion)
|
||||
}
|
||||
|
||||
func (s *Scheme) EncodeToVersionStream(obj Object, destVersion string, stream io.Writer) error {
|
||||
return s.raw.EncodeToVersionStream(obj, destVersion, stream)
|
||||
}
|
||||
|
||||
// Decode converts a YAML or JSON string back into a pointer to an api object.
|
||||
// Deduces the type based upon the APIVersion and Kind fields, which are set
|
||||
// by Encode. Only versioned objects (APIVersion != "") are accepted. The object
|
||||
// will be converted into the in-memory unversioned type before being returned.
|
||||
func (s *Scheme) Decode(data []byte) (Object, error) {
|
||||
obj, err := s.raw.Decode(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
func setTargetVersion(obj Object, raw *conversion.Scheme, gv unversioned.GroupVersion) {
|
||||
if gv.Version == APIVersionInternal {
|
||||
// internal is a special case
|
||||
obj.GetObjectKind().SetGroupVersionKind(nil)
|
||||
} else {
|
||||
gvk, _ := raw.ObjectKind(obj)
|
||||
obj.GetObjectKind().SetGroupVersionKind(&unversioned.GroupVersionKind{Group: gv.Group, Version: gv.Version, Kind: gvk.Kind})
|
||||
}
|
||||
return obj.(Object), nil
|
||||
}
|
||||
|
||||
// DecodeToVersion converts a YAML or JSON string back into a pointer to an api
|
||||
// object. Deduces the type based upon the APIVersion and Kind fields, which
|
||||
// are set by Encode. Only versioned objects (APIVersion != "") are
|
||||
// accepted. The object will be converted into the in-memory versioned type
|
||||
// requested before being returned.
|
||||
func (s *Scheme) DecodeToVersion(data []byte, gv unversioned.GroupVersion) (Object, error) {
|
||||
obj, err := s.raw.DecodeToVersion(data, gv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return obj.(Object), nil
|
||||
}
|
||||
|
||||
// DecodeInto parses a YAML or JSON string and stores it in obj. Returns an error
|
||||
// if data.Kind is set and doesn't match the type of obj. Obj should be a
|
||||
// pointer to an api type.
|
||||
// If obj's APIVersion doesn't match that in data, an attempt will be made to convert
|
||||
// data into obj's version.
|
||||
// TODO: allow Decode/DecodeInto to take a default apiVersion and a default kind, to
|
||||
// be applied if the provided object does not have either field (integrate external
|
||||
// apis into the decoding scheme).
|
||||
func (s *Scheme) DecodeInto(data []byte, obj Object) error {
|
||||
return s.raw.DecodeInto(data, obj)
|
||||
}
|
||||
|
||||
// DecodeIntoWithSpecifiedVersionKind coerces the data into the obj, assuming that the data is
|
||||
// of type GroupVersionKind
|
||||
func (s *Scheme) DecodeIntoWithSpecifiedVersionKind(data []byte, obj Object, gvk unversioned.GroupVersionKind) error {
|
||||
return s.raw.DecodeIntoWithSpecifiedVersionKind(data, obj, gvk)
|
||||
}
|
||||
|
||||
func (s *Scheme) DecodeParametersInto(parameters url.Values, obj Object) error {
|
||||
return s.raw.DecodeParametersInto(parameters, obj)
|
||||
}
|
||||
|
||||
// Copy does a deep copy of an API object. Useful mostly for tests.
|
||||
func (s *Scheme) Copy(src Object) (Object, error) {
|
||||
dst, err := s.raw.DeepCopy(src)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dst.(Object), nil
|
||||
}
|
||||
|
||||
func (s *Scheme) CopyOrDie(obj Object) Object {
|
||||
newObj, err := s.Copy(obj)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return newObj
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user