apiextensions: add x-kubernetes-embedded-resource integration tests

This commit is contained in:
Dr. Stefan Schimanski 2019-06-07 16:34:57 +02:00
parent 35054fa7ec
commit e69f44e28b
2 changed files with 443 additions and 11 deletions

View File

@ -24,6 +24,7 @@ import (
"github.com/coreos/etcd/clientv3"
"github.com/coreos/etcd/pkg/transport"
"sigs.k8s.io/yaml"
apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
"k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
@ -33,9 +34,11 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/diff"
"k8s.io/apimachinery/pkg/util/json"
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/client-go/dynamic"
"k8s.io/utils/pointer"
)
func TestPostInvalidObjectMeta(t *testing.T) {
@ -99,6 +102,17 @@ func TestInvalidObjectMetaInStorage(t *testing.T) {
}
noxuDefinition := fixtures.NewNoxuCustomResourceDefinition(apiextensionsv1beta1.NamespaceScoped)
noxuDefinition.Spec.Validation = &apiextensionsv1beta1.CustomResourceValidation{
OpenAPIV3Schema: &apiextensionsv1beta1.JSONSchemaProps{
Type: "object",
Properties: map[string]apiextensionsv1beta1.JSONSchemaProps{
"embedded": {
Type: "object",
XEmbeddedResource: true,
},
},
},
}
noxuDefinition, err = fixtures.CreateNewCustomResourceDefinition(noxuDefinition, apiExtensionClient, dynamicClient)
if err != nil {
t.Fatal(err)
@ -127,11 +141,17 @@ func TestInvalidObjectMetaInStorage(t *testing.T) {
t.Fatal(err)
}
t.Logf("Creating object with invalid labels manually in etcd")
t.Logf("Creating object with wrongly typed annotations and non-validating labels manually in etcd")
original := fixtures.NewNoxuInstance("default", "foo")
unstructured.SetNestedField(original.UnstructuredContent(), int64(42), "metadata", "unknown")
unstructured.SetNestedField(original.UnstructuredContent(), map[string]interface{}{"foo": int64(42), "bar": "abc"}, "metadata", "labels")
unstructured.SetNestedField(original.UnstructuredContent(), map[string]interface{}{"foo": int64(42), "bar": "abc"}, "metadata", "annotations")
unstructured.SetNestedField(original.UnstructuredContent(), map[string]interface{}{"invalid": "x y"}, "metadata", "labels")
unstructured.SetNestedField(original.UnstructuredContent(), int64(42), "embedded", "metadata", "unknown")
unstructured.SetNestedField(original.UnstructuredContent(), map[string]interface{}{"foo": int64(42), "bar": "abc"}, "embedded", "metadata", "annotations")
unstructured.SetNestedField(original.UnstructuredContent(), map[string]interface{}{"invalid": "x y"}, "embedded", "metadata", "labels")
unstructured.SetNestedField(original.UnstructuredContent(), "Foo", "embedded", "kind")
unstructured.SetNestedField(original.UnstructuredContent(), "foo/v1", "embedded", "apiVersion")
ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), metav1.NamespaceDefault)
key := path.Join("/", restOptions.StorageConfig.Prefix, noxuDefinition.Spec.Group, "noxus/default/foo")
@ -140,25 +160,305 @@ func TestInvalidObjectMetaInStorage(t *testing.T) {
t.Fatalf("unexpected error: %v", err)
}
t.Logf("Checking that ObjectMeta is pruned from unknown fields")
t.Logf("Checking that invalid objects can be deleted")
noxuResourceClient := newNamespacedCustomResourceClient("default", dynamicClient, noxuDefinition)
obj, err := noxuResourceClient.Get("foo", metav1.GetOptions{})
if err != nil {
if err := noxuResourceClient.Delete("foo", &metav1.DeleteOptions{}); err != nil {
t.Fatalf("Unexpected delete error %v", err)
}
if _, err := etcdclient.Put(ctx, key, string(val)); err != nil {
t.Fatalf("unexpected error: %v", err)
}
t.Logf("Checking that ObjectMeta is pruned from unknown fields")
obj, err := noxuResourceClient.Get("foo", metav1.GetOptions{})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
objJSON, _ := json.Marshal(obj.Object)
t.Logf("Got object: %v", string(objJSON))
if unknown, found, err := unstructured.NestedFieldNoCopy(obj.UnstructuredContent(), "metadata", "unknown"); err != nil {
t.Errorf("unexpected error: %v", err)
t.Errorf("Unexpected error: %v", err)
} else if found {
t.Errorf("unexpected to find metadata.unknown=%#v", unknown)
t.Errorf("Unexpected to find metadata.unknown=%#v", unknown)
}
if unknown, found, err := unstructured.NestedFieldNoCopy(obj.UnstructuredContent(), "embedded", "metadata", "unknown"); err != nil {
t.Errorf("Unexpected error: %v", err)
} else if found {
t.Errorf("Unexpected to find embedded.metadata.unknown=%#v", unknown)
}
t.Logf("Checking that ObjectMeta is pruned from invalid typed fields")
t.Logf("Checking that ObjectMeta is pruned from wrongly-typed annotations")
if annotations, found, err := unstructured.NestedStringMap(obj.UnstructuredContent(), "metadata", "annotations"); err != nil {
t.Errorf("Unexpected error: %v", err)
} else if found {
t.Errorf("Unexpected to find metadata.annotations: %#v", annotations)
}
if annotations, found, err := unstructured.NestedStringMap(obj.UnstructuredContent(), "embedded", "metadata", "annotations"); err != nil {
t.Errorf("Unexpected error: %v", err)
} else if found {
t.Errorf("Unexpected to find embedded.metadata.annotations: %#v", annotations)
}
t.Logf("Checking that ObjectMeta still has the non-validating labels")
if labels, found, err := unstructured.NestedStringMap(obj.UnstructuredContent(), "metadata", "labels"); err != nil {
t.Errorf("unexpected error: %v", err)
} else if found && !reflect.DeepEqual(labels, map[string]string{"bar": "abc"}) {
t.Errorf("unexpected to find metadata.lables=%#v", labels)
} else if !found {
t.Errorf("Expected to find metadata.labels, but didn't")
} else if expected := map[string]string{"invalid": "x y"}; !reflect.DeepEqual(labels, expected) {
t.Errorf("Expected metadata.labels to be %#v, got: %#v", expected, labels)
}
if labels, found, err := unstructured.NestedStringMap(obj.UnstructuredContent(), "embedded", "metadata", "labels"); err != nil {
t.Errorf("Unexpected error: %v", err)
} else if !found {
t.Errorf("Expected to find embedded.metadata.labels, but didn't")
} else if expected := map[string]string{"invalid": "x y"}; !reflect.DeepEqual(labels, expected) {
t.Errorf("Expected embedded.metadata.labels to be %#v, got: %#v", expected, labels)
}
t.Logf("Trying to fail on updating with invalid labels")
unstructured.SetNestedField(obj.Object, "changed", "metadata", "labels", "something")
if got, err := noxuResourceClient.Update(obj, metav1.UpdateOptions{}); err == nil {
objJSON, _ := json.Marshal(obj.Object)
gotJSON, _ := json.Marshal(got.Object)
t.Fatalf("Expected update error, but didn't get one\nin: %s\nresponse: %v", string(objJSON), string(gotJSON))
}
t.Logf("Trying to fail on updating with invalid embedded label")
unstructured.SetNestedField(obj.Object, "fixed", "metadata", "labels", "invalid")
if got, err := noxuResourceClient.Update(obj, metav1.UpdateOptions{}); err == nil {
objJSON, _ := json.Marshal(obj.Object)
gotJSON, _ := json.Marshal(got.Object)
t.Fatalf("Expected update error, but didn't get one\nin: %s\nresponse: %v", string(objJSON), string(gotJSON))
}
t.Logf("Fixed all labels and update should work")
unstructured.SetNestedField(obj.Object, "fixed", "embedded", "metadata", "labels", "invalid")
if _, err := noxuResourceClient.Update(obj, metav1.UpdateOptions{}); err != nil {
t.Errorf("Unexpected update error with fixed labels: %v", err)
}
t.Logf("Trying to fail on updating with wrongly-typed embedded label")
unstructured.SetNestedField(obj.Object, int64(42), "embedded", "metadata", "labels", "invalid")
if got, err := noxuResourceClient.Update(obj, metav1.UpdateOptions{}); err == nil {
objJSON, _ := json.Marshal(obj.Object)
gotJSON, _ := json.Marshal(got.Object)
t.Fatalf("Expected update error, but didn't get one\nin: %s\nresponse: %v", string(objJSON), string(gotJSON))
}
}
var embeddedResourceFixture = &apiextensionsv1beta1.CustomResourceDefinition{
ObjectMeta: metav1.ObjectMeta{Name: "foos.tests.apiextensions.k8s.io"},
Spec: apiextensionsv1beta1.CustomResourceDefinitionSpec{
Group: "tests.apiextensions.k8s.io",
Version: "v1beta1",
Names: apiextensionsv1beta1.CustomResourceDefinitionNames{
Plural: "foos",
Singular: "foo",
Kind: "Foo",
ListKind: "FooList",
},
Scope: apiextensionsv1beta1.ClusterScoped,
PreserveUnknownFields: pointer.BoolPtr(true),
Subresources: &apiextensionsv1beta1.CustomResourceSubresources{
Status: &apiextensionsv1beta1.CustomResourceSubresourceStatus{},
},
},
}
const (
embeddedResourceSchema = `
type: object
properties:
embedded:
type: object
x-kubernetes-embedded-resource: true
noEmbeddedObject:
type: object
embeddedNested:
type: object
x-kubernetes-embedded-resource: true
properties:
embedded:
type: object
x-kubernetes-embedded-resource: true
`
embeddedResourceInstance = `
kind: Foo
apiVersion: tests.apiextensions.k8s.io/v1beta1
metadata:
name: foo
embedded:
apiVersion: foo/v1
kind: Foo
metadata:
name: foo
unspecified: bar
noEmbeddedObject:
apiVersion: foo/v1
kind: Foo
metadata:
name: foo
unspecified: bar
embeddedNested:
apiVersion: foo/v1
kind: Foo
metadata:
name: foo
unspecified: bar
embedded:
apiVersion: foo/v1
kind: Foo
metadata:
name: foo
unspecified: bar
`
expectedEmbeddedResourceInstance = `
kind: Foo
apiVersion: tests.apiextensions.k8s.io/v1beta1
embedded:
apiVersion: foo/v1
kind: Foo
metadata:
name: foo
noEmbeddedObject:
apiVersion: foo/v1
kind: Foo
metadata:
name: foo
unspecified: bar
embeddedNested:
apiVersion: foo/v1
kind: Foo
metadata:
name: foo
embedded:
apiVersion: foo/v1
kind: Foo
metadata:
name: foo
`
wronglyTypedEmbeddedResourceInstance = `
kind: Foo
apiVersion: tests.apiextensions.k8s.io/v1beta1
metadata:
name: invalid
embedded:
apiVersion: foo/v1
kind: Foo
metadata:
name: instance
namespace: 42
`
invalidEmbeddedResourceInstance = `
kind: Foo
apiVersion: tests.apiextensions.k8s.io/v1beta1
metadata:
name: invalid
embedded:
apiVersion: foo/v1
kind: "%"
metadata:
name: ..
embeddedNested:
apiVersion: foo/v1
kind: "%"
metadata:
name: ..
embedded:
apiVersion: foo/v1
kind: "%"
metadata:
name: ..
`
)
func TestEmbeddedResources(t *testing.T) {
tearDownFn, apiExtensionClient, dynamicClient, err := fixtures.StartDefaultServerWithClients(t)
if err != nil {
t.Fatal(err)
}
defer tearDownFn()
crd := embeddedResourceFixture.DeepCopy()
crd.Spec.Validation = &apiextensionsv1beta1.CustomResourceValidation{}
if err := yaml.Unmarshal([]byte(embeddedResourceSchema), &crd.Spec.Validation.OpenAPIV3Schema); err != nil {
t.Fatal(err)
}
crd, err = fixtures.CreateNewCustomResourceDefinition(crd, apiExtensionClient, dynamicClient)
if err != nil {
t.Fatal(err)
}
t.Logf("Creating CR and expect 'unspecified' fields to be pruned inside ObjectMetas")
fooClient := dynamicClient.Resource(schema.GroupVersionResource{crd.Spec.Group, crd.Spec.Version, crd.Spec.Names.Plural})
foo := &unstructured.Unstructured{}
if err := yaml.Unmarshal([]byte(embeddedResourceInstance), &foo.Object); err != nil {
t.Fatal(err)
}
foo, err = fooClient.Create(foo, metav1.CreateOptions{})
if err != nil {
t.Fatalf("Unable to create CR: %v", err)
}
t.Logf("CR created: %#v", foo.UnstructuredContent())
t.Logf("Checking that everything unknown inside ObjectMeta is gone")
delete(foo.Object, "metadata")
var expected map[string]interface{}
if err := yaml.Unmarshal([]byte(expectedEmbeddedResourceInstance), &expected); err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(expected, foo.Object) {
t.Errorf("unexpected diff: %s", diff.ObjectDiff(expected, foo.Object))
}
t.Logf("Trying to create wrongly typed CR")
invalid := &unstructured.Unstructured{}
if err := yaml.Unmarshal([]byte(wronglyTypedEmbeddedResourceInstance), &invalid.Object); err != nil {
t.Fatal(err)
}
_, err = fooClient.Create(invalid, metav1.CreateOptions{})
if err == nil {
t.Fatal("Expected creation to fail, but didn't")
}
t.Logf("Creation of wrongly typed object failed with: %v", err)
for _, s := range []string{
`embedded.metadata: Invalid value`,
} {
if !strings.Contains(err.Error(), s) {
t.Errorf("missing error: %s", s)
}
}
t.Logf("Trying to create invalid CR")
wronglyTyped := &unstructured.Unstructured{}
if err := yaml.Unmarshal([]byte(invalidEmbeddedResourceInstance), &wronglyTyped.Object); err != nil {
t.Fatal(err)
}
_, err = fooClient.Create(wronglyTyped, metav1.CreateOptions{})
if err == nil {
t.Fatal("Expected creation to fail, but didn't")
}
t.Logf("Creation of invalid object failed with: %v", err)
for _, s := range []string{
`embedded.kind: Invalid value: "%"`,
`embedded.metadata.name: Invalid value: ".."`,
`embeddedNested.kind: Invalid value: "%"`,
`embeddedNested.metadata.name: Invalid value: ".."`,
`embeddedNested.embedded.kind: Invalid value: "%"`,
`embeddedNested.embedded.metadata.name: Invalid value: ".."`,
} {
if !strings.Contains(err.Error(), s) {
t.Errorf("missing error: %s", s)
}
}
}

View File

@ -18,17 +18,21 @@ package integration
import (
"path"
"reflect"
"strings"
"testing"
"github.com/coreos/etcd/clientv3"
"github.com/coreos/etcd/pkg/transport"
"sigs.k8s.io/yaml"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
types "k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/diff"
"k8s.io/apimachinery/pkg/util/json"
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/client-go/dynamic"
@ -107,6 +111,65 @@ properties:
x-kubernetes-preserve-unknown-fields: true
`
fooSchemaEmbeddedResource = `
type: object
properties:
embeddedPruning:
type: object
x-kubernetes-embedded-resource: true
properties:
specified:
type: string
embeddedPreserving:
type: object
x-kubernetes-embedded-resource: true
x-kubernetes-preserve-unknown-fields: true
embeddedNested:
type: object
x-kubernetes-embedded-resource: true
x-kubernetes-preserve-unknown-fields: true
properties:
embeddedPruning:
type: object
x-kubernetes-embedded-resource: true
properties:
specified:
type: string
`
fooSchemaEmbeddedResourceInstance = fooInstance + `
embeddedPruning:
apiVersion: foo/v1
kind: Foo
metadata:
name: foo
unspecified: bar
unspecified: bar
specified: bar
embeddedPreserving:
apiVersion: foo/v1
kind: Foo
metadata:
name: foo
unspecified: bar
unspecified: bar
embeddedNested:
apiVersion: foo/v1
kind: Foo
metadata:
name: foo
unspecified: bar
unspecified: bar
embeddedPruning:
apiVersion: foo/v1
kind: Foo
metadata:
name: foo
unspecified: bar
unspecified: bar
specified: bar
`
fooInstance = `
kind: Foo
apiVersion: tests.apiextensions.k8s.io/v1beta1
@ -457,3 +520,72 @@ func TestPruningCreatePreservingUnknownFields(t *testing.T) {
}
}
}
func TestPruningEmbeddedResources(t *testing.T) {
tearDownFn, apiExtensionClient, dynamicClient, err := fixtures.StartDefaultServerWithClients(t)
if err != nil {
t.Fatal(err)
}
defer tearDownFn()
crd := pruningFixture.DeepCopy()
crd.Spec.Validation = &apiextensionsv1beta1.CustomResourceValidation{}
if err := yaml.Unmarshal([]byte(fooSchemaEmbeddedResource), &crd.Spec.Validation.OpenAPIV3Schema); err != nil {
t.Fatal(err)
}
crd, err = fixtures.CreateNewCustomResourceDefinition(crd, apiExtensionClient, dynamicClient)
if err != nil {
t.Fatal(err)
}
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})
foo := &unstructured.Unstructured{}
if err := yaml.Unmarshal([]byte(fooSchemaEmbeddedResourceInstance), &foo.Object); err != nil {
t.Fatal(err)
}
foo, err = fooClient.Create(foo, metav1.CreateOptions{})
if err != nil {
t.Fatalf("Unable to create CR: %v", err)
}
t.Logf("CR created: %#v", foo.UnstructuredContent())
t.Logf("Comparing with expected, pruned value")
x := runtime.DeepCopyJSON(foo.Object)
delete(x, "apiVersion")
delete(x, "kind")
delete(x, "metadata")
var expected map[string]interface{}
if err := yaml.Unmarshal([]byte(`
embeddedPruning:
apiVersion: foo/v1
kind: Foo
metadata:
name: foo
specified: bar
embeddedPreserving:
apiVersion: foo/v1
kind: Foo
metadata:
name: foo
unspecified: bar
embeddedNested:
apiVersion: foo/v1
kind: Foo
metadata:
name: foo
embeddedPruning:
apiVersion: foo/v1
kind: Foo
metadata:
name: foo
specified: bar
unspecified: bar
`), &expected); err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(expected, x) {
t.Errorf("unexpected diff: %s", diff.ObjectDiff(expected, x))
}
}