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 json
18
19import (
20	"encoding/json"
21	"io"
22	"strconv"
23	"unsafe"
24
25	"github.com/ghodss/yaml"
26	jsoniter "github.com/json-iterator/go"
27
28	"k8s.io/apimachinery/pkg/runtime"
29	"k8s.io/apimachinery/pkg/runtime/schema"
30	"k8s.io/apimachinery/pkg/runtime/serializer/recognizer"
31	"k8s.io/apimachinery/pkg/util/framer"
32	utilyaml "k8s.io/apimachinery/pkg/util/yaml"
33)
34
35// NewSerializer creates a JSON serializer that handles encoding versioned objects into the proper JSON form. If typer
36// is not nil, the object has the group, version, and kind fields set.
37func NewSerializer(meta MetaFactory, creater runtime.ObjectCreater, typer runtime.ObjectTyper, pretty bool) *Serializer {
38	return &Serializer{
39		meta:    meta,
40		creater: creater,
41		typer:   typer,
42		yaml:    false,
43		pretty:  pretty,
44	}
45}
46
47// NewYAMLSerializer creates a YAML serializer that handles encoding versioned objects into the proper YAML form. If typer
48// is not nil, the object has the group, version, and kind fields set. This serializer supports only the subset of YAML that
49// matches JSON, and will error if constructs are used that do not serialize to JSON.
50func NewYAMLSerializer(meta MetaFactory, creater runtime.ObjectCreater, typer runtime.ObjectTyper) *Serializer {
51	return &Serializer{
52		meta:    meta,
53		creater: creater,
54		typer:   typer,
55		yaml:    true,
56	}
57}
58
59type Serializer struct {
60	meta    MetaFactory
61	creater runtime.ObjectCreater
62	typer   runtime.ObjectTyper
63	yaml    bool
64	pretty  bool
65}
66
67// Serializer implements Serializer
68var _ runtime.Serializer = &Serializer{}
69var _ recognizer.RecognizingDecoder = &Serializer{}
70
71func init() {
72	// Force jsoniter to decode number to interface{} via ints, if possible.
73	decodeNumberAsInt64IfPossible := func(ptr unsafe.Pointer, iter *jsoniter.Iterator) {
74		switch iter.WhatIsNext() {
75		case jsoniter.NumberValue:
76			var number json.Number
77			iter.ReadVal(&number)
78			i64, err := strconv.ParseInt(string(number), 10, 64)
79			if err == nil {
80				*(*interface{})(ptr) = i64
81				return
82			}
83			f64, err := strconv.ParseFloat(string(number), 64)
84			if err == nil {
85				*(*interface{})(ptr) = f64
86				return
87			}
88			// Not much we can do here.
89		default:
90			*(*interface{})(ptr) = iter.Read()
91		}
92	}
93	jsoniter.RegisterTypeDecoderFunc("interface {}", decodeNumberAsInt64IfPossible)
94}
95
96// gvkWithDefaults returns group kind and version defaulting from provided default
97func gvkWithDefaults(actual, defaultGVK schema.GroupVersionKind) schema.GroupVersionKind {
98	if len(actual.Kind) == 0 {
99		actual.Kind = defaultGVK.Kind
100	}
101	if len(actual.Version) == 0 && len(actual.Group) == 0 {
102		actual.Group = defaultGVK.Group
103		actual.Version = defaultGVK.Version
104	}
105	if len(actual.Version) == 0 && actual.Group == defaultGVK.Group {
106		actual.Version = defaultGVK.Version
107	}
108	return actual
109}
110
111// Decode attempts to convert the provided data into YAML or JSON, extract the stored schema kind, apply the provided default gvk, and then
112// load that data into an object matching the desired schema kind or the provided into.
113// If into is *runtime.Unknown, the raw data will be extracted and no decoding will be performed.
114// If into is not registered with the typer, then the object will be straight decoded using normal JSON/YAML unmarshalling.
115// If into is provided and the original data is not fully qualified with kind/version/group, the type of the into will be used to alter the returned gvk.
116// If into is nil or data's gvk different from into's gvk, it will generate a new Object with ObjectCreater.New(gvk)
117// On success or most errors, the method will return the calculated schema kind.
118// The gvk calculate priority will be originalData > default gvk > into
119func (s *Serializer) Decode(originalData []byte, gvk *schema.GroupVersionKind, into runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) {
120	if versioned, ok := into.(*runtime.VersionedObjects); ok {
121		into = versioned.Last()
122		obj, actual, err := s.Decode(originalData, gvk, into)
123		if err != nil {
124			return nil, actual, err
125		}
126		versioned.Objects = []runtime.Object{obj}
127		return versioned, actual, nil
128	}
129
130	data := originalData
131	if s.yaml {
132		altered, err := yaml.YAMLToJSON(data)
133		if err != nil {
134			return nil, nil, err
135		}
136		data = altered
137	}
138
139	actual, err := s.meta.Interpret(data)
140	if err != nil {
141		return nil, nil, err
142	}
143
144	if gvk != nil {
145		*actual = gvkWithDefaults(*actual, *gvk)
146	}
147
148	if unk, ok := into.(*runtime.Unknown); ok && unk != nil {
149		unk.Raw = originalData
150		unk.ContentType = runtime.ContentTypeJSON
151		unk.GetObjectKind().SetGroupVersionKind(*actual)
152		return unk, actual, nil
153	}
154
155	if into != nil {
156		_, isUnstructured := into.(runtime.Unstructured)
157		types, _, err := s.typer.ObjectKinds(into)
158		switch {
159		case runtime.IsNotRegisteredError(err), isUnstructured:
160			if err := jsoniter.ConfigCompatibleWithStandardLibrary.Unmarshal(data, into); err != nil {
161				return nil, actual, err
162			}
163			return into, actual, nil
164		case err != nil:
165			return nil, actual, err
166		default:
167			*actual = gvkWithDefaults(*actual, types[0])
168		}
169	}
170
171	if len(actual.Kind) == 0 {
172		return nil, actual, runtime.NewMissingKindErr(string(originalData))
173	}
174	if len(actual.Version) == 0 {
175		return nil, actual, runtime.NewMissingVersionErr(string(originalData))
176	}
177
178	// use the target if necessary
179	obj, err := runtime.UseOrCreateObject(s.typer, s.creater, *actual, into)
180	if err != nil {
181		return nil, actual, err
182	}
183
184	if err := jsoniter.ConfigCompatibleWithStandardLibrary.Unmarshal(data, obj); err != nil {
185		return nil, actual, err
186	}
187	return obj, actual, nil
188}
189
190// Encode serializes the provided object to the given writer.
191func (s *Serializer) Encode(obj runtime.Object, w io.Writer) error {
192	if s.yaml {
193		json, err := jsoniter.ConfigCompatibleWithStandardLibrary.Marshal(obj)
194		if err != nil {
195			return err
196		}
197		data, err := yaml.JSONToYAML(json)
198		if err != nil {
199			return err
200		}
201		_, err = w.Write(data)
202		return err
203	}
204
205	if s.pretty {
206		data, err := jsoniter.ConfigCompatibleWithStandardLibrary.MarshalIndent(obj, "", "  ")
207		if err != nil {
208			return err
209		}
210		_, err = w.Write(data)
211		return err
212	}
213	encoder := json.NewEncoder(w)
214	return encoder.Encode(obj)
215}
216
217// RecognizesData implements the RecognizingDecoder interface.
218func (s *Serializer) RecognizesData(peek io.Reader) (ok, unknown bool, err error) {
219	if s.yaml {
220		// we could potentially look for '---'
221		return false, true, nil
222	}
223	_, _, ok = utilyaml.GuessJSONStream(peek, 2048)
224	return ok, false, nil
225}
226
227// Framer is the default JSON framing behavior, with newlines delimiting individual objects.
228var Framer = jsonFramer{}
229
230type jsonFramer struct{}
231
232// NewFrameWriter implements stream framing for this serializer
233func (jsonFramer) NewFrameWriter(w io.Writer) io.Writer {
234	// we can write JSON objects directly to the writer, because they are self-framing
235	return w
236}
237
238// NewFrameReader implements stream framing for this serializer
239func (jsonFramer) NewFrameReader(r io.ReadCloser) io.ReadCloser {
240	// we need to extract the JSON chunks of data to pass to Decode()
241	return framer.NewJSONFramedReader(r)
242}
243
244// Framer is the default JSON framing behavior, with newlines delimiting individual objects.
245var YAMLFramer = yamlFramer{}
246
247type yamlFramer struct{}
248
249// NewFrameWriter implements stream framing for this serializer
250func (yamlFramer) NewFrameWriter(w io.Writer) io.Writer {
251	return yamlFrameWriter{w}
252}
253
254// NewFrameReader implements stream framing for this serializer
255func (yamlFramer) NewFrameReader(r io.ReadCloser) io.ReadCloser {
256	// extract the YAML document chunks directly
257	return utilyaml.NewDocumentDecoder(r)
258}
259
260type yamlFrameWriter struct {
261	w io.Writer
262}
263
264// Write separates each document with the YAML document separator (`---` followed by line
265// break). Writers must write well formed YAML documents (include a final line break).
266func (w yamlFrameWriter) Write(data []byte) (n int, err error) {
267	if _, err := w.w.Write([]byte("---\n")); err != nil {
268		return 0, err
269	}
270	return w.w.Write(data)
271}
272