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 serializer
18
19import (
20	"mime"
21	"strings"
22
23	"k8s.io/apimachinery/pkg/runtime"
24	"k8s.io/apimachinery/pkg/runtime/schema"
25	"k8s.io/apimachinery/pkg/runtime/serializer/json"
26	"k8s.io/apimachinery/pkg/runtime/serializer/protobuf"
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
32var serializerExtensions = []func(*runtime.Scheme) (serializerType, bool){}
33
34type 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
51func 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)
55	serializer := protobuf.NewSerializer(scheme, scheme)
56	raw := protobuf.NewRawSerializer(scheme, scheme)
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		},
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		},
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.
98type 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
113func 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.
119func 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			}
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
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
175// WithoutConversion returns a NegotiatedSerializer that performs no conversion, even if the
176// caller requests it.
177func (f CodecFactory) WithoutConversion() runtime.NegotiatedSerializer {
178	return WithoutConversionCodecFactory{f}
179}
180
181// SupportedMediaTypes returns the RFC2046 media types that this factory has serializers for.
182func (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.
195func (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.
201func (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
214func (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.
227func (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.
239func (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.
244func (f CodecFactory) EncoderForVersion(encoder runtime.Encoder, gv runtime.GroupVersioner) runtime.Encoder {
245	return f.CodecForVersions(encoder, nil, gv, nil)
246}
247
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.
251type WithoutConversionCodecFactory struct {
252	CodecFactory
253}
254
255// EncoderForVersion returns an encoder that does not do conversion, but does set the group version kind of the object
256// when serialized.
257func (f WithoutConversionCodecFactory) EncoderForVersion(serializer runtime.Encoder, version runtime.GroupVersioner) runtime.Encoder {
258	return runtime.WithVersionEncoder{
259		Version:     version,
260		Encoder:     serializer,
261		ObjectTyper: f.CodecFactory.scheme,
262	}
263}
264
265// DecoderToVersion returns an decoder that does not do conversion.
266func (f WithoutConversionCodecFactory) DecoderToVersion(serializer runtime.Decoder, _ runtime.GroupVersioner) runtime.Decoder {
267	return runtime.WithoutVersionDecoder{
268		Decoder: serializer,
269	}
270}
271
272// DirectCodecFactory was renamed to WithoutConversionCodecFactory in 1.15.
273// TODO: remove in 1.16.
274type DirectCodecFactory = WithoutConversionCodecFactory
275