Zack Williams | e940c7a | 2019-08-21 14:25:39 -0700 | [diff] [blame] | 1 | /* |
| 2 | Copyright 2014 The Kubernetes Authors. |
| 3 | |
| 4 | Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | you may not use this file except in compliance with the License. |
| 6 | You may obtain a copy of the License at |
| 7 | |
| 8 | http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | |
| 10 | Unless required by applicable law or agreed to in writing, software |
| 11 | distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | See the License for the specific language governing permissions and |
| 14 | limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | package serializer |
| 18 | |
| 19 | import ( |
David Bainbridge | 8697152 | 2019-09-26 22:09:39 +0000 | [diff] [blame] | 20 | "mime" |
| 21 | "strings" |
| 22 | |
Zack Williams | e940c7a | 2019-08-21 14:25:39 -0700 | [diff] [blame] | 23 | "k8s.io/apimachinery/pkg/runtime" |
| 24 | "k8s.io/apimachinery/pkg/runtime/schema" |
| 25 | "k8s.io/apimachinery/pkg/runtime/serializer/json" |
David Bainbridge | 8697152 | 2019-09-26 22:09:39 +0000 | [diff] [blame] | 26 | "k8s.io/apimachinery/pkg/runtime/serializer/protobuf" |
Zack Williams | e940c7a | 2019-08-21 14:25:39 -0700 | [diff] [blame] | 27 | "k8s.io/apimachinery/pkg/runtime/serializer/recognizer" |
| 28 | "k8s.io/apimachinery/pkg/runtime/serializer/versioning" |
| 29 | ) |
| 30 | |
| 31 | // serializerExtensions are for serializers that are conditionally compiled in |
| 32 | var serializerExtensions = []func(*runtime.Scheme) (serializerType, bool){} |
| 33 | |
| 34 | type serializerType struct { |
| 35 | AcceptContentTypes []string |
| 36 | ContentType string |
| 37 | FileExtensions []string |
| 38 | // EncodesAsText should be true if this content type can be represented safely in UTF-8 |
| 39 | EncodesAsText bool |
| 40 | |
| 41 | Serializer runtime.Serializer |
| 42 | PrettySerializer runtime.Serializer |
| 43 | |
| 44 | AcceptStreamContentTypes []string |
| 45 | StreamContentType string |
| 46 | |
| 47 | Framer runtime.Framer |
| 48 | StreamSerializer runtime.Serializer |
| 49 | } |
| 50 | |
| 51 | func newSerializersForScheme(scheme *runtime.Scheme, mf json.MetaFactory) []serializerType { |
| 52 | jsonSerializer := json.NewSerializer(mf, scheme, scheme, false) |
| 53 | jsonPrettySerializer := json.NewSerializer(mf, scheme, scheme, true) |
| 54 | yamlSerializer := json.NewYAMLSerializer(mf, scheme, scheme) |
David Bainbridge | 8697152 | 2019-09-26 22:09:39 +0000 | [diff] [blame] | 55 | serializer := protobuf.NewSerializer(scheme, scheme) |
| 56 | raw := protobuf.NewRawSerializer(scheme, scheme) |
Zack Williams | e940c7a | 2019-08-21 14:25:39 -0700 | [diff] [blame] | 57 | |
| 58 | serializers := []serializerType{ |
| 59 | { |
| 60 | AcceptContentTypes: []string{"application/json"}, |
| 61 | ContentType: "application/json", |
| 62 | FileExtensions: []string{"json"}, |
| 63 | EncodesAsText: true, |
| 64 | Serializer: jsonSerializer, |
| 65 | PrettySerializer: jsonPrettySerializer, |
| 66 | |
| 67 | Framer: json.Framer, |
| 68 | StreamSerializer: jsonSerializer, |
| 69 | }, |
| 70 | { |
| 71 | AcceptContentTypes: []string{"application/yaml"}, |
| 72 | ContentType: "application/yaml", |
| 73 | FileExtensions: []string{"yaml"}, |
| 74 | EncodesAsText: true, |
| 75 | Serializer: yamlSerializer, |
| 76 | }, |
David Bainbridge | 8697152 | 2019-09-26 22:09:39 +0000 | [diff] [blame] | 77 | { |
| 78 | AcceptContentTypes: []string{runtime.ContentTypeProtobuf}, |
| 79 | ContentType: runtime.ContentTypeProtobuf, |
| 80 | FileExtensions: []string{"pb"}, |
| 81 | Serializer: serializer, |
| 82 | |
| 83 | Framer: protobuf.LengthDelimitedFramer, |
| 84 | StreamSerializer: raw, |
| 85 | }, |
Zack Williams | e940c7a | 2019-08-21 14:25:39 -0700 | [diff] [blame] | 86 | } |
| 87 | |
| 88 | for _, fn := range serializerExtensions { |
| 89 | if serializer, ok := fn(scheme); ok { |
| 90 | serializers = append(serializers, serializer) |
| 91 | } |
| 92 | } |
| 93 | return serializers |
| 94 | } |
| 95 | |
| 96 | // CodecFactory provides methods for retrieving codecs and serializers for specific |
| 97 | // versions and content types. |
| 98 | type CodecFactory struct { |
| 99 | scheme *runtime.Scheme |
| 100 | serializers []serializerType |
| 101 | universal runtime.Decoder |
| 102 | accepts []runtime.SerializerInfo |
| 103 | |
| 104 | legacySerializer runtime.Serializer |
| 105 | } |
| 106 | |
| 107 | // NewCodecFactory provides methods for retrieving serializers for the supported wire formats |
| 108 | // and conversion wrappers to define preferred internal and external versions. In the future, |
| 109 | // as the internal version is used less, callers may instead use a defaulting serializer and |
| 110 | // only convert objects which are shared internally (Status, common API machinery). |
| 111 | // TODO: allow other codecs to be compiled in? |
| 112 | // TODO: accept a scheme interface |
| 113 | func NewCodecFactory(scheme *runtime.Scheme) CodecFactory { |
| 114 | serializers := newSerializersForScheme(scheme, json.DefaultMetaFactory) |
| 115 | return newCodecFactory(scheme, serializers) |
| 116 | } |
| 117 | |
| 118 | // newCodecFactory is a helper for testing that allows a different metafactory to be specified. |
| 119 | func newCodecFactory(scheme *runtime.Scheme, serializers []serializerType) CodecFactory { |
| 120 | decoders := make([]runtime.Decoder, 0, len(serializers)) |
| 121 | var accepts []runtime.SerializerInfo |
| 122 | alreadyAccepted := make(map[string]struct{}) |
| 123 | |
| 124 | var legacySerializer runtime.Serializer |
| 125 | for _, d := range serializers { |
| 126 | decoders = append(decoders, d.Serializer) |
| 127 | for _, mediaType := range d.AcceptContentTypes { |
| 128 | if _, ok := alreadyAccepted[mediaType]; ok { |
| 129 | continue |
| 130 | } |
| 131 | alreadyAccepted[mediaType] = struct{}{} |
| 132 | info := runtime.SerializerInfo{ |
| 133 | MediaType: d.ContentType, |
| 134 | EncodesAsText: d.EncodesAsText, |
| 135 | Serializer: d.Serializer, |
| 136 | PrettySerializer: d.PrettySerializer, |
| 137 | } |
David Bainbridge | 8697152 | 2019-09-26 22:09:39 +0000 | [diff] [blame] | 138 | |
| 139 | mediaType, _, err := mime.ParseMediaType(info.MediaType) |
| 140 | if err != nil { |
| 141 | panic(err) |
| 142 | } |
| 143 | parts := strings.SplitN(mediaType, "/", 2) |
| 144 | info.MediaTypeType = parts[0] |
| 145 | info.MediaTypeSubType = parts[1] |
| 146 | |
Zack Williams | e940c7a | 2019-08-21 14:25:39 -0700 | [diff] [blame] | 147 | if d.StreamSerializer != nil { |
| 148 | info.StreamSerializer = &runtime.StreamSerializerInfo{ |
| 149 | Serializer: d.StreamSerializer, |
| 150 | EncodesAsText: d.EncodesAsText, |
| 151 | Framer: d.Framer, |
| 152 | } |
| 153 | } |
| 154 | accepts = append(accepts, info) |
| 155 | if mediaType == runtime.ContentTypeJSON { |
| 156 | legacySerializer = d.Serializer |
| 157 | } |
| 158 | } |
| 159 | } |
| 160 | if legacySerializer == nil { |
| 161 | legacySerializer = serializers[0].Serializer |
| 162 | } |
| 163 | |
| 164 | return CodecFactory{ |
| 165 | scheme: scheme, |
| 166 | serializers: serializers, |
| 167 | universal: recognizer.NewDecoder(decoders...), |
| 168 | |
| 169 | accepts: accepts, |
| 170 | |
| 171 | legacySerializer: legacySerializer, |
| 172 | } |
| 173 | } |
| 174 | |
David Bainbridge | 8697152 | 2019-09-26 22:09:39 +0000 | [diff] [blame] | 175 | // WithoutConversion returns a NegotiatedSerializer that performs no conversion, even if the |
| 176 | // caller requests it. |
| 177 | func (f CodecFactory) WithoutConversion() runtime.NegotiatedSerializer { |
| 178 | return WithoutConversionCodecFactory{f} |
| 179 | } |
| 180 | |
Zack Williams | e940c7a | 2019-08-21 14:25:39 -0700 | [diff] [blame] | 181 | // SupportedMediaTypes returns the RFC2046 media types that this factory has serializers for. |
| 182 | func (f CodecFactory) SupportedMediaTypes() []runtime.SerializerInfo { |
| 183 | return f.accepts |
| 184 | } |
| 185 | |
| 186 | // LegacyCodec encodes output to a given API versions, and decodes output into the internal form from |
| 187 | // any recognized source. The returned codec will always encode output to JSON. If a type is not |
| 188 | // found in the list of versions an error will be returned. |
| 189 | // |
| 190 | // This method is deprecated - clients and servers should negotiate a serializer by mime-type and |
| 191 | // invoke CodecForVersions. Callers that need only to read data should use UniversalDecoder(). |
| 192 | // |
| 193 | // TODO: make this call exist only in pkg/api, and initialize it with the set of default versions. |
| 194 | // All other callers will be forced to request a Codec directly. |
| 195 | func (f CodecFactory) LegacyCodec(version ...schema.GroupVersion) runtime.Codec { |
| 196 | return versioning.NewDefaultingCodecForScheme(f.scheme, f.legacySerializer, f.universal, schema.GroupVersions(version), runtime.InternalGroupVersioner) |
| 197 | } |
| 198 | |
| 199 | // UniversalDeserializer can convert any stored data recognized by this factory into a Go object that satisfies |
| 200 | // runtime.Object. It does not perform conversion. It does not perform defaulting. |
| 201 | func (f CodecFactory) UniversalDeserializer() runtime.Decoder { |
| 202 | return f.universal |
| 203 | } |
| 204 | |
| 205 | // UniversalDecoder returns a runtime.Decoder capable of decoding all known API objects in all known formats. Used |
| 206 | // by clients that do not need to encode objects but want to deserialize API objects stored on disk. Only decodes |
| 207 | // objects in groups registered with the scheme. The GroupVersions passed may be used to select alternate |
| 208 | // versions of objects to return - by default, runtime.APIVersionInternal is used. If any versions are specified, |
| 209 | // unrecognized groups will be returned in the version they are encoded as (no conversion). This decoder performs |
| 210 | // defaulting. |
| 211 | // |
| 212 | // TODO: the decoder will eventually be removed in favor of dealing with objects in their versioned form |
| 213 | // TODO: only accept a group versioner |
| 214 | func (f CodecFactory) UniversalDecoder(versions ...schema.GroupVersion) runtime.Decoder { |
| 215 | var versioner runtime.GroupVersioner |
| 216 | if len(versions) == 0 { |
| 217 | versioner = runtime.InternalGroupVersioner |
| 218 | } else { |
| 219 | versioner = schema.GroupVersions(versions) |
| 220 | } |
| 221 | return f.CodecForVersions(nil, f.universal, nil, versioner) |
| 222 | } |
| 223 | |
| 224 | // CodecForVersions creates a codec with the provided serializer. If an object is decoded and its group is not in the list, |
| 225 | // it will default to runtime.APIVersionInternal. If encode is not specified for an object's group, the object is not |
| 226 | // converted. If encode or decode are nil, no conversion is performed. |
| 227 | func (f CodecFactory) CodecForVersions(encoder runtime.Encoder, decoder runtime.Decoder, encode runtime.GroupVersioner, decode runtime.GroupVersioner) runtime.Codec { |
| 228 | // TODO: these are for backcompat, remove them in the future |
| 229 | if encode == nil { |
| 230 | encode = runtime.DisabledGroupVersioner |
| 231 | } |
| 232 | if decode == nil { |
| 233 | decode = runtime.InternalGroupVersioner |
| 234 | } |
| 235 | return versioning.NewDefaultingCodecForScheme(f.scheme, encoder, decoder, encode, decode) |
| 236 | } |
| 237 | |
| 238 | // DecoderToVersion returns a decoder that targets the provided group version. |
| 239 | func (f CodecFactory) DecoderToVersion(decoder runtime.Decoder, gv runtime.GroupVersioner) runtime.Decoder { |
| 240 | return f.CodecForVersions(nil, decoder, nil, gv) |
| 241 | } |
| 242 | |
| 243 | // EncoderForVersion returns an encoder that targets the provided group version. |
| 244 | func (f CodecFactory) EncoderForVersion(encoder runtime.Encoder, gv runtime.GroupVersioner) runtime.Encoder { |
| 245 | return f.CodecForVersions(encoder, nil, gv, nil) |
| 246 | } |
| 247 | |
David Bainbridge | 8697152 | 2019-09-26 22:09:39 +0000 | [diff] [blame] | 248 | // WithoutConversionCodecFactory is a CodecFactory that will explicitly ignore requests to perform conversion. |
| 249 | // This wrapper is used while code migrates away from using conversion (such as external clients) and in the future |
| 250 | // will be unnecessary when we change the signature of NegotiatedSerializer. |
| 251 | type WithoutConversionCodecFactory struct { |
Zack Williams | e940c7a | 2019-08-21 14:25:39 -0700 | [diff] [blame] | 252 | CodecFactory |
| 253 | } |
| 254 | |
David Bainbridge | 8697152 | 2019-09-26 22:09:39 +0000 | [diff] [blame] | 255 | // EncoderForVersion returns an encoder that does not do conversion, but does set the group version kind of the object |
| 256 | // when serialized. |
| 257 | func (f WithoutConversionCodecFactory) EncoderForVersion(serializer runtime.Encoder, version runtime.GroupVersioner) runtime.Encoder { |
| 258 | return runtime.WithVersionEncoder{ |
Zack Williams | e940c7a | 2019-08-21 14:25:39 -0700 | [diff] [blame] | 259 | Version: version, |
| 260 | Encoder: serializer, |
| 261 | ObjectTyper: f.CodecFactory.scheme, |
| 262 | } |
| 263 | } |
| 264 | |
David Bainbridge | 8697152 | 2019-09-26 22:09:39 +0000 | [diff] [blame] | 265 | // DecoderToVersion returns an decoder that does not do conversion. |
| 266 | func (f WithoutConversionCodecFactory) DecoderToVersion(serializer runtime.Decoder, _ runtime.GroupVersioner) runtime.Decoder { |
| 267 | return runtime.WithoutVersionDecoder{ |
Zack Williams | e940c7a | 2019-08-21 14:25:39 -0700 | [diff] [blame] | 268 | Decoder: serializer, |
| 269 | } |
| 270 | } |
David Bainbridge | 8697152 | 2019-09-26 22:09:39 +0000 | [diff] [blame] | 271 | |
| 272 | // DirectCodecFactory was renamed to WithoutConversionCodecFactory in 1.15. |
| 273 | // TODO: remove in 1.16. |
| 274 | type DirectCodecFactory = WithoutConversionCodecFactory |