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" structuralschema "k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
) )
// Prune removes object fields in obj which are not specified in s. // Prune removes object fields in obj which are not specified in s. It skips TypeMeta and ObjectMeta fields
func Prune(obj interface{}, s *structuralschema.Structural) { // 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) prune(obj, s)
} }
var metaFields = map[string]bool{
"apiVersion": true,
"kind": true,
"metadata": true,
}
func prune(x interface{}, s *structuralschema.Structural) { func prune(x interface{}, s *structuralschema.Structural) {
if s != nil && s.XPreserveUnknownFields { if s != nil && s.XPreserveUnknownFields {
skipPrune(x, s) skipPrune(x, s)
@ -40,6 +55,9 @@ func prune(x interface{}, s *structuralschema.Structural) {
return return
} }
for k, v := range x { for k, v := range x {
if s.XEmbeddedResource && metaFields[k] {
continue
}
prop, ok := s.Properties[k] prop, ok := s.Properties[k]
if ok { if ok {
prune(v, &prop) prune(v, &prop)
@ -72,6 +90,9 @@ func skipPrune(x interface{}, s *structuralschema.Structural) {
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 && metaFields[k] {
continue
}
if prop, ok := s.Properties[k]; ok { if prop, ok := s.Properties[k]; ok {
prune(v, &prop) prune(v, &prop)
} else { } else {

View File

@ -23,31 +23,33 @@ import (
structuralschema "k8s.io/apiextensions-apiserver/pkg/apiserver/schema" structuralschema "k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/diff"
"k8s.io/apimachinery/pkg/util/json" "k8s.io/apimachinery/pkg/util/json"
) )
func TestPrune(t *testing.T) { func TestPrune(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
json string json string
schema *structuralschema.Structural dontPruneMetaAtRoot bool
expected string schema *structuralschema.Structural
expected string
}{ }{
{"empty", "null", nil, "null"}, {name: "empty", json: "null", expected: "null"},
{"scalar", "4", &structuralschema.Structural{}, "4"}, {name: "scalar", json: "4", schema: &structuralschema.Structural{}, expected: "4"},
{"scalar array", "[1,2]", &structuralschema.Structural{ {name: "scalar array", json: "[1,2]", schema: &structuralschema.Structural{
Items: &structuralschema.Structural{}, Items: &structuralschema.Structural{},
}, "[1,2]"}, }, expected: "[1,2]"},
{"object array", `[{"a":1},{"b":1},{"a":1,"b":2,"c":3}]`, &structuralschema.Structural{ {name: "object array", json: `[{"a":1},{"b":1},{"a":1,"b":2,"c":3}]`, schema: &structuralschema.Structural{
Items: &structuralschema.Structural{ Items: &structuralschema.Structural{
Properties: map[string]structuralschema.Structural{ Properties: map[string]structuralschema.Structural{
"a": {}, "a": {},
"c": {}, "c": {},
}, },
}, },
}, `[{"a":1},{},{"a":1,"c":3}]`}, }, expected: `[{"a":1},{},{"a":1,"c":3}]`},
{"object array with nil schema", `[{"a":1},{"b":1},{"a":1,"b":2,"c":3}]`, nil, `[{},{},{}]`}, {name: "object array with nil schema", json: `[{"a":1},{"b":1},{"a":1,"b":2,"c":3}]`, expected: `[{},{},{}]`},
{"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{ {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{ Properties: map[string]structuralschema.Structural{
"array": { "array": {
Items: &structuralschema.Structural{ Items: &structuralschema.Structural{
@ -64,8 +66,8 @@ func TestPrune(t *testing.T) {
}, },
}, },
}, },
}, `{"array":[{"a":1},{},{"a":1,"c":3}],"specified":{"a":1,"c":3}}`}, }, expected: `{"array":[{"a":1},{},{"a":1,"c":3}],"specified":{"a":1,"c":3}}`},
{"nested x-kubernetes-preserve-unknown-fields", ` {name: "nested x-kubernetes-preserve-unknown-fields", json: `
{ {
"unspecified":"bar", "unspecified":"bar",
"alpha": "abc", "alpha": "abc",
@ -84,7 +86,7 @@ func TestPrune(t *testing.T) {
"preserving": {"unspecified": "bar"} "preserving": {"unspecified": "bar"}
} }
} }
`, &structuralschema.Structural{ `, schema: &structuralschema.Structural{
Generic: structuralschema.Generic{Type: "object"}, Generic: structuralschema.Generic{Type: "object"},
Extensions: structuralschema.Extensions{XPreserveUnknownFields: true}, Extensions: structuralschema.Extensions{XPreserveUnknownFields: true},
Properties: map[string]structuralschema.Structural{ Properties: map[string]structuralschema.Structural{
@ -116,7 +118,7 @@ func TestPrune(t *testing.T) {
}, },
}, },
}, },
}, ` }, expected: `
{ {
"unspecified":"bar", "unspecified":"bar",
"alpha": "abc", "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{ Properties: map[string]structuralschema.Structural{
"a": {}, "a": {},
"c": { "c": {
@ -149,8 +151,8 @@ func TestPrune(t *testing.T) {
}, },
}, },
}, },
}, `{"a":1,"c":{"a":1,"b":2,"c":{}}}`}, }, expected: `{"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{ {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{ Properties: map[string]structuralschema.Structural{
"a": {}, "a": {},
"c": { "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 { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
@ -175,7 +483,7 @@ func TestPrune(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
prune(in, tt.schema) Prune(in, tt.schema, tt.dontPruneMetaAtRoot)
if !reflect.DeepEqual(in, expected) { if !reflect.DeepEqual(in, expected) {
var buf bytes.Buffer var buf bytes.Buffer
enc := json.NewEncoder(&buf) enc := json.NewEncoder(&buf)
@ -184,7 +492,7 @@ func TestPrune(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("unexpected result mashalling error: %v", err) 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() b.StartTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
Prune(instances[i], schema) Prune(instances[i], schema, true)
} }
} }