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	"k8s.io/apimachinery/pkg/runtime"
21	"k8s.io/apimachinery/pkg/runtime/schema"
22	"k8s.io/apimachinery/pkg/runtime/serializer/json"
23	"k8s.io/apimachinery/pkg/runtime/serializer/recognizer"
24	"k8s.io/apimachinery/pkg/runtime/serializer/versioning"
25)
26
27// serializerExtensions are for serializers that are conditionally compiled in
28var serializerExtensions = []func(*runtime.Scheme) (serializerType, bool){}
29
30type serializerType struct {
31	AcceptContentTypes []string
32	ContentType        string
33	FileExtensions     []string
34	// EncodesAsText should be true if this content type can be represented safely in UTF-8
35	EncodesAsText bool
36
37	Serializer       runtime.Serializer
38	PrettySerializer runtime.Serializer
39
40	AcceptStreamContentTypes []string
41	StreamContentType        string
42
43	Framer           runtime.Framer
44	StreamSerializer runtime.Serializer
45}
46
47func newSerializersForScheme(scheme *runtime.Scheme, mf json.MetaFactory) []serializerType {
48	jsonSerializer := json.NewSerializer(mf, scheme, scheme, false)
49	jsonPrettySerializer := json.NewSerializer(mf, scheme, scheme, true)
50	yamlSerializer := json.NewYAMLSerializer(mf, scheme, scheme)
51
52	serializers := []serializerType{
53		{
54			AcceptContentTypes: []string{"application/json"},
55			ContentType:        "application/json",
56			FileExtensions:     []string{"json"},
57			EncodesAsText:      true,
58			Serializer:         jsonSerializer,
59			PrettySerializer:   jsonPrettySerializer,
60
61			Framer:           json.Framer,
62			StreamSerializer: jsonSerializer,
63		},
64		{
65			AcceptContentTypes: []string{"application/yaml"},
66			ContentType:        "application/yaml",
67			FileExtensions:     []string{"yaml"},
68			EncodesAsText:      true,
69			Serializer:         yamlSerializer,
70		},
71	}
72
73	for _, fn := range serializerExtensions {
74		if serializer, ok := fn(scheme); ok {
75			serializers = append(serializers, serializer)
76		}
77	}
78	return serializers
79}
80
81// CodecFactory provides methods for retrieving codecs and serializers for specific
82// versions and content types.
83type CodecFactory struct {
84	scheme      *runtime.Scheme
85	serializers []serializerType
86	universal   runtime.Decoder
87	accepts     []runtime.SerializerInfo
88
89	legacySerializer runtime.Serializer
90}
91
92// NewCodecFactory provides methods for retrieving serializers for the supported wire formats
93// and conversion wrappers to define preferred internal and external versions. In the future,
94// as the internal version is used less, callers may instead use a defaulting serializer and
95// only convert objects which are shared internally (Status, common API machinery).
96// TODO: allow other codecs to be compiled in?
97// TODO: accept a scheme interface
98func NewCodecFactory(scheme *runtime.Scheme) CodecFactory {
99	serializers := newSerializersForScheme(scheme, json.DefaultMetaFactory)
100	return newCodecFactory(scheme, serializers)
101}
102
103// newCodecFactory is a helper for testing that allows a different metafactory to be specified.
104func newCodecFactory(scheme *runtime.Scheme, serializers []serializerType) CodecFactory {
105	decoders := make([]runtime.Decoder, 0, len(serializers))
106	var accepts []runtime.SerializerInfo
107	alreadyAccepted := make(map[string]struct{})
108
109	var legacySerializer runtime.Serializer
110	for _, d := range serializers {
111		decoders = append(decoders, d.Serializer)
112		for _, mediaType := range d.AcceptContentTypes {
113			if _, ok := alreadyAccepted[mediaType]; ok {
114				continue
115			}
116			alreadyAccepted[mediaType] = struct{}{}
117			info := runtime.SerializerInfo{
118				MediaType:        d.ContentType,
119				EncodesAsText:    d.EncodesAsText,
120				Serializer:       d.Serializer,
121				PrettySerializer: d.PrettySerializer,
122			}
123			if d.StreamSerializer != nil {
124				info.StreamSerializer = &runtime.StreamSerializerInfo{
125					Serializer:    d.StreamSerializer,
126					EncodesAsText: d.EncodesAsText,
127					Framer:        d.Framer,
128				}
129			}
130			accepts = append(accepts, info)
131			if mediaType == runtime.ContentTypeJSON {
132				legacySerializer = d.Serializer
133			}
134		}
135	}
136	if legacySerializer == nil {
137		legacySerializer = serializers[0].Serializer
138	}
139
140	return CodecFactory{
141		scheme:      scheme,
142		serializers: serializers,
143		universal:   recognizer.NewDecoder(decoders...),
144
145		accepts: accepts,
146
147		legacySerializer: legacySerializer,
148	}
149}
150
151// SupportedMediaTypes returns the RFC2046 media types that this factory has serializers for.
152func (f CodecFactory) SupportedMediaTypes() []runtime.SerializerInfo {
153	return f.accepts
154}
155
156// LegacyCodec encodes output to a given API versions, and decodes output into the internal form from
157// any recognized source. The returned codec will always encode output to JSON. If a type is not
158// found in the list of versions an error will be returned.
159//
160// This method is deprecated - clients and servers should negotiate a serializer by mime-type and
161// invoke CodecForVersions. Callers that need only to read data should use UniversalDecoder().
162//
163// TODO: make this call exist only in pkg/api, and initialize it with the set of default versions.
164//   All other callers will be forced to request a Codec directly.
165func (f CodecFactory) LegacyCodec(version ...schema.GroupVersion) runtime.Codec {
166	return versioning.NewDefaultingCodecForScheme(f.scheme, f.legacySerializer, f.universal, schema.GroupVersions(version), runtime.InternalGroupVersioner)
167}
168
169// UniversalDeserializer can convert any stored data recognized by this factory into a Go object that satisfies
170// runtime.Object. It does not perform conversion. It does not perform defaulting.
171func (f CodecFactory) UniversalDeserializer() runtime.Decoder {
172	return f.universal
173}
174
175// UniversalDecoder returns a runtime.Decoder capable of decoding all known API objects in all known formats. Used
176// by clients that do not need to encode objects but want to deserialize API objects stored on disk. Only decodes
177// objects in groups registered with the scheme. The GroupVersions passed may be used to select alternate
178// versions of objects to return - by default, runtime.APIVersionInternal is used. If any versions are specified,
179// unrecognized groups will be returned in the version they are encoded as (no conversion). This decoder performs
180// defaulting.
181//
182// TODO: the decoder will eventually be removed in favor of dealing with objects in their versioned form
183// TODO: only accept a group versioner
184func (f CodecFactory) UniversalDecoder(versions ...schema.GroupVersion) runtime.Decoder {
185	var versioner runtime.GroupVersioner
186	if len(versions) == 0 {
187		versioner = runtime.InternalGroupVersioner
188	} else {
189		versioner = schema.GroupVersions(versions)
190	}
191	return f.CodecForVersions(nil, f.universal, nil, versioner)
192}
193
194// CodecForVersions creates a codec with the provided serializer. If an object is decoded and its group is not in the list,
195// it will default to runtime.APIVersionInternal. If encode is not specified for an object's group, the object is not
196// converted. If encode or decode are nil, no conversion is performed.
197func (f CodecFactory) CodecForVersions(encoder runtime.Encoder, decoder runtime.Decoder, encode runtime.GroupVersioner, decode runtime.GroupVersioner) runtime.Codec {
198	// TODO: these are for backcompat, remove them in the future
199	if encode == nil {
200		encode = runtime.DisabledGroupVersioner
201	}
202	if decode == nil {
203		decode = runtime.InternalGroupVersioner
204	}
205	return versioning.NewDefaultingCodecForScheme(f.scheme, encoder, decoder, encode, decode)
206}
207
208// DecoderToVersion returns a decoder that targets the provided group version.
209func (f CodecFactory) DecoderToVersion(decoder runtime.Decoder, gv runtime.GroupVersioner) runtime.Decoder {
210	return f.CodecForVersions(nil, decoder, nil, gv)
211}
212
213// EncoderForVersion returns an encoder that targets the provided group version.
214func (f CodecFactory) EncoderForVersion(encoder runtime.Encoder, gv runtime.GroupVersioner) runtime.Encoder {
215	return f.CodecForVersions(encoder, nil, gv, nil)
216}
217
218// DirectCodecFactory provides methods for retrieving "DirectCodec"s, which do not do conversion.
219type DirectCodecFactory struct {
220	CodecFactory
221}
222
223// EncoderForVersion returns an encoder that does not do conversion.
224func (f DirectCodecFactory) EncoderForVersion(serializer runtime.Encoder, version runtime.GroupVersioner) runtime.Encoder {
225	return versioning.DirectEncoder{
226		Version:     version,
227		Encoder:     serializer,
228		ObjectTyper: f.CodecFactory.scheme,
229	}
230}
231
232// DecoderToVersion returns an decoder that does not do conversion. gv is ignored.
233func (f DirectCodecFactory) DecoderToVersion(serializer runtime.Decoder, _ runtime.GroupVersioner) runtime.Decoder {
234	return versioning.DirectDecoder{
235		Decoder: serializer,
236	}
237}
238