blob: 7716cc42178679a7a09540d7973ffb5311d62809 [file] [log] [blame]
Matthias Andreas Benkard832a54e2019-01-29 09:27:38 +01001/*
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 "io"
21
22 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
23 "k8s.io/apimachinery/pkg/runtime"
24 "k8s.io/apimachinery/pkg/runtime/schema"
25)
26
27// NewCodecForScheme is a convenience method for callers that are using a scheme.
28func NewCodecForScheme(
29 // TODO: I should be a scheme interface?
30 scheme *runtime.Scheme,
31 encoder runtime.Encoder,
32 decoder runtime.Decoder,
33 encodeVersion runtime.GroupVersioner,
34 decodeVersion runtime.GroupVersioner,
35) runtime.Codec {
36 return NewCodec(encoder, decoder, runtime.UnsafeObjectConvertor(scheme), scheme, scheme, nil, encodeVersion, decodeVersion)
37}
38
39// NewDefaultingCodecForScheme is a convenience method for callers that are using a scheme.
40func NewDefaultingCodecForScheme(
41 // TODO: I should be a scheme interface?
42 scheme *runtime.Scheme,
43 encoder runtime.Encoder,
44 decoder runtime.Decoder,
45 encodeVersion runtime.GroupVersioner,
46 decodeVersion runtime.GroupVersioner,
47) runtime.Codec {
48 return NewCodec(encoder, decoder, runtime.UnsafeObjectConvertor(scheme), scheme, scheme, scheme, encodeVersion, decodeVersion)
49}
50
51// NewCodec takes objects in their internal versions and converts them to external versions before
52// serializing them. It assumes the serializer provided to it only deals with external versions.
53// This class is also a serializer, but is generally used with a specific version.
54func NewCodec(
55 encoder runtime.Encoder,
56 decoder runtime.Decoder,
57 convertor runtime.ObjectConvertor,
58 creater runtime.ObjectCreater,
59 typer runtime.ObjectTyper,
60 defaulter runtime.ObjectDefaulter,
61 encodeVersion runtime.GroupVersioner,
62 decodeVersion runtime.GroupVersioner,
63) runtime.Codec {
64 internal := &codec{
65 encoder: encoder,
66 decoder: decoder,
67 convertor: convertor,
68 creater: creater,
69 typer: typer,
70 defaulter: defaulter,
71
72 encodeVersion: encodeVersion,
73 decodeVersion: decodeVersion,
74 }
75 return internal
76}
77
78type codec struct {
79 encoder runtime.Encoder
80 decoder runtime.Decoder
81 convertor runtime.ObjectConvertor
82 creater runtime.ObjectCreater
83 typer runtime.ObjectTyper
84 defaulter runtime.ObjectDefaulter
85
86 encodeVersion runtime.GroupVersioner
87 decodeVersion runtime.GroupVersioner
88}
89
90// Decode attempts a decode of the object, then tries to convert it to the internal version. If into is provided and the decoding is
91// successful, the returned runtime.Object will be the value passed as into. Note that this may bypass conversion if you pass an
92// into that matches the serialized version.
93func (c *codec) Decode(data []byte, defaultGVK *schema.GroupVersionKind, into runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) {
94 versioned, isVersioned := into.(*runtime.VersionedObjects)
95 if isVersioned {
96 into = versioned.Last()
97 }
98
99 obj, gvk, err := c.decoder.Decode(data, defaultGVK, into)
100 if err != nil {
101 return nil, gvk, err
102 }
103
104 if d, ok := obj.(runtime.NestedObjectDecoder); ok {
105 if err := d.DecodeNestedObjects(DirectDecoder{c.decoder}); err != nil {
106 return nil, gvk, err
107 }
108 }
109
110 // if we specify a target, use generic conversion.
111 if into != nil {
112 if into == obj {
113 if isVersioned {
114 return versioned, gvk, nil
115 }
116 return into, gvk, nil
117 }
118
119 // perform defaulting if requested
120 if c.defaulter != nil {
121 // create a copy to ensure defaulting is not applied to the original versioned objects
122 if isVersioned {
123 versioned.Objects = []runtime.Object{obj.DeepCopyObject()}
124 }
125 c.defaulter.Default(obj)
126 } else {
127 if isVersioned {
128 versioned.Objects = []runtime.Object{obj}
129 }
130 }
131
132 if err := c.convertor.Convert(obj, into, c.decodeVersion); err != nil {
133 return nil, gvk, err
134 }
135
136 if isVersioned {
137 versioned.Objects = append(versioned.Objects, into)
138 return versioned, gvk, nil
139 }
140 return into, gvk, nil
141 }
142
143 // Convert if needed.
144 if isVersioned {
145 // create a copy, because ConvertToVersion does not guarantee non-mutation of objects
146 versioned.Objects = []runtime.Object{obj.DeepCopyObject()}
147 }
148
149 // perform defaulting if requested
150 if c.defaulter != nil {
151 c.defaulter.Default(obj)
152 }
153
154 out, err := c.convertor.ConvertToVersion(obj, c.decodeVersion)
155 if err != nil {
156 return nil, gvk, err
157 }
158 if isVersioned {
159 if versioned.Last() != out {
160 versioned.Objects = append(versioned.Objects, out)
161 }
162 return versioned, gvk, nil
163 }
164 return out, gvk, nil
165}
166
167// Encode ensures the provided object is output in the appropriate group and version, invoking
168// conversion if necessary. Unversioned objects (according to the ObjectTyper) are output as is.
169func (c *codec) Encode(obj runtime.Object, w io.Writer) error {
170 switch obj := obj.(type) {
171 case *runtime.Unknown:
172 return c.encoder.Encode(obj, w)
173 case runtime.Unstructured:
174 // An unstructured list can contain objects of multiple group version kinds. don't short-circuit just
175 // because the top-level type matches our desired destination type. actually send the object to the converter
176 // to give it a chance to convert the list items if needed.
177 if _, ok := obj.(*unstructured.UnstructuredList); !ok {
178 // 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)
179 objGVK := obj.GetObjectKind().GroupVersionKind()
180 if len(objGVK.Version) == 0 {
181 return c.encoder.Encode(obj, w)
182 }
183 targetGVK, ok := c.encodeVersion.KindForGroupVersionKinds([]schema.GroupVersionKind{objGVK})
184 if !ok {
185 return runtime.NewNotRegisteredGVKErrForTarget(objGVK, c.encodeVersion)
186 }
187 if targetGVK == objGVK {
188 return c.encoder.Encode(obj, w)
189 }
190 }
191 }
192
193 gvks, isUnversioned, err := c.typer.ObjectKinds(obj)
194 if err != nil {
195 return err
196 }
197
198 if c.encodeVersion == nil || isUnversioned {
199 if e, ok := obj.(runtime.NestedObjectEncoder); ok {
200 if err := e.EncodeNestedObjects(DirectEncoder{Encoder: c.encoder, ObjectTyper: c.typer}); err != nil {
201 return err
202 }
203 }
204 objectKind := obj.GetObjectKind()
205 old := objectKind.GroupVersionKind()
206 objectKind.SetGroupVersionKind(gvks[0])
207 err = c.encoder.Encode(obj, w)
208 objectKind.SetGroupVersionKind(old)
209 return err
210 }
211
212 // Perform a conversion if necessary
213 objectKind := obj.GetObjectKind()
214 old := objectKind.GroupVersionKind()
215 out, err := c.convertor.ConvertToVersion(obj, c.encodeVersion)
216 if err != nil {
217 return err
218 }
219
220 if e, ok := out.(runtime.NestedObjectEncoder); ok {
221 if err := e.EncodeNestedObjects(DirectEncoder{Version: c.encodeVersion, Encoder: c.encoder, ObjectTyper: c.typer}); err != nil {
222 return err
223 }
224 }
225
226 // Conversion is responsible for setting the proper group, version, and kind onto the outgoing object
227 err = c.encoder.Encode(out, w)
228 // restore the old GVK, in case conversion returned the same object
229 objectKind.SetGroupVersionKind(old)
230 return err
231}
232
233// DirectEncoder serializes an object and ensures the GVK is set.
234type DirectEncoder struct {
235 Version runtime.GroupVersioner
236 runtime.Encoder
237 runtime.ObjectTyper
238}
239
240// Encode does not do conversion. It sets the gvk during serialization.
241func (e DirectEncoder) Encode(obj runtime.Object, stream io.Writer) error {
242 gvks, _, err := e.ObjectTyper.ObjectKinds(obj)
243 if err != nil {
244 if runtime.IsNotRegisteredError(err) {
245 return e.Encoder.Encode(obj, stream)
246 }
247 return err
248 }
249 kind := obj.GetObjectKind()
250 oldGVK := kind.GroupVersionKind()
251 gvk := gvks[0]
252 if e.Version != nil {
253 preferredGVK, ok := e.Version.KindForGroupVersionKinds(gvks)
254 if ok {
255 gvk = preferredGVK
256 }
257 }
258 kind.SetGroupVersionKind(gvk)
259 err = e.Encoder.Encode(obj, stream)
260 kind.SetGroupVersionKind(oldGVK)
261 return err
262}
263
264// DirectDecoder clears the group version kind of a deserialized object.
265type DirectDecoder struct {
266 runtime.Decoder
267}
268
269// Decode does not do conversion. It removes the gvk during deserialization.
270func (d DirectDecoder) Decode(data []byte, defaults *schema.GroupVersionKind, into runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) {
271 obj, gvk, err := d.Decoder.Decode(data, defaults, into)
272 if obj != nil {
273 kind := obj.GetObjectKind()
274 // clearing the gvk is just a convention of a codec
275 kind.SetGroupVersionKind(schema.GroupVersionKind{})
276 }
277 return obj, gvk, err
278}