apiextensions: don't prune meta fields with x-kubernetes-embedded-resource

This commit is contained in:
Dr. Stefan Schimanski 2019-06-07 15:52:34 +02:00
parent 3cd511b86c
commit f1bc7b69a8
2 changed files with 354 additions and 25 deletions

View File

@ -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 {

View File

@ -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)
}
}