mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-23 03:41:45 +00:00
Add server-side metadata unknown field validation
This commit is contained in:
parent
5b92e46b22
commit
d983fd8078
@ -1331,10 +1331,14 @@ func (v *unstructuredSchemaCoercer) apply(u *unstructured.Unstructured) (unknown
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
objectMeta, foundObjectMeta, err := schemaobjectmeta.GetObjectMeta(u.Object, v.dropInvalidMetadata)
|
objectMeta, foundObjectMeta, metaUnknownFields, err := schemaobjectmeta.GetObjectMetaWithOptions(u.Object, schemaobjectmeta.ObjectMetaOptions{
|
||||||
|
DropMalformedFields: v.dropInvalidMetadata,
|
||||||
|
ReturnUnknownFieldPaths: v.returnUnknownFieldPaths,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
unknownFieldPaths = append(unknownFieldPaths, metaUnknownFields...)
|
||||||
|
|
||||||
// compare group and kind because also other object like DeleteCollection options pass through here
|
// compare group and kind because also other object like DeleteCollection options pass through here
|
||||||
gv, err := schema.ParseGroupVersion(apiVersion)
|
gv, err := schema.ParseGroupVersion(apiVersion)
|
||||||
@ -1345,17 +1349,23 @@ func (v *unstructuredSchemaCoercer) apply(u *unstructured.Unstructured) (unknown
|
|||||||
if gv.Group == v.structuralSchemaGK.Group && kind == v.structuralSchemaGK.Kind {
|
if gv.Group == v.structuralSchemaGK.Group && kind == v.structuralSchemaGK.Kind {
|
||||||
if !v.preserveUnknownFields {
|
if !v.preserveUnknownFields {
|
||||||
// TODO: switch over pruning and coercing at the root to schemaobjectmeta.Coerce too
|
// TODO: switch over pruning and coercing at the root to schemaobjectmeta.Coerce too
|
||||||
pruneOpts := structuralpruning.PruneOptions{}
|
pruneOpts := structuralschema.UnknownFieldPathOptions{}
|
||||||
if v.returnUnknownFieldPaths {
|
if v.returnUnknownFieldPaths {
|
||||||
pruneOpts.ReturnPruned = true
|
pruneOpts.TrackUnknownFieldPaths = true
|
||||||
}
|
}
|
||||||
unknownFieldPaths = structuralpruning.PruneWithOptions(u.Object, v.structuralSchemas[gv.Version], true, pruneOpts)
|
unknownFieldPaths = append(unknownFieldPaths, structuralpruning.PruneWithOptions(u.Object, v.structuralSchemas[gv.Version], true, pruneOpts)...)
|
||||||
structuraldefaulting.PruneNonNullableNullsWithoutDefaults(u.Object, v.structuralSchemas[gv.Version])
|
structuraldefaulting.PruneNonNullableNullsWithoutDefaults(u.Object, v.structuralSchemas[gv.Version])
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := schemaobjectmeta.Coerce(nil, u.Object, v.structuralSchemas[gv.Version], false, v.dropInvalidMetadata); err != nil {
|
err, paths := schemaobjectmeta.CoerceWithOptions(nil, u.Object, v.structuralSchemas[gv.Version], false, schemaobjectmeta.CoerceOptions{
|
||||||
|
DropInvalidFields: v.dropInvalidMetadata,
|
||||||
|
ReturnUnknownFieldPaths: v.returnUnknownFieldPaths,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
unknownFieldPaths = append(unknownFieldPaths, paths...)
|
||||||
|
|
||||||
// fixup missing generation in very old CRs
|
// fixup missing generation in very old CRs
|
||||||
if v.repairGeneration && objectMeta.Generation == 0 {
|
if v.repairGeneration && objectMeta.Generation == 0 {
|
||||||
objectMeta.Generation = 1
|
objectMeta.Generation = 1
|
||||||
|
@ -17,15 +17,34 @@ limitations under the License.
|
|||||||
package objectmeta
|
package objectmeta
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"sort"
|
||||||
|
|
||||||
structuralschema "k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
|
structuralschema "k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// CoerceOptions gives the ability to ReturnUnknownFieldPaths for fields
|
||||||
|
// unrecognized by the schema or DropInvalidFields for fields that are a part
|
||||||
|
// of the schema, but are malformed.
|
||||||
|
type CoerceOptions struct {
|
||||||
|
// DropInvalidFields discards malformed serialized metadata fields that
|
||||||
|
// cannot be successfully decoded to the corresponding ObjectMeta field.
|
||||||
|
// This only applies to fields that are recognized as part of the schema,
|
||||||
|
// but of an invalid type (i.e. cause an error when unmarshaling, rather
|
||||||
|
// than being dropped or causing a strictErr).
|
||||||
|
DropInvalidFields bool
|
||||||
|
// ReturnUnknownFieldPaths will return the paths to fields that are not
|
||||||
|
// recognized as part of the schema.
|
||||||
|
ReturnUnknownFieldPaths bool
|
||||||
|
}
|
||||||
|
|
||||||
// Coerce checks types of embedded ObjectMeta and TypeMeta and prunes unknown fields inside the former.
|
// Coerce checks types of embedded ObjectMeta and TypeMeta and prunes unknown fields inside the former.
|
||||||
// It does coerce ObjectMeta and TypeMeta at the root if isResourceRoot is true.
|
// It does coerce ObjectMeta and TypeMeta at the root if isResourceRoot is true.
|
||||||
// If dropInvalidFields is true, fields of wrong type will be dropped.
|
// If opts.ReturnUnknownFieldPaths is true, it will return the paths of any fields that are not a part of the
|
||||||
func Coerce(pth *field.Path, obj interface{}, s *structuralschema.Structural, isResourceRoot, dropInvalidFields bool) *field.Error {
|
// schema that are dropped when unmarshaling.
|
||||||
|
// If opts.DropInvalidFields is true, fields of wrong type will be dropped.
|
||||||
|
func CoerceWithOptions(pth *field.Path, obj interface{}, s *structuralschema.Structural, isResourceRoot bool, opts CoerceOptions) (*field.Error, []string) {
|
||||||
if isResourceRoot {
|
if isResourceRoot {
|
||||||
if s == nil {
|
if s == nil {
|
||||||
s = &structuralschema.Structural{}
|
s = &structuralschema.Structural{}
|
||||||
@ -36,36 +55,57 @@ func Coerce(pth *field.Path, obj interface{}, s *structuralschema.Structural, is
|
|||||||
s = &clone
|
s = &clone
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
c := coercer{dropInvalidFields: dropInvalidFields}
|
c := coercer{DropInvalidFields: opts.DropInvalidFields, ReturnUnknownFieldPaths: opts.ReturnUnknownFieldPaths}
|
||||||
return c.coerce(pth, obj, s)
|
schemaOpts := &structuralschema.UnknownFieldPathOptions{
|
||||||
|
TrackUnknownFieldPaths: opts.ReturnUnknownFieldPaths,
|
||||||
|
}
|
||||||
|
fieldErr := c.coerce(pth, obj, s, schemaOpts)
|
||||||
|
sort.Strings(schemaOpts.UnknownFieldPaths)
|
||||||
|
return fieldErr, schemaOpts.UnknownFieldPaths
|
||||||
|
}
|
||||||
|
|
||||||
|
// Coerce calls CoerceWithOptions without returning unknown field paths.
|
||||||
|
func Coerce(pth *field.Path, obj interface{}, s *structuralschema.Structural, isResourceRoot, dropInvalidFields bool) *field.Error {
|
||||||
|
fieldErr, _ := CoerceWithOptions(pth, obj, s, isResourceRoot, CoerceOptions{DropInvalidFields: dropInvalidFields})
|
||||||
|
return fieldErr
|
||||||
}
|
}
|
||||||
|
|
||||||
type coercer struct {
|
type coercer struct {
|
||||||
dropInvalidFields bool
|
DropInvalidFields bool
|
||||||
|
ReturnUnknownFieldPaths bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *coercer) coerce(pth *field.Path, x interface{}, s *structuralschema.Structural) *field.Error {
|
func (c *coercer) coerce(pth *field.Path, x interface{}, s *structuralschema.Structural, opts *structuralschema.UnknownFieldPathOptions) *field.Error {
|
||||||
if s == nil {
|
if s == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
origPathLen := len(opts.ParentPath)
|
||||||
|
defer func() {
|
||||||
|
opts.ParentPath = opts.ParentPath[:origPathLen]
|
||||||
|
}()
|
||||||
switch x := x.(type) {
|
switch x := x.(type) {
|
||||||
case map[string]interface{}:
|
case map[string]interface{}:
|
||||||
for k, v := range x {
|
for k, v := range x {
|
||||||
if s.XEmbeddedResource {
|
if s.XEmbeddedResource {
|
||||||
switch k {
|
switch k {
|
||||||
case "apiVersion", "kind":
|
case "apiVersion", "kind":
|
||||||
if _, ok := v.(string); !ok && c.dropInvalidFields {
|
if _, ok := v.(string); !ok && c.DropInvalidFields {
|
||||||
delete(x, k)
|
delete(x, k)
|
||||||
} else if !ok {
|
} else if !ok {
|
||||||
return field.Invalid(pth.Child(k), v, "must be a string")
|
return field.Invalid(pth.Child(k), v, "must be a string")
|
||||||
}
|
}
|
||||||
case "metadata":
|
case "metadata":
|
||||||
meta, found, err := GetObjectMeta(x, c.dropInvalidFields)
|
meta, found, unknownFields, err := GetObjectMetaWithOptions(x, ObjectMetaOptions{
|
||||||
|
DropMalformedFields: c.DropInvalidFields,
|
||||||
|
ReturnUnknownFieldPaths: c.ReturnUnknownFieldPaths,
|
||||||
|
ParentPath: pth,
|
||||||
|
})
|
||||||
|
opts.UnknownFieldPaths = append(opts.UnknownFieldPaths, unknownFields...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !c.dropInvalidFields {
|
if !c.DropInvalidFields {
|
||||||
return field.Invalid(pth.Child("metadata"), v, err.Error())
|
return field.Invalid(pth.Child("metadata"), v, err.Error())
|
||||||
}
|
}
|
||||||
// pass through on error if dropInvalidFields is true
|
// pass through on error if DropInvalidFields is true
|
||||||
} else if found {
|
} else if found {
|
||||||
if err := SetObjectMeta(x, meta); err != nil {
|
if err := SetObjectMeta(x, meta); err != nil {
|
||||||
return field.Invalid(pth.Child("metadata"), v, err.Error())
|
return field.Invalid(pth.Child("metadata"), v, err.Error())
|
||||||
@ -78,20 +118,26 @@ func (c *coercer) coerce(pth *field.Path, x interface{}, s *structuralschema.Str
|
|||||||
}
|
}
|
||||||
prop, ok := s.Properties[k]
|
prop, ok := s.Properties[k]
|
||||||
if ok {
|
if ok {
|
||||||
if err := c.coerce(pth.Child(k), v, &prop); err != nil {
|
opts.AppendKey(k)
|
||||||
|
if err := c.coerce(pth.Child(k), v, &prop, opts); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
opts.ParentPath = opts.ParentPath[:origPathLen]
|
||||||
} else if s.AdditionalProperties != nil {
|
} else if s.AdditionalProperties != nil {
|
||||||
if err := c.coerce(pth.Key(k), v, s.AdditionalProperties.Structural); err != nil {
|
opts.AppendKey(k)
|
||||||
|
if err := c.coerce(pth.Key(k), v, s.AdditionalProperties.Structural, opts); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
opts.ParentPath = opts.ParentPath[:origPathLen]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case []interface{}:
|
case []interface{}:
|
||||||
for i, v := range x {
|
for i, v := range x {
|
||||||
if err := c.coerce(pth.Index(i), v, s.Items); err != nil {
|
opts.AppendIndex(i)
|
||||||
|
if err := c.coerce(pth.Index(i), v, s.Items, opts); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
opts.ParentPath = opts.ParentPath[:origPathLen]
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
// scalars, do nothing
|
// scalars, do nothing
|
||||||
|
@ -19,6 +19,7 @@ package objectmeta
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
structuralschema "k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
|
structuralschema "k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
|
||||||
@ -28,13 +29,14 @@ import (
|
|||||||
|
|
||||||
func TestCoerce(t *testing.T) {
|
func TestCoerce(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
json string
|
json string
|
||||||
includeRoot bool
|
includeRoot bool
|
||||||
dropInvalidFields bool
|
dropInvalidFields bool
|
||||||
schema *structuralschema.Structural
|
schema *structuralschema.Structural
|
||||||
expected string
|
expected string
|
||||||
expectedError bool
|
expectedError bool
|
||||||
|
expectedUnknownFields []string
|
||||||
}{
|
}{
|
||||||
{name: "empty", json: "null", schema: nil, expected: "null"},
|
{name: "empty", json: "null", schema: nil, expected: "null"},
|
||||||
{name: "scalar", json: "4", schema: &structuralschema.Structural{}, expected: "4"},
|
{name: "scalar", json: "4", schema: &structuralschema.Structural{}, expected: "4"},
|
||||||
@ -199,7 +201,12 @@ func TestCoerce(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`},
|
`, expectedUnknownFields: []string{
|
||||||
|
"nested.metadata.unspecified",
|
||||||
|
"nested.spec.embedded.metadata.unspecified",
|
||||||
|
"preserving.metadata.unspecified",
|
||||||
|
"pruned.metadata.unspecified",
|
||||||
|
}},
|
||||||
{name: "x-kubernetes-embedded-resource, with includeRoot=true", json: `
|
{name: "x-kubernetes-embedded-resource, with includeRoot=true", json: `
|
||||||
{
|
{
|
||||||
"apiVersion": "foo/v1",
|
"apiVersion": "foo/v1",
|
||||||
@ -356,8 +363,13 @@ func TestCoerce(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}`, expectedUnknownFields: []string{
|
||||||
`},
|
"metadata.unspecified",
|
||||||
|
"nested.metadata.unspecified",
|
||||||
|
"nested.spec.embedded.metadata.unspecified",
|
||||||
|
"preserving.metadata.unspecified",
|
||||||
|
"pruned.metadata.unspecified",
|
||||||
|
}},
|
||||||
{name: "without name", json: `
|
{name: "without name", json: `
|
||||||
{
|
{
|
||||||
"apiVersion": "foo/v1",
|
"apiVersion": "foo/v1",
|
||||||
@ -495,7 +507,10 @@ func TestCoerce(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err := Coerce(nil, in, tt.schema, tt.includeRoot, tt.dropInvalidFields)
|
err, unknownFields := CoerceWithOptions(nil, in, tt.schema, tt.includeRoot, CoerceOptions{
|
||||||
|
DropInvalidFields: tt.dropInvalidFields,
|
||||||
|
ReturnUnknownFieldPaths: true,
|
||||||
|
})
|
||||||
if tt.expectedError && err == nil {
|
if tt.expectedError && err == nil {
|
||||||
t.Error("expected error, but did not get any")
|
t.Error("expected error, but did not get any")
|
||||||
} else if !tt.expectedError && err != nil {
|
} else if !tt.expectedError && err != nil {
|
||||||
@ -510,6 +525,9 @@ func TestCoerce(t *testing.T) {
|
|||||||
}
|
}
|
||||||
t.Errorf("expected: %s\ngot: %s\ndiff: %s", tt.expected, buf.String(), diff.ObjectDiff(expected, in))
|
t.Errorf("expected: %s\ngot: %s\ndiff: %s", tt.expected, buf.String(), diff.ObjectDiff(expected, in))
|
||||||
}
|
}
|
||||||
|
if !reflect.DeepEqual(unknownFields, tt.expectedUnknownFields) {
|
||||||
|
t.Errorf("expected unknown fields:\n\t%v\ngot:\n\t%v\n", strings.Join(tt.expectedUnknownFields, "\n\t"), strings.Join(unknownFields, "\n\t"))
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,35 +23,83 @@ import (
|
|||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
utiljson "k8s.io/apimachinery/pkg/util/json"
|
utiljson "k8s.io/apimachinery/pkg/util/json"
|
||||||
|
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||||
|
kjson "sigs.k8s.io/json"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetObjectMeta does conversion of JSON to ObjectMeta. It first tries json.Unmarshal into a metav1.ObjectMeta
|
// GetObjectMeta calls GetObjectMetaWithOptions without returning unknown field paths.
|
||||||
// type. If that does not work and dropMalformedFields is true, it does field-by-field best-effort conversion
|
|
||||||
// throwing away fields which lead to errors.
|
|
||||||
func GetObjectMeta(obj map[string]interface{}, dropMalformedFields bool) (*metav1.ObjectMeta, bool, error) {
|
func GetObjectMeta(obj map[string]interface{}, dropMalformedFields bool) (*metav1.ObjectMeta, bool, error) {
|
||||||
|
meta, found, _, err := GetObjectMetaWithOptions(obj, ObjectMetaOptions{
|
||||||
|
DropMalformedFields: dropMalformedFields,
|
||||||
|
})
|
||||||
|
return meta, found, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ObjectMetaOptions provides the options for how GetObjectMeta should retrieve the object meta.
|
||||||
|
type ObjectMetaOptions struct {
|
||||||
|
// DropMalformedFields discards malformed serialized metadata fields that
|
||||||
|
// cannot be successfully decoded to the corresponding ObjectMeta field.
|
||||||
|
// This only applies to fields that are recognized as part of the schema,
|
||||||
|
// but of an invalid type (i.e. cause an error when unmarshaling, rather
|
||||||
|
// than being dropped or causing a strictErr).
|
||||||
|
DropMalformedFields bool
|
||||||
|
// ReturnUnknownFieldPaths will return the paths to fields that are not
|
||||||
|
// recognized as part of the schema.
|
||||||
|
ReturnUnknownFieldPaths bool
|
||||||
|
// ParentPath provides the current path up to the given ObjectMeta.
|
||||||
|
// If nil, the metadata is assumed to be at the root of the object.
|
||||||
|
ParentPath *field.Path
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetObjectMetaWithOptions does conversion of JSON to ObjectMeta.
|
||||||
|
// It first tries json.Unmarshal into a metav1.ObjectMeta
|
||||||
|
// type. If that does not work and opts.DropMalformedFields is true, it does field-by-field best-effort conversion
|
||||||
|
// throwing away fields which lead to errors.
|
||||||
|
// If opts.ReturnedUnknownFields is true, it will UnmarshalStrict instead, returning the paths of any unknown fields
|
||||||
|
// it encounters (i.e. paths returned as strict errs from UnmarshalStrict)
|
||||||
|
func GetObjectMetaWithOptions(obj map[string]interface{}, opts ObjectMetaOptions) (*metav1.ObjectMeta, bool, []string, error) {
|
||||||
metadata, found := obj["metadata"]
|
metadata, found := obj["metadata"]
|
||||||
if !found {
|
if !found {
|
||||||
return nil, false, nil
|
return nil, false, nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// round-trip through JSON first, hoping that unmarshalling just works
|
// round-trip through JSON first, hoping that unmarshalling just works
|
||||||
objectMeta := &metav1.ObjectMeta{}
|
objectMeta := &metav1.ObjectMeta{}
|
||||||
metadataBytes, err := utiljson.Marshal(metadata)
|
metadataBytes, err := utiljson.Marshal(metadata)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false, err
|
return nil, false, nil, err
|
||||||
}
|
}
|
||||||
if err = utiljson.Unmarshal(metadataBytes, objectMeta); err == nil {
|
var unmarshalErr error
|
||||||
// if successful, return
|
if opts.ReturnUnknownFieldPaths {
|
||||||
return objectMeta, true, nil
|
var strictErrs []error
|
||||||
|
strictErrs, unmarshalErr = kjson.UnmarshalStrict(metadataBytes, objectMeta)
|
||||||
|
if unmarshalErr == nil {
|
||||||
|
if len(strictErrs) > 0 {
|
||||||
|
unknownPaths := []string{}
|
||||||
|
prefix := opts.ParentPath.Child("metadata").String()
|
||||||
|
for _, err := range strictErrs {
|
||||||
|
if fieldPathErr, ok := err.(kjson.FieldError); ok {
|
||||||
|
unknownPaths = append(unknownPaths, prefix+"."+fieldPathErr.FieldPath())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return objectMeta, true, unknownPaths, nil
|
||||||
|
}
|
||||||
|
return objectMeta, true, nil, nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if unmarshalErr = utiljson.Unmarshal(metadataBytes, objectMeta); unmarshalErr == nil {
|
||||||
|
// if successful, return
|
||||||
|
return objectMeta, true, nil, nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if !dropMalformedFields {
|
if !opts.DropMalformedFields {
|
||||||
// if we're not trying to drop malformed fields, return the error
|
// if we're not trying to drop malformed fields, return the error
|
||||||
return nil, true, err
|
return nil, true, nil, unmarshalErr
|
||||||
}
|
}
|
||||||
|
|
||||||
metadataMap, ok := metadata.(map[string]interface{})
|
metadataMap, ok := metadata.(map[string]interface{})
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, false, fmt.Errorf("invalid metadata: expected object, got %T", metadata)
|
return nil, false, nil, fmt.Errorf("invalid metadata: expected object, got %T", metadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Go field by field accumulating into the metadata object.
|
// Go field by field accumulating into the metadata object.
|
||||||
@ -59,18 +107,31 @@ func GetObjectMeta(obj map[string]interface{}, dropMalformedFields bool) (*metav
|
|||||||
// each iteration preserving the old key-values.
|
// each iteration preserving the old key-values.
|
||||||
accumulatedObjectMeta := &metav1.ObjectMeta{}
|
accumulatedObjectMeta := &metav1.ObjectMeta{}
|
||||||
testObjectMeta := &metav1.ObjectMeta{}
|
testObjectMeta := &metav1.ObjectMeta{}
|
||||||
|
var unknownFields []string
|
||||||
for k, v := range metadataMap {
|
for k, v := range metadataMap {
|
||||||
// serialize a single field
|
// serialize a single field
|
||||||
if singleFieldBytes, err := utiljson.Marshal(map[string]interface{}{k: v}); err == nil {
|
if singleFieldBytes, err := utiljson.Marshal(map[string]interface{}{k: v}); err == nil {
|
||||||
// do a test unmarshal
|
// do a test unmarshal
|
||||||
if utiljson.Unmarshal(singleFieldBytes, testObjectMeta) == nil {
|
if utiljson.Unmarshal(singleFieldBytes, testObjectMeta) == nil {
|
||||||
// if that succeeds, unmarshal for real
|
// if that succeeds, unmarshal for real
|
||||||
utiljson.Unmarshal(singleFieldBytes, accumulatedObjectMeta)
|
if opts.ReturnUnknownFieldPaths {
|
||||||
|
strictErrs, _ := kjson.UnmarshalStrict(singleFieldBytes, accumulatedObjectMeta)
|
||||||
|
if len(strictErrs) > 0 {
|
||||||
|
prefix := opts.ParentPath.Child("metadata").String()
|
||||||
|
for _, err := range strictErrs {
|
||||||
|
if fieldPathErr, ok := err.(kjson.FieldError); ok {
|
||||||
|
unknownFields = append(unknownFields, prefix+"."+fieldPathErr.FieldPath())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
utiljson.Unmarshal(singleFieldBytes, accumulatedObjectMeta)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return accumulatedObjectMeta, true, nil
|
return accumulatedObjectMeta, true, unknownFields, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetObjectMeta writes back ObjectMeta into a JSON data structure.
|
// SetObjectMeta writes back ObjectMeta into a JSON data structure.
|
||||||
|
@ -161,6 +161,194 @@ func TestMalformedObjectMetaFields(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetObjectMetaWithOptions(t *testing.T) {
|
||||||
|
unknownAndMalformed := map[string]interface{}{
|
||||||
|
"kind": "Pod",
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "my-meta",
|
||||||
|
"unknownField": "foo",
|
||||||
|
"generateName": nil,
|
||||||
|
"generation": nil,
|
||||||
|
"labels": map[string]string{
|
||||||
|
"foo": "bar",
|
||||||
|
},
|
||||||
|
"annotations": 11,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
unknownOnly := map[string]interface{}{
|
||||||
|
"kind": "Pod",
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "my-meta",
|
||||||
|
"unknownField": "foo",
|
||||||
|
"generateName": nil,
|
||||||
|
"generation": nil,
|
||||||
|
"labels": map[string]string{
|
||||||
|
"foo": "bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
malformedOnly := map[string]interface{}{
|
||||||
|
"kind": "Pod",
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "my-meta",
|
||||||
|
"generateName": nil,
|
||||||
|
"generation": nil,
|
||||||
|
"labels": map[string]string{
|
||||||
|
"foo": "bar",
|
||||||
|
},
|
||||||
|
"annotations": 11,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var testcases = []struct {
|
||||||
|
obj map[string]interface{}
|
||||||
|
dropMalformedFields bool
|
||||||
|
returnUnknownFieldPaths bool
|
||||||
|
expectedObject *metav1.ObjectMeta
|
||||||
|
expectedUnknownPaths []string
|
||||||
|
expectedErr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
obj: unknownAndMalformed,
|
||||||
|
dropMalformedFields: false,
|
||||||
|
returnUnknownFieldPaths: false,
|
||||||
|
expectedErr: "json: cannot unmarshal number into Go struct field ObjectMeta.annotations of type map[string]string",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
obj: unknownAndMalformed,
|
||||||
|
dropMalformedFields: true,
|
||||||
|
returnUnknownFieldPaths: false,
|
||||||
|
expectedObject: &metav1.ObjectMeta{
|
||||||
|
Name: "my-meta",
|
||||||
|
Labels: map[string]string{
|
||||||
|
"foo": "bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
obj: unknownAndMalformed,
|
||||||
|
dropMalformedFields: false,
|
||||||
|
returnUnknownFieldPaths: true,
|
||||||
|
expectedErr: "json: cannot unmarshal number into Go struct field ObjectMeta.annotations of type map[string]string",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
obj: unknownAndMalformed,
|
||||||
|
dropMalformedFields: true,
|
||||||
|
returnUnknownFieldPaths: true,
|
||||||
|
expectedObject: &metav1.ObjectMeta{
|
||||||
|
Name: "my-meta",
|
||||||
|
Labels: map[string]string{
|
||||||
|
"foo": "bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedUnknownPaths: []string{"metadata.unknownField"},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
obj: unknownOnly,
|
||||||
|
dropMalformedFields: false,
|
||||||
|
returnUnknownFieldPaths: false,
|
||||||
|
expectedObject: &metav1.ObjectMeta{
|
||||||
|
Name: "my-meta",
|
||||||
|
Labels: map[string]string{
|
||||||
|
"foo": "bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
obj: unknownOnly,
|
||||||
|
dropMalformedFields: true,
|
||||||
|
returnUnknownFieldPaths: false,
|
||||||
|
expectedObject: &metav1.ObjectMeta{
|
||||||
|
Name: "my-meta",
|
||||||
|
Labels: map[string]string{
|
||||||
|
"foo": "bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
obj: unknownOnly,
|
||||||
|
dropMalformedFields: false,
|
||||||
|
returnUnknownFieldPaths: true,
|
||||||
|
expectedObject: &metav1.ObjectMeta{
|
||||||
|
Name: "my-meta",
|
||||||
|
Labels: map[string]string{
|
||||||
|
"foo": "bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedUnknownPaths: []string{"metadata.unknownField"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
obj: unknownOnly,
|
||||||
|
dropMalformedFields: true,
|
||||||
|
returnUnknownFieldPaths: true,
|
||||||
|
expectedObject: &metav1.ObjectMeta{
|
||||||
|
Name: "my-meta",
|
||||||
|
Labels: map[string]string{
|
||||||
|
"foo": "bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedUnknownPaths: []string{"metadata.unknownField"},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
obj: malformedOnly,
|
||||||
|
dropMalformedFields: false,
|
||||||
|
returnUnknownFieldPaths: false,
|
||||||
|
expectedErr: "json: cannot unmarshal number into Go struct field ObjectMeta.annotations of type map[string]string",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
obj: malformedOnly,
|
||||||
|
dropMalformedFields: true,
|
||||||
|
returnUnknownFieldPaths: false,
|
||||||
|
expectedObject: &metav1.ObjectMeta{
|
||||||
|
Name: "my-meta",
|
||||||
|
Labels: map[string]string{
|
||||||
|
"foo": "bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
obj: malformedOnly,
|
||||||
|
dropMalformedFields: false,
|
||||||
|
returnUnknownFieldPaths: true,
|
||||||
|
expectedErr: "json: cannot unmarshal number into Go struct field ObjectMeta.annotations of type map[string]string",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
obj: malformedOnly,
|
||||||
|
dropMalformedFields: true,
|
||||||
|
returnUnknownFieldPaths: true,
|
||||||
|
expectedObject: &metav1.ObjectMeta{
|
||||||
|
Name: "my-meta",
|
||||||
|
Labels: map[string]string{
|
||||||
|
"foo": "bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range testcases {
|
||||||
|
opts := ObjectMetaOptions{
|
||||||
|
ReturnUnknownFieldPaths: tc.returnUnknownFieldPaths,
|
||||||
|
DropMalformedFields: tc.dropMalformedFields,
|
||||||
|
}
|
||||||
|
obj, _, unknownPaths, err := GetObjectMetaWithOptions(tc.obj, opts)
|
||||||
|
if !reflect.DeepEqual(tc.expectedObject, obj) {
|
||||||
|
t.Errorf("expected: %v, got: %v", tc.expectedObject, obj)
|
||||||
|
}
|
||||||
|
if (err == nil && tc.expectedErr != "") || err != nil && (err.Error() != tc.expectedErr) {
|
||||||
|
t.Errorf("expected: %v, got: %v", tc.expectedErr, err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(tc.expectedUnknownPaths, unknownPaths) {
|
||||||
|
t.Errorf("expected: %v, got: %v", tc.expectedUnknownPaths, unknownPaths)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestGetObjectMetaNils(t *testing.T) {
|
func TestGetObjectMetaNils(t *testing.T) {
|
||||||
u := &unstructured.Unstructured{
|
u := &unstructured.Unstructured{
|
||||||
Object: map[string]interface{}{
|
Object: map[string]interface{}{
|
||||||
|
@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2022 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 schema
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UnknownFieldPathOptions allow for tracking paths to unknown fields.
|
||||||
|
type UnknownFieldPathOptions struct {
|
||||||
|
// TrackUnknownFieldPaths determines whether or not unknown field
|
||||||
|
// paths should be stored or not.
|
||||||
|
TrackUnknownFieldPaths bool
|
||||||
|
// ParentPath builds the path to unknown fields as the object
|
||||||
|
// is recursively traversed.
|
||||||
|
ParentPath []string
|
||||||
|
// UnknownFieldPaths is the list of all unknown fields identified.
|
||||||
|
UnknownFieldPaths []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// RecordUnknownFields adds a path to an unknown field to the
|
||||||
|
// record of UnknownFieldPaths, if TrackUnknownFieldPaths is true
|
||||||
|
func (o *UnknownFieldPathOptions) RecordUnknownField(field string) {
|
||||||
|
if !o.TrackUnknownFieldPaths {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
l := len(o.ParentPath)
|
||||||
|
o.AppendKey(field)
|
||||||
|
o.UnknownFieldPaths = append(o.UnknownFieldPaths, strings.Join(o.ParentPath, ""))
|
||||||
|
o.ParentPath = o.ParentPath[:l]
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppendKey adds a key (i.e. field) to the current parent
|
||||||
|
// path, if TrackUnknownFieldPaths is true.
|
||||||
|
func (o *UnknownFieldPathOptions) AppendKey(key string) {
|
||||||
|
if !o.TrackUnknownFieldPaths {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(o.ParentPath) > 0 {
|
||||||
|
o.ParentPath = append(o.ParentPath, ".")
|
||||||
|
}
|
||||||
|
o.ParentPath = append(o.ParentPath, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppendIndex adds an index to the most recent field of
|
||||||
|
// the current parent path, if TrackUnknownFieldPaths is true.
|
||||||
|
func (o *UnknownFieldPathOptions) AppendIndex(index int) {
|
||||||
|
if !o.TrackUnknownFieldPaths {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
o.ParentPath = append(o.ParentPath, "[", strconv.Itoa(index), "]")
|
||||||
|
}
|
@ -18,37 +18,15 @@ package pruning
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
structuralschema "k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
|
structuralschema "k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PruneOptions sets options for pruning
|
|
||||||
// unknown fields
|
|
||||||
type PruneOptions struct {
|
|
||||||
// parentPath collects the path that the pruning
|
|
||||||
// takes as it traverses the object.
|
|
||||||
// It is used to report the full path to any unknown
|
|
||||||
// fields that the pruning encounters.
|
|
||||||
// It is only populated if ReturnPruned is true.
|
|
||||||
parentPath []string
|
|
||||||
|
|
||||||
// prunedPaths collects pruned field paths resulting from
|
|
||||||
// calls to recordPrunedKey.
|
|
||||||
// It is only populated if ReturnPruned is true.
|
|
||||||
prunedPaths []string
|
|
||||||
|
|
||||||
// ReturnPruned defines whether we want to track the
|
|
||||||
// fields that are pruned
|
|
||||||
ReturnPruned bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// PruneWithOptions removes object fields in obj which are not specified in s. It skips TypeMeta
|
// PruneWithOptions removes object fields in obj which are not specified in s. It skips TypeMeta
|
||||||
// and ObjectMeta fields if XEmbeddedResource is set to true, or for the root if isResourceRoot=true,
|
// and ObjectMeta fields if XEmbeddedResource is set to true, or for the root if isResourceRoot=true,
|
||||||
// i.e. it does not prune unknown metadata fields.
|
// i.e. it does not prune unknown metadata fields.
|
||||||
// It returns the set of fields that it prunes if opts.ReturnPruned is true
|
// It returns the set of fields that it prunes if opts.TrackUnknownFieldPaths is true
|
||||||
func PruneWithOptions(obj interface{}, s *structuralschema.Structural, isResourceRoot bool, opts PruneOptions) []string {
|
func PruneWithOptions(obj interface{}, s *structuralschema.Structural, isResourceRoot bool, opts structuralschema.UnknownFieldPathOptions) []string {
|
||||||
if isResourceRoot {
|
if isResourceRoot {
|
||||||
if s == nil {
|
if s == nil {
|
||||||
s = &structuralschema.Structural{}
|
s = &structuralschema.Structural{}
|
||||||
@ -60,14 +38,14 @@ func PruneWithOptions(obj interface{}, s *structuralschema.Structural, isResourc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
prune(obj, s, &opts)
|
prune(obj, s, &opts)
|
||||||
sort.Strings(opts.prunedPaths)
|
sort.Strings(opts.UnknownFieldPaths)
|
||||||
return opts.prunedPaths
|
return opts.UnknownFieldPaths
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prune is equivalent to
|
// Prune is equivalent to
|
||||||
// PruneWithOptions(obj, s, isResourceRoot, PruneOptions{})
|
// PruneWithOptions(obj, s, isResourceRoot, structuralschema.UnknownFieldPathOptions{})
|
||||||
func Prune(obj interface{}, s *structuralschema.Structural, isResourceRoot bool) {
|
func Prune(obj interface{}, s *structuralschema.Structural, isResourceRoot bool) {
|
||||||
PruneWithOptions(obj, s, isResourceRoot, PruneOptions{})
|
PruneWithOptions(obj, s, isResourceRoot, structuralschema.UnknownFieldPathOptions{})
|
||||||
}
|
}
|
||||||
|
|
||||||
var metaFields = map[string]bool{
|
var metaFields = map[string]bool{
|
||||||
@ -76,48 +54,21 @@ var metaFields = map[string]bool{
|
|||||||
"metadata": true,
|
"metadata": true,
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *PruneOptions) recordPrunedKey(key string) {
|
func prune(x interface{}, s *structuralschema.Structural, opts *structuralschema.UnknownFieldPathOptions) {
|
||||||
if !p.ReturnPruned {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
l := len(p.parentPath)
|
|
||||||
p.appendKey(key)
|
|
||||||
p.prunedPaths = append(p.prunedPaths, strings.Join(p.parentPath, ""))
|
|
||||||
p.parentPath = p.parentPath[:l]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *PruneOptions) appendKey(key string) {
|
|
||||||
if !p.ReturnPruned {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if len(p.parentPath) > 0 {
|
|
||||||
p.parentPath = append(p.parentPath, ".")
|
|
||||||
}
|
|
||||||
p.parentPath = append(p.parentPath, key)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *PruneOptions) appendIndex(index int) {
|
|
||||||
if !p.ReturnPruned {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
p.parentPath = append(p.parentPath, "[", strconv.Itoa(index), "]")
|
|
||||||
}
|
|
||||||
|
|
||||||
func prune(x interface{}, s *structuralschema.Structural, opts *PruneOptions) {
|
|
||||||
if s != nil && s.XPreserveUnknownFields {
|
if s != nil && s.XPreserveUnknownFields {
|
||||||
skipPrune(x, s, opts)
|
skipPrune(x, s, opts)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
origPathLen := len(opts.parentPath)
|
origPathLen := len(opts.ParentPath)
|
||||||
defer func() {
|
defer func() {
|
||||||
opts.parentPath = opts.parentPath[:origPathLen]
|
opts.ParentPath = opts.ParentPath[:origPathLen]
|
||||||
}()
|
}()
|
||||||
switch x := x.(type) {
|
switch x := x.(type) {
|
||||||
case map[string]interface{}:
|
case map[string]interface{}:
|
||||||
if s == nil {
|
if s == nil {
|
||||||
for k := range x {
|
for k := range x {
|
||||||
opts.recordPrunedKey(k)
|
opts.RecordUnknownField(k)
|
||||||
delete(x, k)
|
delete(x, k)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
@ -128,16 +79,16 @@ func prune(x interface{}, s *structuralschema.Structural, opts *PruneOptions) {
|
|||||||
}
|
}
|
||||||
prop, ok := s.Properties[k]
|
prop, ok := s.Properties[k]
|
||||||
if ok {
|
if ok {
|
||||||
opts.appendKey(k)
|
opts.AppendKey(k)
|
||||||
prune(v, &prop, opts)
|
prune(v, &prop, opts)
|
||||||
opts.parentPath = opts.parentPath[:origPathLen]
|
opts.ParentPath = opts.ParentPath[:origPathLen]
|
||||||
} else if s.AdditionalProperties != nil {
|
} else if s.AdditionalProperties != nil {
|
||||||
opts.appendKey(k)
|
opts.AppendKey(k)
|
||||||
prune(v, s.AdditionalProperties.Structural, opts)
|
prune(v, s.AdditionalProperties.Structural, opts)
|
||||||
opts.parentPath = opts.parentPath[:origPathLen]
|
opts.ParentPath = opts.ParentPath[:origPathLen]
|
||||||
} else {
|
} else {
|
||||||
if !metaFields[k] || len(opts.parentPath) > 0 {
|
if !metaFields[k] || len(opts.ParentPath) > 0 {
|
||||||
opts.recordPrunedKey(k)
|
opts.RecordUnknownField(k)
|
||||||
}
|
}
|
||||||
delete(x, k)
|
delete(x, k)
|
||||||
}
|
}
|
||||||
@ -145,29 +96,29 @@ func prune(x interface{}, s *structuralschema.Structural, opts *PruneOptions) {
|
|||||||
case []interface{}:
|
case []interface{}:
|
||||||
if s == nil {
|
if s == nil {
|
||||||
for i, v := range x {
|
for i, v := range x {
|
||||||
opts.appendIndex(i)
|
opts.AppendIndex(i)
|
||||||
prune(v, nil, opts)
|
prune(v, nil, opts)
|
||||||
opts.parentPath = opts.parentPath[:origPathLen]
|
opts.ParentPath = opts.ParentPath[:origPathLen]
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for i, v := range x {
|
for i, v := range x {
|
||||||
opts.appendIndex(i)
|
opts.AppendIndex(i)
|
||||||
prune(v, s.Items, opts)
|
prune(v, s.Items, opts)
|
||||||
opts.parentPath = opts.parentPath[:origPathLen]
|
opts.ParentPath = opts.ParentPath[:origPathLen]
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
// scalars, do nothing
|
// scalars, do nothing
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func skipPrune(x interface{}, s *structuralschema.Structural, opts *PruneOptions) {
|
func skipPrune(x interface{}, s *structuralschema.Structural, opts *structuralschema.UnknownFieldPathOptions) {
|
||||||
if s == nil {
|
if s == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
origPathLen := len(opts.parentPath)
|
origPathLen := len(opts.ParentPath)
|
||||||
defer func() {
|
defer func() {
|
||||||
opts.parentPath = opts.parentPath[:origPathLen]
|
opts.ParentPath = opts.ParentPath[:origPathLen]
|
||||||
}()
|
}()
|
||||||
|
|
||||||
switch x := x.(type) {
|
switch x := x.(type) {
|
||||||
@ -177,20 +128,20 @@ func skipPrune(x interface{}, s *structuralschema.Structural, opts *PruneOptions
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if prop, ok := s.Properties[k]; ok {
|
if prop, ok := s.Properties[k]; ok {
|
||||||
opts.appendKey(k)
|
opts.AppendKey(k)
|
||||||
prune(v, &prop, opts)
|
prune(v, &prop, opts)
|
||||||
opts.parentPath = opts.parentPath[:origPathLen]
|
opts.ParentPath = opts.ParentPath[:origPathLen]
|
||||||
} else if s.AdditionalProperties != nil {
|
} else if s.AdditionalProperties != nil {
|
||||||
opts.appendKey(k)
|
opts.AppendKey(k)
|
||||||
prune(v, s.AdditionalProperties.Structural, opts)
|
prune(v, s.AdditionalProperties.Structural, opts)
|
||||||
opts.parentPath = opts.parentPath[:origPathLen]
|
opts.ParentPath = opts.ParentPath[:origPathLen]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case []interface{}:
|
case []interface{}:
|
||||||
for i, v := range x {
|
for i, v := range x {
|
||||||
opts.appendIndex(i)
|
opts.AppendIndex(i)
|
||||||
skipPrune(v, s.Items, opts)
|
skipPrune(v, s.Items, opts)
|
||||||
opts.parentPath = opts.parentPath[:origPathLen]
|
opts.ParentPath = opts.ParentPath[:origPathLen]
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
// scalars, do nothing
|
// scalars, do nothing
|
||||||
|
@ -575,8 +575,8 @@ func TestPrune(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
pruned := PruneWithOptions(in, tt.schema, tt.isResourceRoot, PruneOptions{
|
pruned := PruneWithOptions(in, tt.schema, tt.isResourceRoot, structuralschema.UnknownFieldPathOptions{
|
||||||
ReturnPruned: true,
|
TrackUnknownFieldPaths: true,
|
||||||
})
|
})
|
||||||
if !reflect.DeepEqual(in, expectedObject) {
|
if !reflect.DeepEqual(in, expectedObject) {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
@ -592,8 +592,8 @@ func TestPrune(t *testing.T) {
|
|||||||
t.Errorf("expected pruned:\n\t%v\ngot:\n\t%v\n", strings.Join(tt.expectedPruned, "\n\t"), strings.Join(pruned, "\n\t"))
|
t.Errorf("expected pruned:\n\t%v\ngot:\n\t%v\n", strings.Join(tt.expectedPruned, "\n\t"), strings.Join(pruned, "\n\t"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// now check that pruned is empty when ReturnPruned is false
|
// now check that pruned is empty when TrackUnknownFieldPaths is false
|
||||||
emptyPruned := PruneWithOptions(in, tt.schema, tt.isResourceRoot, PruneOptions{})
|
emptyPruned := PruneWithOptions(in, tt.schema, tt.isResourceRoot, structuralschema.UnknownFieldPathOptions{})
|
||||||
if !reflect.DeepEqual(in, expectedObject) {
|
if !reflect.DeepEqual(in, expectedObject) {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
enc := json.NewEncoder(&buf)
|
enc := json.NewEncoder(&buf)
|
||||||
|
@ -38,6 +38,7 @@ import (
|
|||||||
clientset "k8s.io/client-go/kubernetes"
|
clientset "k8s.io/client-go/kubernetes"
|
||||||
"k8s.io/client-go/rest"
|
"k8s.io/client-go/rest"
|
||||||
featuregatetesting "k8s.io/component-base/featuregate/testing"
|
featuregatetesting "k8s.io/component-base/featuregate/testing"
|
||||||
|
"k8s.io/klog/v2"
|
||||||
kubeapiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing"
|
kubeapiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing"
|
||||||
|
|
||||||
"k8s.io/kubernetes/test/integration/framework"
|
"k8s.io/kubernetes/test/integration/framework"
|
||||||
@ -49,8 +50,10 @@ var (
|
|||||||
"apiVersion": "apps/v1",
|
"apiVersion": "apps/v1",
|
||||||
"kind": "Deployment",
|
"kind": "Deployment",
|
||||||
"metadata": {
|
"metadata": {
|
||||||
|
"name": "dupename",
|
||||||
"name": "%s",
|
"name": "%s",
|
||||||
"labels": {"app": "nginx"}
|
"labels": {"app": "nginx"},
|
||||||
|
"unknownMeta": "metaVal"
|
||||||
},
|
},
|
||||||
"spec": {
|
"spec": {
|
||||||
"unknown1": "val1",
|
"unknown1": "val1",
|
||||||
@ -118,7 +121,9 @@ var (
|
|||||||
invalidBodyYAML = `apiVersion: apps/v1
|
invalidBodyYAML = `apiVersion: apps/v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
|
name: dupename
|
||||||
name: %s
|
name: %s
|
||||||
|
unknownMeta: metaVal
|
||||||
labels:
|
labels:
|
||||||
app: nginx
|
app: nginx
|
||||||
spec:
|
spec:
|
||||||
@ -236,7 +241,9 @@ spec:
|
|||||||
"apiVersion": "%s",
|
"apiVersion": "%s",
|
||||||
"kind": "%s",
|
"kind": "%s",
|
||||||
"metadata": {
|
"metadata": {
|
||||||
|
"name": "dupename",
|
||||||
"name": "%s",
|
"name": "%s",
|
||||||
|
"unknownMeta": "metaVal",
|
||||||
"resourceVersion": "%s"
|
"resourceVersion": "%s"
|
||||||
},
|
},
|
||||||
"spec": {
|
"spec": {
|
||||||
@ -252,7 +259,16 @@ spec:
|
|||||||
"hostPort": 8081,
|
"hostPort": 8081,
|
||||||
"hostPort": 8082,
|
"hostPort": 8082,
|
||||||
"unknownNested": "val"
|
"unknownNested": "val"
|
||||||
}]
|
}],
|
||||||
|
"embeddedObj": {
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "ConfigMap",
|
||||||
|
"metadata": {
|
||||||
|
"name": "my-cm",
|
||||||
|
"namespace": "my-ns",
|
||||||
|
"unknownEmbeddedMeta": "foo"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
|
|
||||||
@ -271,7 +287,14 @@ spec:
|
|||||||
"containerPort": 8080,
|
"containerPort": 8080,
|
||||||
"protocol": "TCP",
|
"protocol": "TCP",
|
||||||
"hostPort": 8081
|
"hostPort": 8081
|
||||||
}]
|
}],
|
||||||
|
"embeddedObj": {
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "ConfigMap",
|
||||||
|
"metadata": {
|
||||||
|
"name": "my-cm"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
@ -280,8 +303,10 @@ spec:
|
|||||||
apiVersion: "%s"
|
apiVersion: "%s"
|
||||||
kind: "%s"
|
kind: "%s"
|
||||||
metadata:
|
metadata:
|
||||||
|
name: dupename
|
||||||
name: "%s"
|
name: "%s"
|
||||||
resourceVersion: "%s"
|
resourceVersion: "%s"
|
||||||
|
unknownMeta: metaVal
|
||||||
spec:
|
spec:
|
||||||
unknown1: val1
|
unknown1: val1
|
||||||
unknownDupe: valDupe
|
unknownDupe: valDupe
|
||||||
@ -294,7 +319,14 @@ spec:
|
|||||||
protocol: TCP
|
protocol: TCP
|
||||||
hostPort: 8081
|
hostPort: 8081
|
||||||
hostPort: 8082
|
hostPort: 8082
|
||||||
unknownNested: val`
|
unknownNested: val
|
||||||
|
embeddedObj:
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: my-cm
|
||||||
|
namespace: my-ns
|
||||||
|
unknownEmbeddedMeta: foo`
|
||||||
|
|
||||||
crdValidBodyYAML = `
|
crdValidBodyYAML = `
|
||||||
apiVersion: "%s"
|
apiVersion: "%s"
|
||||||
@ -308,7 +340,13 @@ spec:
|
|||||||
- name: portName
|
- name: portName
|
||||||
containerPort: 8080
|
containerPort: 8080
|
||||||
protocol: TCP
|
protocol: TCP
|
||||||
hostPort: 8081`
|
hostPort: 8081
|
||||||
|
embeddedObj:
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: my-cm
|
||||||
|
namespace: my-ns`
|
||||||
|
|
||||||
crdApplyInvalidBody = `
|
crdApplyInvalidBody = `
|
||||||
{
|
{
|
||||||
@ -326,7 +364,15 @@ spec:
|
|||||||
"protocol": "TCP",
|
"protocol": "TCP",
|
||||||
"hostPort": 8081,
|
"hostPort": 8081,
|
||||||
"hostPort": 8082
|
"hostPort": 8082
|
||||||
}]
|
}],
|
||||||
|
"embeddedObj": {
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "ConfigMap",
|
||||||
|
"metadata": {
|
||||||
|
"name": "my-cm",
|
||||||
|
"namespace": "my-ns"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
|
|
||||||
@ -344,7 +390,15 @@ spec:
|
|||||||
"containerPort": 8080,
|
"containerPort": 8080,
|
||||||
"protocol": "TCP",
|
"protocol": "TCP",
|
||||||
"hostPort": 8082
|
"hostPort": 8082
|
||||||
}]
|
}],
|
||||||
|
"embeddedObj": {
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "ConfigMap",
|
||||||
|
"metadata": {
|
||||||
|
"name": "my-cm",
|
||||||
|
"namespace": "my-ns"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
|
|
||||||
@ -362,7 +416,15 @@ spec:
|
|||||||
"containerPort": 8080,
|
"containerPort": 8080,
|
||||||
"protocol": "TCP",
|
"protocol": "TCP",
|
||||||
"hostPort": 8083
|
"hostPort": 8083
|
||||||
}]
|
}],
|
||||||
|
"embeddedObj": {
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "ConfigMap",
|
||||||
|
"metadata": {
|
||||||
|
"name": "my-cm",
|
||||||
|
"namespace": "my-ns"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
|
|
||||||
@ -397,6 +459,21 @@ spec:
|
|||||||
"knownField1": {
|
"knownField1": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"embeddedObj": {
|
||||||
|
"x-kubernetes-embedded-resource": true,
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"apiversion": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"kind": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"ports": {
|
"ports": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"x-kubernetes-list-map-keys": [
|
"x-kubernetes-list-map-keys": [
|
||||||
@ -516,7 +593,7 @@ func testFieldValidationPost(t *testing.T, client clientset.Interface) {
|
|||||||
FieldValidation: "Strict",
|
FieldValidation: "Strict",
|
||||||
},
|
},
|
||||||
bodyBase: invalidBodyJSON,
|
bodyBase: invalidBodyJSON,
|
||||||
strictDecodingError: `strict decoding error: unknown field "spec.unknown1", unknown field "spec.unknownDupe", duplicate field "spec.paused", unknown field "spec.template.spec.containers[0].unknownNested", duplicate field "spec.template.spec.containers[0].imagePullPolicy"`,
|
strictDecodingError: `strict decoding error: duplicate field "metadata.name", unknown field "metadata.unknownMeta", unknown field "spec.unknown1", unknown field "spec.unknownDupe", duplicate field "spec.paused", unknown field "spec.template.spec.containers[0].unknownNested", duplicate field "spec.template.spec.containers[0].imagePullPolicy"`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "post-warn-validation",
|
name: "post-warn-validation",
|
||||||
@ -525,6 +602,8 @@ func testFieldValidationPost(t *testing.T, client clientset.Interface) {
|
|||||||
},
|
},
|
||||||
bodyBase: invalidBodyJSON,
|
bodyBase: invalidBodyJSON,
|
||||||
strictDecodingWarnings: []string{
|
strictDecodingWarnings: []string{
|
||||||
|
`duplicate field "metadata.name"`,
|
||||||
|
`unknown field "metadata.unknownMeta"`,
|
||||||
`unknown field "spec.unknown1"`,
|
`unknown field "spec.unknown1"`,
|
||||||
`unknown field "spec.unknownDupe"`,
|
`unknown field "spec.unknownDupe"`,
|
||||||
`duplicate field "spec.paused"`,
|
`duplicate field "spec.paused"`,
|
||||||
@ -546,6 +625,8 @@ func testFieldValidationPost(t *testing.T, client clientset.Interface) {
|
|||||||
name: "post-no-validation",
|
name: "post-no-validation",
|
||||||
bodyBase: invalidBodyJSON,
|
bodyBase: invalidBodyJSON,
|
||||||
strictDecodingWarnings: []string{
|
strictDecodingWarnings: []string{
|
||||||
|
`duplicate field "metadata.name"`,
|
||||||
|
`unknown field "metadata.unknownMeta"`,
|
||||||
`unknown field "spec.unknown1"`,
|
`unknown field "spec.unknown1"`,
|
||||||
`unknown field "spec.unknownDupe"`,
|
`unknown field "spec.unknownDupe"`,
|
||||||
`duplicate field "spec.paused"`,
|
`duplicate field "spec.paused"`,
|
||||||
@ -564,9 +645,10 @@ func testFieldValidationPost(t *testing.T, client clientset.Interface) {
|
|||||||
bodyBase: invalidBodyYAML,
|
bodyBase: invalidBodyYAML,
|
||||||
contentType: "application/yaml",
|
contentType: "application/yaml",
|
||||||
strictDecodingError: `strict decoding error: yaml: unmarshal errors:
|
strictDecodingError: `strict decoding error: yaml: unmarshal errors:
|
||||||
line 10: key "unknownDupe" already set in map
|
line 5: key "name" already set in map
|
||||||
line 12: key "paused" already set in map
|
line 12: key "unknownDupe" already set in map
|
||||||
line 26: key "imagePullPolicy" already set in map, unknown field "spec.template.spec.containers[0].unknownNested", unknown field "spec.unknown1", unknown field "spec.unknownDupe"`,
|
line 14: key "paused" already set in map
|
||||||
|
line 28: key "imagePullPolicy" already set in map, unknown field "metadata.unknownMeta", unknown field "spec.template.spec.containers[0].unknownNested", unknown field "spec.unknown1", unknown field "spec.unknownDupe"`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "post-warn-validation-yaml",
|
name: "post-warn-validation-yaml",
|
||||||
@ -576,9 +658,11 @@ func testFieldValidationPost(t *testing.T, client clientset.Interface) {
|
|||||||
bodyBase: invalidBodyYAML,
|
bodyBase: invalidBodyYAML,
|
||||||
contentType: "application/yaml",
|
contentType: "application/yaml",
|
||||||
strictDecodingWarnings: []string{
|
strictDecodingWarnings: []string{
|
||||||
`line 10: key "unknownDupe" already set in map`,
|
`line 5: key "name" already set in map`,
|
||||||
`line 12: key "paused" already set in map`,
|
`line 12: key "unknownDupe" already set in map`,
|
||||||
`line 26: key "imagePullPolicy" already set in map`,
|
`line 14: key "paused" already set in map`,
|
||||||
|
`line 28: key "imagePullPolicy" already set in map`,
|
||||||
|
`unknown field "metadata.unknownMeta"`,
|
||||||
`unknown field "spec.template.spec.containers[0].unknownNested"`,
|
`unknown field "spec.template.spec.containers[0].unknownNested"`,
|
||||||
`unknown field "spec.unknown1"`,
|
`unknown field "spec.unknown1"`,
|
||||||
`unknown field "spec.unknownDupe"`,
|
`unknown field "spec.unknownDupe"`,
|
||||||
@ -597,9 +681,11 @@ func testFieldValidationPost(t *testing.T, client clientset.Interface) {
|
|||||||
bodyBase: invalidBodyYAML,
|
bodyBase: invalidBodyYAML,
|
||||||
contentType: "application/yaml",
|
contentType: "application/yaml",
|
||||||
strictDecodingWarnings: []string{
|
strictDecodingWarnings: []string{
|
||||||
`line 10: key "unknownDupe" already set in map`,
|
`line 5: key "name" already set in map`,
|
||||||
`line 12: key "paused" already set in map`,
|
`line 12: key "unknownDupe" already set in map`,
|
||||||
`line 26: key "imagePullPolicy" already set in map`,
|
`line 14: key "paused" already set in map`,
|
||||||
|
`line 28: key "imagePullPolicy" already set in map`,
|
||||||
|
`unknown field "metadata.unknownMeta"`,
|
||||||
`unknown field "spec.template.spec.containers[0].unknownNested"`,
|
`unknown field "spec.template.spec.containers[0].unknownNested"`,
|
||||||
`unknown field "spec.unknown1"`,
|
`unknown field "spec.unknown1"`,
|
||||||
`unknown field "spec.unknownDupe"`,
|
`unknown field "spec.unknownDupe"`,
|
||||||
@ -609,6 +695,7 @@ func testFieldValidationPost(t *testing.T, client clientset.Interface) {
|
|||||||
|
|
||||||
for _, tc := range testcases {
|
for _, tc := range testcases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
klog.Warningf("running tc named: %s", tc.name)
|
||||||
body := []byte(fmt.Sprintf(tc.bodyBase, fmt.Sprintf("test-deployment-%s", tc.name)))
|
body := []byte(fmt.Sprintf(tc.bodyBase, fmt.Sprintf("test-deployment-%s", tc.name)))
|
||||||
req := client.CoreV1().RESTClient().Post().
|
req := client.CoreV1().RESTClient().Post().
|
||||||
AbsPath("/apis/apps/v1").
|
AbsPath("/apis/apps/v1").
|
||||||
@ -667,7 +754,7 @@ func testFieldValidationPut(t *testing.T, client clientset.Interface) {
|
|||||||
FieldValidation: "Strict",
|
FieldValidation: "Strict",
|
||||||
},
|
},
|
||||||
putBodyBase: invalidBodyJSON,
|
putBodyBase: invalidBodyJSON,
|
||||||
strictDecodingError: `strict decoding error: unknown field "spec.unknown1", unknown field "spec.unknownDupe", duplicate field "spec.paused", unknown field "spec.template.spec.containers[0].unknownNested", duplicate field "spec.template.spec.containers[0].imagePullPolicy"`,
|
strictDecodingError: `strict decoding error: duplicate field "metadata.name", unknown field "metadata.unknownMeta", unknown field "spec.unknown1", unknown field "spec.unknownDupe", duplicate field "spec.paused", unknown field "spec.template.spec.containers[0].unknownNested", duplicate field "spec.template.spec.containers[0].imagePullPolicy"`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "put-warn-validation",
|
name: "put-warn-validation",
|
||||||
@ -676,6 +763,8 @@ func testFieldValidationPut(t *testing.T, client clientset.Interface) {
|
|||||||
},
|
},
|
||||||
putBodyBase: invalidBodyJSON,
|
putBodyBase: invalidBodyJSON,
|
||||||
strictDecodingWarnings: []string{
|
strictDecodingWarnings: []string{
|
||||||
|
`duplicate field "metadata.name"`,
|
||||||
|
`unknown field "metadata.unknownMeta"`,
|
||||||
`unknown field "spec.unknown1"`,
|
`unknown field "spec.unknown1"`,
|
||||||
`unknown field "spec.unknownDupe"`,
|
`unknown field "spec.unknownDupe"`,
|
||||||
`duplicate field "spec.paused"`,
|
`duplicate field "spec.paused"`,
|
||||||
@ -687,16 +776,18 @@ func testFieldValidationPut(t *testing.T, client clientset.Interface) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "put-default-ignore-validation",
|
name: "put-ignore-validation",
|
||||||
opts: metav1.UpdateOptions{
|
opts: metav1.UpdateOptions{
|
||||||
FieldValidation: "Ignore",
|
FieldValidation: "Ignore",
|
||||||
},
|
},
|
||||||
putBodyBase: invalidBodyJSON,
|
putBodyBase: invalidBodyJSON,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "put-ignore-validation",
|
name: "put-no-validation",
|
||||||
putBodyBase: invalidBodyJSON,
|
putBodyBase: invalidBodyJSON,
|
||||||
strictDecodingWarnings: []string{
|
strictDecodingWarnings: []string{
|
||||||
|
`duplicate field "metadata.name"`,
|
||||||
|
`unknown field "metadata.unknownMeta"`,
|
||||||
`unknown field "spec.unknown1"`,
|
`unknown field "spec.unknown1"`,
|
||||||
`unknown field "spec.unknownDupe"`,
|
`unknown field "spec.unknownDupe"`,
|
||||||
`duplicate field "spec.paused"`,
|
`duplicate field "spec.paused"`,
|
||||||
@ -715,9 +806,10 @@ func testFieldValidationPut(t *testing.T, client clientset.Interface) {
|
|||||||
putBodyBase: invalidBodyYAML,
|
putBodyBase: invalidBodyYAML,
|
||||||
contentType: "application/yaml",
|
contentType: "application/yaml",
|
||||||
strictDecodingError: `strict decoding error: yaml: unmarshal errors:
|
strictDecodingError: `strict decoding error: yaml: unmarshal errors:
|
||||||
line 10: key "unknownDupe" already set in map
|
line 5: key "name" already set in map
|
||||||
line 12: key "paused" already set in map
|
line 12: key "unknownDupe" already set in map
|
||||||
line 26: key "imagePullPolicy" already set in map, unknown field "spec.template.spec.containers[0].unknownNested", unknown field "spec.unknown1", unknown field "spec.unknownDupe"`,
|
line 14: key "paused" already set in map
|
||||||
|
line 28: key "imagePullPolicy" already set in map, unknown field "metadata.unknownMeta", unknown field "spec.template.spec.containers[0].unknownNested", unknown field "spec.unknown1", unknown field "spec.unknownDupe"`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "put-warn-validation-yaml",
|
name: "put-warn-validation-yaml",
|
||||||
@ -727,9 +819,11 @@ func testFieldValidationPut(t *testing.T, client clientset.Interface) {
|
|||||||
putBodyBase: invalidBodyYAML,
|
putBodyBase: invalidBodyYAML,
|
||||||
contentType: "application/yaml",
|
contentType: "application/yaml",
|
||||||
strictDecodingWarnings: []string{
|
strictDecodingWarnings: []string{
|
||||||
`line 10: key "unknownDupe" already set in map`,
|
`line 5: key "name" already set in map`,
|
||||||
`line 12: key "paused" already set in map`,
|
`line 12: key "unknownDupe" already set in map`,
|
||||||
`line 26: key "imagePullPolicy" already set in map`,
|
`line 14: key "paused" already set in map`,
|
||||||
|
`line 28: key "imagePullPolicy" already set in map`,
|
||||||
|
`unknown field "metadata.unknownMeta"`,
|
||||||
`unknown field "spec.template.spec.containers[0].unknownNested"`,
|
`unknown field "spec.template.spec.containers[0].unknownNested"`,
|
||||||
`unknown field "spec.unknown1"`,
|
`unknown field "spec.unknown1"`,
|
||||||
`unknown field "spec.unknownDupe"`,
|
`unknown field "spec.unknownDupe"`,
|
||||||
@ -748,9 +842,11 @@ func testFieldValidationPut(t *testing.T, client clientset.Interface) {
|
|||||||
putBodyBase: invalidBodyYAML,
|
putBodyBase: invalidBodyYAML,
|
||||||
contentType: "application/yaml",
|
contentType: "application/yaml",
|
||||||
strictDecodingWarnings: []string{
|
strictDecodingWarnings: []string{
|
||||||
`line 10: key "unknownDupe" already set in map`,
|
`line 5: key "name" already set in map`,
|
||||||
`line 12: key "paused" already set in map`,
|
`line 12: key "unknownDupe" already set in map`,
|
||||||
`line 26: key "imagePullPolicy" already set in map`,
|
`line 14: key "paused" already set in map`,
|
||||||
|
`line 28: key "imagePullPolicy" already set in map`,
|
||||||
|
`unknown field "metadata.unknownMeta"`,
|
||||||
`unknown field "spec.template.spec.containers[0].unknownNested"`,
|
`unknown field "spec.template.spec.containers[0].unknownNested"`,
|
||||||
`unknown field "spec.unknown1"`,
|
`unknown field "spec.unknown1"`,
|
||||||
`unknown field "spec.unknownDupe"`,
|
`unknown field "spec.unknownDupe"`,
|
||||||
@ -1421,7 +1517,7 @@ func testFieldValidationPostCRD(t *testing.T, rest rest.Interface, gvk schema.Gr
|
|||||||
FieldValidation: "Strict",
|
FieldValidation: "Strict",
|
||||||
},
|
},
|
||||||
body: crdInvalidBody,
|
body: crdInvalidBody,
|
||||||
strictDecodingError: `strict decoding error: duplicate field "spec.unknownDupe", duplicate field "spec.knownField1", duplicate field "spec.ports[0].hostPort", unknown field "spec.ports[0].unknownNested", unknown field "spec.unknown1", unknown field "spec.unknownDupe"`,
|
strictDecodingError: `strict decoding error: duplicate field "metadata.name", duplicate field "spec.unknownDupe", duplicate field "spec.knownField1", duplicate field "spec.ports[0].hostPort", unknown field "metadata.unknownMeta", unknown field "spec.ports[0].unknownNested", unknown field "spec.unknown1", unknown field "spec.unknownDupe", unknown field "spec.embeddedObj.metadata.unknownEmbeddedMeta"`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "crd-post-warn-validation",
|
name: "crd-post-warn-validation",
|
||||||
@ -1430,12 +1526,15 @@ func testFieldValidationPostCRD(t *testing.T, rest rest.Interface, gvk schema.Gr
|
|||||||
},
|
},
|
||||||
body: crdInvalidBody,
|
body: crdInvalidBody,
|
||||||
strictDecodingWarnings: []string{
|
strictDecodingWarnings: []string{
|
||||||
|
`duplicate field "metadata.name"`,
|
||||||
`duplicate field "spec.unknownDupe"`,
|
`duplicate field "spec.unknownDupe"`,
|
||||||
`duplicate field "spec.knownField1"`,
|
`duplicate field "spec.knownField1"`,
|
||||||
`duplicate field "spec.ports[0].hostPort"`,
|
`duplicate field "spec.ports[0].hostPort"`,
|
||||||
|
`unknown field "metadata.unknownMeta"`,
|
||||||
`unknown field "spec.ports[0].unknownNested"`,
|
`unknown field "spec.ports[0].unknownNested"`,
|
||||||
`unknown field "spec.unknown1"`,
|
`unknown field "spec.unknown1"`,
|
||||||
`unknown field "spec.unknownDupe"`,
|
`unknown field "spec.unknownDupe"`,
|
||||||
|
`unknown field "spec.embeddedObj.metadata.unknownEmbeddedMeta"`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1449,12 +1548,15 @@ func testFieldValidationPostCRD(t *testing.T, rest rest.Interface, gvk schema.Gr
|
|||||||
name: "crd-post-no-validation",
|
name: "crd-post-no-validation",
|
||||||
body: crdInvalidBody,
|
body: crdInvalidBody,
|
||||||
strictDecodingWarnings: []string{
|
strictDecodingWarnings: []string{
|
||||||
|
`duplicate field "metadata.name"`,
|
||||||
`duplicate field "spec.unknownDupe"`,
|
`duplicate field "spec.unknownDupe"`,
|
||||||
`duplicate field "spec.knownField1"`,
|
`duplicate field "spec.knownField1"`,
|
||||||
`duplicate field "spec.ports[0].hostPort"`,
|
`duplicate field "spec.ports[0].hostPort"`,
|
||||||
|
`unknown field "metadata.unknownMeta"`,
|
||||||
`unknown field "spec.ports[0].unknownNested"`,
|
`unknown field "spec.ports[0].unknownNested"`,
|
||||||
`unknown field "spec.unknown1"`,
|
`unknown field "spec.unknown1"`,
|
||||||
`unknown field "spec.unknownDupe"`,
|
`unknown field "spec.unknownDupe"`,
|
||||||
|
`unknown field "spec.embeddedObj.metadata.unknownEmbeddedMeta"`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1465,9 +1567,10 @@ func testFieldValidationPostCRD(t *testing.T, rest rest.Interface, gvk schema.Gr
|
|||||||
body: crdInvalidBodyYAML,
|
body: crdInvalidBodyYAML,
|
||||||
contentType: "application/yaml",
|
contentType: "application/yaml",
|
||||||
strictDecodingError: `strict decoding error: yaml: unmarshal errors:
|
strictDecodingError: `strict decoding error: yaml: unmarshal errors:
|
||||||
line 10: key "unknownDupe" already set in map
|
line 6: key "name" already set in map
|
||||||
line 12: key "knownField1" already set in map
|
line 12: key "unknownDupe" already set in map
|
||||||
line 18: key "hostPort" already set in map, unknown field "spec.ports[0].unknownNested", unknown field "spec.unknown1", unknown field "spec.unknownDupe"`,
|
line 14: key "knownField1" already set in map
|
||||||
|
line 20: key "hostPort" already set in map, unknown field "metadata.unknownMeta", unknown field "spec.ports[0].unknownNested", unknown field "spec.unknown1", unknown field "spec.unknownDupe", unknown field "spec.embeddedObj.metadata.unknownEmbeddedMeta"`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "crd-post-warn-validation-yaml",
|
name: "crd-post-warn-validation-yaml",
|
||||||
@ -1477,12 +1580,15 @@ func testFieldValidationPostCRD(t *testing.T, rest rest.Interface, gvk schema.Gr
|
|||||||
body: crdInvalidBodyYAML,
|
body: crdInvalidBodyYAML,
|
||||||
contentType: "application/yaml",
|
contentType: "application/yaml",
|
||||||
strictDecodingWarnings: []string{
|
strictDecodingWarnings: []string{
|
||||||
`line 10: key "unknownDupe" already set in map`,
|
`line 6: key "name" already set in map`,
|
||||||
`line 12: key "knownField1" already set in map`,
|
`line 12: key "unknownDupe" already set in map`,
|
||||||
`line 18: key "hostPort" already set in map`,
|
`line 14: key "knownField1" already set in map`,
|
||||||
|
`line 20: key "hostPort" already set in map`,
|
||||||
|
`unknown field "metadata.unknownMeta"`,
|
||||||
`unknown field "spec.ports[0].unknownNested"`,
|
`unknown field "spec.ports[0].unknownNested"`,
|
||||||
`unknown field "spec.unknown1"`,
|
`unknown field "spec.unknown1"`,
|
||||||
`unknown field "spec.unknownDupe"`,
|
`unknown field "spec.unknownDupe"`,
|
||||||
|
`unknown field "spec.embeddedObj.metadata.unknownEmbeddedMeta"`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1498,17 +1604,21 @@ func testFieldValidationPostCRD(t *testing.T, rest rest.Interface, gvk schema.Gr
|
|||||||
body: crdInvalidBodyYAML,
|
body: crdInvalidBodyYAML,
|
||||||
contentType: "application/yaml",
|
contentType: "application/yaml",
|
||||||
strictDecodingWarnings: []string{
|
strictDecodingWarnings: []string{
|
||||||
`line 10: key "unknownDupe" already set in map`,
|
`line 6: key "name" already set in map`,
|
||||||
`line 12: key "knownField1" already set in map`,
|
`line 12: key "unknownDupe" already set in map`,
|
||||||
`line 18: key "hostPort" already set in map`,
|
`line 14: key "knownField1" already set in map`,
|
||||||
|
`line 20: key "hostPort" already set in map`,
|
||||||
|
`unknown field "metadata.unknownMeta"`,
|
||||||
`unknown field "spec.ports[0].unknownNested"`,
|
`unknown field "spec.ports[0].unknownNested"`,
|
||||||
`unknown field "spec.unknown1"`,
|
`unknown field "spec.unknown1"`,
|
||||||
`unknown field "spec.unknownDupe"`,
|
`unknown field "spec.unknownDupe"`,
|
||||||
|
`unknown field "spec.embeddedObj.metadata.unknownEmbeddedMeta"`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tc := range testcases {
|
for _, tc := range testcases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
klog.Warningf("running tc named: %s", tc.name)
|
||||||
kind := gvk.Kind
|
kind := gvk.Kind
|
||||||
apiVersion := gvk.Group + "/" + gvk.Version
|
apiVersion := gvk.Group + "/" + gvk.Version
|
||||||
|
|
||||||
@ -1558,7 +1668,7 @@ func testFieldValidationPostCRDSchemaless(t *testing.T, rest rest.Interface, gvk
|
|||||||
FieldValidation: "Strict",
|
FieldValidation: "Strict",
|
||||||
},
|
},
|
||||||
body: crdInvalidBody,
|
body: crdInvalidBody,
|
||||||
strictDecodingError: `strict decoding error: duplicate field "spec.unknownDupe", duplicate field "spec.knownField1", duplicate field "spec.ports[0].hostPort", unknown field "spec.ports[0].unknownNested"`,
|
strictDecodingError: `strict decoding error: duplicate field "metadata.name", duplicate field "spec.unknownDupe", duplicate field "spec.knownField1", duplicate field "spec.ports[0].hostPort", unknown field "metadata.unknownMeta", unknown field "spec.ports[0].unknownNested", unknown field "spec.embeddedObj.metadata.unknownEmbeddedMeta"`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "schemaless-crd-post-warn-validation",
|
name: "schemaless-crd-post-warn-validation",
|
||||||
@ -1567,10 +1677,13 @@ func testFieldValidationPostCRDSchemaless(t *testing.T, rest rest.Interface, gvk
|
|||||||
},
|
},
|
||||||
body: crdInvalidBody,
|
body: crdInvalidBody,
|
||||||
strictDecodingWarnings: []string{
|
strictDecodingWarnings: []string{
|
||||||
|
`duplicate field "metadata.name"`,
|
||||||
`duplicate field "spec.unknownDupe"`,
|
`duplicate field "spec.unknownDupe"`,
|
||||||
`duplicate field "spec.knownField1"`,
|
`duplicate field "spec.knownField1"`,
|
||||||
`duplicate field "spec.ports[0].hostPort"`,
|
`duplicate field "spec.ports[0].hostPort"`,
|
||||||
|
`unknown field "metadata.unknownMeta"`,
|
||||||
`unknown field "spec.ports[0].unknownNested"`,
|
`unknown field "spec.ports[0].unknownNested"`,
|
||||||
|
`unknown field "spec.embeddedObj.metadata.unknownEmbeddedMeta"`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1584,10 +1697,13 @@ func testFieldValidationPostCRDSchemaless(t *testing.T, rest rest.Interface, gvk
|
|||||||
name: "schemaless-crd-post-no-validation",
|
name: "schemaless-crd-post-no-validation",
|
||||||
body: crdInvalidBody,
|
body: crdInvalidBody,
|
||||||
strictDecodingWarnings: []string{
|
strictDecodingWarnings: []string{
|
||||||
|
`duplicate field "metadata.name"`,
|
||||||
`duplicate field "spec.unknownDupe"`,
|
`duplicate field "spec.unknownDupe"`,
|
||||||
`duplicate field "spec.knownField1"`,
|
`duplicate field "spec.knownField1"`,
|
||||||
`duplicate field "spec.ports[0].hostPort"`,
|
`duplicate field "spec.ports[0].hostPort"`,
|
||||||
|
`unknown field "metadata.unknownMeta"`,
|
||||||
`unknown field "spec.ports[0].unknownNested"`,
|
`unknown field "spec.ports[0].unknownNested"`,
|
||||||
|
`unknown field "spec.embeddedObj.metadata.unknownEmbeddedMeta"`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1598,9 +1714,10 @@ func testFieldValidationPostCRDSchemaless(t *testing.T, rest rest.Interface, gvk
|
|||||||
body: crdInvalidBodyYAML,
|
body: crdInvalidBodyYAML,
|
||||||
contentType: "application/yaml",
|
contentType: "application/yaml",
|
||||||
strictDecodingError: `strict decoding error: yaml: unmarshal errors:
|
strictDecodingError: `strict decoding error: yaml: unmarshal errors:
|
||||||
line 10: key "unknownDupe" already set in map
|
line 6: key "name" already set in map
|
||||||
line 12: key "knownField1" already set in map
|
line 12: key "unknownDupe" already set in map
|
||||||
line 18: key "hostPort" already set in map, unknown field "spec.ports[0].unknownNested"`,
|
line 14: key "knownField1" already set in map
|
||||||
|
line 20: key "hostPort" already set in map, unknown field "metadata.unknownMeta", unknown field "spec.ports[0].unknownNested", unknown field "spec.embeddedObj.metadata.unknownEmbeddedMeta"`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "schemaless-crd-post-warn-validation-yaml",
|
name: "schemaless-crd-post-warn-validation-yaml",
|
||||||
@ -1610,10 +1727,13 @@ func testFieldValidationPostCRDSchemaless(t *testing.T, rest rest.Interface, gvk
|
|||||||
body: crdInvalidBodyYAML,
|
body: crdInvalidBodyYAML,
|
||||||
contentType: "application/yaml",
|
contentType: "application/yaml",
|
||||||
strictDecodingWarnings: []string{
|
strictDecodingWarnings: []string{
|
||||||
`line 10: key "unknownDupe" already set in map`,
|
`line 6: key "name" already set in map`,
|
||||||
`line 12: key "knownField1" already set in map`,
|
`line 12: key "unknownDupe" already set in map`,
|
||||||
`line 18: key "hostPort" already set in map`,
|
`line 14: key "knownField1" already set in map`,
|
||||||
|
`line 20: key "hostPort" already set in map`,
|
||||||
|
`unknown field "metadata.unknownMeta"`,
|
||||||
`unknown field "spec.ports[0].unknownNested"`,
|
`unknown field "spec.ports[0].unknownNested"`,
|
||||||
|
`unknown field "spec.embeddedObj.metadata.unknownEmbeddedMeta"`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1629,10 +1749,13 @@ func testFieldValidationPostCRDSchemaless(t *testing.T, rest rest.Interface, gvk
|
|||||||
body: crdInvalidBodyYAML,
|
body: crdInvalidBodyYAML,
|
||||||
contentType: "application/yaml",
|
contentType: "application/yaml",
|
||||||
strictDecodingWarnings: []string{
|
strictDecodingWarnings: []string{
|
||||||
`line 10: key "unknownDupe" already set in map`,
|
`line 6: key "name" already set in map`,
|
||||||
`line 12: key "knownField1" already set in map`,
|
`line 12: key "unknownDupe" already set in map`,
|
||||||
`line 18: key "hostPort" already set in map`,
|
`line 14: key "knownField1" already set in map`,
|
||||||
|
`line 20: key "hostPort" already set in map`,
|
||||||
|
`unknown field "metadata.unknownMeta"`,
|
||||||
`unknown field "spec.ports[0].unknownNested"`,
|
`unknown field "spec.ports[0].unknownNested"`,
|
||||||
|
`unknown field "spec.embeddedObj.metadata.unknownEmbeddedMeta"`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -1695,7 +1818,7 @@ func testFieldValidationPutCRD(t *testing.T, rest rest.Interface, gvk schema.Gro
|
|||||||
FieldValidation: "Strict",
|
FieldValidation: "Strict",
|
||||||
},
|
},
|
||||||
putBody: crdInvalidBody,
|
putBody: crdInvalidBody,
|
||||||
strictDecodingError: `strict decoding error: duplicate field "spec.unknownDupe", duplicate field "spec.knownField1", duplicate field "spec.ports[0].hostPort", unknown field "spec.ports[0].unknownNested", unknown field "spec.unknown1", unknown field "spec.unknownDupe"`,
|
strictDecodingError: `strict decoding error: duplicate field "metadata.name", duplicate field "spec.unknownDupe", duplicate field "spec.knownField1", duplicate field "spec.ports[0].hostPort", unknown field "metadata.unknownMeta", unknown field "spec.ports[0].unknownNested", unknown field "spec.unknown1", unknown field "spec.unknownDupe", unknown field "spec.embeddedObj.metadata.unknownEmbeddedMeta"`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "crd-put-warn-validation",
|
name: "crd-put-warn-validation",
|
||||||
@ -1704,12 +1827,15 @@ func testFieldValidationPutCRD(t *testing.T, rest rest.Interface, gvk schema.Gro
|
|||||||
},
|
},
|
||||||
putBody: crdInvalidBody,
|
putBody: crdInvalidBody,
|
||||||
strictDecodingWarnings: []string{
|
strictDecodingWarnings: []string{
|
||||||
|
`duplicate field "metadata.name"`,
|
||||||
`duplicate field "spec.unknownDupe"`,
|
`duplicate field "spec.unknownDupe"`,
|
||||||
`duplicate field "spec.knownField1"`,
|
`duplicate field "spec.knownField1"`,
|
||||||
`duplicate field "spec.ports[0].hostPort"`,
|
`duplicate field "spec.ports[0].hostPort"`,
|
||||||
|
`unknown field "metadata.unknownMeta"`,
|
||||||
`unknown field "spec.ports[0].unknownNested"`,
|
`unknown field "spec.ports[0].unknownNested"`,
|
||||||
`unknown field "spec.unknown1"`,
|
`unknown field "spec.unknown1"`,
|
||||||
`unknown field "spec.unknownDupe"`,
|
`unknown field "spec.unknownDupe"`,
|
||||||
|
`unknown field "spec.embeddedObj.metadata.unknownEmbeddedMeta"`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1723,12 +1849,15 @@ func testFieldValidationPutCRD(t *testing.T, rest rest.Interface, gvk schema.Gro
|
|||||||
name: "crd-put-no-validation",
|
name: "crd-put-no-validation",
|
||||||
putBody: crdInvalidBody,
|
putBody: crdInvalidBody,
|
||||||
strictDecodingWarnings: []string{
|
strictDecodingWarnings: []string{
|
||||||
|
`duplicate field "metadata.name"`,
|
||||||
`duplicate field "spec.unknownDupe"`,
|
`duplicate field "spec.unknownDupe"`,
|
||||||
`duplicate field "spec.knownField1"`,
|
`duplicate field "spec.knownField1"`,
|
||||||
`duplicate field "spec.ports[0].hostPort"`,
|
`duplicate field "spec.ports[0].hostPort"`,
|
||||||
|
`unknown field "metadata.unknownMeta"`,
|
||||||
`unknown field "spec.ports[0].unknownNested"`,
|
`unknown field "spec.ports[0].unknownNested"`,
|
||||||
`unknown field "spec.unknown1"`,
|
`unknown field "spec.unknown1"`,
|
||||||
`unknown field "spec.unknownDupe"`,
|
`unknown field "spec.unknownDupe"`,
|
||||||
|
`unknown field "spec.embeddedObj.metadata.unknownEmbeddedMeta"`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1739,9 +1868,10 @@ func testFieldValidationPutCRD(t *testing.T, rest rest.Interface, gvk schema.Gro
|
|||||||
putBody: crdInvalidBodyYAML,
|
putBody: crdInvalidBodyYAML,
|
||||||
contentType: "application/yaml",
|
contentType: "application/yaml",
|
||||||
strictDecodingError: `strict decoding error: yaml: unmarshal errors:
|
strictDecodingError: `strict decoding error: yaml: unmarshal errors:
|
||||||
line 10: key "unknownDupe" already set in map
|
line 6: key "name" already set in map
|
||||||
line 12: key "knownField1" already set in map
|
line 12: key "unknownDupe" already set in map
|
||||||
line 18: key "hostPort" already set in map, unknown field "spec.ports[0].unknownNested", unknown field "spec.unknown1", unknown field "spec.unknownDupe"`,
|
line 14: key "knownField1" already set in map
|
||||||
|
line 20: key "hostPort" already set in map, unknown field "metadata.unknownMeta", unknown field "spec.ports[0].unknownNested", unknown field "spec.unknown1", unknown field "spec.unknownDupe", unknown field "spec.embeddedObj.metadata.unknownEmbeddedMeta"`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "crd-put-warn-validation-yaml",
|
name: "crd-put-warn-validation-yaml",
|
||||||
@ -1751,12 +1881,15 @@ func testFieldValidationPutCRD(t *testing.T, rest rest.Interface, gvk schema.Gro
|
|||||||
putBody: crdInvalidBodyYAML,
|
putBody: crdInvalidBodyYAML,
|
||||||
contentType: "application/yaml",
|
contentType: "application/yaml",
|
||||||
strictDecodingWarnings: []string{
|
strictDecodingWarnings: []string{
|
||||||
`line 10: key "unknownDupe" already set in map`,
|
`line 6: key "name" already set in map`,
|
||||||
`line 12: key "knownField1" already set in map`,
|
`line 12: key "unknownDupe" already set in map`,
|
||||||
`line 18: key "hostPort" already set in map`,
|
`line 14: key "knownField1" already set in map`,
|
||||||
|
`line 20: key "hostPort" already set in map`,
|
||||||
|
`unknown field "metadata.unknownMeta"`,
|
||||||
`unknown field "spec.ports[0].unknownNested"`,
|
`unknown field "spec.ports[0].unknownNested"`,
|
||||||
`unknown field "spec.unknown1"`,
|
`unknown field "spec.unknown1"`,
|
||||||
`unknown field "spec.unknownDupe"`,
|
`unknown field "spec.unknownDupe"`,
|
||||||
|
`unknown field "spec.embeddedObj.metadata.unknownEmbeddedMeta"`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1772,12 +1905,15 @@ func testFieldValidationPutCRD(t *testing.T, rest rest.Interface, gvk schema.Gro
|
|||||||
putBody: crdInvalidBodyYAML,
|
putBody: crdInvalidBodyYAML,
|
||||||
contentType: "application/yaml",
|
contentType: "application/yaml",
|
||||||
strictDecodingWarnings: []string{
|
strictDecodingWarnings: []string{
|
||||||
`line 10: key "unknownDupe" already set in map`,
|
`line 6: key "name" already set in map`,
|
||||||
`line 12: key "knownField1" already set in map`,
|
`line 12: key "unknownDupe" already set in map`,
|
||||||
`line 18: key "hostPort" already set in map`,
|
`line 14: key "knownField1" already set in map`,
|
||||||
|
`line 20: key "hostPort" already set in map`,
|
||||||
|
`unknown field "metadata.unknownMeta"`,
|
||||||
`unknown field "spec.ports[0].unknownNested"`,
|
`unknown field "spec.ports[0].unknownNested"`,
|
||||||
`unknown field "spec.unknown1"`,
|
`unknown field "spec.unknown1"`,
|
||||||
`unknown field "spec.unknownDupe"`,
|
`unknown field "spec.unknownDupe"`,
|
||||||
|
`unknown field "spec.embeddedObj.metadata.unknownEmbeddedMeta"`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -1847,7 +1983,7 @@ func testFieldValidationPutCRDSchemaless(t *testing.T, rest rest.Interface, gvk
|
|||||||
FieldValidation: "Strict",
|
FieldValidation: "Strict",
|
||||||
},
|
},
|
||||||
putBody: crdInvalidBody,
|
putBody: crdInvalidBody,
|
||||||
strictDecodingError: `strict decoding error: duplicate field "spec.unknownDupe", duplicate field "spec.knownField1", duplicate field "spec.ports[0].hostPort", unknown field "spec.ports[0].unknownNested"`,
|
strictDecodingError: `strict decoding error: duplicate field "metadata.name", duplicate field "spec.unknownDupe", duplicate field "spec.knownField1", duplicate field "spec.ports[0].hostPort", unknown field "metadata.unknownMeta", unknown field "spec.ports[0].unknownNested", unknown field "spec.embeddedObj.metadata.unknownEmbeddedMeta"`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "schemaless-crd-put-warn-validation",
|
name: "schemaless-crd-put-warn-validation",
|
||||||
@ -1856,10 +1992,13 @@ func testFieldValidationPutCRDSchemaless(t *testing.T, rest rest.Interface, gvk
|
|||||||
},
|
},
|
||||||
putBody: crdInvalidBody,
|
putBody: crdInvalidBody,
|
||||||
strictDecodingWarnings: []string{
|
strictDecodingWarnings: []string{
|
||||||
|
`duplicate field "metadata.name"`,
|
||||||
`duplicate field "spec.unknownDupe"`,
|
`duplicate field "spec.unknownDupe"`,
|
||||||
`duplicate field "spec.knownField1"`,
|
`duplicate field "spec.knownField1"`,
|
||||||
`duplicate field "spec.ports[0].hostPort"`,
|
`duplicate field "spec.ports[0].hostPort"`,
|
||||||
|
`unknown field "metadata.unknownMeta"`,
|
||||||
`unknown field "spec.ports[0].unknownNested"`,
|
`unknown field "spec.ports[0].unknownNested"`,
|
||||||
|
`unknown field "spec.embeddedObj.metadata.unknownEmbeddedMeta"`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1873,10 +2012,13 @@ func testFieldValidationPutCRDSchemaless(t *testing.T, rest rest.Interface, gvk
|
|||||||
name: "schemaless-crd-put-no-validation",
|
name: "schemaless-crd-put-no-validation",
|
||||||
putBody: crdInvalidBody,
|
putBody: crdInvalidBody,
|
||||||
strictDecodingWarnings: []string{
|
strictDecodingWarnings: []string{
|
||||||
|
`duplicate field "metadata.name"`,
|
||||||
`duplicate field "spec.unknownDupe"`,
|
`duplicate field "spec.unknownDupe"`,
|
||||||
`duplicate field "spec.knownField1"`,
|
`duplicate field "spec.knownField1"`,
|
||||||
`duplicate field "spec.ports[0].hostPort"`,
|
`duplicate field "spec.ports[0].hostPort"`,
|
||||||
|
`unknown field "metadata.unknownMeta"`,
|
||||||
`unknown field "spec.ports[0].unknownNested"`,
|
`unknown field "spec.ports[0].unknownNested"`,
|
||||||
|
`unknown field "spec.embeddedObj.metadata.unknownEmbeddedMeta"`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1887,9 +2029,10 @@ func testFieldValidationPutCRDSchemaless(t *testing.T, rest rest.Interface, gvk
|
|||||||
putBody: crdInvalidBodyYAML,
|
putBody: crdInvalidBodyYAML,
|
||||||
contentType: "application/yaml",
|
contentType: "application/yaml",
|
||||||
strictDecodingError: `strict decoding error: yaml: unmarshal errors:
|
strictDecodingError: `strict decoding error: yaml: unmarshal errors:
|
||||||
line 10: key "unknownDupe" already set in map
|
line 6: key "name" already set in map
|
||||||
line 12: key "knownField1" already set in map
|
line 12: key "unknownDupe" already set in map
|
||||||
line 18: key "hostPort" already set in map, unknown field "spec.ports[0].unknownNested"`,
|
line 14: key "knownField1" already set in map
|
||||||
|
line 20: key "hostPort" already set in map, unknown field "metadata.unknownMeta", unknown field "spec.ports[0].unknownNested", unknown field "spec.embeddedObj.metadata.unknownEmbeddedMeta"`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "schemaless-crd-put-warn-validation-yaml",
|
name: "schemaless-crd-put-warn-validation-yaml",
|
||||||
@ -1899,10 +2042,13 @@ func testFieldValidationPutCRDSchemaless(t *testing.T, rest rest.Interface, gvk
|
|||||||
putBody: crdInvalidBodyYAML,
|
putBody: crdInvalidBodyYAML,
|
||||||
contentType: "application/yaml",
|
contentType: "application/yaml",
|
||||||
strictDecodingWarnings: []string{
|
strictDecodingWarnings: []string{
|
||||||
`line 10: key "unknownDupe" already set in map`,
|
`line 6: key "name" already set in map`,
|
||||||
`line 12: key "knownField1" already set in map`,
|
`line 12: key "unknownDupe" already set in map`,
|
||||||
`line 18: key "hostPort" already set in map`,
|
`line 14: key "knownField1" already set in map`,
|
||||||
|
`line 20: key "hostPort" already set in map`,
|
||||||
|
`unknown field "metadata.unknownMeta"`,
|
||||||
`unknown field "spec.ports[0].unknownNested"`,
|
`unknown field "spec.ports[0].unknownNested"`,
|
||||||
|
`unknown field "spec.embeddedObj.metadata.unknownEmbeddedMeta"`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1918,10 +2064,13 @@ func testFieldValidationPutCRDSchemaless(t *testing.T, rest rest.Interface, gvk
|
|||||||
putBody: crdInvalidBodyYAML,
|
putBody: crdInvalidBodyYAML,
|
||||||
contentType: "application/yaml",
|
contentType: "application/yaml",
|
||||||
strictDecodingWarnings: []string{
|
strictDecodingWarnings: []string{
|
||||||
`line 10: key "unknownDupe" already set in map`,
|
`line 6: key "name" already set in map`,
|
||||||
`line 12: key "knownField1" already set in map`,
|
`line 12: key "unknownDupe" already set in map`,
|
||||||
`line 18: key "hostPort" already set in map`,
|
`line 14: key "knownField1" already set in map`,
|
||||||
|
`line 20: key "hostPort" already set in map`,
|
||||||
|
`unknown field "metadata.unknownMeta"`,
|
||||||
`unknown field "spec.ports[0].unknownNested"`,
|
`unknown field "spec.ports[0].unknownNested"`,
|
||||||
|
`unknown field "spec.embeddedObj.metadata.unknownEmbeddedMeta"`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
10
vendor/sigs.k8s.io/json/internal/golang/encoding/json/decode.go
generated
vendored
10
vendor/sigs.k8s.io/json/internal/golang/encoding/json/decode.go
generated
vendored
@ -695,7 +695,7 @@ func (d *decodeState) object(v reflect.Value) error {
|
|||||||
seenKeys = map[string]struct{}{}
|
seenKeys = map[string]struct{}{}
|
||||||
}
|
}
|
||||||
if _, seen := seenKeys[fieldName]; seen {
|
if _, seen := seenKeys[fieldName]; seen {
|
||||||
d.saveStrictError(d.newFieldError("duplicate field", fieldName))
|
d.saveStrictError(d.newFieldError(duplicateStrictErrType, fieldName))
|
||||||
} else {
|
} else {
|
||||||
seenKeys[fieldName] = struct{}{}
|
seenKeys[fieldName] = struct{}{}
|
||||||
}
|
}
|
||||||
@ -711,7 +711,7 @@ func (d *decodeState) object(v reflect.Value) error {
|
|||||||
var seenKeys uint64
|
var seenKeys uint64
|
||||||
checkDuplicateField = func(fieldNameIndex int, fieldName string) {
|
checkDuplicateField = func(fieldNameIndex int, fieldName string) {
|
||||||
if seenKeys&(1<<fieldNameIndex) != 0 {
|
if seenKeys&(1<<fieldNameIndex) != 0 {
|
||||||
d.saveStrictError(d.newFieldError("duplicate field", fieldName))
|
d.saveStrictError(d.newFieldError(duplicateStrictErrType, fieldName))
|
||||||
} else {
|
} else {
|
||||||
seenKeys = seenKeys | (1 << fieldNameIndex)
|
seenKeys = seenKeys | (1 << fieldNameIndex)
|
||||||
}
|
}
|
||||||
@ -724,7 +724,7 @@ func (d *decodeState) object(v reflect.Value) error {
|
|||||||
seenIndexes = make([]bool, len(fields.list))
|
seenIndexes = make([]bool, len(fields.list))
|
||||||
}
|
}
|
||||||
if seenIndexes[fieldNameIndex] {
|
if seenIndexes[fieldNameIndex] {
|
||||||
d.saveStrictError(d.newFieldError("duplicate field", fieldName))
|
d.saveStrictError(d.newFieldError(duplicateStrictErrType, fieldName))
|
||||||
} else {
|
} else {
|
||||||
seenIndexes[fieldNameIndex] = true
|
seenIndexes[fieldNameIndex] = true
|
||||||
}
|
}
|
||||||
@ -836,7 +836,7 @@ func (d *decodeState) object(v reflect.Value) error {
|
|||||||
d.errorContext.Struct = t
|
d.errorContext.Struct = t
|
||||||
d.appendStrictFieldStackKey(f.name)
|
d.appendStrictFieldStackKey(f.name)
|
||||||
} else if d.disallowUnknownFields {
|
} else if d.disallowUnknownFields {
|
||||||
d.saveStrictError(d.newFieldError("unknown field", string(key)))
|
d.saveStrictError(d.newFieldError(unknownStrictErrType, string(key)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1231,7 +1231,7 @@ func (d *decodeState) objectInterface() map[string]interface{} {
|
|||||||
|
|
||||||
if d.disallowDuplicateFields {
|
if d.disallowDuplicateFields {
|
||||||
if _, exists := m[key]; exists {
|
if _, exists := m[key]; exists {
|
||||||
d.saveStrictError(d.newFieldError("duplicate field", key))
|
d.saveStrictError(d.newFieldError(duplicateStrictErrType, key))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
42
vendor/sigs.k8s.io/json/internal/golang/encoding/json/kubernetes_patch.go
generated
vendored
42
vendor/sigs.k8s.io/json/internal/golang/encoding/json/kubernetes_patch.go
generated
vendored
@ -18,7 +18,6 @@ package json
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
gojson "encoding/json"
|
gojson "encoding/json"
|
||||||
"fmt"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
@ -71,17 +70,23 @@ func (d *Decoder) DisallowDuplicateFields() {
|
|||||||
d.d.disallowDuplicateFields = true
|
d.d.disallowDuplicateFields = true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *decodeState) newFieldError(msg, field string) error {
|
func (d *decodeState) newFieldError(errType strictErrType, field string) *strictError {
|
||||||
if len(d.strictFieldStack) > 0 {
|
if len(d.strictFieldStack) > 0 {
|
||||||
return fmt.Errorf("%s %q", msg, strings.Join(d.strictFieldStack, "")+"."+field)
|
return &strictError{
|
||||||
|
ErrType: errType,
|
||||||
|
Path: strings.Join(d.strictFieldStack, "") + "." + field,
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return fmt.Errorf("%s %q", msg, field)
|
return &strictError{
|
||||||
|
ErrType: errType,
|
||||||
|
Path: field,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// saveStrictError saves a strict decoding error,
|
// saveStrictError saves a strict decoding error,
|
||||||
// for reporting at the end of the unmarshal if no other errors occurred.
|
// for reporting at the end of the unmarshal if no other errors occurred.
|
||||||
func (d *decodeState) saveStrictError(err error) {
|
func (d *decodeState) saveStrictError(err *strictError) {
|
||||||
// prevent excessive numbers of accumulated errors
|
// prevent excessive numbers of accumulated errors
|
||||||
if len(d.savedStrictErrors) >= 100 {
|
if len(d.savedStrictErrors) >= 100 {
|
||||||
return
|
return
|
||||||
@ -118,6 +123,33 @@ func (d *decodeState) appendStrictFieldStackIndex(i int) {
|
|||||||
d.strictFieldStack = append(d.strictFieldStack, "[", strconv.Itoa(i), "]")
|
d.strictFieldStack = append(d.strictFieldStack, "[", strconv.Itoa(i), "]")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type strictErrType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
unknownStrictErrType strictErrType = "unknown field"
|
||||||
|
duplicateStrictErrType strictErrType = "duplicate field"
|
||||||
|
)
|
||||||
|
|
||||||
|
// strictError is a strict decoding error
|
||||||
|
// It has an ErrType (either unknown or duplicate)
|
||||||
|
// and a path to the erroneous field
|
||||||
|
type strictError struct {
|
||||||
|
ErrType strictErrType
|
||||||
|
Path string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *strictError) Error() string {
|
||||||
|
return string(e.ErrType) + " " + strconv.Quote(e.Path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *strictError) FieldPath() string {
|
||||||
|
return e.Path
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *strictError) SetFieldPath(path string) {
|
||||||
|
e.Path = path
|
||||||
|
}
|
||||||
|
|
||||||
// UnmarshalStrictError holds errors resulting from use of strict disallow___ decoder directives.
|
// UnmarshalStrictError holds errors resulting from use of strict disallow___ decoder directives.
|
||||||
// If this is returned from Unmarshal(), it means the decoding was successful in all other respects.
|
// If this is returned from Unmarshal(), it means the decoding was successful in all other respects.
|
||||||
type UnmarshalStrictError struct {
|
type UnmarshalStrictError struct {
|
||||||
|
8
vendor/sigs.k8s.io/json/json.go
generated
vendored
8
vendor/sigs.k8s.io/json/json.go
generated
vendored
@ -137,3 +137,11 @@ func SyntaxErrorOffset(err error) (isSyntaxError bool, offset int64) {
|
|||||||
return false, 0
|
return false, 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FieldError are errors that provide access to
|
||||||
|
// the path of the erroneous field
|
||||||
|
type FieldError interface {
|
||||||
|
Error() string
|
||||||
|
FieldPath() string
|
||||||
|
SetFieldPath(path string)
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user