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 "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} 279