mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-01 15:58:37 +00:00
Add CBOR Serializer implementation.
This commit is contained in:
parent
d76d7a1e7a
commit
066421f108
@ -17,5 +17,228 @@ limitations under the License.
|
||||
package cbor
|
||||
|
||||
import (
|
||||
_ "github.com/fxamacker/cbor/v2"
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer/cbor/internal/modes"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer/recognizer"
|
||||
util "k8s.io/apimachinery/pkg/util/runtime"
|
||||
|
||||
"github.com/fxamacker/cbor/v2"
|
||||
)
|
||||
|
||||
type metaFactory interface {
|
||||
// Interpret should return the version and kind of the wire-format of the object.
|
||||
Interpret(data []byte) (*schema.GroupVersionKind, error)
|
||||
}
|
||||
|
||||
type defaultMetaFactory struct{}
|
||||
|
||||
func (mf *defaultMetaFactory) Interpret(data []byte) (*schema.GroupVersionKind, error) {
|
||||
var tm metav1.TypeMeta
|
||||
// The input is expected to include additional map keys besides apiVersion and kind, so use
|
||||
// lax mode for decoding into TypeMeta.
|
||||
if err := modes.DecodeLax.Unmarshal(data, &tm); err != nil {
|
||||
return nil, fmt.Errorf("unable to determine group/version/kind: %w", err)
|
||||
}
|
||||
actual := tm.GetObjectKind().GroupVersionKind()
|
||||
return &actual, nil
|
||||
}
|
||||
|
||||
type Serializer interface {
|
||||
runtime.Serializer
|
||||
recognizer.RecognizingDecoder
|
||||
}
|
||||
|
||||
var _ Serializer = &serializer{}
|
||||
|
||||
type options struct {
|
||||
strict bool
|
||||
}
|
||||
|
||||
type Option func(*options)
|
||||
|
||||
func Strict(s bool) Option {
|
||||
return func(opts *options) {
|
||||
opts.strict = s
|
||||
}
|
||||
}
|
||||
|
||||
type serializer struct {
|
||||
metaFactory metaFactory
|
||||
creater runtime.ObjectCreater
|
||||
typer runtime.ObjectTyper
|
||||
options options
|
||||
}
|
||||
|
||||
func NewSerializer(creater runtime.ObjectCreater, typer runtime.ObjectTyper, options ...Option) Serializer {
|
||||
return newSerializer(&defaultMetaFactory{}, creater, typer, options...)
|
||||
}
|
||||
|
||||
func newSerializer(metaFactory metaFactory, creater runtime.ObjectCreater, typer runtime.ObjectTyper, options ...Option) *serializer {
|
||||
s := &serializer{
|
||||
metaFactory: metaFactory,
|
||||
creater: creater,
|
||||
typer: typer,
|
||||
}
|
||||
for _, o := range options {
|
||||
o(&s.options)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *serializer) Identifier() runtime.Identifier {
|
||||
return "cbor"
|
||||
}
|
||||
|
||||
func (s *serializer) Encode(obj runtime.Object, w io.Writer) error {
|
||||
if _, err := w.Write(selfDescribedCBOR); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
e := modes.Encode.NewEncoder(w)
|
||||
if u, ok := obj.(runtime.Unstructured); ok {
|
||||
return e.Encode(u.UnstructuredContent())
|
||||
}
|
||||
return e.Encode(obj)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// diagnose returns the diagnostic encoding of a well-formed CBOR data item.
|
||||
func diagnose(data []byte) string {
|
||||
diag, err := modes.Diagnostic.Diagnose(data)
|
||||
if err != nil {
|
||||
// Since the input must already be well-formed CBOR, converting it to diagnostic
|
||||
// notation should not fail.
|
||||
util.HandleError(err)
|
||||
|
||||
return hex.EncodeToString(data)
|
||||
}
|
||||
return diag
|
||||
}
|
||||
|
||||
func (s *serializer) unmarshal(data []byte, into interface{}) (strict, lax error) {
|
||||
if u, ok := into.(runtime.Unstructured); ok {
|
||||
var content map[string]interface{}
|
||||
defer func() {
|
||||
// TODO: The UnstructuredList implementation of SetUnstructuredContent is
|
||||
// not identical to what unstructuredJSONScheme does: (1) it retains the
|
||||
// "items" key in its Object field, and (2) it does not infer a singular
|
||||
// Kind from the list's Kind and populate omitted apiVersion/kind for all
|
||||
// entries in Items.
|
||||
u.SetUnstructuredContent(content)
|
||||
}()
|
||||
into = &content
|
||||
}
|
||||
|
||||
if !s.options.strict {
|
||||
return nil, modes.DecodeLax.Unmarshal(data, into)
|
||||
}
|
||||
|
||||
err := modes.Decode.Unmarshal(data, into)
|
||||
// TODO: UnknownFieldError is ambiguous. It only provides the index of the first problematic
|
||||
// map entry encountered and does not indicate which map the index refers to.
|
||||
var unknownField *cbor.UnknownFieldError
|
||||
if errors.As(err, &unknownField) {
|
||||
// Unlike JSON, there are no strict errors in CBOR for duplicate map keys. CBOR maps
|
||||
// with duplicate keys are considered invalid according to the spec and are rejected
|
||||
// entirely.
|
||||
return runtime.NewStrictDecodingError([]error{unknownField}), modes.DecodeLax.Unmarshal(data, into)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func (s *serializer) Decode(data []byte, gvk *schema.GroupVersionKind, into runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) {
|
||||
// A preliminary pass over the input to obtain the actual GVK is redundant on a successful
|
||||
// decode into Unstructured.
|
||||
if _, ok := into.(runtime.Unstructured); ok {
|
||||
if _, unmarshalErr := s.unmarshal(data, into); unmarshalErr != nil {
|
||||
actual, interpretErr := s.metaFactory.Interpret(data)
|
||||
if interpretErr != nil {
|
||||
return nil, nil, interpretErr
|
||||
}
|
||||
|
||||
if gvk != nil {
|
||||
*actual = gvkWithDefaults(*actual, *gvk)
|
||||
}
|
||||
|
||||
return nil, actual, unmarshalErr
|
||||
}
|
||||
|
||||
actual := into.GetObjectKind().GroupVersionKind()
|
||||
if len(actual.Kind) == 0 {
|
||||
return nil, &actual, runtime.NewMissingKindErr(diagnose(data))
|
||||
}
|
||||
if len(actual.Version) == 0 {
|
||||
return nil, &actual, runtime.NewMissingVersionErr(diagnose(data))
|
||||
}
|
||||
|
||||
return into, &actual, nil
|
||||
}
|
||||
|
||||
actual, err := s.metaFactory.Interpret(data)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if gvk != nil {
|
||||
*actual = gvkWithDefaults(*actual, *gvk)
|
||||
}
|
||||
|
||||
if into != nil {
|
||||
types, _, err := s.typer.ObjectKinds(into)
|
||||
if err != nil {
|
||||
return nil, actual, err
|
||||
}
|
||||
*actual = gvkWithDefaults(*actual, types[0])
|
||||
}
|
||||
|
||||
if len(actual.Kind) == 0 {
|
||||
return nil, actual, runtime.NewMissingKindErr(diagnose(data))
|
||||
}
|
||||
if len(actual.Version) == 0 {
|
||||
return nil, actual, runtime.NewMissingVersionErr(diagnose(data))
|
||||
}
|
||||
|
||||
obj, err := runtime.UseOrCreateObject(s.typer, s.creater, *actual, into)
|
||||
if err != nil {
|
||||
return nil, actual, err
|
||||
}
|
||||
|
||||
strict, err := s.unmarshal(data, obj)
|
||||
if err != nil {
|
||||
return nil, actual, err
|
||||
}
|
||||
return obj, actual, strict
|
||||
}
|
||||
|
||||
// selfDescribedCBOR is the CBOR encoding of the head of tag number 55799. This tag, specified in
|
||||
// RFC 8949 Section 3.4.6 "Self-Described CBOR", encloses all output from the encoder, has no
|
||||
// special semantics, and is used as a magic number to recognize CBOR-encoded data items.
|
||||
//
|
||||
// See https://www.rfc-editor.org/rfc/rfc8949.html#name-self-described-cbor.
|
||||
var selfDescribedCBOR = []byte{0xd9, 0xd9, 0xf7}
|
||||
|
||||
func (s *serializer) RecognizesData(data []byte) (ok, unknown bool, err error) {
|
||||
return bytes.HasPrefix(data, selfDescribedCBOR), false, nil
|
||||
}
|
||||
|
@ -0,0 +1,475 @@
|
||||
/*
|
||||
Copyright 2024 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.
|
||||
*/
|
||||
|
||||
// The tests in this package focus on the correctness of its implementation of
|
||||
// runtime.Serializer. The specific behavior of marshaling Go values to CBOR bytes and back is
|
||||
// tested in the ./internal/modes package, which is used both by the Serializer implementation and
|
||||
// the package-scoped Marshal/Unmarshal functions in the ./direct package.
|
||||
package cbor
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"io"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer/cbor/internal/modes"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
)
|
||||
|
||||
func TestRecognizesData(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
in []byte
|
||||
recognizes bool
|
||||
}{
|
||||
{
|
||||
in: nil,
|
||||
recognizes: false,
|
||||
},
|
||||
{
|
||||
in: []byte{},
|
||||
recognizes: false,
|
||||
},
|
||||
{
|
||||
in: []byte{0xd9},
|
||||
recognizes: false,
|
||||
},
|
||||
{
|
||||
in: []byte{0xd9, 0xd9},
|
||||
recognizes: false,
|
||||
},
|
||||
{
|
||||
in: []byte{0xd9, 0xd9, 0xf7},
|
||||
recognizes: true,
|
||||
},
|
||||
{
|
||||
in: []byte{0xff, 0xff, 0xff},
|
||||
recognizes: false,
|
||||
},
|
||||
{
|
||||
in: []byte{0xd9, 0xd9, 0xf7, 0x01, 0x02, 0x03},
|
||||
recognizes: true,
|
||||
},
|
||||
{
|
||||
in: []byte{0xff, 0xff, 0xff, 0x01, 0x02, 0x03},
|
||||
recognizes: false,
|
||||
},
|
||||
} {
|
||||
t.Run(hex.EncodeToString(tc.in), func(t *testing.T) {
|
||||
s := NewSerializer(nil, nil)
|
||||
recognizes, unknown, err := s.RecognizesData(tc.in)
|
||||
if recognizes != tc.recognizes {
|
||||
t.Errorf("expected recognized to be %t, got %t", tc.recognizes, recognizes)
|
||||
}
|
||||
if unknown {
|
||||
t.Error("expected unknown to be false, got true")
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("expected nil error, got: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type stubWriter struct {
|
||||
n int
|
||||
err error
|
||||
}
|
||||
|
||||
func (w stubWriter) Write([]byte) (int, error) {
|
||||
return w.n, w.err
|
||||
}
|
||||
|
||||
// anyObject wraps arbitrary concrete values to be encoded or decoded.
|
||||
type anyObject struct {
|
||||
Value interface{}
|
||||
}
|
||||
|
||||
func (p anyObject) GetObjectKind() schema.ObjectKind {
|
||||
return schema.EmptyObjectKind
|
||||
}
|
||||
|
||||
func (anyObject) DeepCopyObject() runtime.Object {
|
||||
panic("unimplemented")
|
||||
}
|
||||
|
||||
func (p anyObject) MarshalCBOR() ([]byte, error) {
|
||||
return modes.Encode.Marshal(p.Value)
|
||||
}
|
||||
|
||||
func (p *anyObject) UnmarshalCBOR(in []byte) error {
|
||||
return modes.Decode.Unmarshal(in, &p.Value)
|
||||
}
|
||||
|
||||
func TestEncode(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
in runtime.Object
|
||||
assertOnWriter func() (io.Writer, func(*testing.T))
|
||||
assertOnError func(*testing.T, error)
|
||||
}{
|
||||
{
|
||||
name: "io error writing self described cbor tag",
|
||||
assertOnWriter: func() (io.Writer, func(*testing.T)) {
|
||||
return stubWriter{err: io.ErrShortWrite}, func(*testing.T) {}
|
||||
},
|
||||
assertOnError: func(t *testing.T, err error) {
|
||||
if !errors.Is(err, io.ErrShortWrite) {
|
||||
t.Errorf("expected io.ErrShortWrite, got: %v", err)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "output enclosed by self-described CBOR tag",
|
||||
in: anyObject{},
|
||||
assertOnWriter: func() (io.Writer, func(*testing.T)) {
|
||||
var b bytes.Buffer
|
||||
return &b, func(t *testing.T) {
|
||||
if !bytes.HasPrefix(b.Bytes(), []byte{0xd9, 0xd9, 0xf7}) {
|
||||
t.Errorf("expected output to have prefix 0xd9d9f7: 0x%x", b.Bytes())
|
||||
}
|
||||
}
|
||||
},
|
||||
assertOnError: func(t *testing.T, err error) {
|
||||
if err != nil {
|
||||
t.Errorf("expected nil error, got: %v", err)
|
||||
}
|
||||
},
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
s := NewSerializer(nil, nil)
|
||||
w, assertOnWriter := tc.assertOnWriter()
|
||||
err := s.Encode(tc.in, w)
|
||||
tc.assertOnError(t, err)
|
||||
assertOnWriter(t)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecode(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
options []Option
|
||||
data []byte
|
||||
gvk *schema.GroupVersionKind
|
||||
metaFactory metaFactory
|
||||
typer runtime.ObjectTyper
|
||||
creater runtime.ObjectCreater
|
||||
into runtime.Object
|
||||
expectedObj runtime.Object
|
||||
expectedGVK *schema.GroupVersionKind
|
||||
assertOnError func(*testing.T, error)
|
||||
}{
|
||||
{
|
||||
name: "error determining gvk",
|
||||
metaFactory: stubMetaFactory{err: errors.New("test")},
|
||||
assertOnError: func(t *testing.T, err error) {
|
||||
if err == nil || err.Error() != "test" {
|
||||
t.Errorf("expected error \"test\", got: %v", err)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "typer does not recognize into",
|
||||
gvk: &schema.GroupVersionKind{Group: "x", Version: "y", Kind: "z"},
|
||||
metaFactory: stubMetaFactory{gvk: &schema.GroupVersionKind{}},
|
||||
typer: notRegisteredTyper{},
|
||||
into: &anyObject{},
|
||||
expectedObj: nil,
|
||||
expectedGVK: &schema.GroupVersionKind{Group: "x", Version: "y", Kind: "z"},
|
||||
assertOnError: func(t *testing.T, err error) {
|
||||
if !runtime.IsNotRegisteredError(err) {
|
||||
t.Errorf("expected NotRegisteredError, got: %v", err)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "gvk from type of into",
|
||||
data: []byte{0xf6},
|
||||
gvk: &schema.GroupVersionKind{},
|
||||
metaFactory: stubMetaFactory{gvk: &schema.GroupVersionKind{}},
|
||||
typer: stubTyper{gvks: []schema.GroupVersionKind{{Group: "x", Version: "y", Kind: "z"}}},
|
||||
into: &anyObject{},
|
||||
expectedObj: &anyObject{},
|
||||
expectedGVK: &schema.GroupVersionKind{Group: "x", Version: "y", Kind: "z"},
|
||||
assertOnError: func(t *testing.T, err error) {
|
||||
if err != nil {
|
||||
t.Errorf("expected nil error, got: %v", err)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "strict mode strict error",
|
||||
options: []Option{Strict(true)},
|
||||
data: []byte{0xa1, 0x61, 'z', 0x01}, // {'z': 1}
|
||||
gvk: &schema.GroupVersionKind{},
|
||||
metaFactory: stubMetaFactory{gvk: &schema.GroupVersionKind{}},
|
||||
typer: stubTyper{gvks: []schema.GroupVersionKind{{Group: "x", Version: "y", Kind: "z"}}},
|
||||
into: &metav1.PartialObjectMetadata{},
|
||||
expectedObj: &metav1.PartialObjectMetadata{},
|
||||
expectedGVK: &schema.GroupVersionKind{Group: "x", Version: "y", Kind: "z"},
|
||||
assertOnError: func(t *testing.T, err error) {
|
||||
if !runtime.IsStrictDecodingError(err) {
|
||||
t.Errorf("expected StrictDecodingError, got: %v", err)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no strict mode no strict error",
|
||||
data: []byte{0xa1, 0x61, 'z', 0x01}, // {'z': 1}
|
||||
gvk: &schema.GroupVersionKind{},
|
||||
metaFactory: stubMetaFactory{gvk: &schema.GroupVersionKind{}},
|
||||
typer: stubTyper{gvks: []schema.GroupVersionKind{{Group: "x", Version: "y", Kind: "z"}}},
|
||||
into: &metav1.PartialObjectMetadata{},
|
||||
expectedObj: &metav1.PartialObjectMetadata{},
|
||||
expectedGVK: &schema.GroupVersionKind{Group: "x", Version: "y", Kind: "z"},
|
||||
assertOnError: func(t *testing.T, err error) {
|
||||
if err != nil {
|
||||
t.Errorf("expected nil error, got: %v", err)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "unknown error from typer on into",
|
||||
gvk: &schema.GroupVersionKind{},
|
||||
metaFactory: stubMetaFactory{gvk: &schema.GroupVersionKind{}},
|
||||
typer: stubTyper{err: errors.New("test")},
|
||||
into: &anyObject{},
|
||||
expectedObj: nil,
|
||||
expectedGVK: &schema.GroupVersionKind{},
|
||||
assertOnError: func(t *testing.T, err error) {
|
||||
if err == nil || err.Error() != "test" {
|
||||
t.Errorf("expected error \"test\", got: %v", err)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "missing kind",
|
||||
gvk: &schema.GroupVersionKind{Version: "v"},
|
||||
metaFactory: stubMetaFactory{gvk: &schema.GroupVersionKind{}},
|
||||
expectedObj: nil,
|
||||
expectedGVK: &schema.GroupVersionKind{Version: "v"},
|
||||
assertOnError: func(t *testing.T, err error) {
|
||||
if !runtime.IsMissingKind(err) {
|
||||
t.Errorf("expected MissingKind, got: %v", err)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "missing version",
|
||||
gvk: &schema.GroupVersionKind{Kind: "k"},
|
||||
metaFactory: stubMetaFactory{gvk: &schema.GroupVersionKind{}},
|
||||
expectedObj: nil,
|
||||
expectedGVK: &schema.GroupVersionKind{Kind: "k"},
|
||||
assertOnError: func(t *testing.T, err error) {
|
||||
if !runtime.IsMissingVersion(err) {
|
||||
t.Errorf("expected MissingVersion, got: %v", err)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "creater error",
|
||||
gvk: &schema.GroupVersionKind{Group: "x", Version: "y", Kind: "z"},
|
||||
metaFactory: stubMetaFactory{gvk: &schema.GroupVersionKind{}},
|
||||
creater: stubCreater{err: errors.New("test")},
|
||||
expectedObj: nil,
|
||||
expectedGVK: &schema.GroupVersionKind{Group: "x", Version: "y", Kind: "z"},
|
||||
assertOnError: func(t *testing.T, err error) {
|
||||
if err == nil || err.Error() != "test" {
|
||||
t.Errorf("expected error \"test\", got: %v", err)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "unmarshal error",
|
||||
data: nil, // EOF
|
||||
gvk: &schema.GroupVersionKind{Group: "x", Version: "y", Kind: "z"},
|
||||
metaFactory: stubMetaFactory{gvk: &schema.GroupVersionKind{}},
|
||||
creater: stubCreater{obj: &anyObject{}},
|
||||
expectedObj: nil,
|
||||
expectedGVK: &schema.GroupVersionKind{Group: "x", Version: "y", Kind: "z"},
|
||||
assertOnError: func(t *testing.T, err error) {
|
||||
if !errors.Is(err, io.EOF) {
|
||||
t.Errorf("expected EOF, got: %v", err)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "strict mode unmarshal error",
|
||||
options: []Option{Strict(true)},
|
||||
data: nil, // EOF
|
||||
gvk: &schema.GroupVersionKind{Group: "x", Version: "y", Kind: "z"},
|
||||
metaFactory: stubMetaFactory{gvk: &schema.GroupVersionKind{}},
|
||||
creater: stubCreater{obj: &anyObject{}},
|
||||
expectedObj: nil,
|
||||
expectedGVK: &schema.GroupVersionKind{Group: "x", Version: "y", Kind: "z"},
|
||||
assertOnError: func(t *testing.T, err error) {
|
||||
if !errors.Is(err, io.EOF) {
|
||||
t.Errorf("expected EOF, got: %v", err)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "into unstructured unmarshal error",
|
||||
data: nil, // EOF
|
||||
gvk: &schema.GroupVersionKind{Group: "x", Version: "y", Kind: "z"},
|
||||
metaFactory: stubMetaFactory{gvk: &schema.GroupVersionKind{}},
|
||||
into: &unstructured.Unstructured{},
|
||||
expectedObj: nil,
|
||||
expectedGVK: &schema.GroupVersionKind{Group: "x", Version: "y", Kind: "z"},
|
||||
assertOnError: func(t *testing.T, err error) {
|
||||
if !errors.Is(err, io.EOF) {
|
||||
t.Errorf("expected EOF, got: %v", err)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "into unstructured missing kind",
|
||||
data: []byte("\xa1\x6aapiVersion\x61v"),
|
||||
into: &unstructured.Unstructured{},
|
||||
expectedObj: nil,
|
||||
expectedGVK: &schema.GroupVersionKind{Version: "v"},
|
||||
assertOnError: func(t *testing.T, err error) {
|
||||
if !runtime.IsMissingKind(err) {
|
||||
t.Errorf("expected MissingKind, got: %v", err)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "into unstructured missing version",
|
||||
data: []byte("\xa1\x64kind\x61k"),
|
||||
into: &unstructured.Unstructured{},
|
||||
expectedObj: nil,
|
||||
expectedGVK: &schema.GroupVersionKind{Kind: "k"},
|
||||
assertOnError: func(t *testing.T, err error) {
|
||||
if !runtime.IsMissingVersion(err) {
|
||||
t.Errorf("expected MissingVersion, got: %v", err)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "into unstructured",
|
||||
data: []byte("\xa2\x6aapiVersion\x61v\x64kind\x61k"),
|
||||
into: &unstructured.Unstructured{},
|
||||
expectedObj: &unstructured.Unstructured{Object: map[string]interface{}{
|
||||
"apiVersion": "v",
|
||||
"kind": "k",
|
||||
}},
|
||||
expectedGVK: &schema.GroupVersionKind{Version: "v", Kind: "k"},
|
||||
assertOnError: func(t *testing.T, err error) {
|
||||
if err != nil {
|
||||
t.Errorf("expected nil error, got: %v", err)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "using unstructured creater",
|
||||
data: []byte("\xa2\x6aapiVersion\x61v\x64kind\x61k"),
|
||||
metaFactory: &defaultMetaFactory{},
|
||||
creater: stubCreater{obj: &unstructured.Unstructured{}},
|
||||
expectedObj: &unstructured.Unstructured{Object: map[string]interface{}{
|
||||
"apiVersion": "v",
|
||||
"kind": "k",
|
||||
}},
|
||||
expectedGVK: &schema.GroupVersionKind{Version: "v", Kind: "k"},
|
||||
assertOnError: func(t *testing.T, err error) {
|
||||
if err != nil {
|
||||
t.Errorf("expected nil error, got: %v", err)
|
||||
}
|
||||
},
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
s := newSerializer(tc.metaFactory, tc.creater, tc.typer, tc.options...)
|
||||
|
||||
actualObj, actualGVK, err := s.Decode(tc.data, tc.gvk, tc.into)
|
||||
tc.assertOnError(t, err)
|
||||
|
||||
if !reflect.DeepEqual(tc.expectedObj, actualObj) {
|
||||
t.Error(cmp.Diff(tc.expectedObj, actualObj))
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(tc.expectedGVK, actualGVK); diff != "" {
|
||||
t.Error(diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMetaFactoryInterpret(t *testing.T) {
|
||||
mf := &defaultMetaFactory{}
|
||||
_, err := mf.Interpret(nil)
|
||||
if err == nil {
|
||||
t.Error("expected non-nil error")
|
||||
}
|
||||
gvk, err := mf.Interpret([]byte("\xa2\x6aapiVersion\x63a/b\x64kind\x61c"))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if diff := cmp.Diff(&schema.GroupVersionKind{Group: "a", Version: "b", Kind: "c"}, gvk); diff != "" {
|
||||
t.Error(diff)
|
||||
}
|
||||
}
|
||||
|
||||
type stubTyper struct {
|
||||
gvks []schema.GroupVersionKind
|
||||
unversioned bool
|
||||
err error
|
||||
}
|
||||
|
||||
func (t stubTyper) ObjectKinds(obj runtime.Object) ([]schema.GroupVersionKind, bool, error) {
|
||||
return t.gvks, t.unversioned, t.err
|
||||
}
|
||||
|
||||
func (stubTyper) Recognizes(schema.GroupVersionKind) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
type stubCreater struct {
|
||||
obj runtime.Object
|
||||
err error
|
||||
}
|
||||
|
||||
func (c stubCreater) New(gvk schema.GroupVersionKind) (runtime.Object, error) {
|
||||
return c.obj, c.err
|
||||
}
|
||||
|
||||
type notRegisteredTyper struct{}
|
||||
|
||||
func (notRegisteredTyper) ObjectKinds(obj runtime.Object) ([]schema.GroupVersionKind, bool, error) {
|
||||
return nil, false, runtime.NewNotRegisteredErrForType("test", reflect.TypeOf(obj))
|
||||
}
|
||||
|
||||
func (notRegisteredTyper) Recognizes(schema.GroupVersionKind) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
type stubMetaFactory struct {
|
||||
gvk *schema.GroupVersionKind
|
||||
err error
|
||||
}
|
||||
|
||||
func (mf stubMetaFactory) Interpret([]byte) (*schema.GroupVersionKind, error) {
|
||||
return mf.gvk, mf.err
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
/*
|
||||
Copyright 2024 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 direct provides functions for marshaling and unmarshaling between arbitrary Go values and
|
||||
// CBOR data, with behavior that is compatible with that of the CBOR serializer. In particular,
|
||||
// types that implement cbor.Marshaler and cbor.Unmarshaler should use these functions.
|
||||
package direct
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer/cbor/internal/modes"
|
||||
)
|
||||
|
||||
func Marshal(src interface{}) ([]byte, error) {
|
||||
return modes.Encode.Marshal(src)
|
||||
}
|
||||
|
||||
func Unmarshal(src []byte, dst interface{}) error {
|
||||
return modes.Decode.Unmarshal(src, dst)
|
||||
}
|
||||
|
||||
func Diagnose(src []byte) (string, error) {
|
||||
return modes.Diagnostic.Diagnose(src)
|
||||
}
|
@ -0,0 +1,103 @@
|
||||
/*
|
||||
Copyright 2024 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 modes
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/fxamacker/cbor/v2"
|
||||
)
|
||||
|
||||
var Decode cbor.DecMode = func() cbor.DecMode {
|
||||
decode, err := cbor.DecOptions{
|
||||
// Maps with duplicate keys are well-formed but invalid according to the CBOR spec
|
||||
// and never acceptable. Unlike the JSON serializer, inputs containing duplicate map
|
||||
// keys are rejected outright and not surfaced as a strict decoding error.
|
||||
DupMapKey: cbor.DupMapKeyEnforcedAPF,
|
||||
|
||||
// For JSON parity, decoding an RFC3339 string into time.Time needs to be accepted
|
||||
// with or without tagging. If a tag number is present, it must be valid.
|
||||
TimeTag: cbor.DecTagOptional,
|
||||
|
||||
// Observed depth up to 16 in fuzzed batch/v1 CronJobList. JSON implementation limit
|
||||
// is 10000.
|
||||
MaxNestedLevels: 64,
|
||||
|
||||
MaxArrayElements: 1024,
|
||||
MaxMapPairs: 1024,
|
||||
|
||||
// Indefinite-length sequences aren't produced by this serializer, but other
|
||||
// implementations can.
|
||||
IndefLength: cbor.IndefLengthAllowed,
|
||||
|
||||
// Accept inputs that contain CBOR tags.
|
||||
TagsMd: cbor.TagsAllowed,
|
||||
|
||||
// Decode type 0 (unsigned integer) as int64.
|
||||
// TODO: IntDecConvertSignedOrFail errors on overflow, JSON will try to fall back to float64.
|
||||
IntDec: cbor.IntDecConvertSignedOrFail,
|
||||
|
||||
// Disable producing map[cbor.ByteString]interface{}, which is not acceptable for
|
||||
// decodes into interface{}.
|
||||
MapKeyByteString: cbor.MapKeyByteStringForbidden,
|
||||
|
||||
// Error on map keys that don't map to a field in the destination struct.
|
||||
ExtraReturnErrors: cbor.ExtraDecErrorUnknownField,
|
||||
|
||||
// Decode maps into concrete type map[string]interface{} when the destination is an
|
||||
// interface{}.
|
||||
DefaultMapType: reflect.TypeOf(map[string]interface{}(nil)),
|
||||
|
||||
// A CBOR text string whose content is not a valid UTF-8 sequence is well-formed but
|
||||
// invalid according to the CBOR spec. Reject invalid inputs. Encoders are
|
||||
// responsible for ensuring that all text strings they produce contain valid UTF-8
|
||||
// sequences and may use the byte string major type to encode strings that have not
|
||||
// been validated.
|
||||
UTF8: cbor.UTF8RejectInvalid,
|
||||
|
||||
// Never make a case-insensitive match between a map key and a struct field.
|
||||
FieldNameMatching: cbor.FieldNameMatchingCaseSensitive,
|
||||
|
||||
// Produce string concrete values when decoding a CBOR byte string into interface{}.
|
||||
DefaultByteStringType: reflect.TypeOf(""),
|
||||
|
||||
// Allow CBOR byte strings to be decoded into string destination values.
|
||||
ByteStringToString: cbor.ByteStringToStringAllowed,
|
||||
|
||||
// Allow CBOR byte strings to match struct fields when appearing as a map key.
|
||||
FieldNameByteString: cbor.FieldNameByteStringAllowed,
|
||||
|
||||
// When decoding an unrecognized tag to interface{}, return the decoded tag content
|
||||
// instead of the default, a cbor.Tag representing a (number, content) pair.
|
||||
UnrecognizedTagToAny: cbor.UnrecognizedTagContentToAny,
|
||||
}.DecMode()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return decode
|
||||
}()
|
||||
|
||||
// DecodeLax is derived from Decode, but does not complain about unknown fields in the input.
|
||||
var DecodeLax cbor.DecMode = func() cbor.DecMode {
|
||||
opts := Decode.DecOptions()
|
||||
opts.ExtraReturnErrors &^= cbor.ExtraDecErrorUnknownField // clear bit
|
||||
dm, err := opts.DecMode()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return dm
|
||||
}()
|
@ -0,0 +1,36 @@
|
||||
/*
|
||||
Copyright 2024 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 modes
|
||||
|
||||
import (
|
||||
"github.com/fxamacker/cbor/v2"
|
||||
)
|
||||
|
||||
var Diagnostic cbor.DiagMode = func() cbor.DiagMode {
|
||||
opts := Decode.DecOptions()
|
||||
diagnostic, err := cbor.DiagOptions{
|
||||
ByteStringText: true,
|
||||
|
||||
MaxNestedLevels: opts.MaxNestedLevels,
|
||||
MaxArrayElements: opts.MaxArrayElements,
|
||||
MaxMapPairs: opts.MaxMapPairs,
|
||||
}.DiagMode()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return diagnostic
|
||||
}()
|
@ -0,0 +1,96 @@
|
||||
/*
|
||||
Copyright 2024 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 modes
|
||||
|
||||
import (
|
||||
"github.com/fxamacker/cbor/v2"
|
||||
)
|
||||
|
||||
var Encode cbor.EncMode = func() cbor.EncMode {
|
||||
encode, err := cbor.EncOptions{
|
||||
// Map keys need to be sorted to have deterministic output, and this is the order
|
||||
// defined in RFC 8949 4.2.1 "Core Deterministic Encoding Requirements".
|
||||
Sort: cbor.SortBytewiseLexical,
|
||||
|
||||
// CBOR supports distinct types for IEEE-754 float16, float32, and float64. Store
|
||||
// floats in the smallest width that preserves value so that equivalent float32 and
|
||||
// float64 values encode to identical bytes, as they do in a JSON
|
||||
// encoding. Satisfies one of the "Core Deterministic Encoding Requirements".
|
||||
ShortestFloat: cbor.ShortestFloat16,
|
||||
|
||||
// ShortestFloat doesn't apply to NaN or Inf values. Inf values are losslessly
|
||||
// encoded to float16. RFC 8949 recommends choosing a single representation of NaN
|
||||
// in applications that do not smuggle additional information inside NaN values, we
|
||||
// use 0x7e00.
|
||||
NaNConvert: cbor.NaNConvert7e00,
|
||||
InfConvert: cbor.InfConvertFloat16,
|
||||
|
||||
// Prefer encoding math/big.Int to one of the 64-bit integer types if it fits. When
|
||||
// later decoded into Unstructured, the set of allowable concrete numeric types is
|
||||
// limited to int64 and float64, so the distinction between big integer and integer
|
||||
// can't be preserved.
|
||||
BigIntConvert: cbor.BigIntConvertShortest,
|
||||
|
||||
// MarshalJSON for time.Time writes RFC3339 with nanos.
|
||||
Time: cbor.TimeRFC3339Nano,
|
||||
|
||||
// The decoder must be able to accept RFC3339 strings with or without tag 0 (e.g. by
|
||||
// the end of time.Time -> JSON -> Unstructured -> CBOR, the CBOR encoder has no
|
||||
// reliable way of knowing that a particular string originated from serializing a
|
||||
// time.Time), so producing tag 0 has little use.
|
||||
TimeTag: cbor.EncTagNone,
|
||||
|
||||
// Indefinite-length items have multiple encodings and aren't being used anyway, so
|
||||
// disable to avoid an opportunity for nondeterminism.
|
||||
IndefLength: cbor.IndefLengthForbidden,
|
||||
|
||||
// Preserve distinction between nil and empty for slices and maps.
|
||||
NilContainers: cbor.NilContainerAsNull,
|
||||
|
||||
// OK to produce tags.
|
||||
TagsMd: cbor.TagsAllowed,
|
||||
|
||||
// Use the same definition of "empty" as encoding/json.
|
||||
OmitEmpty: cbor.OmitEmptyGoValue,
|
||||
|
||||
// The CBOR types text string and byte string are structurally equivalent, with the
|
||||
// semantic difference that a text string whose content is an invalid UTF-8 sequence
|
||||
// is itself invalid. We reject all invalid text strings at decode time and do not
|
||||
// validate or sanitize all Go strings at encode time. Encoding Go strings to the
|
||||
// byte string type is comparable to the existing Protobuf behavior and cheaply
|
||||
// ensures that the output is valid CBOR.
|
||||
String: cbor.StringToByteString,
|
||||
|
||||
// Encode struct field names to the byte string type rather than the text string
|
||||
// type.
|
||||
FieldName: cbor.FieldNameToByteString,
|
||||
}.EncMode()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return encode
|
||||
}()
|
||||
|
||||
var EncodeNondeterministic cbor.EncMode = func() cbor.EncMode {
|
||||
opts := Encode.EncOptions()
|
||||
opts.Sort = cbor.SortNone
|
||||
em, err := opts.EncMode()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return em
|
||||
}()
|
1
vendor/modules.txt
vendored
1
vendor/modules.txt
vendored
@ -1313,6 +1313,7 @@ k8s.io/apimachinery/pkg/runtime
|
||||
k8s.io/apimachinery/pkg/runtime/schema
|
||||
k8s.io/apimachinery/pkg/runtime/serializer
|
||||
k8s.io/apimachinery/pkg/runtime/serializer/cbor
|
||||
k8s.io/apimachinery/pkg/runtime/serializer/cbor/internal/modes
|
||||
k8s.io/apimachinery/pkg/runtime/serializer/json
|
||||
k8s.io/apimachinery/pkg/runtime/serializer/protobuf
|
||||
k8s.io/apimachinery/pkg/runtime/serializer/recognizer
|
||||
|
Loading…
Reference in New Issue
Block a user