a3bf13711c
I had to run `make generate`. Some API functions got additional parameters `Options` and `Context`. I used empty options and `context.TODO()` for now. Signed-off-by: leonnicolas <leonloechner@gmx.de>
397 lines
12 KiB
Go
397 lines
12 KiB
Go
/*
|
|
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"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/url"
|
|
"reflect"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"k8s.io/apimachinery/pkg/conversion/queryparams"
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
"k8s.io/klog/v2"
|
|
)
|
|
|
|
// 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)
|
|
}
|
|
|
|
// 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{}
|
|
|
|
const noopEncoderIdentifier Identifier = "noop"
|
|
|
|
func (n NoopEncoder) Encode(obj Object, w io.Writer) error {
|
|
// There is no need to handle runtime.CacheableObject, as we don't
|
|
// process the obj at all.
|
|
return fmt.Errorf("encoding is not allowed for this codec: %v", reflect.TypeOf(n.Decoder))
|
|
}
|
|
|
|
// Identifier implements runtime.Encoder interface.
|
|
func (n NoopEncoder) Identifier() Identifier {
|
|
return noopEncoderIdentifier
|
|
}
|
|
|
|
// 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
|
|
|
|
identifier Identifier
|
|
}
|
|
|
|
func NewBase64Serializer(e Encoder, d Decoder) Serializer {
|
|
return &base64Serializer{
|
|
Encoder: e,
|
|
Decoder: d,
|
|
identifier: identifier(e),
|
|
}
|
|
}
|
|
|
|
func identifier(e Encoder) Identifier {
|
|
result := map[string]string{
|
|
"name": "base64",
|
|
}
|
|
if e != nil {
|
|
result["encoder"] = string(e.Identifier())
|
|
}
|
|
identifier, err := json.Marshal(result)
|
|
if err != nil {
|
|
klog.Fatalf("Failed marshaling identifier for base64Serializer: %v", err)
|
|
}
|
|
return Identifier(identifier)
|
|
}
|
|
|
|
func (s base64Serializer) Encode(obj Object, stream io.Writer) error {
|
|
if co, ok := obj.(CacheableObject); ok {
|
|
return co.CacheEncode(s.Identifier(), s.doEncode, stream)
|
|
}
|
|
return s.doEncode(obj, stream)
|
|
}
|
|
|
|
func (s base64Serializer) doEncode(obj Object, stream io.Writer) error {
|
|
e := base64.NewEncoder(base64.StdEncoding, stream)
|
|
err := s.Encoder.Encode(obj, e)
|
|
e.Close()
|
|
return err
|
|
}
|
|
|
|
// Identifier implements runtime.Encoder interface.
|
|
func (s base64Serializer) Identifier() Identifier {
|
|
return s.identifier
|
|
}
|
|
|
|
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{}
|
|
)
|
|
|
|
const (
|
|
internalGroupVersionerIdentifier = "internal"
|
|
disabledGroupVersionerIdentifier = "disabled"
|
|
)
|
|
|
|
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
|
|
}
|
|
|
|
// Identifier implements GroupVersioner interface.
|
|
func (internalGroupVersioner) Identifier() string {
|
|
return internalGroupVersionerIdentifier
|
|
}
|
|
|
|
type disabledGroupVersioner struct{}
|
|
|
|
// KindForGroupVersionKinds returns false for any input.
|
|
func (disabledGroupVersioner) KindForGroupVersionKinds(kinds []schema.GroupVersionKind) (schema.GroupVersionKind, bool) {
|
|
return schema.GroupVersionKind{}, false
|
|
}
|
|
|
|
// Identifier implements GroupVersioner interface.
|
|
func (disabledGroupVersioner) Identifier() string {
|
|
return disabledGroupVersionerIdentifier
|
|
}
|
|
|
|
// 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
|
|
coerce bool
|
|
}
|
|
|
|
// 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}
|
|
}
|
|
|
|
// NewCoercingMultiGroupVersioner returns the provided group version for any incoming kind.
|
|
// Incoming kinds that match the provided groupKinds are preferred.
|
|
// Kind may be empty in the provided group kind, in which case any kind will match.
|
|
// Examples:
|
|
// gv=mygroup/__internal, groupKinds=mygroup/Foo, anothergroup/Bar
|
|
// KindForGroupVersionKinds(yetanother/v1/Baz, anothergroup/v1/Bar) -> mygroup/__internal/Bar (matched preferred group/kind)
|
|
//
|
|
// gv=mygroup/__internal, groupKinds=mygroup, anothergroup
|
|
// KindForGroupVersionKinds(yetanother/v1/Baz, anothergroup/v1/Bar) -> mygroup/__internal/Bar (matched preferred group)
|
|
//
|
|
// gv=mygroup/__internal, groupKinds=mygroup, anothergroup
|
|
// KindForGroupVersionKinds(yetanother/v1/Baz, yetanother/v1/Bar) -> mygroup/__internal/Baz (no preferred group/kind match, uses first kind in list)
|
|
func NewCoercingMultiGroupVersioner(gv schema.GroupVersion, groupKinds ...schema.GroupKind) GroupVersioner {
|
|
return multiGroupVersioner{target: gv, acceptedGroupKinds: groupKinds, coerce: true}
|
|
}
|
|
|
|
// 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
|
|
}
|
|
}
|
|
if v.coerce && len(kinds) > 0 {
|
|
return v.target.WithKind(kinds[0].Kind), true
|
|
}
|
|
return schema.GroupVersionKind{}, false
|
|
}
|
|
|
|
// Identifier implements GroupVersioner interface.
|
|
func (v multiGroupVersioner) Identifier() string {
|
|
groupKinds := make([]string, 0, len(v.acceptedGroupKinds))
|
|
for _, gk := range v.acceptedGroupKinds {
|
|
groupKinds = append(groupKinds, gk.String())
|
|
}
|
|
result := map[string]string{
|
|
"name": "multi",
|
|
"target": v.target.String(),
|
|
"accepted": strings.Join(groupKinds, ","),
|
|
"coerce": strconv.FormatBool(v.coerce),
|
|
}
|
|
identifier, err := json.Marshal(result)
|
|
if err != nil {
|
|
klog.Fatalf("Failed marshaling Identifier for %#v: %v", v, err)
|
|
}
|
|
return string(identifier)
|
|
}
|