1/*
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 versioning
18
19import (
20	"encoding/json"
21	"io"
22	"reflect"
23	"sync"
24
25	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
26	"k8s.io/apimachinery/pkg/runtime"
27	"k8s.io/apimachinery/pkg/runtime/schema"
28	"k8s.io/klog/v2"
29)
30
31// NewDefaultingCodecForScheme is a convenience method for callers that are using a scheme.
32func NewDefaultingCodecForScheme(
33	// TODO: I should be a scheme interface?
34	scheme *runtime.Scheme,
35	encoder runtime.Encoder,
36	decoder runtime.Decoder,
37	encodeVersion runtime.GroupVersioner,
38	decodeVersion runtime.GroupVersioner,
39) runtime.Codec {
40	return NewCodec(encoder, decoder, runtime.UnsafeObjectConvertor(scheme), scheme, scheme, scheme, encodeVersion, decodeVersion, scheme.Name())
41}
42
43// NewCodec takes objects in their internal versions and converts them to external versions before
44// serializing them. It assumes the serializer provided to it only deals with external versions.
45// This class is also a serializer, but is generally used with a specific version.
46func NewCodec(
47	encoder runtime.Encoder,
48	decoder runtime.Decoder,
49	convertor runtime.ObjectConvertor,
50	creater runtime.ObjectCreater,
51	typer runtime.ObjectTyper,
52	defaulter runtime.ObjectDefaulter,
53	encodeVersion runtime.GroupVersioner,
54	decodeVersion runtime.GroupVersioner,
55	originalSchemeName string,
56) runtime.Codec {
57	internal := &codec{
58		encoder:   encoder,
59		decoder:   decoder,
60		convertor: convertor,
61		creater:   creater,
62		typer:     typer,
63		defaulter: defaulter,
64
65		encodeVersion: encodeVersion,
66		decodeVersion: decodeVersion,
67
68		identifier: identifier(encodeVersion, encoder),
69
70		originalSchemeName: originalSchemeName,
71	}
72	return internal
73}
74
75type codec struct {
76	encoder   runtime.Encoder
77	decoder   runtime.Decoder
78	convertor runtime.ObjectConvertor
79	creater   runtime.ObjectCreater
80	typer     runtime.ObjectTyper
81	defaulter runtime.ObjectDefaulter
82
83	encodeVersion runtime.GroupVersioner
84	decodeVersion runtime.GroupVersioner
85
86	identifier runtime.Identifier
87
88	// originalSchemeName is optional, but when filled in it holds the name of the scheme from which this codec originates
89	originalSchemeName string
90}
91
92var identifiersMap sync.Map
93
94type codecIdentifier struct {
95	EncodeGV string `json:"encodeGV,omitempty"`
96	Encoder  string `json:"encoder,omitempty"`
97	Name     string `json:"name,omitempty"`
98}
99
100// identifier computes Identifier of Encoder based on codec parameters.
101func identifier(encodeGV runtime.GroupVersioner, encoder runtime.Encoder) runtime.Identifier {
102	result := codecIdentifier{
103		Name: "versioning",
104	}
105
106	if encodeGV != nil {
107		result.EncodeGV = encodeGV.Identifier()
108	}
109	if encoder != nil {
110		result.Encoder = string(encoder.Identifier())
111	}
112	if id, ok := identifiersMap.Load(result); ok {
113		return id.(runtime.Identifier)
114	}
115	identifier, err := json.Marshal(result)
116	if err != nil {
117		klog.Fatalf("Failed marshaling identifier for codec: %v", err)
118	}
119	identifiersMap.Store(result, runtime.Identifier(identifier))
120	return runtime.Identifier(identifier)
121}
122
123// Decode attempts a decode of the object, then tries to convert it to the internal version. If into is provided and the decoding is
124// successful, the returned runtime.Object will be the value passed as into. Note that this may bypass conversion if you pass an
125// into that matches the serialized version.
126func (c *codec) Decode(data []byte, defaultGVK *schema.GroupVersionKind, into runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) {
127	// If the into object is unstructured and expresses an opinion about its group/version,
128	// create a new instance of the type so we always exercise the conversion path (skips short-circuiting on `into == obj`)
129	decodeInto := into
130	if into != nil {
131		if _, ok := into.(runtime.Unstructured); ok && !into.GetObjectKind().GroupVersionKind().GroupVersion().Empty() {
132			decodeInto = reflect.New(reflect.TypeOf(into).Elem()).Interface().(runtime.Object)
133		}
134	}
135
136	obj, gvk, err := c.decoder.Decode(data, defaultGVK, decodeInto)
137	if err != nil {
138		return nil, gvk, err
139	}
140
141	if d, ok := obj.(runtime.NestedObjectDecoder); ok {
142		if err := d.DecodeNestedObjects(runtime.WithoutVersionDecoder{c.decoder}); err != nil {
143			return nil, gvk, err
144		}
145	}
146
147	// if we specify a target, use generic conversion.
148	if into != nil {
149		// perform defaulting if requested
150		if c.defaulter != nil {
151			c.defaulter.Default(obj)
152		}
153
154		// Short-circuit conversion if the into object is same object
155		if into == obj {
156			return into, gvk, nil
157		}
158
159		if err := c.convertor.Convert(obj, into, c.decodeVersion); err != nil {
160			return nil, gvk, err
161		}
162
163		return into, gvk, nil
164	}
165
166	// perform defaulting if requested
167	if c.defaulter != nil {
168		c.defaulter.Default(obj)
169	}
170
171	out, err := c.convertor.ConvertToVersion(obj, c.decodeVersion)
172	if err != nil {
173		return nil, gvk, err
174	}
175	return out, gvk, nil
176}
177
178// Encode ensures the provided object is output in the appropriate group and version, invoking
179// conversion if necessary. Unversioned objects (according to the ObjectTyper) are output as is.
180func (c *codec) Encode(obj runtime.Object, w io.Writer) error {
181	if co, ok := obj.(runtime.CacheableObject); ok {
182		return co.CacheEncode(c.Identifier(), c.doEncode, w)
183	}
184	return c.doEncode(obj, w)
185}
186
187func (c *codec) doEncode(obj runtime.Object, w io.Writer) error {
188	switch obj := obj.(type) {
189	case *runtime.Unknown:
190		return c.encoder.Encode(obj, w)
191	case runtime.Unstructured:
192		// An unstructured list can contain objects of multiple group version kinds. don't short-circuit just
193		// because the top-level type matches our desired destination type. actually send the object to the converter
194		// to give it a chance to convert the list items if needed.
195		if _, ok := obj.(*unstructured.UnstructuredList); !ok {
196			// avoid conversion roundtrip if GVK is the right one already or is empty (yes, this is a hack, but the old behaviour we rely on in kubectl)
197			objGVK := obj.GetObjectKind().GroupVersionKind()
198			if len(objGVK.Version) == 0 {
199				return c.encoder.Encode(obj, w)
200			}
201			targetGVK, ok := c.encodeVersion.KindForGroupVersionKinds([]schema.GroupVersionKind{objGVK})
202			if !ok {
203				return runtime.NewNotRegisteredGVKErrForTarget(c.originalSchemeName, objGVK, c.encodeVersion)
204			}
205			if targetGVK == objGVK {
206				return c.encoder.Encode(obj, w)
207			}
208		}
209	}
210
211	gvks, isUnversioned, err := c.typer.ObjectKinds(obj)
212	if err != nil {
213		return err
214	}
215
216	objectKind := obj.GetObjectKind()
217	old := objectKind.GroupVersionKind()
218	// restore the old GVK after encoding
219	defer objectKind.SetGroupVersionKind(old)
220
221	if c.encodeVersion == nil || isUnversioned {
222		if e, ok := obj.(runtime.NestedObjectEncoder); ok {
223			if err := e.EncodeNestedObjects(runtime.WithVersionEncoder{Encoder: c.encoder, ObjectTyper: c.typer}); err != nil {
224				return err
225			}
226		}
227		objectKind.SetGroupVersionKind(gvks[0])
228		return c.encoder.Encode(obj, w)
229	}
230
231	// Perform a conversion if necessary
232	out, err := c.convertor.ConvertToVersion(obj, c.encodeVersion)
233	if err != nil {
234		return err
235	}
236
237	if e, ok := out.(runtime.NestedObjectEncoder); ok {
238		if err := e.EncodeNestedObjects(runtime.WithVersionEncoder{Version: c.encodeVersion, Encoder: c.encoder, ObjectTyper: c.typer}); err != nil {
239			return err
240		}
241	}
242
243	// Conversion is responsible for setting the proper group, version, and kind onto the outgoing object
244	return c.encoder.Encode(out, w)
245}
246
247// Identifier implements runtime.Encoder interface.
248func (c *codec) Identifier() runtime.Identifier {
249	return c.identifier
250}
251