| /* |
| Copyright 2014 The Kubernetes Authors. |
| |
| Licensed under the Apache License, Version 2.0 (the "License"); |
| you may not use this file except in compliance with the License. |
| You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| */ |
| |
| package serializer |
| |
| import ( |
| "mime" |
| "strings" |
| |
| "k8s.io/apimachinery/pkg/runtime" |
| "k8s.io/apimachinery/pkg/runtime/schema" |
| "k8s.io/apimachinery/pkg/runtime/serializer/json" |
| "k8s.io/apimachinery/pkg/runtime/serializer/protobuf" |
| "k8s.io/apimachinery/pkg/runtime/serializer/recognizer" |
| "k8s.io/apimachinery/pkg/runtime/serializer/versioning" |
| ) |
| |
| // serializerExtensions are for serializers that are conditionally compiled in |
| var serializerExtensions = []func(*runtime.Scheme) (serializerType, bool){} |
| |
| type serializerType struct { |
| AcceptContentTypes []string |
| ContentType string |
| FileExtensions []string |
| // EncodesAsText should be true if this content type can be represented safely in UTF-8 |
| EncodesAsText bool |
| |
| Serializer runtime.Serializer |
| PrettySerializer runtime.Serializer |
| |
| AcceptStreamContentTypes []string |
| StreamContentType string |
| |
| Framer runtime.Framer |
| StreamSerializer runtime.Serializer |
| } |
| |
| func newSerializersForScheme(scheme *runtime.Scheme, mf json.MetaFactory) []serializerType { |
| jsonSerializer := json.NewSerializer(mf, scheme, scheme, false) |
| jsonPrettySerializer := json.NewSerializer(mf, scheme, scheme, true) |
| yamlSerializer := json.NewYAMLSerializer(mf, scheme, scheme) |
| serializer := protobuf.NewSerializer(scheme, scheme) |
| raw := protobuf.NewRawSerializer(scheme, scheme) |
| |
| serializers := []serializerType{ |
| { |
| AcceptContentTypes: []string{"application/json"}, |
| ContentType: "application/json", |
| FileExtensions: []string{"json"}, |
| EncodesAsText: true, |
| Serializer: jsonSerializer, |
| PrettySerializer: jsonPrettySerializer, |
| |
| Framer: json.Framer, |
| StreamSerializer: jsonSerializer, |
| }, |
| { |
| AcceptContentTypes: []string{"application/yaml"}, |
| ContentType: "application/yaml", |
| FileExtensions: []string{"yaml"}, |
| EncodesAsText: true, |
| Serializer: yamlSerializer, |
| }, |
| { |
| AcceptContentTypes: []string{runtime.ContentTypeProtobuf}, |
| ContentType: runtime.ContentTypeProtobuf, |
| FileExtensions: []string{"pb"}, |
| Serializer: serializer, |
| |
| Framer: protobuf.LengthDelimitedFramer, |
| StreamSerializer: raw, |
| }, |
| } |
| |
| for _, fn := range serializerExtensions { |
| if serializer, ok := fn(scheme); ok { |
| serializers = append(serializers, serializer) |
| } |
| } |
| return serializers |
| } |
| |
| // CodecFactory provides methods for retrieving codecs and serializers for specific |
| // versions and content types. |
| type CodecFactory struct { |
| scheme *runtime.Scheme |
| serializers []serializerType |
| universal runtime.Decoder |
| accepts []runtime.SerializerInfo |
| |
| legacySerializer runtime.Serializer |
| } |
| |
| // NewCodecFactory provides methods for retrieving serializers for the supported wire formats |
| // and conversion wrappers to define preferred internal and external versions. In the future, |
| // as the internal version is used less, callers may instead use a defaulting serializer and |
| // only convert objects which are shared internally (Status, common API machinery). |
| // TODO: allow other codecs to be compiled in? |
| // TODO: accept a scheme interface |
| func NewCodecFactory(scheme *runtime.Scheme) CodecFactory { |
| serializers := newSerializersForScheme(scheme, json.DefaultMetaFactory) |
| return newCodecFactory(scheme, serializers) |
| } |
| |
| // newCodecFactory is a helper for testing that allows a different metafactory to be specified. |
| func newCodecFactory(scheme *runtime.Scheme, serializers []serializerType) CodecFactory { |
| decoders := make([]runtime.Decoder, 0, len(serializers)) |
| var accepts []runtime.SerializerInfo |
| alreadyAccepted := make(map[string]struct{}) |
| |
| var legacySerializer runtime.Serializer |
| for _, d := range serializers { |
| decoders = append(decoders, d.Serializer) |
| for _, mediaType := range d.AcceptContentTypes { |
| if _, ok := alreadyAccepted[mediaType]; ok { |
| continue |
| } |
| alreadyAccepted[mediaType] = struct{}{} |
| info := runtime.SerializerInfo{ |
| MediaType: d.ContentType, |
| EncodesAsText: d.EncodesAsText, |
| Serializer: d.Serializer, |
| PrettySerializer: d.PrettySerializer, |
| } |
| |
| mediaType, _, err := mime.ParseMediaType(info.MediaType) |
| if err != nil { |
| panic(err) |
| } |
| parts := strings.SplitN(mediaType, "/", 2) |
| info.MediaTypeType = parts[0] |
| info.MediaTypeSubType = parts[1] |
| |
| if d.StreamSerializer != nil { |
| info.StreamSerializer = &runtime.StreamSerializerInfo{ |
| Serializer: d.StreamSerializer, |
| EncodesAsText: d.EncodesAsText, |
| Framer: d.Framer, |
| } |
| } |
| accepts = append(accepts, info) |
| if mediaType == runtime.ContentTypeJSON { |
| legacySerializer = d.Serializer |
| } |
| } |
| } |
| if legacySerializer == nil { |
| legacySerializer = serializers[0].Serializer |
| } |
| |
| return CodecFactory{ |
| scheme: scheme, |
| serializers: serializers, |
| universal: recognizer.NewDecoder(decoders...), |
| |
| accepts: accepts, |
| |
| legacySerializer: legacySerializer, |
| } |
| } |
| |
| // WithoutConversion returns a NegotiatedSerializer that performs no conversion, even if the |
| // caller requests it. |
| func (f CodecFactory) WithoutConversion() runtime.NegotiatedSerializer { |
| return WithoutConversionCodecFactory{f} |
| } |
| |
| // SupportedMediaTypes returns the RFC2046 media types that this factory has serializers for. |
| func (f CodecFactory) SupportedMediaTypes() []runtime.SerializerInfo { |
| return f.accepts |
| } |
| |
| // LegacyCodec encodes output to a given API versions, and decodes output into the internal form from |
| // any recognized source. The returned codec will always encode output to JSON. If a type is not |
| // found in the list of versions an error will be returned. |
| // |
| // This method is deprecated - clients and servers should negotiate a serializer by mime-type and |
| // invoke CodecForVersions. Callers that need only to read data should use UniversalDecoder(). |
| // |
| // TODO: make this call exist only in pkg/api, and initialize it with the set of default versions. |
| // All other callers will be forced to request a Codec directly. |
| func (f CodecFactory) LegacyCodec(version ...schema.GroupVersion) runtime.Codec { |
| return versioning.NewDefaultingCodecForScheme(f.scheme, f.legacySerializer, f.universal, schema.GroupVersions(version), runtime.InternalGroupVersioner) |
| } |
| |
| // UniversalDeserializer can convert any stored data recognized by this factory into a Go object that satisfies |
| // runtime.Object. It does not perform conversion. It does not perform defaulting. |
| func (f CodecFactory) UniversalDeserializer() runtime.Decoder { |
| return f.universal |
| } |
| |
| // UniversalDecoder returns a runtime.Decoder capable of decoding all known API objects in all known formats. Used |
| // by clients that do not need to encode objects but want to deserialize API objects stored on disk. Only decodes |
| // objects in groups registered with the scheme. The GroupVersions passed may be used to select alternate |
| // versions of objects to return - by default, runtime.APIVersionInternal is used. If any versions are specified, |
| // unrecognized groups will be returned in the version they are encoded as (no conversion). This decoder performs |
| // defaulting. |
| // |
| // TODO: the decoder will eventually be removed in favor of dealing with objects in their versioned form |
| // TODO: only accept a group versioner |
| func (f CodecFactory) UniversalDecoder(versions ...schema.GroupVersion) runtime.Decoder { |
| var versioner runtime.GroupVersioner |
| if len(versions) == 0 { |
| versioner = runtime.InternalGroupVersioner |
| } else { |
| versioner = schema.GroupVersions(versions) |
| } |
| return f.CodecForVersions(nil, f.universal, nil, versioner) |
| } |
| |
| // CodecForVersions creates a codec with the provided serializer. If an object is decoded and its group is not in the list, |
| // it will default to runtime.APIVersionInternal. If encode is not specified for an object's group, the object is not |
| // converted. If encode or decode are nil, no conversion is performed. |
| func (f CodecFactory) CodecForVersions(encoder runtime.Encoder, decoder runtime.Decoder, encode runtime.GroupVersioner, decode runtime.GroupVersioner) runtime.Codec { |
| // TODO: these are for backcompat, remove them in the future |
| if encode == nil { |
| encode = runtime.DisabledGroupVersioner |
| } |
| if decode == nil { |
| decode = runtime.InternalGroupVersioner |
| } |
| return versioning.NewDefaultingCodecForScheme(f.scheme, encoder, decoder, encode, decode) |
| } |
| |
| // DecoderToVersion returns a decoder that targets the provided group version. |
| func (f CodecFactory) DecoderToVersion(decoder runtime.Decoder, gv runtime.GroupVersioner) runtime.Decoder { |
| return f.CodecForVersions(nil, decoder, nil, gv) |
| } |
| |
| // EncoderForVersion returns an encoder that targets the provided group version. |
| func (f CodecFactory) EncoderForVersion(encoder runtime.Encoder, gv runtime.GroupVersioner) runtime.Encoder { |
| return f.CodecForVersions(encoder, nil, gv, nil) |
| } |
| |
| // WithoutConversionCodecFactory is a CodecFactory that will explicitly ignore requests to perform conversion. |
| // This wrapper is used while code migrates away from using conversion (such as external clients) and in the future |
| // will be unnecessary when we change the signature of NegotiatedSerializer. |
| type WithoutConversionCodecFactory struct { |
| CodecFactory |
| } |
| |
| // EncoderForVersion returns an encoder that does not do conversion, but does set the group version kind of the object |
| // when serialized. |
| func (f WithoutConversionCodecFactory) EncoderForVersion(serializer runtime.Encoder, version runtime.GroupVersioner) runtime.Encoder { |
| return runtime.WithVersionEncoder{ |
| Version: version, |
| Encoder: serializer, |
| ObjectTyper: f.CodecFactory.scheme, |
| } |
| } |
| |
| // DecoderToVersion returns an decoder that does not do conversion. |
| func (f WithoutConversionCodecFactory) DecoderToVersion(serializer runtime.Decoder, _ runtime.GroupVersioner) runtime.Decoder { |
| return runtime.WithoutVersionDecoder{ |
| Decoder: serializer, |
| } |
| } |
| |
| // DirectCodecFactory was renamed to WithoutConversionCodecFactory in 1.15. |
| // TODO: remove in 1.16. |
| type DirectCodecFactory = WithoutConversionCodecFactory |