2022-07-07 20:11:50 +00:00
/ *
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 json
import (
"encoding/json"
"io"
"strconv"
2024-02-24 00:34:55 +00:00
kjson "sigs.k8s.io/json"
2022-07-07 20:11:50 +00:00
"sigs.k8s.io/yaml"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime/serializer/recognizer"
"k8s.io/apimachinery/pkg/util/framer"
utilyaml "k8s.io/apimachinery/pkg/util/yaml"
"k8s.io/klog/v2"
)
// NewSerializer creates a JSON serializer that handles encoding versioned objects into the proper JSON form. If typer
// is not nil, the object has the group, version, and kind fields set.
// Deprecated: use NewSerializerWithOptions instead.
func NewSerializer ( meta MetaFactory , creater runtime . ObjectCreater , typer runtime . ObjectTyper , pretty bool ) * Serializer {
return NewSerializerWithOptions ( meta , creater , typer , SerializerOptions { false , pretty , false } )
}
// NewYAMLSerializer creates a YAML serializer that handles encoding versioned objects into the proper YAML form. If typer
// is not nil, the object has the group, version, and kind fields set. This serializer supports only the subset of YAML that
// matches JSON, and will error if constructs are used that do not serialize to JSON.
// Deprecated: use NewSerializerWithOptions instead.
func NewYAMLSerializer ( meta MetaFactory , creater runtime . ObjectCreater , typer runtime . ObjectTyper ) * Serializer {
return NewSerializerWithOptions ( meta , creater , typer , SerializerOptions { true , false , false } )
}
// NewSerializerWithOptions creates a JSON/YAML serializer that handles encoding versioned objects into the proper JSON/YAML
// form. If typer is not nil, the object has the group, version, and kind fields set. Options are copied into the Serializer
// and are immutable.
func NewSerializerWithOptions ( meta MetaFactory , creater runtime . ObjectCreater , typer runtime . ObjectTyper , options SerializerOptions ) * Serializer {
return & Serializer {
meta : meta ,
creater : creater ,
typer : typer ,
options : options ,
identifier : identifier ( options ) ,
}
}
// identifier computes Identifier of Encoder based on the given options.
func identifier ( options SerializerOptions ) runtime . Identifier {
result := map [ string ] string {
"name" : "json" ,
"yaml" : strconv . FormatBool ( options . Yaml ) ,
"pretty" : strconv . FormatBool ( options . Pretty ) ,
2024-02-24 00:34:55 +00:00
"strict" : strconv . FormatBool ( options . Strict ) ,
2022-07-07 20:11:50 +00:00
}
identifier , err := json . Marshal ( result )
if err != nil {
klog . Fatalf ( "Failed marshaling identifier for json Serializer: %v" , err )
}
return runtime . Identifier ( identifier )
}
// SerializerOptions holds the options which are used to configure a JSON/YAML serializer.
// example:
// (1) To configure a JSON serializer, set `Yaml` to `false`.
// (2) To configure a YAML serializer, set `Yaml` to `true`.
// (3) To configure a strict serializer that can return strictDecodingError, set `Strict` to `true`.
type SerializerOptions struct {
// Yaml: configures the Serializer to work with JSON(false) or YAML(true).
// When `Yaml` is enabled, this serializer only supports the subset of YAML that
// matches JSON, and will error if constructs are used that do not serialize to JSON.
Yaml bool
// Pretty: configures a JSON enabled Serializer(`Yaml: false`) to produce human-readable output.
// This option is silently ignored when `Yaml` is `true`.
Pretty bool
// Strict: configures the Serializer to return strictDecodingError's when duplicate fields are present decoding JSON or YAML.
// Note that enabling this option is not as performant as the non-strict variant, and should not be used in fast paths.
Strict bool
}
// Serializer handles encoding versioned objects into the proper JSON form
type Serializer struct {
meta MetaFactory
options SerializerOptions
creater runtime . ObjectCreater
typer runtime . ObjectTyper
identifier runtime . Identifier
}
// Serializer implements Serializer
var _ runtime . Serializer = & Serializer { }
var _ recognizer . RecognizingDecoder = & Serializer { }
// gvkWithDefaults returns group kind and version defaulting from provided default
func gvkWithDefaults ( actual , defaultGVK schema . GroupVersionKind ) schema . GroupVersionKind {
if len ( actual . Kind ) == 0 {
actual . Kind = defaultGVK . Kind
}
if len ( actual . Version ) == 0 && len ( actual . Group ) == 0 {
actual . Group = defaultGVK . Group
actual . Version = defaultGVK . Version
}
if len ( actual . Version ) == 0 && actual . Group == defaultGVK . Group {
actual . Version = defaultGVK . Version
}
return actual
}
// Decode attempts to convert the provided data into YAML or JSON, extract the stored schema kind, apply the provided default gvk, and then
// load that data into an object matching the desired schema kind or the provided into.
// If into is *runtime.Unknown, the raw data will be extracted and no decoding will be performed.
// If into is not registered with the typer, then the object will be straight decoded using normal JSON/YAML unmarshalling.
// 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.
// If into is nil or data's gvk different from into's gvk, it will generate a new Object with ObjectCreater.New(gvk)
// On success or most errors, the method will return the calculated schema kind.
// The gvk calculate priority will be originalData > default gvk > into
func ( s * Serializer ) Decode ( originalData [ ] byte , gvk * schema . GroupVersionKind , into runtime . Object ) ( runtime . Object , * schema . GroupVersionKind , error ) {
data := originalData
if s . options . Yaml {
altered , err := yaml . YAMLToJSON ( data )
if err != nil {
return nil , nil , err
}
data = altered
}
actual , err := s . meta . Interpret ( data )
if err != nil {
return nil , nil , err
}
if gvk != nil {
* actual = gvkWithDefaults ( * actual , * gvk )
}
if unk , ok := into . ( * runtime . Unknown ) ; ok && unk != nil {
unk . Raw = originalData
unk . ContentType = runtime . ContentTypeJSON
unk . GetObjectKind ( ) . SetGroupVersionKind ( * actual )
return unk , actual , nil
}
if into != nil {
_ , isUnstructured := into . ( runtime . Unstructured )
types , _ , err := s . typer . ObjectKinds ( into )
switch {
case runtime . IsNotRegisteredError ( err ) , isUnstructured :
2024-02-24 00:34:55 +00:00
strictErrs , err := s . unmarshal ( into , data , originalData )
if err != nil {
2022-07-07 20:11:50 +00:00
return nil , actual , err
}
2024-02-24 00:34:55 +00:00
// when decoding directly into a provided unstructured object,
// extract the actual gvk decoded from the provided data,
// and ensure it is non-empty.
if isUnstructured {
* actual = into . GetObjectKind ( ) . GroupVersionKind ( )
if len ( actual . Kind ) == 0 {
return nil , actual , runtime . NewMissingKindErr ( string ( originalData ) )
}
// TODO(109023): require apiVersion here as well once unstructuredJSONScheme#Decode does
}
if len ( strictErrs ) > 0 {
return into , actual , runtime . NewStrictDecodingError ( strictErrs )
}
2022-07-07 20:11:50 +00:00
return into , actual , nil
case err != nil :
return nil , actual , err
default :
* actual = gvkWithDefaults ( * actual , types [ 0 ] )
}
}
if len ( actual . Kind ) == 0 {
return nil , actual , runtime . NewMissingKindErr ( string ( originalData ) )
}
if len ( actual . Version ) == 0 {
return nil , actual , runtime . NewMissingVersionErr ( string ( originalData ) )
}
// use the target if necessary
obj , err := runtime . UseOrCreateObject ( s . typer , s . creater , * actual , into )
if err != nil {
return nil , actual , err
}
2024-02-24 00:34:55 +00:00
strictErrs , err := s . unmarshal ( obj , data , originalData )
2022-07-07 20:11:50 +00:00
if err != nil {
2024-02-24 00:34:55 +00:00
return nil , actual , err
} else if len ( strictErrs ) > 0 {
return obj , actual , runtime . NewStrictDecodingError ( strictErrs )
2022-07-07 20:11:50 +00:00
}
return obj , actual , nil
}
// Encode serializes the provided object to the given writer.
func ( s * Serializer ) Encode ( obj runtime . Object , w io . Writer ) error {
if co , ok := obj . ( runtime . CacheableObject ) ; ok {
return co . CacheEncode ( s . Identifier ( ) , s . doEncode , w )
}
return s . doEncode ( obj , w )
}
func ( s * Serializer ) doEncode ( obj runtime . Object , w io . Writer ) error {
if s . options . Yaml {
2024-02-24 00:34:55 +00:00
json , err := json . Marshal ( obj )
2022-07-07 20:11:50 +00:00
if err != nil {
return err
}
data , err := yaml . JSONToYAML ( json )
if err != nil {
return err
}
_ , err = w . Write ( data )
return err
}
if s . options . Pretty {
2024-02-24 00:34:55 +00:00
data , err := json . MarshalIndent ( obj , "" , " " )
2022-07-07 20:11:50 +00:00
if err != nil {
return err
}
_ , err = w . Write ( data )
return err
}
encoder := json . NewEncoder ( w )
return encoder . Encode ( obj )
}
2024-02-24 00:34:55 +00:00
// IsStrict indicates whether the serializer
// uses strict decoding or not
func ( s * Serializer ) IsStrict ( ) bool {
return s . options . Strict
}
func ( s * Serializer ) unmarshal ( into runtime . Object , data , originalData [ ] byte ) ( strictErrs [ ] error , err error ) {
// If the deserializer is non-strict, return here.
if ! s . options . Strict {
if err := kjson . UnmarshalCaseSensitivePreserveInts ( data , into ) ; err != nil {
return nil , err
}
return nil , nil
}
if s . options . Yaml {
// In strict mode pass the original data through the YAMLToJSONStrict converter.
// This is done to catch duplicate fields in YAML that would have been dropped in the original YAMLToJSON conversion.
// TODO: rework YAMLToJSONStrict to return warnings about duplicate fields without terminating so we don't have to do this twice.
_ , err := yaml . YAMLToJSONStrict ( originalData )
if err != nil {
strictErrs = append ( strictErrs , err )
}
}
var strictJSONErrs [ ] error
if u , isUnstructured := into . ( runtime . Unstructured ) ; isUnstructured {
// Unstructured is a custom unmarshaler that gets delegated
// to, so in order to detect strict JSON errors we need
// to unmarshal directly into the object.
m := map [ string ] interface { } { }
strictJSONErrs , err = kjson . UnmarshalStrict ( data , & m )
u . SetUnstructuredContent ( m )
} else {
strictJSONErrs , err = kjson . UnmarshalStrict ( data , into )
}
if err != nil {
// fatal decoding error, not due to strictness
return nil , err
}
strictErrs = append ( strictErrs , strictJSONErrs ... )
return strictErrs , nil
}
2022-07-07 20:11:50 +00:00
// Identifier implements runtime.Encoder interface.
func ( s * Serializer ) Identifier ( ) runtime . Identifier {
return s . identifier
}
// RecognizesData implements the RecognizingDecoder interface.
func ( s * Serializer ) RecognizesData ( data [ ] byte ) ( ok , unknown bool , err error ) {
if s . options . Yaml {
// we could potentially look for '---'
return false , true , nil
}
return utilyaml . IsJSONBuffer ( data ) , false , nil
}
// Framer is the default JSON framing behavior, with newlines delimiting individual objects.
var Framer = jsonFramer { }
type jsonFramer struct { }
// NewFrameWriter implements stream framing for this serializer
func ( jsonFramer ) NewFrameWriter ( w io . Writer ) io . Writer {
// we can write JSON objects directly to the writer, because they are self-framing
return w
}
// NewFrameReader implements stream framing for this serializer
func ( jsonFramer ) NewFrameReader ( r io . ReadCloser ) io . ReadCloser {
// we need to extract the JSON chunks of data to pass to Decode()
return framer . NewJSONFramedReader ( r )
}
// YAMLFramer is the default JSON framing behavior, with newlines delimiting individual objects.
var YAMLFramer = yamlFramer { }
type yamlFramer struct { }
// NewFrameWriter implements stream framing for this serializer
func ( yamlFramer ) NewFrameWriter ( w io . Writer ) io . Writer {
return yamlFrameWriter { w }
}
// NewFrameReader implements stream framing for this serializer
func ( yamlFramer ) NewFrameReader ( r io . ReadCloser ) io . ReadCloser {
// extract the YAML document chunks directly
return utilyaml . NewDocumentDecoder ( r )
}
type yamlFrameWriter struct {
w io . Writer
}
// Write separates each document with the YAML document separator (`---` followed by line
// break). Writers must write well formed YAML documents (include a final line break).
func ( w yamlFrameWriter ) Write ( data [ ] byte ) ( n int , err error ) {
if _ , err := w . w . Write ( [ ] byte ( "---\n" ) ) ; err != nil {
return 0 , err
}
return w . w . Write ( data )
}