mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-23 11:50:44 +00:00
apiextensions: don't prune meta fields with x-kubernetes-embedded-resource
This commit is contained in:
parent
3cd511b86c
commit
f1bc7b69a8
@ -20,11 +20,26 @@ import (
|
||||
structuralschema "k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
|
||||
)
|
||||
|
||||
// Prune removes object fields in obj which are not specified in s.
|
||||
func Prune(obj interface{}, s *structuralschema.Structural) {
|
||||
// Prune 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 root=true.
|
||||
func Prune(obj interface{}, s *structuralschema.Structural, root bool) {
|
||||
if root {
|
||||
if s == nil {
|
||||
s = &structuralschema.Structural{}
|
||||
}
|
||||
clone := *s
|
||||
clone.XEmbeddedResource = true
|
||||
s = &clone
|
||||
}
|
||||
prune(obj, s)
|
||||
}
|
||||
|
||||
var metaFields = map[string]bool{
|
||||
"apiVersion": true,
|
||||
"kind": true,
|
||||
"metadata": true,
|
||||
}
|
||||
|
||||
func prune(x interface{}, s *structuralschema.Structural) {
|
||||
if s != nil && s.XPreserveUnknownFields {
|
||||
skipPrune(x, s)
|
||||
@ -40,6 +55,9 @@ func prune(x interface{}, s *structuralschema.Structural) {
|
||||
return
|
||||
}
|
||||
for k, v := range x {
|
||||
if s.XEmbeddedResource && metaFields[k] {
|
||||
continue
|
||||
}
|
||||
prop, ok := s.Properties[k]
|
||||
if ok {
|
||||
prune(v, &prop)
|
||||
@ -72,6 +90,9 @@ func skipPrune(x interface{}, s *structuralschema.Structural) {
|
||||
switch x := x.(type) {
|
||||
case map[string]interface{}:
|
||||
for k, v := range x {
|
||||
if s.XEmbeddedResource && metaFields[k] {
|
||||
continue
|
||||
}
|
||||
if prop, ok := s.Properties[k]; ok {
|
||||
prune(v, &prop)
|
||||
} else {
|
||||
|
@ -23,31 +23,33 @@ import (
|
||||
|
||||
structuralschema "k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/diff"
|
||||
"k8s.io/apimachinery/pkg/util/json"
|
||||
)
|
||||
|
||||
func TestPrune(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
json string
|
||||
schema *structuralschema.Structural
|
||||
expected string
|
||||
name string
|
||||
json string
|
||||
dontPruneMetaAtRoot bool
|
||||
schema *structuralschema.Structural
|
||||
expected string
|
||||
}{
|
||||
{"empty", "null", nil, "null"},
|
||||
{"scalar", "4", &structuralschema.Structural{}, "4"},
|
||||
{"scalar array", "[1,2]", &structuralschema.Structural{
|
||||
{name: "empty", json: "null", expected: "null"},
|
||||
{name: "scalar", json: "4", schema: &structuralschema.Structural{}, expected: "4"},
|
||||
{name: "scalar array", json: "[1,2]", schema: &structuralschema.Structural{
|
||||
Items: &structuralschema.Structural{},
|
||||
}, "[1,2]"},
|
||||
{"object array", `[{"a":1},{"b":1},{"a":1,"b":2,"c":3}]`, &structuralschema.Structural{
|
||||
}, expected: "[1,2]"},
|
||||
{name: "object array", json: `[{"a":1},{"b":1},{"a":1,"b":2,"c":3}]`, schema: &structuralschema.Structural{
|
||||
Items: &structuralschema.Structural{
|
||||
Properties: map[string]structuralschema.Structural{
|
||||
"a": {},
|
||||
"c": {},
|
||||
},
|
||||
},
|
||||
}, `[{"a":1},{},{"a":1,"c":3}]`},
|
||||
{"object array with nil schema", `[{"a":1},{"b":1},{"a":1,"b":2,"c":3}]`, nil, `[{},{},{}]`},
|
||||
{"object array object", `{"array":[{"a":1},{"b":1},{"a":1,"b":2,"c":3}],"unspecified":{"a":1},"specified":{"a":1,"b":2,"c":3}}`, &structuralschema.Structural{
|
||||
}, expected: `[{"a":1},{},{"a":1,"c":3}]`},
|
||||
{name: "object array with nil schema", json: `[{"a":1},{"b":1},{"a":1,"b":2,"c":3}]`, expected: `[{},{},{}]`},
|
||||
{name: "object array object", json: `{"array":[{"a":1},{"b":1},{"a":1,"b":2,"c":3}],"unspecified":{"a":1},"specified":{"a":1,"b":2,"c":3}}`, schema: &structuralschema.Structural{
|
||||
Properties: map[string]structuralschema.Structural{
|
||||
"array": {
|
||||
Items: &structuralschema.Structural{
|
||||
@ -64,8 +66,8 @@ func TestPrune(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
}, `{"array":[{"a":1},{},{"a":1,"c":3}],"specified":{"a":1,"c":3}}`},
|
||||
{"nested x-kubernetes-preserve-unknown-fields", `
|
||||
}, expected: `{"array":[{"a":1},{},{"a":1,"c":3}],"specified":{"a":1,"c":3}}`},
|
||||
{name: "nested x-kubernetes-preserve-unknown-fields", json: `
|
||||
{
|
||||
"unspecified":"bar",
|
||||
"alpha": "abc",
|
||||
@ -84,7 +86,7 @@ func TestPrune(t *testing.T) {
|
||||
"preserving": {"unspecified": "bar"}
|
||||
}
|
||||
}
|
||||
`, &structuralschema.Structural{
|
||||
`, schema: &structuralschema.Structural{
|
||||
Generic: structuralschema.Generic{Type: "object"},
|
||||
Extensions: structuralschema.Extensions{XPreserveUnknownFields: true},
|
||||
Properties: map[string]structuralschema.Structural{
|
||||
@ -116,7 +118,7 @@ func TestPrune(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
}, `
|
||||
}, expected: `
|
||||
{
|
||||
"unspecified":"bar",
|
||||
"alpha": "abc",
|
||||
@ -134,7 +136,7 @@ func TestPrune(t *testing.T) {
|
||||
}
|
||||
}
|
||||
`},
|
||||
{"additionalProperties with schema", `{"a":1,"b":1,"c":{"a":1,"b":2,"c":{"a":1}}}`, &structuralschema.Structural{
|
||||
{name: "additionalProperties with schema", json: `{"a":1,"b":1,"c":{"a":1,"b":2,"c":{"a":1}}}`, schema: &structuralschema.Structural{
|
||||
Properties: map[string]structuralschema.Structural{
|
||||
"a": {},
|
||||
"c": {
|
||||
@ -149,8 +151,8 @@ func TestPrune(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
}, `{"a":1,"c":{"a":1,"b":2,"c":{}}}`},
|
||||
{"additionalProperties with bool", `{"a":1,"b":1,"c":{"a":1,"b":2,"c":{"a":1}}}`, &structuralschema.Structural{
|
||||
}, expected: `{"a":1,"c":{"a":1,"b":2,"c":{}}}`},
|
||||
{name: "additionalProperties with bool", json: `{"a":1,"b":1,"c":{"a":1,"b":2,"c":{"a":1}}}`, schema: &structuralschema.Structural{
|
||||
Properties: map[string]structuralschema.Structural{
|
||||
"a": {},
|
||||
"c": {
|
||||
@ -161,7 +163,313 @@ func TestPrune(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
}, `{"a":1,"c":{"a":1,"b":2,"c":{}}}`},
|
||||
}, expected: `{"a":1,"c":{"a":1,"b":2,"c":{}}}`},
|
||||
{name: "x-kubernetes-embedded-resource", json: `
|
||||
{
|
||||
"apiVersion": "foo/v1",
|
||||
"kind": "Foo",
|
||||
"metadata": {
|
||||
"name": "instance",
|
||||
"unspecified": "bar"
|
||||
},
|
||||
"unspecified":"bar",
|
||||
"pruned": {
|
||||
"apiVersion": "foo/v1",
|
||||
"kind": "Foo",
|
||||
"unspecified": "bar",
|
||||
"metadata": {
|
||||
"name": "instance",
|
||||
"unspecified": "bar"
|
||||
},
|
||||
"spec": {
|
||||
"unspecified": "bar"
|
||||
}
|
||||
},
|
||||
"preserving": {
|
||||
"apiVersion": "foo/v1",
|
||||
"kind": "Foo",
|
||||
"unspecified": "bar",
|
||||
"metadata": {
|
||||
"name": "instance",
|
||||
"unspecified": "bar"
|
||||
},
|
||||
"spec": {
|
||||
"unspecified": "bar"
|
||||
}
|
||||
},
|
||||
"nested": {
|
||||
"apiVersion": "foo/v1",
|
||||
"kind": "Foo",
|
||||
"unspecified": "bar",
|
||||
"metadata": {
|
||||
"name": "instance",
|
||||
"unspecified": "bar"
|
||||
},
|
||||
"spec": {
|
||||
"unspecified": "bar",
|
||||
"embedded": {
|
||||
"apiVersion": "foo/v1",
|
||||
"kind": "Foo",
|
||||
"unspecified": "bar",
|
||||
"metadata": {
|
||||
"name": "instance",
|
||||
"unspecified": "bar"
|
||||
},
|
||||
"spec": {
|
||||
"unspecified": "bar"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`, schema: &structuralschema.Structural{
|
||||
Generic: structuralschema.Generic{Type: "object"},
|
||||
Properties: map[string]structuralschema.Structural{
|
||||
"pruned": {
|
||||
Generic: structuralschema.Generic{Type: "object"},
|
||||
Extensions: structuralschema.Extensions{
|
||||
XEmbeddedResource: true,
|
||||
},
|
||||
Properties: map[string]structuralschema.Structural{
|
||||
"spec": {
|
||||
Generic: structuralschema.Generic{Type: "object"},
|
||||
},
|
||||
},
|
||||
},
|
||||
"preserving": {
|
||||
Generic: structuralschema.Generic{Type: "object"},
|
||||
Extensions: structuralschema.Extensions{
|
||||
XEmbeddedResource: true,
|
||||
XPreserveUnknownFields: true,
|
||||
},
|
||||
},
|
||||
"nested": {
|
||||
Generic: structuralschema.Generic{Type: "object"},
|
||||
Extensions: structuralschema.Extensions{
|
||||
XEmbeddedResource: true,
|
||||
},
|
||||
Properties: map[string]structuralschema.Structural{
|
||||
"spec": {
|
||||
Generic: structuralschema.Generic{Type: "object"},
|
||||
Properties: map[string]structuralschema.Structural{
|
||||
"embedded": {
|
||||
Generic: structuralschema.Generic{Type: "object"},
|
||||
Extensions: structuralschema.Extensions{
|
||||
XEmbeddedResource: true,
|
||||
},
|
||||
Properties: map[string]structuralschema.Structural{
|
||||
"spec": {
|
||||
Generic: structuralschema.Generic{Type: "object"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, expected: `
|
||||
{
|
||||
"pruned": {
|
||||
"apiVersion": "foo/v1",
|
||||
"kind": "Foo",
|
||||
"metadata": {
|
||||
"name": "instance",
|
||||
"unspecified": "bar"
|
||||
},
|
||||
"spec": {
|
||||
}
|
||||
},
|
||||
"preserving": {
|
||||
"apiVersion": "foo/v1",
|
||||
"kind": "Foo",
|
||||
"unspecified": "bar",
|
||||
"metadata": {
|
||||
"name": "instance",
|
||||
"unspecified": "bar"
|
||||
},
|
||||
"spec": {
|
||||
"unspecified": "bar"
|
||||
}
|
||||
},
|
||||
"nested": {
|
||||
"apiVersion": "foo/v1",
|
||||
"kind": "Foo",
|
||||
"metadata": {
|
||||
"name": "instance",
|
||||
"unspecified": "bar"
|
||||
},
|
||||
"spec": {
|
||||
"embedded": {
|
||||
"apiVersion": "foo/v1",
|
||||
"kind": "Foo",
|
||||
"metadata": {
|
||||
"name": "instance",
|
||||
"unspecified": "bar"
|
||||
},
|
||||
"spec": {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`},
|
||||
{name: "x-kubernetes-embedded-resource, with root=true", json: `
|
||||
{
|
||||
"apiVersion": "foo/v1",
|
||||
"kind": "Foo",
|
||||
"metadata": {
|
||||
"name": "instance",
|
||||
"unspecified": "bar"
|
||||
},
|
||||
"unspecified":"bar",
|
||||
"pruned": {
|
||||
"apiVersion": "foo/v1",
|
||||
"kind": "Foo",
|
||||
"unspecified": "bar",
|
||||
"metadata": {
|
||||
"name": "instance",
|
||||
"unspecified": "bar"
|
||||
},
|
||||
"spec": {
|
||||
"unspecified": "bar"
|
||||
}
|
||||
},
|
||||
"preserving": {
|
||||
"apiVersion": "foo/v1",
|
||||
"kind": "Foo",
|
||||
"unspecified": "bar",
|
||||
"metadata": {
|
||||
"name": "instance",
|
||||
"unspecified": "bar"
|
||||
},
|
||||
"spec": {
|
||||
"unspecified": "bar"
|
||||
}
|
||||
},
|
||||
"nested": {
|
||||
"apiVersion": "foo/v1",
|
||||
"kind": "Foo",
|
||||
"unspecified": "bar",
|
||||
"metadata": {
|
||||
"name": "instance",
|
||||
"unspecified": "bar"
|
||||
},
|
||||
"spec": {
|
||||
"unspecified": "bar",
|
||||
"embedded": {
|
||||
"apiVersion": "foo/v1",
|
||||
"kind": "Foo",
|
||||
"unspecified": "bar",
|
||||
"metadata": {
|
||||
"name": "instance",
|
||||
"unspecified": "bar"
|
||||
},
|
||||
"spec": {
|
||||
"unspecified": "bar"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`, dontPruneMetaAtRoot: true, schema: &structuralschema.Structural{
|
||||
Generic: structuralschema.Generic{Type: "object"},
|
||||
Properties: map[string]structuralschema.Structural{
|
||||
"pruned": {
|
||||
Generic: structuralschema.Generic{Type: "object"},
|
||||
Extensions: structuralschema.Extensions{
|
||||
XEmbeddedResource: true,
|
||||
},
|
||||
Properties: map[string]structuralschema.Structural{
|
||||
"spec": {
|
||||
Generic: structuralschema.Generic{Type: "object"},
|
||||
},
|
||||
},
|
||||
},
|
||||
"preserving": {
|
||||
Generic: structuralschema.Generic{Type: "object"},
|
||||
Extensions: structuralschema.Extensions{
|
||||
XEmbeddedResource: true,
|
||||
XPreserveUnknownFields: true,
|
||||
},
|
||||
},
|
||||
"nested": {
|
||||
Generic: structuralschema.Generic{Type: "object"},
|
||||
Extensions: structuralschema.Extensions{
|
||||
XEmbeddedResource: true,
|
||||
},
|
||||
Properties: map[string]structuralschema.Structural{
|
||||
"spec": {
|
||||
Generic: structuralschema.Generic{Type: "object"},
|
||||
Properties: map[string]structuralschema.Structural{
|
||||
"embedded": {
|
||||
Generic: structuralschema.Generic{Type: "object"},
|
||||
Extensions: structuralschema.Extensions{
|
||||
XEmbeddedResource: true,
|
||||
},
|
||||
Properties: map[string]structuralschema.Structural{
|
||||
"spec": {
|
||||
Generic: structuralschema.Generic{Type: "object"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, expected: `
|
||||
{
|
||||
"apiVersion": "foo/v1",
|
||||
"kind": "Foo",
|
||||
"metadata": {
|
||||
"name": "instance",
|
||||
"unspecified": "bar"
|
||||
},
|
||||
"pruned": {
|
||||
"apiVersion": "foo/v1",
|
||||
"kind": "Foo",
|
||||
"metadata": {
|
||||
"name": "instance",
|
||||
"unspecified": "bar"
|
||||
},
|
||||
"spec": {
|
||||
}
|
||||
},
|
||||
"preserving": {
|
||||
"apiVersion": "foo/v1",
|
||||
"kind": "Foo",
|
||||
"unspecified": "bar",
|
||||
"metadata": {
|
||||
"name": "instance",
|
||||
"unspecified": "bar"
|
||||
},
|
||||
"spec": {
|
||||
"unspecified": "bar"
|
||||
}
|
||||
},
|
||||
"nested": {
|
||||
"apiVersion": "foo/v1",
|
||||
"kind": "Foo",
|
||||
"metadata": {
|
||||
"name": "instance",
|
||||
"unspecified": "bar"
|
||||
},
|
||||
"spec": {
|
||||
"embedded": {
|
||||
"apiVersion": "foo/v1",
|
||||
"kind": "Foo",
|
||||
"metadata": {
|
||||
"name": "instance",
|
||||
"unspecified": "bar"
|
||||
},
|
||||
"spec": {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
@ -175,7 +483,7 @@ func TestPrune(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
prune(in, tt.schema)
|
||||
Prune(in, tt.schema, tt.dontPruneMetaAtRoot)
|
||||
if !reflect.DeepEqual(in, expected) {
|
||||
var buf bytes.Buffer
|
||||
enc := json.NewEncoder(&buf)
|
||||
@ -184,7 +492,7 @@ func TestPrune(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected result mashalling error: %v", err)
|
||||
}
|
||||
t.Errorf("expected: %s\ngot: %s", tt.expected, buf.String())
|
||||
t.Errorf("expected: %s\ngot: %s\ndiff: %s", tt.expected, buf.String(), diff.ObjectDiff(expected, in))
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -260,7 +568,7 @@ func BenchmarkPrune(b *testing.B) {
|
||||
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
Prune(instances[i], schema)
|
||||
Prune(instances[i], schema, true)
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user