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// CaseSensitiveJsonIterator returns a jsoniterator API that's configured to be 97// case-sensitive when unmarshalling, and otherwise compatible with 98// the encoding/json standard library. 99func CaseSensitiveJsonIterator() jsoniter.API { 100 return jsoniter.Config{ 101 EscapeHTML: true, 102 SortMapKeys: true, 103 ValidateJsonRawMessage: true, 104 CaseSensitive: true, 105 }.Froze() 106} 107 108var caseSensitiveJsonIterator = CaseSensitiveJsonIterator() 109 110// gvkWithDefaults returns group kind and version defaulting from provided default 111func gvkWithDefaults(actual, defaultGVK schema.GroupVersionKind) schema.GroupVersionKind { 112 if len(actual.Kind) == 0 { 113 actual.Kind = defaultGVK.Kind 114 } 115 if len(actual.Version) == 0 && len(actual.Group) == 0 { 116 actual.Group = defaultGVK.Group 117 actual.Version = defaultGVK.Version 118 } 119 if len(actual.Version) == 0 && actual.Group == defaultGVK.Group { 120 actual.Version = defaultGVK.Version 121 } 122 return actual 123} 124 125// Decode attempts to convert the provided data into YAML or JSON, extract the stored schema kind, apply the provided default gvk, and then 126// load that data into an object matching the desired schema kind or the provided into. 127// If into is *runtime.Unknown, the raw data will be extracted and no decoding will be performed. 128// If into is not registered with the typer, then the object will be straight decoded using normal JSON/YAML unmarshalling. 129// 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. 130// If into is nil or data's gvk different from into's gvk, it will generate a new Object with ObjectCreater.New(gvk) 131// On success or most errors, the method will return the calculated schema kind. 132// The gvk calculate priority will be originalData > default gvk > into 133func (s *Serializer) Decode(originalData []byte, gvk *schema.GroupVersionKind, into runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) { 134 if versioned, ok := into.(*runtime.VersionedObjects); ok { 135 into = versioned.Last() 136 obj, actual, err := s.Decode(originalData, gvk, into) 137 if err != nil { 138 return nil, actual, err 139 } 140 versioned.Objects = []runtime.Object{obj} 141 return versioned, actual, nil 142 } 143 144 data := originalData 145 if s.yaml { 146 altered, err := yaml.YAMLToJSON(data) 147 if err != nil { 148 return nil, nil, err 149 } 150 data = altered 151 } 152 153 actual, err := s.meta.Interpret(data) 154 if err != nil { 155 return nil, nil, err 156 } 157 158 if gvk != nil { 159 *actual = gvkWithDefaults(*actual, *gvk) 160 } 161 162 if unk, ok := into.(*runtime.Unknown); ok && unk != nil { 163 unk.Raw = originalData 164 unk.ContentType = runtime.ContentTypeJSON 165 unk.GetObjectKind().SetGroupVersionKind(*actual) 166 return unk, actual, nil 167 } 168 169 if into != nil { 170 _, isUnstructured := into.(runtime.Unstructured) 171 types, _, err := s.typer.ObjectKinds(into) 172 switch { 173 case runtime.IsNotRegisteredError(err), isUnstructured: 174 if err := caseSensitiveJsonIterator.Unmarshal(data, into); err != nil { 175 return nil, actual, err 176 } 177 return into, actual, nil 178 case err != nil: 179 return nil, actual, err 180 default: 181 *actual = gvkWithDefaults(*actual, types[0]) 182 } 183 } 184 185 if len(actual.Kind) == 0 { 186 return nil, actual, runtime.NewMissingKindErr(string(originalData)) 187 } 188 if len(actual.Version) == 0 { 189 return nil, actual, runtime.NewMissingVersionErr(string(originalData)) 190 } 191 192 // use the target if necessary 193 obj, err := runtime.UseOrCreateObject(s.typer, s.creater, *actual, into) 194 if err != nil { 195 return nil, actual, err 196 } 197 198 if err := caseSensitiveJsonIterator.Unmarshal(data, obj); err != nil { 199 return nil, actual, err 200 } 201 return obj, actual, nil 202} 203 204// Encode serializes the provided object to the given writer. 205func (s *Serializer) Encode(obj runtime.Object, w io.Writer) error { 206 if s.yaml { 207 json, err := caseSensitiveJsonIterator.Marshal(obj) 208 if err != nil { 209 return err 210 } 211 data, err := yaml.JSONToYAML(json) 212 if err != nil { 213 return err 214 } 215 _, err = w.Write(data) 216 return err 217 } 218 219 if s.pretty { 220 data, err := caseSensitiveJsonIterator.MarshalIndent(obj, "", " ") 221 if err != nil { 222 return err 223 } 224 _, err = w.Write(data) 225 return err 226 } 227 encoder := json.NewEncoder(w) 228 return encoder.Encode(obj) 229} 230 231// RecognizesData implements the RecognizingDecoder interface. 232func (s *Serializer) RecognizesData(peek io.Reader) (ok, unknown bool, err error) { 233 if s.yaml { 234 // we could potentially look for '---' 235 return false, true, nil 236 } 237 _, _, ok = utilyaml.GuessJSONStream(peek, 2048) 238 return ok, false, nil 239} 240 241// Framer is the default JSON framing behavior, with newlines delimiting individual objects. 242var Framer = jsonFramer{} 243 244type jsonFramer struct{} 245 246// NewFrameWriter implements stream framing for this serializer 247func (jsonFramer) NewFrameWriter(w io.Writer) io.Writer { 248 // we can write JSON objects directly to the writer, because they are self-framing 249 return w 250} 251 252// NewFrameReader implements stream framing for this serializer 253func (jsonFramer) NewFrameReader(r io.ReadCloser) io.ReadCloser { 254 // we need to extract the JSON chunks of data to pass to Decode() 255 return framer.NewJSONFramedReader(r) 256} 257 258// Framer is the default JSON framing behavior, with newlines delimiting individual objects. 259var YAMLFramer = yamlFramer{} 260 261type yamlFramer struct{} 262 263// NewFrameWriter implements stream framing for this serializer 264func (yamlFramer) NewFrameWriter(w io.Writer) io.Writer { 265 return yamlFrameWriter{w} 266} 267 268// NewFrameReader implements stream framing for this serializer 269func (yamlFramer) NewFrameReader(r io.ReadCloser) io.ReadCloser { 270 // extract the YAML document chunks directly 271 return utilyaml.NewDocumentDecoder(r) 272} 273 274type yamlFrameWriter struct { 275 w io.Writer 276} 277 278// Write separates each document with the YAML document separator (`---` followed by line 279// break). Writers must write well formed YAML documents (include a final line break). 280func (w yamlFrameWriter) Write(data []byte) (n int, err error) { 281 if _, err := w.w.Write([]byte("---\n")); err != nil { 282 return 0, err 283 } 284 return w.w.Write(data) 285} 286