mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-25 20:53:33 +00:00
Merge pull request #74111 from stewart-yu/stewart-config-serializer
Implement a dedicated serializer package for ComponentConfigs
This commit is contained in:
commit
01f04be28a
@ -120,3 +120,32 @@ func IsMissingVersion(err error) bool {
|
|||||||
_, ok := err.(*missingVersionErr)
|
_, ok := err.(*missingVersionErr)
|
||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// strictDecodingError is a base error type that is returned by a strict Decoder such
|
||||||
|
// as UniversalStrictDecoder.
|
||||||
|
type strictDecodingError struct {
|
||||||
|
message string
|
||||||
|
data string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewStrictDecodingError creates a new strictDecodingError object.
|
||||||
|
func NewStrictDecodingError(message string, data string) error {
|
||||||
|
return &strictDecodingError{
|
||||||
|
message: message,
|
||||||
|
data: data,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *strictDecodingError) Error() string {
|
||||||
|
return fmt.Sprintf("strict decoder error for %s: %s", e.data, e.message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsStrictDecodingError returns true if the error indicates that the provided object
|
||||||
|
// strictness violations.
|
||||||
|
func IsStrictDecodingError(err error) bool {
|
||||||
|
if err == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
_, ok := err.(*strictDecodingError)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
@ -35,34 +35,50 @@ import (
|
|||||||
|
|
||||||
// NewSerializer creates a JSON serializer that handles encoding versioned objects into the proper JSON form. If typer
|
// 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.
|
// 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 {
|
func NewSerializer(meta MetaFactory, creater runtime.ObjectCreater, typer runtime.ObjectTyper, pretty bool) *Serializer {
|
||||||
return &Serializer{
|
return NewSerializerWithOptions(meta, creater, typer, &SerializerOptions{false, pretty, false})
|
||||||
meta: meta,
|
|
||||||
creater: creater,
|
|
||||||
typer: typer,
|
|
||||||
yaml: false,
|
|
||||||
pretty: pretty,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewYAMLSerializer creates a YAML serializer that handles encoding versioned objects into the proper YAML form. If typer
|
// 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
|
// 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.
|
// 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 {
|
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.
|
||||||
|
func NewSerializerWithOptions(meta MetaFactory, creater runtime.ObjectCreater, typer runtime.ObjectTyper, serializerOptions *SerializerOptions) *Serializer {
|
||||||
return &Serializer{
|
return &Serializer{
|
||||||
meta: meta,
|
meta: meta,
|
||||||
creater: creater,
|
creater: creater,
|
||||||
typer: typer,
|
typer: typer,
|
||||||
yaml: true,
|
SerializerOptions: serializerOptions,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SerializerOptions holds the options which are used to creating a JSON/YAML serializer.
|
||||||
|
// For example:
|
||||||
|
// (1) we can creates a JSON serializer once we set `Yaml` to `false`.
|
||||||
|
// (2) we can creates a YAML serializer once we set `Yaml` to `true`. This serializer supports only the subset of YAML that
|
||||||
|
// matches JSON, and will error if constructs are used that do not serialize to JSON.
|
||||||
|
// Please note that `Pretty` is silently ignored when `Yaml` is `true`.
|
||||||
|
// (3) we can creates a strict JSON/YAML serializer that can also return errors of type strictDecodingError, once we set
|
||||||
|
// `Strict` to `true`. And note that this serializer is not as performant as the non-strict variant, and should not be
|
||||||
|
// used in fast paths.
|
||||||
|
type SerializerOptions struct {
|
||||||
|
Yaml bool
|
||||||
|
Pretty bool
|
||||||
|
Strict bool
|
||||||
|
}
|
||||||
|
|
||||||
type Serializer struct {
|
type Serializer struct {
|
||||||
meta MetaFactory
|
meta MetaFactory
|
||||||
creater runtime.ObjectCreater
|
creater runtime.ObjectCreater
|
||||||
typer runtime.ObjectTyper
|
typer runtime.ObjectTyper
|
||||||
yaml bool
|
*SerializerOptions
|
||||||
pretty bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Serializer implements Serializer
|
// Serializer implements Serializer
|
||||||
@ -119,11 +135,28 @@ func CaseSensitiveJsonIterator() jsoniter.API {
|
|||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
|
||||||
// Private copy of jsoniter to try to shield against possible mutations
|
// StrictCaseSensitiveJsonIterator returns a jsoniterator API that's configured to be
|
||||||
|
// case-sensitive, but also disallows unknown fields when unmarshalling. It is compatible with
|
||||||
|
// the encoding/json standard library.
|
||||||
|
func StrictCaseSensitiveJsonIterator() jsoniter.API {
|
||||||
|
config := jsoniter.Config{
|
||||||
|
EscapeHTML: true,
|
||||||
|
SortMapKeys: true,
|
||||||
|
ValidateJsonRawMessage: true,
|
||||||
|
CaseSensitive: true,
|
||||||
|
DisallowUnknownFields: true,
|
||||||
|
}.Froze()
|
||||||
|
// Force jsoniter to decode number to interface{} via int64/float64, if possible.
|
||||||
|
config.RegisterExtension(&customNumberExtension{})
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
// Private copies of jsoniter to try to shield against possible mutations
|
||||||
// from outside. Still does not protect from package level jsoniter.Register*() functions - someone calling them
|
// from outside. Still does not protect from package level jsoniter.Register*() functions - someone calling them
|
||||||
// in some other library will mess with every usage of the jsoniter library in the whole program.
|
// in some other library will mess with every usage of the jsoniter library in the whole program.
|
||||||
// See https://github.com/json-iterator/go/issues/265
|
// See https://github.com/json-iterator/go/issues/265
|
||||||
var caseSensitiveJsonIterator = CaseSensitiveJsonIterator()
|
var caseSensitiveJsonIterator = CaseSensitiveJsonIterator()
|
||||||
|
var strictCaseSensitiveJsonIterator = StrictCaseSensitiveJsonIterator()
|
||||||
|
|
||||||
// gvkWithDefaults returns group kind and version defaulting from provided default
|
// gvkWithDefaults returns group kind and version defaulting from provided default
|
||||||
func gvkWithDefaults(actual, defaultGVK schema.GroupVersionKind) schema.GroupVersionKind {
|
func gvkWithDefaults(actual, defaultGVK schema.GroupVersionKind) schema.GroupVersionKind {
|
||||||
@ -160,7 +193,7 @@ func (s *Serializer) Decode(originalData []byte, gvk *schema.GroupVersionKind, i
|
|||||||
}
|
}
|
||||||
|
|
||||||
data := originalData
|
data := originalData
|
||||||
if s.yaml {
|
if s.Yaml {
|
||||||
altered, err := yaml.YAMLToJSON(data)
|
altered, err := yaml.YAMLToJSON(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
@ -216,12 +249,38 @@ func (s *Serializer) Decode(originalData []byte, gvk *schema.GroupVersionKind, i
|
|||||||
if err := caseSensitiveJsonIterator.Unmarshal(data, obj); err != nil {
|
if err := caseSensitiveJsonIterator.Unmarshal(data, obj); err != nil {
|
||||||
return nil, actual, err
|
return nil, actual, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the deserializer is non-strict, return successfully here.
|
||||||
|
if !s.Strict {
|
||||||
|
return obj, actual, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// In strict mode pass the data trough the YAMLToJSONStrict converter.
|
||||||
|
// This is done to catch duplicate fields regardless of encoding (JSON or YAML). For JSON data,
|
||||||
|
// the output would equal the input, unless there is a parsing error such as duplicate fields.
|
||||||
|
// As we know this was successful in the non-strict case, the only error that may be returned here
|
||||||
|
// is because of the newly-added strictness. hence we know we can return the typed strictDecoderError
|
||||||
|
// the actual error is that the object contains duplicate fields.
|
||||||
|
altered, err := yaml.YAMLToJSONStrict(originalData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, actual, runtime.NewStrictDecodingError(err.Error(), string(originalData))
|
||||||
|
}
|
||||||
|
// As performance is not an issue for now for the strict deserializer (one has regardless to do
|
||||||
|
// the unmarshal twice), we take the sanitized, altered data that is guaranteed to have no duplicated
|
||||||
|
// fields, and unmarshal this into a copy of the already-populated obj. Any error that occurs here is
|
||||||
|
// due to that a matching field doesn't exist in the object. hence we can return a typed strictDecoderError,
|
||||||
|
// the actual error is that the object contains unknown field.
|
||||||
|
strictObj := obj.DeepCopyObject()
|
||||||
|
if err := strictCaseSensitiveJsonIterator.Unmarshal(altered, strictObj); err != nil {
|
||||||
|
return nil, actual, runtime.NewStrictDecodingError(err.Error(), string(originalData))
|
||||||
|
}
|
||||||
|
// Always return the same object as the non-strict serializer to avoid any deviations.
|
||||||
return obj, actual, nil
|
return obj, actual, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encode serializes the provided object to the given writer.
|
// Encode serializes the provided object to the given writer.
|
||||||
func (s *Serializer) Encode(obj runtime.Object, w io.Writer) error {
|
func (s *Serializer) Encode(obj runtime.Object, w io.Writer) error {
|
||||||
if s.yaml {
|
if s.Yaml {
|
||||||
json, err := caseSensitiveJsonIterator.Marshal(obj)
|
json, err := caseSensitiveJsonIterator.Marshal(obj)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -234,7 +293,7 @@ func (s *Serializer) Encode(obj runtime.Object, w io.Writer) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.pretty {
|
if s.Pretty {
|
||||||
data, err := caseSensitiveJsonIterator.MarshalIndent(obj, "", " ")
|
data, err := caseSensitiveJsonIterator.MarshalIndent(obj, "", " ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -248,7 +307,7 @@ func (s *Serializer) Encode(obj runtime.Object, w io.Writer) error {
|
|||||||
|
|
||||||
// RecognizesData implements the RecognizingDecoder interface.
|
// RecognizesData implements the RecognizingDecoder interface.
|
||||||
func (s *Serializer) RecognizesData(peek io.Reader) (ok, unknown bool, err error) {
|
func (s *Serializer) RecognizesData(peek io.Reader) (ok, unknown bool, err error) {
|
||||||
if s.yaml {
|
if s.Yaml {
|
||||||
// we could potentially look for '---'
|
// we could potentially look for '---'
|
||||||
return false, true, nil
|
return false, true, nil
|
||||||
}
|
}
|
||||||
|
@ -59,8 +59,22 @@ type DecodableSpec struct {
|
|||||||
func (d *testDecodable) GetObjectKind() schema.ObjectKind { return d }
|
func (d *testDecodable) GetObjectKind() schema.ObjectKind { return d }
|
||||||
func (d *testDecodable) SetGroupVersionKind(gvk schema.GroupVersionKind) { d.gvk = gvk }
|
func (d *testDecodable) SetGroupVersionKind(gvk schema.GroupVersionKind) { d.gvk = gvk }
|
||||||
func (d *testDecodable) GroupVersionKind() schema.GroupVersionKind { return d.gvk }
|
func (d *testDecodable) GroupVersionKind() schema.GroupVersionKind { return d.gvk }
|
||||||
func (d *testDecodable) DeepCopyObject() runtime.Object {
|
func (in *testDecodable) DeepCopyObject() runtime.Object {
|
||||||
panic("testDecodable does not support DeepCopy")
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(testDecodable)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
func (in *testDecodable) DeepCopyInto(out *testDecodable) {
|
||||||
|
*out = *in
|
||||||
|
out.Other = in.Other
|
||||||
|
out.Value = in.Value
|
||||||
|
out.Spec = in.Spec
|
||||||
|
out.Interface = in.Interface
|
||||||
|
out.gvk = in.gvk
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDecode(t *testing.T) {
|
func TestDecode(t *testing.T) {
|
||||||
@ -69,6 +83,7 @@ func TestDecode(t *testing.T) {
|
|||||||
typer runtime.ObjectTyper
|
typer runtime.ObjectTyper
|
||||||
yaml bool
|
yaml bool
|
||||||
pretty bool
|
pretty bool
|
||||||
|
strict bool
|
||||||
|
|
||||||
data []byte
|
data []byte
|
||||||
defaultGVK *schema.GroupVersionKind
|
defaultGVK *schema.GroupVersionKind
|
||||||
@ -274,14 +289,138 @@ func TestDecode(t *testing.T) {
|
|||||||
Spec: DecodableSpec{A: 1, H: 3},
|
Spec: DecodableSpec{A: 1, H: 3},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
// Unknown fields should return an error from the strict JSON deserializer.
|
||||||
|
{
|
||||||
|
data: []byte(`{"unknown": 1}`),
|
||||||
|
into: &testDecodable{},
|
||||||
|
typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}},
|
||||||
|
expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"},
|
||||||
|
errFn: func(err error) bool {
|
||||||
|
return strings.Contains(err.Error(), "found unknown field")
|
||||||
|
},
|
||||||
|
strict: true,
|
||||||
|
},
|
||||||
|
// Unknown fields should return an error from the strict YAML deserializer.
|
||||||
|
{
|
||||||
|
data: []byte("unknown: 1\n"),
|
||||||
|
into: &testDecodable{},
|
||||||
|
typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}},
|
||||||
|
expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"},
|
||||||
|
errFn: func(err error) bool {
|
||||||
|
return strings.Contains(err.Error(), "found unknown field")
|
||||||
|
},
|
||||||
|
yaml: true,
|
||||||
|
strict: true,
|
||||||
|
},
|
||||||
|
// Duplicate fields should return an error from the strict JSON deserializer.
|
||||||
|
{
|
||||||
|
data: []byte(`{"value":1,"value":1}`),
|
||||||
|
into: &testDecodable{},
|
||||||
|
typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}},
|
||||||
|
expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"},
|
||||||
|
errFn: func(err error) bool {
|
||||||
|
return strings.Contains(err.Error(), "already set in map")
|
||||||
|
},
|
||||||
|
strict: true,
|
||||||
|
},
|
||||||
|
// Duplicate fields should return an error from the strict YAML deserializer.
|
||||||
|
{
|
||||||
|
data: []byte("value: 1\n" +
|
||||||
|
"value: 1\n"),
|
||||||
|
into: &testDecodable{},
|
||||||
|
typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}},
|
||||||
|
expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"},
|
||||||
|
errFn: func(err error) bool {
|
||||||
|
return strings.Contains(err.Error(), "already set in map")
|
||||||
|
},
|
||||||
|
yaml: true,
|
||||||
|
strict: true,
|
||||||
|
},
|
||||||
|
// Strict JSON decode should fail for untagged fields.
|
||||||
|
{
|
||||||
|
data: []byte(`{"kind":"Test","apiVersion":"other/blah","value":1,"Other":"test"}`),
|
||||||
|
into: &testDecodable{},
|
||||||
|
typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}},
|
||||||
|
expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"},
|
||||||
|
errFn: func(err error) bool {
|
||||||
|
return strings.Contains(err.Error(), "found unknown field")
|
||||||
|
},
|
||||||
|
strict: true,
|
||||||
|
},
|
||||||
|
// Strict YAML decode should fail for untagged fields.
|
||||||
|
{
|
||||||
|
data: []byte("kind: Test\n" +
|
||||||
|
"apiVersion: other/blah\n" +
|
||||||
|
"value: 1\n" +
|
||||||
|
"Other: test\n"),
|
||||||
|
into: &testDecodable{},
|
||||||
|
typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}},
|
||||||
|
expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"},
|
||||||
|
errFn: func(err error) bool {
|
||||||
|
return strings.Contains(err.Error(), "found unknown field")
|
||||||
|
},
|
||||||
|
yaml: true,
|
||||||
|
strict: true,
|
||||||
|
},
|
||||||
|
// Strict JSON decode into unregistered objects directly.
|
||||||
|
{
|
||||||
|
data: []byte(`{"kind":"Test","apiVersion":"other/blah","value":1,"Other":"test"}`),
|
||||||
|
into: &testDecodable{},
|
||||||
|
typer: &mockTyper{err: runtime.NewNotRegisteredErrForKind("mock", schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"})},
|
||||||
|
expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"},
|
||||||
|
expectedObject: &testDecodable{
|
||||||
|
Other: "test",
|
||||||
|
Value: 1,
|
||||||
|
},
|
||||||
|
strict: true,
|
||||||
|
},
|
||||||
|
// Strict YAML decode into unregistered objects directly.
|
||||||
|
{
|
||||||
|
data: []byte("kind: Test\n" +
|
||||||
|
"apiVersion: other/blah\n" +
|
||||||
|
"value: 1\n" +
|
||||||
|
"Other: test\n"),
|
||||||
|
into: &testDecodable{},
|
||||||
|
typer: &mockTyper{err: runtime.NewNotRegisteredErrForKind("mock", schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"})},
|
||||||
|
expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"},
|
||||||
|
expectedObject: &testDecodable{
|
||||||
|
Other: "test",
|
||||||
|
Value: 1,
|
||||||
|
},
|
||||||
|
yaml: true,
|
||||||
|
strict: true,
|
||||||
|
},
|
||||||
|
// Valid strict JSON decode without GVK.
|
||||||
|
{
|
||||||
|
data: []byte(`{"value":1234}`),
|
||||||
|
into: &testDecodable{},
|
||||||
|
typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}},
|
||||||
|
expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"},
|
||||||
|
expectedObject: &testDecodable{
|
||||||
|
Value: 1234,
|
||||||
|
},
|
||||||
|
strict: true,
|
||||||
|
},
|
||||||
|
// Valid strict YAML decode without GVK.
|
||||||
|
{
|
||||||
|
data: []byte("value: 1234\n"),
|
||||||
|
into: &testDecodable{},
|
||||||
|
typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}},
|
||||||
|
expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"},
|
||||||
|
expectedObject: &testDecodable{
|
||||||
|
Value: 1234,
|
||||||
|
},
|
||||||
|
yaml: true,
|
||||||
|
strict: true,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, test := range testCases {
|
for i, test := range testCases {
|
||||||
var s runtime.Serializer
|
var s runtime.Serializer
|
||||||
if test.yaml {
|
if test.yaml {
|
||||||
s = json.NewYAMLSerializer(json.DefaultMetaFactory, test.creater, test.typer)
|
s = json.NewSerializerWithOptions(json.DefaultMetaFactory, test.creater, test.typer, &json.SerializerOptions{Yaml: test.yaml, Pretty: false, Strict: test.strict})
|
||||||
} else {
|
} else {
|
||||||
s = json.NewSerializer(json.DefaultMetaFactory, test.creater, test.typer, test.pretty)
|
s = json.NewSerializerWithOptions(json.DefaultMetaFactory, test.creater, test.typer, &json.SerializerOptions{Yaml: test.yaml, Pretty: test.pretty, Strict: test.strict})
|
||||||
}
|
}
|
||||||
obj, gvk, err := s.Decode([]byte(test.data), test.defaultGVK, test.into)
|
obj, gvk, err := s.Decode([]byte(test.data), test.defaultGVK, test.into)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user