blob: a92863139ede340e713c0d14dcfcbd9bf58f746c [file] [log] [blame]
Matteo Scandoloa4285862020-12-01 18:10:10 -08001/*
2Copyright 2014 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package runtime
18
19import (
20 "bytes"
21 "encoding/base64"
22 "encoding/json"
23 "fmt"
24 "io"
25 "net/url"
26 "reflect"
27 "strconv"
28 "strings"
29
30 "k8s.io/apimachinery/pkg/conversion/queryparams"
31 "k8s.io/apimachinery/pkg/runtime/schema"
32 "k8s.io/klog/v2"
33)
34
35// codec binds an encoder and decoder.
36type codec struct {
37 Encoder
38 Decoder
39}
40
41// NewCodec creates a Codec from an Encoder and Decoder.
42func NewCodec(e Encoder, d Decoder) Codec {
43 return codec{e, d}
44}
45
46// Encode is a convenience wrapper for encoding to a []byte from an Encoder
47func Encode(e Encoder, obj Object) ([]byte, error) {
48 // TODO: reuse buffer
49 buf := &bytes.Buffer{}
50 if err := e.Encode(obj, buf); err != nil {
51 return nil, err
52 }
53 return buf.Bytes(), nil
54}
55
56// Decode is a convenience wrapper for decoding data into an Object.
57func Decode(d Decoder, data []byte) (Object, error) {
58 obj, _, err := d.Decode(data, nil, nil)
59 return obj, err
60}
61
62// DecodeInto performs a Decode into the provided object.
63func DecodeInto(d Decoder, data []byte, into Object) error {
64 out, gvk, err := d.Decode(data, nil, into)
65 if err != nil {
66 return err
67 }
68 if out != into {
69 return fmt.Errorf("unable to decode %s into %v", gvk, reflect.TypeOf(into))
70 }
71 return nil
72}
73
74// EncodeOrDie is a version of Encode which will panic instead of returning an error. For tests.
75func EncodeOrDie(e Encoder, obj Object) string {
76 bytes, err := Encode(e, obj)
77 if err != nil {
78 panic(err)
79 }
80 return string(bytes)
81}
82
83// UseOrCreateObject returns obj if the canonical ObjectKind returned by the provided typer matches gvk, or
84// invokes the ObjectCreator to instantiate a new gvk. Returns an error if the typer cannot find the object.
85func UseOrCreateObject(t ObjectTyper, c ObjectCreater, gvk schema.GroupVersionKind, obj Object) (Object, error) {
86 if obj != nil {
87 kinds, _, err := t.ObjectKinds(obj)
88 if err != nil {
89 return nil, err
90 }
91 for _, kind := range kinds {
92 if gvk == kind {
93 return obj, nil
94 }
95 }
96 }
97 return c.New(gvk)
98}
99
100// NoopEncoder converts an Decoder to a Serializer or Codec for code that expects them but only uses decoding.
101type NoopEncoder struct {
102 Decoder
103}
104
105var _ Serializer = NoopEncoder{}
106
107const noopEncoderIdentifier Identifier = "noop"
108
109func (n NoopEncoder) Encode(obj Object, w io.Writer) error {
110 // There is no need to handle runtime.CacheableObject, as we don't
111 // process the obj at all.
112 return fmt.Errorf("encoding is not allowed for this codec: %v", reflect.TypeOf(n.Decoder))
113}
114
115// Identifier implements runtime.Encoder interface.
116func (n NoopEncoder) Identifier() Identifier {
117 return noopEncoderIdentifier
118}
119
120// NoopDecoder converts an Encoder to a Serializer or Codec for code that expects them but only uses encoding.
121type NoopDecoder struct {
122 Encoder
123}
124
125var _ Serializer = NoopDecoder{}
126
127func (n NoopDecoder) Decode(data []byte, gvk *schema.GroupVersionKind, into Object) (Object, *schema.GroupVersionKind, error) {
128 return nil, nil, fmt.Errorf("decoding is not allowed for this codec: %v", reflect.TypeOf(n.Encoder))
129}
130
131// NewParameterCodec creates a ParameterCodec capable of transforming url values into versioned objects and back.
132func NewParameterCodec(scheme *Scheme) ParameterCodec {
133 return &parameterCodec{
134 typer: scheme,
135 convertor: scheme,
136 creator: scheme,
137 defaulter: scheme,
138 }
139}
140
141// parameterCodec implements conversion to and from query parameters and objects.
142type parameterCodec struct {
143 typer ObjectTyper
144 convertor ObjectConvertor
145 creator ObjectCreater
146 defaulter ObjectDefaulter
147}
148
149var _ ParameterCodec = &parameterCodec{}
150
151// DecodeParameters converts the provided url.Values into an object of type From with the kind of into, and then
152// converts that object to into (if necessary). Returns an error if the operation cannot be completed.
153func (c *parameterCodec) DecodeParameters(parameters url.Values, from schema.GroupVersion, into Object) error {
154 if len(parameters) == 0 {
155 return nil
156 }
157 targetGVKs, _, err := c.typer.ObjectKinds(into)
158 if err != nil {
159 return err
160 }
161 for i := range targetGVKs {
162 if targetGVKs[i].GroupVersion() == from {
163 if err := c.convertor.Convert(&parameters, into, nil); err != nil {
164 return err
165 }
166 // in the case where we going into the same object we're receiving, default on the outbound object
167 if c.defaulter != nil {
168 c.defaulter.Default(into)
169 }
170 return nil
171 }
172 }
173
174 input, err := c.creator.New(from.WithKind(targetGVKs[0].Kind))
175 if err != nil {
176 return err
177 }
178 if err := c.convertor.Convert(&parameters, input, nil); err != nil {
179 return err
180 }
181 // if we have defaulter, default the input before converting to output
182 if c.defaulter != nil {
183 c.defaulter.Default(input)
184 }
185 return c.convertor.Convert(input, into, nil)
186}
187
188// EncodeParameters converts the provided object into the to version, then converts that object to url.Values.
189// Returns an error if conversion is not possible.
190func (c *parameterCodec) EncodeParameters(obj Object, to schema.GroupVersion) (url.Values, error) {
191 gvks, _, err := c.typer.ObjectKinds(obj)
192 if err != nil {
193 return nil, err
194 }
195 gvk := gvks[0]
196 if to != gvk.GroupVersion() {
197 out, err := c.convertor.ConvertToVersion(obj, to)
198 if err != nil {
199 return nil, err
200 }
201 obj = out
202 }
203 return queryparams.Convert(obj)
204}
205
206type base64Serializer struct {
207 Encoder
208 Decoder
209
210 identifier Identifier
211}
212
213func NewBase64Serializer(e Encoder, d Decoder) Serializer {
214 return &base64Serializer{
215 Encoder: e,
216 Decoder: d,
217 identifier: identifier(e),
218 }
219}
220
221func identifier(e Encoder) Identifier {
222 result := map[string]string{
223 "name": "base64",
224 }
225 if e != nil {
226 result["encoder"] = string(e.Identifier())
227 }
228 identifier, err := json.Marshal(result)
229 if err != nil {
230 klog.Fatalf("Failed marshaling identifier for base64Serializer: %v", err)
231 }
232 return Identifier(identifier)
233}
234
235func (s base64Serializer) Encode(obj Object, stream io.Writer) error {
236 if co, ok := obj.(CacheableObject); ok {
237 return co.CacheEncode(s.Identifier(), s.doEncode, stream)
238 }
239 return s.doEncode(obj, stream)
240}
241
242func (s base64Serializer) doEncode(obj Object, stream io.Writer) error {
243 e := base64.NewEncoder(base64.StdEncoding, stream)
244 err := s.Encoder.Encode(obj, e)
245 e.Close()
246 return err
247}
248
249// Identifier implements runtime.Encoder interface.
250func (s base64Serializer) Identifier() Identifier {
251 return s.identifier
252}
253
254func (s base64Serializer) Decode(data []byte, defaults *schema.GroupVersionKind, into Object) (Object, *schema.GroupVersionKind, error) {
255 out := make([]byte, base64.StdEncoding.DecodedLen(len(data)))
256 n, err := base64.StdEncoding.Decode(out, data)
257 if err != nil {
258 return nil, nil, err
259 }
260 return s.Decoder.Decode(out[:n], defaults, into)
261}
262
263// SerializerInfoForMediaType returns the first info in types that has a matching media type (which cannot
264// include media-type parameters), or the first info with an empty media type, or false if no type matches.
265func SerializerInfoForMediaType(types []SerializerInfo, mediaType string) (SerializerInfo, bool) {
266 for _, info := range types {
267 if info.MediaType == mediaType {
268 return info, true
269 }
270 }
271 for _, info := range types {
272 if len(info.MediaType) == 0 {
273 return info, true
274 }
275 }
276 return SerializerInfo{}, false
277}
278
279var (
280 // InternalGroupVersioner will always prefer the internal version for a given group version kind.
281 InternalGroupVersioner GroupVersioner = internalGroupVersioner{}
282 // DisabledGroupVersioner will reject all kinds passed to it.
283 DisabledGroupVersioner GroupVersioner = disabledGroupVersioner{}
284)
285
286const (
287 internalGroupVersionerIdentifier = "internal"
288 disabledGroupVersionerIdentifier = "disabled"
289)
290
291type internalGroupVersioner struct{}
292
293// KindForGroupVersionKinds returns an internal Kind if one is found, or converts the first provided kind to the internal version.
294func (internalGroupVersioner) KindForGroupVersionKinds(kinds []schema.GroupVersionKind) (schema.GroupVersionKind, bool) {
295 for _, kind := range kinds {
296 if kind.Version == APIVersionInternal {
297 return kind, true
298 }
299 }
300 for _, kind := range kinds {
301 return schema.GroupVersionKind{Group: kind.Group, Version: APIVersionInternal, Kind: kind.Kind}, true
302 }
303 return schema.GroupVersionKind{}, false
304}
305
306// Identifier implements GroupVersioner interface.
307func (internalGroupVersioner) Identifier() string {
308 return internalGroupVersionerIdentifier
309}
310
311type disabledGroupVersioner struct{}
312
313// KindForGroupVersionKinds returns false for any input.
314func (disabledGroupVersioner) KindForGroupVersionKinds(kinds []schema.GroupVersionKind) (schema.GroupVersionKind, bool) {
315 return schema.GroupVersionKind{}, false
316}
317
318// Identifier implements GroupVersioner interface.
319func (disabledGroupVersioner) Identifier() string {
320 return disabledGroupVersionerIdentifier
321}
322
323// Assert that schema.GroupVersion and GroupVersions implement GroupVersioner
324var _ GroupVersioner = schema.GroupVersion{}
325var _ GroupVersioner = schema.GroupVersions{}
326var _ GroupVersioner = multiGroupVersioner{}
327
328type multiGroupVersioner struct {
329 target schema.GroupVersion
330 acceptedGroupKinds []schema.GroupKind
331 coerce bool
332}
333
334// NewMultiGroupVersioner returns the provided group version for any kind that matches one of the provided group kinds.
335// Kind may be empty in the provided group kind, in which case any kind will match.
336func NewMultiGroupVersioner(gv schema.GroupVersion, groupKinds ...schema.GroupKind) GroupVersioner {
337 if len(groupKinds) == 0 || (len(groupKinds) == 1 && groupKinds[0].Group == gv.Group) {
338 return gv
339 }
340 return multiGroupVersioner{target: gv, acceptedGroupKinds: groupKinds}
341}
342
343// NewCoercingMultiGroupVersioner returns the provided group version for any incoming kind.
344// Incoming kinds that match the provided groupKinds are preferred.
345// Kind may be empty in the provided group kind, in which case any kind will match.
346// Examples:
347// gv=mygroup/__internal, groupKinds=mygroup/Foo, anothergroup/Bar
348// KindForGroupVersionKinds(yetanother/v1/Baz, anothergroup/v1/Bar) -> mygroup/__internal/Bar (matched preferred group/kind)
349//
350// gv=mygroup/__internal, groupKinds=mygroup, anothergroup
351// KindForGroupVersionKinds(yetanother/v1/Baz, anothergroup/v1/Bar) -> mygroup/__internal/Bar (matched preferred group)
352//
353// gv=mygroup/__internal, groupKinds=mygroup, anothergroup
354// KindForGroupVersionKinds(yetanother/v1/Baz, yetanother/v1/Bar) -> mygroup/__internal/Baz (no preferred group/kind match, uses first kind in list)
355func NewCoercingMultiGroupVersioner(gv schema.GroupVersion, groupKinds ...schema.GroupKind) GroupVersioner {
356 return multiGroupVersioner{target: gv, acceptedGroupKinds: groupKinds, coerce: true}
357}
358
359// KindForGroupVersionKinds returns the target group version if any kind matches any of the original group kinds. It will
360// use the originating kind where possible.
361func (v multiGroupVersioner) KindForGroupVersionKinds(kinds []schema.GroupVersionKind) (schema.GroupVersionKind, bool) {
362 for _, src := range kinds {
363 for _, kind := range v.acceptedGroupKinds {
364 if kind.Group != src.Group {
365 continue
366 }
367 if len(kind.Kind) > 0 && kind.Kind != src.Kind {
368 continue
369 }
370 return v.target.WithKind(src.Kind), true
371 }
372 }
373 if v.coerce && len(kinds) > 0 {
374 return v.target.WithKind(kinds[0].Kind), true
375 }
376 return schema.GroupVersionKind{}, false
377}
378
379// Identifier implements GroupVersioner interface.
380func (v multiGroupVersioner) Identifier() string {
381 groupKinds := make([]string, 0, len(v.acceptedGroupKinds))
382 for _, gk := range v.acceptedGroupKinds {
383 groupKinds = append(groupKinds, gk.String())
384 }
385 result := map[string]string{
386 "name": "multi",
387 "target": v.target.String(),
388 "accepted": strings.Join(groupKinds, ","),
389 "coerce": strconv.FormatBool(v.coerce),
390 }
391 identifier, err := json.Marshal(result)
392 if err != nil {
393 klog.Fatalf("Failed marshaling Identifier for %#v: %v", v, err)
394 }
395 return string(identifier)
396}