You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
330 lines
10 KiB
330 lines
10 KiB
/* |
|
Copyright 2014 The Kubernetes Authors. |
|
|
|
Licensed under the Apache License, Version 2.0 (the "License"); |
|
you may not use this file except in compliance with the License. |
|
You may obtain a copy of the License at |
|
|
|
http://www.apache.org/licenses/LICENSE-2.0 |
|
|
|
Unless required by applicable law or agreed to in writing, software |
|
distributed under the License is distributed on an "AS IS" BASIS, |
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
See the License for the specific language governing permissions and |
|
limitations under the License. |
|
*/ |
|
|
|
package runtime |
|
|
|
import ( |
|
"bytes" |
|
"encoding/base64" |
|
"fmt" |
|
"io" |
|
"net/url" |
|
"reflect" |
|
|
|
"k8s.io/apimachinery/pkg/conversion/queryparams" |
|
"k8s.io/apimachinery/pkg/runtime/schema" |
|
) |
|
|
|
// codec binds an encoder and decoder. |
|
type codec struct { |
|
Encoder |
|
Decoder |
|
} |
|
|
|
// NewCodec creates a Codec from an Encoder and Decoder. |
|
func NewCodec(e Encoder, d Decoder) Codec { |
|
return codec{e, d} |
|
} |
|
|
|
// Encode is a convenience wrapper for encoding to a []byte from an Encoder |
|
func Encode(e Encoder, obj Object) ([]byte, error) { |
|
// TODO: reuse buffer |
|
buf := &bytes.Buffer{} |
|
if err := e.Encode(obj, buf); err != nil { |
|
return nil, err |
|
} |
|
return buf.Bytes(), nil |
|
} |
|
|
|
// Decode is a convenience wrapper for decoding data into an Object. |
|
func Decode(d Decoder, data []byte) (Object, error) { |
|
obj, _, err := d.Decode(data, nil, nil) |
|
return obj, err |
|
} |
|
|
|
// DecodeInto performs a Decode into the provided object. |
|
func DecodeInto(d Decoder, data []byte, into Object) error { |
|
out, gvk, err := d.Decode(data, nil, into) |
|
if err != nil { |
|
return err |
|
} |
|
if out != into { |
|
return fmt.Errorf("unable to decode %s into %v", gvk, reflect.TypeOf(into)) |
|
} |
|
return nil |
|
} |
|
|
|
// EncodeOrDie is a version of Encode which will panic instead of returning an error. For tests. |
|
func EncodeOrDie(e Encoder, obj Object) string { |
|
bytes, err := Encode(e, obj) |
|
if err != nil { |
|
panic(err) |
|
} |
|
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 *schema.GroupVersionKind, into Object) (Object, *schema.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 schema.GroupVersionKind, obj Object) (Object, error) { |
|
if obj != nil { |
|
kinds, _, err := t.ObjectKinds(obj) |
|
if err != nil { |
|
return nil, err |
|
} |
|
for _, kind := range kinds { |
|
if gvk == kind { |
|
return obj, nil |
|
} |
|
} |
|
} |
|
return c.New(gvk) |
|
} |
|
|
|
// NoopEncoder converts an Decoder to a Serializer or Codec for code that expects them but only uses decoding. |
|
type NoopEncoder struct { |
|
Decoder |
|
} |
|
|
|
var _ Serializer = NoopEncoder{} |
|
|
|
func (n NoopEncoder) Encode(obj Object, w io.Writer) error { |
|
return fmt.Errorf("encoding is not allowed for this codec: %v", reflect.TypeOf(n.Decoder)) |
|
} |
|
|
|
// NoopDecoder converts an Encoder to a Serializer or Codec for code that expects them but only uses encoding. |
|
type NoopDecoder struct { |
|
Encoder |
|
} |
|
|
|
var _ Serializer = NoopDecoder{} |
|
|
|
func (n NoopDecoder) Decode(data []byte, gvk *schema.GroupVersionKind, into Object) (Object, *schema.GroupVersionKind, error) { |
|
return nil, nil, fmt.Errorf("decoding is not allowed for this codec: %v", reflect.TypeOf(n.Encoder)) |
|
} |
|
|
|
// NewParameterCodec creates a ParameterCodec capable of transforming url values into versioned objects and back. |
|
func NewParameterCodec(scheme *Scheme) ParameterCodec { |
|
return ¶meterCodec{ |
|
typer: scheme, |
|
convertor: scheme, |
|
creator: scheme, |
|
defaulter: scheme, |
|
} |
|
} |
|
|
|
// parameterCodec implements conversion to and from query parameters and objects. |
|
type parameterCodec struct { |
|
typer ObjectTyper |
|
convertor ObjectConvertor |
|
creator ObjectCreater |
|
defaulter ObjectDefaulter |
|
} |
|
|
|
var _ ParameterCodec = ¶meterCodec{} |
|
|
|
// DecodeParameters converts the provided url.Values into an object of type From with the kind of into, and then |
|
// converts that object to into (if necessary). Returns an error if the operation cannot be completed. |
|
func (c *parameterCodec) DecodeParameters(parameters url.Values, from schema.GroupVersion, into Object) error { |
|
if len(parameters) == 0 { |
|
return nil |
|
} |
|
targetGVKs, _, err := c.typer.ObjectKinds(into) |
|
if err != nil { |
|
return err |
|
} |
|
for i := range targetGVKs { |
|
if targetGVKs[i].GroupVersion() == from { |
|
if err := c.convertor.Convert(¶meters, into, nil); err != nil { |
|
return err |
|
} |
|
// in the case where we going into the same object we're receiving, default on the outbound object |
|
if c.defaulter != nil { |
|
c.defaulter.Default(into) |
|
} |
|
return nil |
|
} |
|
} |
|
|
|
input, err := c.creator.New(from.WithKind(targetGVKs[0].Kind)) |
|
if err != nil { |
|
return err |
|
} |
|
if err := c.convertor.Convert(¶meters, input, nil); err != nil { |
|
return err |
|
} |
|
// if we have defaulter, default the input before converting to output |
|
if c.defaulter != nil { |
|
c.defaulter.Default(input) |
|
} |
|
return c.convertor.Convert(input, into, nil) |
|
} |
|
|
|
// EncodeParameters converts the provided object into the to version, then converts that object to url.Values. |
|
// Returns an error if conversion is not possible. |
|
func (c *parameterCodec) EncodeParameters(obj Object, to schema.GroupVersion) (url.Values, error) { |
|
gvks, _, err := c.typer.ObjectKinds(obj) |
|
if err != nil { |
|
return nil, err |
|
} |
|
gvk := gvks[0] |
|
if to != gvk.GroupVersion() { |
|
out, err := c.convertor.ConvertToVersion(obj, to) |
|
if err != nil { |
|
return nil, err |
|
} |
|
obj = out |
|
} |
|
return queryparams.Convert(obj) |
|
} |
|
|
|
type base64Serializer struct { |
|
Encoder |
|
Decoder |
|
} |
|
|
|
func NewBase64Serializer(e Encoder, d Decoder) Serializer { |
|
return &base64Serializer{e, d} |
|
} |
|
|
|
func (s base64Serializer) Encode(obj Object, stream io.Writer) error { |
|
e := base64.NewEncoder(base64.StdEncoding, stream) |
|
err := s.Encoder.Encode(obj, e) |
|
e.Close() |
|
return err |
|
} |
|
|
|
func (s base64Serializer) Decode(data []byte, defaults *schema.GroupVersionKind, into Object) (Object, *schema.GroupVersionKind, error) { |
|
out := make([]byte, base64.StdEncoding.DecodedLen(len(data))) |
|
n, err := base64.StdEncoding.Decode(out, data) |
|
if err != nil { |
|
return nil, nil, err |
|
} |
|
return s.Decoder.Decode(out[:n], defaults, into) |
|
} |
|
|
|
// SerializerInfoForMediaType returns the first info in types that has a matching media type (which cannot |
|
// include media-type parameters), or the first info with an empty media type, or false if no type matches. |
|
func SerializerInfoForMediaType(types []SerializerInfo, mediaType string) (SerializerInfo, bool) { |
|
for _, info := range types { |
|
if info.MediaType == mediaType { |
|
return info, true |
|
} |
|
} |
|
for _, info := range types { |
|
if len(info.MediaType) == 0 { |
|
return info, true |
|
} |
|
} |
|
return SerializerInfo{}, false |
|
} |
|
|
|
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 []schema.GroupVersionKind) (schema.GroupVersionKind, bool) { |
|
for _, kind := range kinds { |
|
if kind.Version == APIVersionInternal { |
|
return kind, true |
|
} |
|
} |
|
for _, kind := range kinds { |
|
return schema.GroupVersionKind{Group: kind.Group, Version: APIVersionInternal, Kind: kind.Kind}, true |
|
} |
|
return schema.GroupVersionKind{}, false |
|
} |
|
|
|
type disabledGroupVersioner struct{} |
|
|
|
// KindForGroupVersionKinds returns false for any input. |
|
func (disabledGroupVersioner) KindForGroupVersionKinds(kinds []schema.GroupVersionKind) (schema.GroupVersionKind, bool) { |
|
return schema.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 occurred. |
|
func (gvs GroupVersioners) KindForGroupVersionKinds(kinds []schema.GroupVersionKind) (schema.GroupVersionKind, bool) { |
|
for _, gv := range gvs { |
|
target, ok := gv.KindForGroupVersionKinds(kinds) |
|
if !ok { |
|
continue |
|
} |
|
return target, true |
|
} |
|
return schema.GroupVersionKind{}, false |
|
} |
|
|
|
// Assert that schema.GroupVersion and GroupVersions implement GroupVersioner |
|
var _ GroupVersioner = schema.GroupVersion{} |
|
var _ GroupVersioner = schema.GroupVersions{} |
|
var _ GroupVersioner = multiGroupVersioner{} |
|
|
|
type multiGroupVersioner struct { |
|
target schema.GroupVersion |
|
acceptedGroupKinds []schema.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 schema.GroupVersion, groupKinds ...schema.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 []schema.GroupVersionKind) (schema.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 schema.GroupVersionKind{}, false |
|
}
|
|
|