mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-25 20:53:33 +00:00
apiextension: prune default values in storage
This commit is contained in:
parent
135902b0f4
commit
4fd200c148
@ -614,11 +614,24 @@ func (r *crdHandler) getOrCreateServingInfoFor(uid types.UID, name string) (*crd
|
|||||||
if val == nil {
|
if val == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
structuralSchemas[v.Name], err = structuralschema.NewStructural(val.OpenAPIV3Schema)
|
s, err := structuralschema.NewStructural(val.OpenAPIV3Schema)
|
||||||
if *crd.Spec.PreserveUnknownFields == false && err != nil {
|
if *crd.Spec.PreserveUnknownFields == false && err != nil {
|
||||||
utilruntime.HandleError(err)
|
// This should never happen. If it does, it is a programming error.
|
||||||
|
utilruntime.HandleError(fmt.Errorf("failed to convert schema to structural: %v", err))
|
||||||
return nil, fmt.Errorf("the server could not properly serve the CR schema") // validation should avoid this
|
return nil, fmt.Errorf("the server could not properly serve the CR schema") // validation should avoid this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if *crd.Spec.PreserveUnknownFields == false {
|
||||||
|
// we don't own s completely, e.g. defaults are not deep-copied. So better make a copy here.
|
||||||
|
s = s.DeepCopy()
|
||||||
|
|
||||||
|
if err := structuraldefaulting.PruneDefaults(s); err != nil {
|
||||||
|
// This should never happen. If it does, it is a programming error.
|
||||||
|
utilruntime.HandleError(fmt.Errorf("failed to prune defaults: %v", err))
|
||||||
|
return nil, fmt.Errorf("the server could not properly serve the CR schema") // validation should avoid this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
structuralSchemas[v.Name] = s
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, v := range crd.Spec.Versions {
|
for _, v := range crd.Spec.Versions {
|
||||||
|
@ -4,6 +4,7 @@ go_library(
|
|||||||
name = "go_default_library",
|
name = "go_default_library",
|
||||||
srcs = [
|
srcs = [
|
||||||
"algorithm.go",
|
"algorithm.go",
|
||||||
|
"prune.go",
|
||||||
"surroundingobject.go",
|
"surroundingobject.go",
|
||||||
"validation.go",
|
"validation.go",
|
||||||
],
|
],
|
||||||
|
@ -0,0 +1,91 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2019 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 defaulting
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
structuralschema "k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
|
||||||
|
structuralobjectmeta "k8s.io/apiextensions-apiserver/pkg/apiserver/schema/objectmeta"
|
||||||
|
"k8s.io/apiextensions-apiserver/pkg/apiserver/schema/pruning"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PruneDefaults prunes default values according to the schema and according to
|
||||||
|
// the ObjectMeta definition of the running server. It mutates the passed schema.
|
||||||
|
func PruneDefaults(s *structuralschema.Structural) error {
|
||||||
|
p := pruner{s}
|
||||||
|
_, err := p.pruneDefaults(s, NewRootObjectFunc())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
type pruner struct {
|
||||||
|
rootSchema *structuralschema.Structural
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *pruner) pruneDefaults(s *structuralschema.Structural, f SurroundingObjectFunc) (changed bool, err error) {
|
||||||
|
if s == nil {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.Default.Object != nil {
|
||||||
|
orig := runtime.DeepCopyJSONValue(s.Default.Object)
|
||||||
|
|
||||||
|
obj, acc, err := f(s.Default.Object)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("failed to prune default value: %v", err)
|
||||||
|
}
|
||||||
|
if err := structuralobjectmeta.Coerce(nil, obj, p.rootSchema, true, true); err != nil {
|
||||||
|
return false, fmt.Errorf("failed to prune default value: %v", err)
|
||||||
|
}
|
||||||
|
pruning.Prune(obj, p.rootSchema, true)
|
||||||
|
s.Default.Object, _, err = acc(obj)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("failed to prune default value: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
changed = changed || !reflect.DeepEqual(orig, s.Default.Object)
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.AdditionalProperties != nil && s.AdditionalProperties.Structural != nil {
|
||||||
|
c, err := p.pruneDefaults(s.AdditionalProperties.Structural, f.Child("*"))
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
changed = changed || c
|
||||||
|
}
|
||||||
|
if s.Items != nil {
|
||||||
|
c, err := p.pruneDefaults(s.Items, f.Index())
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
changed = changed || c
|
||||||
|
}
|
||||||
|
for k, subSchema := range s.Properties {
|
||||||
|
c, err := p.pruneDefaults(&subSchema, f.Child(k))
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if c {
|
||||||
|
s.Properties[k] = subSchema
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return changed, nil
|
||||||
|
}
|
@ -36,6 +36,7 @@ go_test(
|
|||||||
"//staging/src/k8s.io/apiextensions-apiserver/pkg/cmd/server/options:go_default_library",
|
"//staging/src/k8s.io/apiextensions-apiserver/pkg/cmd/server/options:go_default_library",
|
||||||
"//staging/src/k8s.io/apiextensions-apiserver/pkg/features:go_default_library",
|
"//staging/src/k8s.io/apiextensions-apiserver/pkg/features:go_default_library",
|
||||||
"//staging/src/k8s.io/apiextensions-apiserver/test/integration/fixtures:go_default_library",
|
"//staging/src/k8s.io/apiextensions-apiserver/test/integration/fixtures:go_default_library",
|
||||||
|
"//staging/src/k8s.io/apiextensions-apiserver/test/integration/storage:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/api/meta:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/api/meta:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||||
|
@ -18,6 +18,7 @@ package integration
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@ -32,11 +33,15 @@ import (
|
|||||||
"k8s.io/apimachinery/pkg/util/wait"
|
"k8s.io/apimachinery/pkg/util/wait"
|
||||||
"k8s.io/apimachinery/pkg/watch"
|
"k8s.io/apimachinery/pkg/watch"
|
||||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
|
"k8s.io/client-go/dynamic"
|
||||||
utilfeaturetesting "k8s.io/component-base/featuregate/testing"
|
utilfeaturetesting "k8s.io/component-base/featuregate/testing"
|
||||||
|
|
||||||
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||||
|
"k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
|
||||||
|
serveroptions "k8s.io/apiextensions-apiserver/pkg/cmd/server/options"
|
||||||
"k8s.io/apiextensions-apiserver/pkg/features"
|
"k8s.io/apiextensions-apiserver/pkg/features"
|
||||||
"k8s.io/apiextensions-apiserver/test/integration/fixtures"
|
"k8s.io/apiextensions-apiserver/test/integration/fixtures"
|
||||||
|
"k8s.io/apiextensions-apiserver/test/integration/storage"
|
||||||
)
|
)
|
||||||
|
|
||||||
var defaultingFixture = &apiextensionsv1.CustomResourceDefinition{
|
var defaultingFixture = &apiextensionsv1.CustomResourceDefinition{
|
||||||
@ -146,6 +151,13 @@ properties:
|
|||||||
default: "v1beta2"
|
default: "v1beta2"
|
||||||
`
|
`
|
||||||
|
|
||||||
|
const defaultingFooInstance = `
|
||||||
|
kind: Foo
|
||||||
|
apiVersion: tests.example.com/v1beta1
|
||||||
|
metadata:
|
||||||
|
name: foo
|
||||||
|
`
|
||||||
|
|
||||||
func TestCustomResourceDefaultingWithWatchCache(t *testing.T) {
|
func TestCustomResourceDefaultingWithWatchCache(t *testing.T) {
|
||||||
testDefaulting(t, true)
|
testDefaulting(t, true)
|
||||||
}
|
}
|
||||||
@ -252,7 +264,7 @@ func testDefaulting(t *testing.T, watchCache bool) {
|
|||||||
t.Logf("Creating CR and expecting defaulted fields in spec, but status does not exist at all")
|
t.Logf("Creating CR and expecting defaulted fields in spec, but status does not exist at all")
|
||||||
fooClient := dynamicClient.Resource(schema.GroupVersionResource{crd.Spec.Group, crd.Spec.Versions[0].Name, crd.Spec.Names.Plural})
|
fooClient := dynamicClient.Resource(schema.GroupVersionResource{crd.Spec.Group, crd.Spec.Versions[0].Name, crd.Spec.Names.Plural})
|
||||||
foo := &unstructured.Unstructured{}
|
foo := &unstructured.Unstructured{}
|
||||||
if err := yaml.Unmarshal([]byte(fooInstance), &foo.Object); err != nil {
|
if err := yaml.Unmarshal([]byte(defaultingFooInstance), &foo.Object); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
unstructured.SetNestedField(foo.Object, "a", "spec", "a")
|
unstructured.SetNestedField(foo.Object, "a", "spec", "a")
|
||||||
@ -400,6 +412,275 @@ func testDefaulting(t *testing.T, watchCache bool) {
|
|||||||
mustNotExist(foo.Object, [][]string{{"spec", "c"}})
|
mustNotExist(foo.Object, [][]string{{"spec", "c"}})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var metaDefaultingFixture = &apiextensionsv1.CustomResourceDefinition{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "foos.tests.example.com"},
|
||||||
|
Spec: apiextensionsv1.CustomResourceDefinitionSpec{
|
||||||
|
Group: "tests.example.com",
|
||||||
|
Versions: []apiextensionsv1.CustomResourceDefinitionVersion{
|
||||||
|
{
|
||||||
|
Name: "v1beta1",
|
||||||
|
Storage: true,
|
||||||
|
Served: true,
|
||||||
|
Subresources: &apiextensionsv1.CustomResourceSubresources{
|
||||||
|
Status: &apiextensionsv1.CustomResourceSubresourceStatus{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Names: apiextensionsv1.CustomResourceDefinitionNames{
|
||||||
|
Plural: "foos",
|
||||||
|
Singular: "foo",
|
||||||
|
Kind: "Foo",
|
||||||
|
ListKind: "FooList",
|
||||||
|
},
|
||||||
|
Scope: apiextensionsv1.ClusterScoped,
|
||||||
|
PreserveUnknownFields: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const metaDefaultingFooV1beta1Schema = `
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
fields:
|
||||||
|
type: object
|
||||||
|
x-kubernetes-embedded-resource: true
|
||||||
|
properties:
|
||||||
|
apiVersion:
|
||||||
|
type: string
|
||||||
|
default: foos/v1
|
||||||
|
kind:
|
||||||
|
type: string
|
||||||
|
default: Foo
|
||||||
|
metadata:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
default: Bar
|
||||||
|
unknown:
|
||||||
|
type: string
|
||||||
|
default: unknown
|
||||||
|
fullMetadata:
|
||||||
|
type: object
|
||||||
|
x-kubernetes-embedded-resource: true
|
||||||
|
properties:
|
||||||
|
apiVersion:
|
||||||
|
type: string
|
||||||
|
default: foos/v1
|
||||||
|
kind:
|
||||||
|
type: string
|
||||||
|
default: Foo
|
||||||
|
metadata:
|
||||||
|
type: object
|
||||||
|
default:
|
||||||
|
name: Bar
|
||||||
|
unknown: unknown
|
||||||
|
fullObject:
|
||||||
|
type: object
|
||||||
|
x-kubernetes-embedded-resource: true
|
||||||
|
properties:
|
||||||
|
foo:
|
||||||
|
type: string
|
||||||
|
default:
|
||||||
|
apiVersion: foos/v1
|
||||||
|
kind: Foo
|
||||||
|
metadata:
|
||||||
|
name: Bar
|
||||||
|
unknown: unknown
|
||||||
|
spanning:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
embedded:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
foo:
|
||||||
|
type: string
|
||||||
|
x-kubernetes-embedded-resource: true
|
||||||
|
default:
|
||||||
|
embedded:
|
||||||
|
apiVersion: foos/v1
|
||||||
|
kind: Foo
|
||||||
|
metadata:
|
||||||
|
name: Bar
|
||||||
|
unknown: unknown
|
||||||
|
preserve-fields:
|
||||||
|
type: object
|
||||||
|
x-kubernetes-embedded-resource: true
|
||||||
|
x-kubernetes-preserve-unknown-fields: true
|
||||||
|
properties:
|
||||||
|
apiVersion:
|
||||||
|
type: string
|
||||||
|
default: foos/v1
|
||||||
|
kind:
|
||||||
|
type: string
|
||||||
|
default: Foo
|
||||||
|
metadata:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
default: Bar
|
||||||
|
unknown:
|
||||||
|
type: string
|
||||||
|
default: unknown
|
||||||
|
preserve-fullMetadata:
|
||||||
|
type: object
|
||||||
|
x-kubernetes-embedded-resource: true
|
||||||
|
x-kubernetes-preserve-unknown-fields: true
|
||||||
|
properties:
|
||||||
|
apiVersion:
|
||||||
|
type: string
|
||||||
|
default: foos/v1
|
||||||
|
kind:
|
||||||
|
type: string
|
||||||
|
default: Foo
|
||||||
|
metadata:
|
||||||
|
type: object
|
||||||
|
default:
|
||||||
|
name: Bar
|
||||||
|
unknown: unknown
|
||||||
|
preserve-fullObject:
|
||||||
|
type: object
|
||||||
|
x-kubernetes-embedded-resource: true
|
||||||
|
x-kubernetes-preserve-unknown-fields: true
|
||||||
|
default:
|
||||||
|
apiVersion: foos/v1
|
||||||
|
kind: Foo
|
||||||
|
metadata:
|
||||||
|
name: Bar
|
||||||
|
unknown: unknown
|
||||||
|
preserve-spanning:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
embedded:
|
||||||
|
type: object
|
||||||
|
x-kubernetes-embedded-resource: true
|
||||||
|
x-kubernetes-preserve-unknown-fields: true
|
||||||
|
default:
|
||||||
|
embedded:
|
||||||
|
apiVersion: foos/v1
|
||||||
|
kind: Foo
|
||||||
|
metadata:
|
||||||
|
name: Bar
|
||||||
|
unknown: unknown
|
||||||
|
`
|
||||||
|
|
||||||
|
const metaDefaultingFooInstance = `
|
||||||
|
kind: Foo
|
||||||
|
apiVersion: tests.example.com/v1beta1
|
||||||
|
metadata:
|
||||||
|
name: foo
|
||||||
|
`
|
||||||
|
|
||||||
|
func TestCustomResourceDefaultingOfMetaFields(t *testing.T) {
|
||||||
|
defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CustomResourceDefaulting, true)()
|
||||||
|
|
||||||
|
tearDown, config, options, err := fixtures.StartDefaultServer(t)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
apiExtensionClient, err := clientset.NewForConfig(config)
|
||||||
|
if err != nil {
|
||||||
|
tearDown()
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
dynamicClient, err := dynamic.NewForConfig(config)
|
||||||
|
if err != nil {
|
||||||
|
tearDown()
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer tearDown()
|
||||||
|
|
||||||
|
crd := metaDefaultingFixture.DeepCopy()
|
||||||
|
crd.Spec.Versions[0].Schema = &apiextensionsv1.CustomResourceValidation{}
|
||||||
|
if err := yaml.Unmarshal([]byte(metaDefaultingFooV1beta1Schema), &crd.Spec.Versions[0].Schema.OpenAPIV3Schema); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
crd, err = fixtures.CreateNewV1CustomResourceDefinition(crd, apiExtensionClient, dynamicClient)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Creating CR and expecting defaulted, embedded objects, with the unknown ObjectMeta fields pruned")
|
||||||
|
fooClient := dynamicClient.Resource(schema.GroupVersionResource{Group: crd.Spec.Group, Version: crd.Spec.Versions[0].Name, Resource: crd.Spec.Names.Plural})
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
path []string
|
||||||
|
value interface{}
|
||||||
|
}{
|
||||||
|
{[]string{"fields"}, map[string]interface{}{"metadata": map[string]interface{}{}}},
|
||||||
|
{[]string{"fullMetadata"}, map[string]interface{}{}},
|
||||||
|
{[]string{"fullObject"}, nil},
|
||||||
|
{[]string{"spanning", "embedded"}, nil},
|
||||||
|
{[]string{"preserve-fields"}, map[string]interface{}{"metadata": map[string]interface{}{}}},
|
||||||
|
{[]string{"preserve-fullMetadata"}, map[string]interface{}{}},
|
||||||
|
{[]string{"preserve-fullObject"}, nil},
|
||||||
|
{[]string{"preserve-spanning", "embedded"}, nil},
|
||||||
|
}
|
||||||
|
|
||||||
|
returnedFoo := &unstructured.Unstructured{}
|
||||||
|
if err := yaml.Unmarshal([]byte(metaDefaultingFooInstance), &returnedFoo.Object); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for _, tst := range tests {
|
||||||
|
if tst.value != nil {
|
||||||
|
if err := unstructured.SetNestedField(returnedFoo.Object, tst.value, tst.path...); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
returnedFoo, err = fooClient.Create(returnedFoo, metav1.CreateOptions{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unable to create CR: %v", err)
|
||||||
|
}
|
||||||
|
t.Logf("CR created: %#v", returnedFoo.UnstructuredContent())
|
||||||
|
|
||||||
|
// get persisted object
|
||||||
|
RESTOptionsGetter := serveroptions.NewCRDRESTOptionsGetter(*options.RecommendedOptions.Etcd)
|
||||||
|
restOptions, err := RESTOptionsGetter.GetRESTOptions(schema.GroupResource{Group: crd.Spec.Group, Resource: crd.Spec.Names.Plural})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
etcdClient, _, err := storage.GetEtcdClients(restOptions.StorageConfig.Transport)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer etcdClient.Close()
|
||||||
|
etcdObjectReader := storage.NewEtcdObjectReader(etcdClient, &restOptions, crd)
|
||||||
|
|
||||||
|
persistedFoo, err := etcdObjectReader.GetStoredCustomResource("", returnedFoo.GetName())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unable read CR from stored: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// check that the returned and persisted object is pruned
|
||||||
|
for _, tst := range tests {
|
||||||
|
for _, foo := range []*unstructured.Unstructured{returnedFoo, persistedFoo} {
|
||||||
|
source := "request"
|
||||||
|
if foo == persistedFoo {
|
||||||
|
source = "persisted"
|
||||||
|
}
|
||||||
|
t.Run(fmt.Sprintf("%s of %s object", strings.Join(tst.path, "."), source), func(t *testing.T) {
|
||||||
|
obj, found, err := unstructured.NestedMap(foo.Object, tst.path...)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
t.Errorf("expected defaulted objected, didn't find any")
|
||||||
|
} else if expected := map[string]interface{}{
|
||||||
|
"apiVersion": "foos/v1",
|
||||||
|
"kind": "Foo",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "Bar",
|
||||||
|
},
|
||||||
|
}; !reflect.DeepEqual(obj, expected) {
|
||||||
|
t.Errorf("unexpected defaulted object\n expected: %v\n got: %v", expected, obj)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func jsonPtr(x interface{}) *apiextensionsv1.JSON {
|
func jsonPtr(x interface{}) *apiextensionsv1.JSON {
|
||||||
bs, err := json.Marshal(x)
|
bs, err := json.Marshal(x)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -137,7 +137,7 @@ properties:
|
|||||||
type: string
|
type: string
|
||||||
`
|
`
|
||||||
|
|
||||||
fooSchemaEmbeddedResourceInstance = fooInstance + `
|
fooSchemaEmbeddedResourceInstance = pruningFooInstance + `
|
||||||
embeddedPruning:
|
embeddedPruning:
|
||||||
apiVersion: foo/v1
|
apiVersion: foo/v1
|
||||||
kind: Foo
|
kind: Foo
|
||||||
@ -170,7 +170,7 @@ embeddedNested:
|
|||||||
specified: bar
|
specified: bar
|
||||||
`
|
`
|
||||||
|
|
||||||
fooInstance = `
|
pruningFooInstance = `
|
||||||
kind: Foo
|
kind: Foo
|
||||||
apiVersion: tests.example.com/v1beta1
|
apiVersion: tests.example.com/v1beta1
|
||||||
metadata:
|
metadata:
|
||||||
@ -199,7 +199,7 @@ func TestPruningCreate(t *testing.T) {
|
|||||||
t.Logf("Creating CR and expect 'unspecified' fields to be pruned")
|
t.Logf("Creating CR and expect 'unspecified' fields to be pruned")
|
||||||
fooClient := dynamicClient.Resource(schema.GroupVersionResource{crd.Spec.Group, crd.Spec.Version, crd.Spec.Names.Plural})
|
fooClient := dynamicClient.Resource(schema.GroupVersionResource{crd.Spec.Group, crd.Spec.Version, crd.Spec.Names.Plural})
|
||||||
foo := &unstructured.Unstructured{}
|
foo := &unstructured.Unstructured{}
|
||||||
if err := yaml.Unmarshal([]byte(fooInstance), &foo.Object); err != nil {
|
if err := yaml.Unmarshal([]byte(pruningFooInstance), &foo.Object); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
unstructured.SetNestedField(foo.Object, "bar", "unspecified")
|
unstructured.SetNestedField(foo.Object, "bar", "unspecified")
|
||||||
@ -251,7 +251,7 @@ func TestPruningStatus(t *testing.T) {
|
|||||||
t.Logf("Creating CR and expect 'unspecified' fields to be pruned")
|
t.Logf("Creating CR and expect 'unspecified' fields to be pruned")
|
||||||
fooClient := dynamicClient.Resource(schema.GroupVersionResource{crd.Spec.Group, crd.Spec.Version, crd.Spec.Names.Plural})
|
fooClient := dynamicClient.Resource(schema.GroupVersionResource{crd.Spec.Group, crd.Spec.Version, crd.Spec.Names.Plural})
|
||||||
foo := &unstructured.Unstructured{}
|
foo := &unstructured.Unstructured{}
|
||||||
if err := yaml.Unmarshal([]byte(fooInstance), &foo.Object); err != nil {
|
if err := yaml.Unmarshal([]byte(pruningFooInstance), &foo.Object); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
foo, err = fooClient.Create(foo, metav1.CreateOptions{})
|
foo, err = fooClient.Create(foo, metav1.CreateOptions{})
|
||||||
@ -342,7 +342,7 @@ func TestPruningFromStorage(t *testing.T) {
|
|||||||
t.Logf("Creating object with unknown field manually in etcd")
|
t.Logf("Creating object with unknown field manually in etcd")
|
||||||
|
|
||||||
original := &unstructured.Unstructured{}
|
original := &unstructured.Unstructured{}
|
||||||
if err := yaml.Unmarshal([]byte(fooInstance), &original.Object); err != nil {
|
if err := yaml.Unmarshal([]byte(pruningFooInstance), &original.Object); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
unstructured.SetNestedField(original.Object, "bar", "unspecified")
|
unstructured.SetNestedField(original.Object, "bar", "unspecified")
|
||||||
@ -404,7 +404,7 @@ func TestPruningPatch(t *testing.T) {
|
|||||||
|
|
||||||
fooClient := dynamicClient.Resource(schema.GroupVersionResource{crd.Spec.Group, crd.Spec.Version, crd.Spec.Names.Plural})
|
fooClient := dynamicClient.Resource(schema.GroupVersionResource{crd.Spec.Group, crd.Spec.Version, crd.Spec.Names.Plural})
|
||||||
foo := &unstructured.Unstructured{}
|
foo := &unstructured.Unstructured{}
|
||||||
if err := yaml.Unmarshal([]byte(fooInstance), &foo.Object); err != nil {
|
if err := yaml.Unmarshal([]byte(pruningFooInstance), &foo.Object); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
foo, err = fooClient.Create(foo, metav1.CreateOptions{})
|
foo, err = fooClient.Create(foo, metav1.CreateOptions{})
|
||||||
@ -457,7 +457,7 @@ func TestPruningCreatePreservingUnknownFields(t *testing.T) {
|
|||||||
t.Logf("Creating CR and expect 'unspecified' field to be pruned")
|
t.Logf("Creating CR and expect 'unspecified' field to be pruned")
|
||||||
fooClient := dynamicClient.Resource(schema.GroupVersionResource{crd.Spec.Group, crd.Spec.Version, crd.Spec.Names.Plural})
|
fooClient := dynamicClient.Resource(schema.GroupVersionResource{crd.Spec.Group, crd.Spec.Version, crd.Spec.Names.Plural})
|
||||||
foo := &unstructured.Unstructured{}
|
foo := &unstructured.Unstructured{}
|
||||||
if err := yaml.Unmarshal([]byte(fooInstance), &foo.Object); err != nil {
|
if err := yaml.Unmarshal([]byte(pruningFooInstance), &foo.Object); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
unstructured.SetNestedField(foo.Object, "bar", "unspecified")
|
unstructured.SetNestedField(foo.Object, "bar", "unspecified")
|
||||||
|
Loading…
Reference in New Issue
Block a user