Merge pull request #81497 from jpbetz/crd-e2e-v1

Upgrade e2e tests to use CRD v1 APIs
This commit is contained in:
Kubernetes Prow Robot 2019-08-20 01:59:33 -07:00 committed by GitHub
commit 8f887cad45
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 370 additions and 170 deletions

View File

@ -89,6 +89,7 @@ go_library(
importmap = "k8s.io/kubernetes/vendor/k8s.io/apiextensions-apiserver/test/integration", importmap = "k8s.io/kubernetes/vendor/k8s.io/apiextensions-apiserver/test/integration",
importpath = "k8s.io/apiextensions-apiserver/test/integration", importpath = "k8s.io/apiextensions-apiserver/test/integration",
deps = [ deps = [
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1:go_default_library",
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1:go_default_library", "//staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1:go_default_library",
"//staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset:go_default_library", "//staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset: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",

View File

@ -32,6 +32,7 @@ go_library(
"//staging/src/k8s.io/client-go/restmapper:go_default_library", "//staging/src/k8s.io/client-go/restmapper:go_default_library",
"//staging/src/k8s.io/client-go/scale:go_default_library", "//staging/src/k8s.io/client-go/scale:go_default_library",
"//vendor/github.com/pborman/uuid:go_default_library", "//vendor/github.com/pborman/uuid:go_default_library",
"//vendor/k8s.io/utils/pointer:go_default_library",
], ],
) )

View File

@ -20,6 +20,8 @@ import (
"fmt" "fmt"
"time" "time"
"k8s.io/utils/pointer"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
"k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
@ -81,6 +83,36 @@ func NewNoxuCustomResourceDefinition(scope apiextensionsv1beta1.ResourceScope) *
} }
} }
// NewNoxuV1CustomResourceDefinition returns a WishIHadChosenNoxu CRD.
func NewNoxuV1CustomResourceDefinition(scope apiextensionsv1.ResourceScope) *apiextensionsv1.CustomResourceDefinition {
return &apiextensionsv1.CustomResourceDefinition{
ObjectMeta: metav1.ObjectMeta{Name: "noxus.mygroup.example.com"},
Spec: apiextensionsv1.CustomResourceDefinitionSpec{
Group: "mygroup.example.com",
Versions: []apiextensionsv1.CustomResourceDefinitionVersion{{
Name: "v1beta1",
Served: true,
Storage: true,
Schema: &apiextensionsv1.CustomResourceValidation{
OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{
XPreserveUnknownFields: pointer.BoolPtr(true),
Type: "object",
},
},
}},
Names: apiextensionsv1.CustomResourceDefinitionNames{
Plural: "noxus",
Singular: "nonenglishnoxu",
Kind: "WishIHadChosenNoxu",
ShortNames: []string{"foo", "bar", "abc", "def"},
ListKind: "NoxuItemList",
Categories: []string{"all"},
},
Scope: scope,
},
}
}
// NewVersionedNoxuInstance returns a WishIHadChosenNoxu instance for a given version // NewVersionedNoxuInstance returns a WishIHadChosenNoxu instance for a given version
func NewVersionedNoxuInstance(namespace, name, version string) *unstructured.Unstructured { func NewVersionedNoxuInstance(namespace, name, version string) *unstructured.Unstructured {
return &unstructured.Unstructured{ return &unstructured.Unstructured{
@ -211,6 +243,19 @@ func servedVersions(crd *apiextensionsv1beta1.CustomResourceDefinition) []string
return versions return versions
} }
func servedV1Versions(crd *apiextensionsv1.CustomResourceDefinition) []string {
if len(crd.Spec.Versions) == 0 {
return []string{}
}
var versions []string
for _, v := range crd.Spec.Versions {
if v.Served {
versions = append(versions, v.Name)
}
}
return versions
}
func existsInDiscovery(crd *apiextensionsv1beta1.CustomResourceDefinition, apiExtensionsClient clientset.Interface, version string) (bool, error) { func existsInDiscovery(crd *apiextensionsv1beta1.CustomResourceDefinition, apiExtensionsClient clientset.Interface, version string) (bool, error) {
groupResource, err := apiExtensionsClient.Discovery().ServerResourcesForGroupVersion(crd.Spec.Group + "/" + version) groupResource, err := apiExtensionsClient.Discovery().ServerResourcesForGroupVersion(crd.Spec.Group + "/" + version)
if err != nil { if err != nil {
@ -227,6 +272,22 @@ func existsInDiscovery(crd *apiextensionsv1beta1.CustomResourceDefinition, apiEx
return false, nil return false, nil
} }
func existsInDiscoveryV1(crd *apiextensionsv1.CustomResourceDefinition, apiExtensionsClient clientset.Interface, version string) (bool, error) {
groupResource, err := apiExtensionsClient.Discovery().ServerResourcesForGroupVersion(crd.Spec.Group + "/" + version)
if err != nil {
if errors.IsNotFound(err) {
return false, nil
}
return false, err
}
for _, g := range groupResource.APIResources {
if g.Name == crd.Spec.Names.Plural {
return true, nil
}
}
return false, nil
}
// CreateNewCustomResourceDefinitionWatchUnsafe creates the CRD and makes sure // CreateNewCustomResourceDefinitionWatchUnsafe creates the CRD and makes sure
// the apiextension apiserver has installed the CRD. But it's not safe to watch // the apiextension apiserver has installed the CRD. But it's not safe to watch
// the created CR. Please call CreateNewCustomResourceDefinition if you need to // the created CR. Please call CreateNewCustomResourceDefinition if you need to
@ -428,6 +489,23 @@ func DeleteCustomResourceDefinition(crd *apiextensionsv1beta1.CustomResourceDefi
return nil return nil
} }
// DeleteV1CustomResourceDefinition deletes a CRD and waits until it disappears from discovery.
func DeleteV1CustomResourceDefinition(crd *apiextensionsv1.CustomResourceDefinition, apiExtensionsClient clientset.Interface) error {
if err := apiExtensionsClient.ApiextensionsV1beta1().CustomResourceDefinitions().Delete(crd.Name, nil); err != nil {
return err
}
for _, version := range servedV1Versions(crd) {
err := wait.PollImmediate(500*time.Millisecond, 30*time.Second, func() (bool, error) {
exists, err := existsInDiscoveryV1(crd, apiExtensionsClient, version)
return !exists, err
})
if err != nil {
return err
}
}
return nil
}
// DeleteCustomResourceDefinitions deletes all CRD matching the provided deleteListOpts and waits until all the CRDs disappear from discovery. // DeleteCustomResourceDefinitions deletes all CRD matching the provided deleteListOpts and waits until all the CRDs disappear from discovery.
func DeleteCustomResourceDefinitions(deleteListOpts metav1.ListOptions, apiExtensionsClient clientset.Interface) error { func DeleteCustomResourceDefinitions(deleteListOpts metav1.ListOptions, apiExtensionsClient clientset.Interface) error {
list, err := apiExtensionsClient.ApiextensionsV1beta1().CustomResourceDefinitions().List(deleteListOpts) list, err := apiExtensionsClient.ApiextensionsV1beta1().CustomResourceDefinitions().List(deleteListOpts)
@ -451,6 +529,29 @@ func DeleteCustomResourceDefinitions(deleteListOpts metav1.ListOptions, apiExten
return nil return nil
} }
// DeleteV1CustomResourceDefinitions deletes all CRD matching the provided deleteListOpts and waits until all the CRDs disappear from discovery.
func DeleteV1CustomResourceDefinitions(deleteListOpts metav1.ListOptions, apiExtensionsClient clientset.Interface) error {
list, err := apiExtensionsClient.ApiextensionsV1().CustomResourceDefinitions().List(deleteListOpts)
if err != nil {
return err
}
if err = apiExtensionsClient.ApiextensionsV1().CustomResourceDefinitions().DeleteCollection(nil, deleteListOpts); err != nil {
return err
}
for _, crd := range list.Items {
for _, version := range servedV1Versions(&crd) {
err := wait.PollImmediate(500*time.Millisecond, 30*time.Second, func() (bool, error) {
exists, err := existsInDiscoveryV1(&crd, apiExtensionsClient, version)
return !exists, err
})
if err != nil {
return err
}
}
}
return nil
}
// CreateNewVersionedScaleClient returns a scale client. // CreateNewVersionedScaleClient returns a scale client.
func CreateNewVersionedScaleClient(crd *apiextensionsv1beta1.CustomResourceDefinition, config *rest.Config, version string) (scale.ScalesGetter, error) { func CreateNewVersionedScaleClient(crd *apiextensionsv1beta1.CustomResourceDefinition, config *rest.Config, version string) (scale.ScalesGetter, error) {
discoveryClient, err := discovery.NewDiscoveryClientForConfig(config) discoveryClient, err := discovery.NewDiscoveryClientForConfig(config)

View File

@ -20,6 +20,7 @@ import (
"fmt" "fmt"
"testing" "testing"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
"k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
"k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/errors"
@ -95,6 +96,25 @@ func UpdateCustomResourceDefinitionWithRetry(client clientset.Interface, name st
return nil, fmt.Errorf("too many retries after conflicts updating CustomResourceDefinition %q", name) return nil, fmt.Errorf("too many retries after conflicts updating CustomResourceDefinition %q", name)
} }
// UpdateV1CustomResourceDefinitionWithRetry updates a CRD, retrying up to 5 times on version conflict errors.
func UpdateV1CustomResourceDefinitionWithRetry(client clientset.Interface, name string, update func(*apiextensionsv1.CustomResourceDefinition)) (*apiextensionsv1.CustomResourceDefinition, error) {
for i := 0; i < 5; i++ {
crd, err := client.ApiextensionsV1().CustomResourceDefinitions().Get(name, metav1.GetOptions{})
if err != nil {
return nil, fmt.Errorf("failed to get CustomResourceDefinition %q: %v", name, err)
}
update(crd)
crd, err = client.ApiextensionsV1().CustomResourceDefinitions().Update(crd)
if err == nil {
return crd, nil
}
if !errors.IsConflict(err) {
return nil, fmt.Errorf("failed to update CustomResourceDefinition %q: %v", name, err)
}
}
return nil, fmt.Errorf("too many retries after conflicts updating CustomResourceDefinition %q", name)
}
// getSchemaForVersion returns the validation schema for given version in given CRD. // getSchemaForVersion returns the validation schema for given version in given CRD.
func getSchemaForVersion(crd *apiextensionsv1beta1.CustomResourceDefinition, version string) (*apiextensionsv1beta1.CustomResourceValidation, error) { func getSchemaForVersion(crd *apiextensionsv1beta1.CustomResourceDefinition, version string) (*apiextensionsv1beta1.CustomResourceValidation, error) {
if !hasPerVersionSchema(crd.Spec.Versions) { if !hasPerVersionSchema(crd.Spec.Versions) {

View File

@ -41,6 +41,7 @@ go_library(
"//staging/src/k8s.io/api/rbac/v1:go_default_library", "//staging/src/k8s.io/api/rbac/v1:go_default_library",
"//staging/src/k8s.io/api/scheduling/v1:go_default_library", "//staging/src/k8s.io/api/scheduling/v1:go_default_library",
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions:go_default_library", "//staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions:go_default_library",
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1:go_default_library",
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1:go_default_library", "//staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1:go_default_library",
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/validation:go_default_library", "//staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/validation:go_default_library",
"//staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset:go_default_library", "//staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset:go_default_library",

View File

@ -39,7 +39,7 @@ import (
imageutils "k8s.io/kubernetes/test/utils/image" imageutils "k8s.io/kubernetes/test/utils/image"
"k8s.io/utils/pointer" "k8s.io/utils/pointer"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/apiextensions-apiserver/test/integration" "k8s.io/apiextensions-apiserver/test/integration"
// ensure libs have a chance to initialize // ensure libs have a chance to initialize
@ -56,15 +56,15 @@ const (
var serverCRDConversionWebhookVersion = utilversion.MustParseSemantic("v1.13.0-alpha") var serverCRDConversionWebhookVersion = utilversion.MustParseSemantic("v1.13.0-alpha")
var apiVersions = []v1beta1.CustomResourceDefinitionVersion{ var apiVersions = []apiextensionsv1.CustomResourceDefinitionVersion{
{ {
Name: "v1", Name: "v1",
Served: true, Served: true,
Storage: true, Storage: true,
Schema: &v1beta1.CustomResourceValidation{ Schema: &apiextensionsv1.CustomResourceValidation{
OpenAPIV3Schema: &v1beta1.JSONSchemaProps{ OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{
Type: "object", Type: "object",
Properties: map[string]v1beta1.JSONSchemaProps{ Properties: map[string]apiextensionsv1.JSONSchemaProps{
"hostPort": {Type: "string"}, "hostPort": {Type: "string"},
}, },
}, },
@ -74,10 +74,10 @@ var apiVersions = []v1beta1.CustomResourceDefinitionVersion{
Name: "v2", Name: "v2",
Served: true, Served: true,
Storage: false, Storage: false,
Schema: &v1beta1.CustomResourceValidation{ Schema: &apiextensionsv1.CustomResourceValidation{
OpenAPIV3Schema: &v1beta1.JSONSchemaProps{ OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{
Type: "object", Type: "object",
Properties: map[string]v1beta1.JSONSchemaProps{ Properties: map[string]apiextensionsv1.JSONSchemaProps{
"host": {Type: "string"}, "host": {Type: "string"},
"port": {Type: "string"}, "port": {Type: "string"},
}, },
@ -86,15 +86,15 @@ var apiVersions = []v1beta1.CustomResourceDefinitionVersion{
}, },
} }
var alternativeAPIVersions = []v1beta1.CustomResourceDefinitionVersion{ var alternativeAPIVersions = []apiextensionsv1.CustomResourceDefinitionVersion{
{ {
Name: "v1", Name: "v1",
Served: true, Served: true,
Storage: false, Storage: false,
Schema: &v1beta1.CustomResourceValidation{ Schema: &apiextensionsv1.CustomResourceValidation{
OpenAPIV3Schema: &v1beta1.JSONSchemaProps{ OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{
Type: "object", Type: "object",
Properties: map[string]v1beta1.JSONSchemaProps{ Properties: map[string]apiextensionsv1.JSONSchemaProps{
"hostPort": {Type: "string"}, "hostPort": {Type: "string"},
}, },
}, },
@ -104,10 +104,10 @@ var alternativeAPIVersions = []v1beta1.CustomResourceDefinitionVersion{
Name: "v2", Name: "v2",
Served: true, Served: true,
Storage: true, Storage: true,
Schema: &v1beta1.CustomResourceValidation{ Schema: &apiextensionsv1.CustomResourceValidation{
OpenAPIV3Schema: &v1beta1.JSONSchemaProps{ OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{
Type: "object", Type: "object",
Properties: map[string]v1beta1.JSONSchemaProps{ Properties: map[string]apiextensionsv1.JSONSchemaProps{
"host": {Type: "string"}, "host": {Type: "string"},
"port": {Type: "string"}, "port": {Type: "string"},
}, },
@ -142,21 +142,24 @@ var _ = SIGDescribe("CustomResourceConversionWebhook", func() {
}) })
ginkgo.It("Should be able to convert from CR v1 to CR v2", func() { ginkgo.It("Should be able to convert from CR v1 to CR v2", func() {
testcrd, err := crd.CreateMultiVersionTestCRD(f, "stable.example.com", func(crd *v1beta1.CustomResourceDefinition) { testcrd, err := crd.CreateMultiVersionTestCRD(f, "stable.example.com", func(crd *apiextensionsv1.CustomResourceDefinition) {
crd.Spec.Versions = apiVersions crd.Spec.Versions = apiVersions
crd.Spec.Conversion = &v1beta1.CustomResourceConversion{ crd.Spec.Conversion = &apiextensionsv1.CustomResourceConversion{
Strategy: v1beta1.WebhookConverter, Strategy: apiextensionsv1.WebhookConverter,
WebhookClientConfig: &v1beta1.WebhookClientConfig{ Webhook: &apiextensionsv1.WebhookConversion{
CABundle: context.signingCert, ClientConfig: &apiextensionsv1.WebhookClientConfig{
Service: &v1beta1.ServiceReference{ CABundle: context.signingCert,
Namespace: f.Namespace.Name, Service: &apiextensionsv1.ServiceReference{
Name: serviceCRDName, Namespace: f.Namespace.Name,
Path: pointer.StringPtr("/crdconvert"), Name: serviceCRDName,
Port: pointer.Int32Ptr(serviceCRDPort), Path: pointer.StringPtr("/crdconvert"),
Port: pointer.Int32Ptr(serviceCRDPort),
},
}, },
ConversionReviewVersions: []string{"v1", "v1beta1"},
}, },
} }
crd.Spec.PreserveUnknownFields = pointer.BoolPtr(false) crd.Spec.PreserveUnknownFields = false
}) })
if err != nil { if err != nil {
return return
@ -166,21 +169,24 @@ var _ = SIGDescribe("CustomResourceConversionWebhook", func() {
}) })
ginkgo.It("Should be able to convert a non homogeneous list of CRs", func() { ginkgo.It("Should be able to convert a non homogeneous list of CRs", func() {
testcrd, err := crd.CreateMultiVersionTestCRD(f, "stable.example.com", func(crd *v1beta1.CustomResourceDefinition) { testcrd, err := crd.CreateMultiVersionTestCRD(f, "stable.example.com", func(crd *apiextensionsv1.CustomResourceDefinition) {
crd.Spec.Versions = apiVersions crd.Spec.Versions = apiVersions
crd.Spec.Conversion = &v1beta1.CustomResourceConversion{ crd.Spec.Conversion = &apiextensionsv1.CustomResourceConversion{
Strategy: v1beta1.WebhookConverter, Strategy: apiextensionsv1.WebhookConverter,
WebhookClientConfig: &v1beta1.WebhookClientConfig{ Webhook: &apiextensionsv1.WebhookConversion{
CABundle: context.signingCert, ClientConfig: &apiextensionsv1.WebhookClientConfig{
Service: &v1beta1.ServiceReference{ CABundle: context.signingCert,
Namespace: f.Namespace.Name, Service: &apiextensionsv1.ServiceReference{
Name: serviceCRDName, Namespace: f.Namespace.Name,
Path: pointer.StringPtr("/crdconvert"), Name: serviceCRDName,
Port: pointer.Int32Ptr(serviceCRDPort), Path: pointer.StringPtr("/crdconvert"),
Port: pointer.Int32Ptr(serviceCRDPort),
},
}, },
ConversionReviewVersions: []string{"v1", "v1beta1"},
}, },
} }
crd.Spec.PreserveUnknownFields = pointer.BoolPtr(false) crd.Spec.PreserveUnknownFields = false
}) })
if err != nil { if err != nil {
return return
@ -339,7 +345,7 @@ func deployCustomResourceWebhookAndService(f *framework.Framework, image string,
framework.ExpectNoError(err, "waiting for service %s/%s have %d endpoint", namespace, serviceCRDName, 1) framework.ExpectNoError(err, "waiting for service %s/%s have %d endpoint", namespace, serviceCRDName, 1)
} }
func verifyV1Object(f *framework.Framework, crd *v1beta1.CustomResourceDefinition, obj *unstructured.Unstructured) { func verifyV1Object(f *framework.Framework, crd *apiextensionsv1.CustomResourceDefinition, obj *unstructured.Unstructured) {
gomega.Expect(obj.GetAPIVersion()).To(gomega.BeEquivalentTo(crd.Spec.Group + "/v1")) gomega.Expect(obj.GetAPIVersion()).To(gomega.BeEquivalentTo(crd.Spec.Group + "/v1"))
hostPort, exists := obj.Object["hostPort"] hostPort, exists := obj.Object["hostPort"]
gomega.Expect(exists).To(gomega.BeTrue()) gomega.Expect(exists).To(gomega.BeTrue())
@ -350,7 +356,7 @@ func verifyV1Object(f *framework.Framework, crd *v1beta1.CustomResourceDefinitio
gomega.Expect(portExists).To(gomega.BeFalse()) gomega.Expect(portExists).To(gomega.BeFalse())
} }
func verifyV2Object(f *framework.Framework, crd *v1beta1.CustomResourceDefinition, obj *unstructured.Unstructured) { func verifyV2Object(f *framework.Framework, crd *apiextensionsv1.CustomResourceDefinition, obj *unstructured.Unstructured) {
gomega.Expect(obj.GetAPIVersion()).To(gomega.BeEquivalentTo(crd.Spec.Group + "/v2")) gomega.Expect(obj.GetAPIVersion()).To(gomega.BeEquivalentTo(crd.Spec.Group + "/v2"))
_, hostPortExists := obj.Object["hostPort"] _, hostPortExists := obj.Object["hostPort"]
gomega.Expect(hostPortExists).To(gomega.BeFalse()) gomega.Expect(hostPortExists).To(gomega.BeFalse())
@ -362,7 +368,7 @@ func verifyV2Object(f *framework.Framework, crd *v1beta1.CustomResourceDefinitio
gomega.Expect(port).To(gomega.BeEquivalentTo("8080")) gomega.Expect(port).To(gomega.BeEquivalentTo("8080"))
} }
func testCustomResourceConversionWebhook(f *framework.Framework, crd *v1beta1.CustomResourceDefinition, customResourceClients map[string]dynamic.ResourceInterface) { func testCustomResourceConversionWebhook(f *framework.Framework, crd *apiextensionsv1.CustomResourceDefinition, customResourceClients map[string]dynamic.ResourceInterface) {
name := "cr-instance-1" name := "cr-instance-1"
ginkgo.By("Creating a v1 custom resource") ginkgo.By("Creating a v1 custom resource")
crInstance := &unstructured.Unstructured{ crInstance := &unstructured.Unstructured{
@ -380,6 +386,7 @@ func testCustomResourceConversionWebhook(f *framework.Framework, crd *v1beta1.Cu
gomega.Expect(err).To(gomega.BeNil()) gomega.Expect(err).To(gomega.BeNil())
ginkgo.By("v2 custom resource should be converted") ginkgo.By("v2 custom resource should be converted")
v2crd, err := customResourceClients["v2"].Get(name, metav1.GetOptions{}) v2crd, err := customResourceClients["v2"].Get(name, metav1.GetOptions{})
framework.ExpectNoError(err, "Getting v2 of custom resource %s", name)
verifyV2Object(f, crd, v2crd) verifyV2Object(f, crd, v2crd)
} }
@ -404,7 +411,7 @@ func testCRListConversion(f *framework.Framework, testCrd *crd.TestCrd) {
gomega.Expect(err).To(gomega.BeNil()) gomega.Expect(err).To(gomega.BeNil())
// Now cr-instance-1 is stored as v1. lets change storage version // Now cr-instance-1 is stored as v1. lets change storage version
crd, err = integration.UpdateCustomResourceDefinitionWithRetry(testCrd.APIExtensionClient, crd.Name, func(c *v1beta1.CustomResourceDefinition) { crd, err = integration.UpdateV1CustomResourceDefinitionWithRetry(testCrd.APIExtensionClient, crd.Name, func(c *apiextensionsv1.CustomResourceDefinition) {
c.Spec.Versions = alternativeAPIVersions c.Spec.Versions = alternativeAPIVersions
}) })
gomega.Expect(err).To(gomega.BeNil()) gomega.Expect(err).To(gomega.BeNil())

View File

@ -27,9 +27,10 @@ import (
"github.com/go-openapi/spec" "github.com/go-openapi/spec"
"github.com/onsi/ginkgo" "github.com/onsi/ginkgo"
"k8s.io/utils/pointer"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/apiextensions-apiserver/pkg/apiserver/validation" "k8s.io/apiextensions-apiserver/pkg/apiserver/validation"
apiequality "k8s.io/apimachinery/pkg/api/equality" apiequality "k8s.io/apimachinery/pkg/api/equality"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -337,8 +338,11 @@ var _ = SIGDescribe("CustomResourcePublishOpenAPI", func() {
} }
ginkgo.By("rename a version") ginkgo.By("rename a version")
patch := []byte(`{"spec":{"versions":[{"name":"v2","served":true,"storage":true},{"name":"v4","served":true,"storage":false}]}}`) patch := []byte(`[
crdMultiVer.Crd, err = crdMultiVer.APIExtensionClient.ApiextensionsV1beta1().CustomResourceDefinitions().Patch(crdMultiVer.Crd.Name, types.MergePatchType, patch) {"op":"test","path":"/spec/versions/1/name","value":"v3"},
{"op": "replace", "path": "/spec/versions/1/name", "value": "v4"}
]`)
crdMultiVer.Crd, err = crdMultiVer.APIExtensionClient.ApiextensionsV1().CustomResourceDefinitions().Patch(crdMultiVer.Crd.Name, types.JSONPatchType, patch)
if err != nil { if err != nil {
e2elog.Failf("%v", err) e2elog.Failf("%v", err)
} }
@ -379,12 +383,12 @@ var _ = SIGDescribe("CustomResourcePublishOpenAPI", func() {
} }
ginkgo.By("mark a version not serverd") ginkgo.By("mark a version not serverd")
crd.Crd, err = crd.APIExtensionClient.ApiextensionsV1beta1().CustomResourceDefinitions().Get(crd.Crd.Name, metav1.GetOptions{}) crd.Crd, err = crd.APIExtensionClient.ApiextensionsV1().CustomResourceDefinitions().Get(crd.Crd.Name, metav1.GetOptions{})
if err != nil { if err != nil {
e2elog.Failf("%v", err) e2elog.Failf("%v", err)
} }
crd.Crd.Spec.Versions[1].Served = false crd.Crd.Spec.Versions[1].Served = false
crd.Crd, err = crd.APIExtensionClient.ApiextensionsV1beta1().CustomResourceDefinitions().Update(crd.Crd) crd.Crd, err = crd.APIExtensionClient.ApiextensionsV1().CustomResourceDefinitions().Update(crd.Crd)
if err != nil { if err != nil {
e2elog.Failf("%v", err) e2elog.Failf("%v", err)
} }
@ -415,35 +419,42 @@ func setupCRD(f *framework.Framework, schema []byte, groupSuffix string, version
} }
func setupCRDAndVerifySchema(f *framework.Framework, schema, expect []byte, groupSuffix string, versions ...string) (*crd.TestCrd, error) { func setupCRDAndVerifySchema(f *framework.Framework, schema, expect []byte, groupSuffix string, versions ...string) (*crd.TestCrd, error) {
group := fmt.Sprintf("%s-test-%s.k8s.io", f.BaseName, groupSuffix) group := fmt.Sprintf("%s-test-%s.example.com", f.BaseName, groupSuffix)
if len(versions) == 0 { if len(versions) == 0 {
return nil, fmt.Errorf("require at least one version for CRD") return nil, fmt.Errorf("require at least one version for CRD")
} }
props := &v1beta1.JSONSchemaProps{} props := &apiextensionsv1.JSONSchemaProps{}
if schema != nil { if schema != nil {
if err := yaml.Unmarshal(schema, props); err != nil { if err := yaml.Unmarshal(schema, props); err != nil {
return nil, err return nil, err
} }
} }
crd, err := crd.CreateMultiVersionTestCRD(f, group, func(crd *v1beta1.CustomResourceDefinition) { crd, err := crd.CreateMultiVersionTestCRD(f, group, func(crd *apiextensionsv1.CustomResourceDefinition) {
var apiVersions []v1beta1.CustomResourceDefinitionVersion var apiVersions []apiextensionsv1.CustomResourceDefinitionVersion
for i, version := range versions { for i, version := range versions {
apiVersions = append(apiVersions, v1beta1.CustomResourceDefinitionVersion{ version := apiextensionsv1.CustomResourceDefinitionVersion{
Name: version, Name: version,
Served: true, Served: true,
Storage: i == 0, Storage: i == 0,
}) }
// set up validation when input schema isn't nil
if schema != nil {
version.Schema = &apiextensionsv1.CustomResourceValidation{
OpenAPIV3Schema: props,
}
} else {
version.Schema = &apiextensionsv1.CustomResourceValidation{
OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{
XPreserveUnknownFields: pointer.BoolPtr(true),
Type: "object",
},
}
}
apiVersions = append(apiVersions, version)
} }
crd.Spec.Versions = apiVersions crd.Spec.Versions = apiVersions
// set up validation when input schema isn't nil
if schema != nil {
crd.Spec.Validation = &v1beta1.CustomResourceValidation{
OpenAPIV3Schema: props,
}
}
}) })
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to create CRD: %v", err) return nil, fmt.Errorf("failed to create CRD: %v", err)
@ -573,12 +584,12 @@ func waitForOpenAPISchema(c k8sclientset.Interface, pred func(*spec.Swagger) (bo
// convertJSONSchemaProps converts JSONSchemaProps in YAML to spec.Schema // convertJSONSchemaProps converts JSONSchemaProps in YAML to spec.Schema
func convertJSONSchemaProps(in []byte, out *spec.Schema) error { func convertJSONSchemaProps(in []byte, out *spec.Schema) error {
external := v1beta1.JSONSchemaProps{} external := apiextensionsv1.JSONSchemaProps{}
if err := yaml.UnmarshalStrict(in, &external); err != nil { if err := yaml.UnmarshalStrict(in, &external); err != nil {
return err return err
} }
internal := apiextensions.JSONSchemaProps{} internal := apiextensions.JSONSchemaProps{}
if err := v1beta1.Convert_v1beta1_JSONSchemaProps_To_apiextensions_JSONSchemaProps(&external, &internal, nil); err != nil { if err := apiextensionsv1.Convert_v1_JSONSchemaProps_To_apiextensions_JSONSchemaProps(&external, &internal, nil); err != nil {
return err return err
} }
if err := validation.ConvertJSONSchemaProps(&internal, out); err != nil { if err := validation.ConvertJSONSchemaProps(&internal, out); err != nil {

View File

@ -19,7 +19,7 @@ package apimachinery
import ( import (
"fmt" "fmt"
apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
"k8s.io/apiextensions-apiserver/test/integration/fixtures" "k8s.io/apiextensions-apiserver/test/integration/fixtures"
"k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/api/meta"
@ -63,21 +63,22 @@ var _ = SIGDescribe("CustomResourceDefinition Watch", func() {
e2elog.Failf("failed to initialize apiExtensionClient: %v", err) e2elog.Failf("failed to initialize apiExtensionClient: %v", err)
} }
noxuDefinition := fixtures.NewNoxuCustomResourceDefinition(apiextensionsv1beta1.ClusterScoped) noxuDefinition := fixtures.NewNoxuV1CustomResourceDefinition(apiextensionsv1.ClusterScoped)
noxuDefinition, err = fixtures.CreateNewCustomResourceDefinition(noxuDefinition, apiExtensionClient, f.DynamicClient) noxuDefinition, err = fixtures.CreateNewV1CustomResourceDefinition(noxuDefinition, apiExtensionClient, f.DynamicClient)
if err != nil { if err != nil {
e2elog.Failf("failed to create CustomResourceDefinition: %v", err) e2elog.Failf("failed to create CustomResourceDefinition: %v", err)
} }
defer func() { defer func() {
err = fixtures.DeleteCustomResourceDefinition(noxuDefinition, apiExtensionClient) err = fixtures.DeleteV1CustomResourceDefinition(noxuDefinition, apiExtensionClient)
if err != nil { if err != nil {
e2elog.Failf("failed to delete CustomResourceDefinition: %v", err) e2elog.Failf("failed to delete CustomResourceDefinition: %v", err)
} }
}() }()
ns := "" ns := ""
noxuResourceClient := newNamespacedCustomResourceClient(ns, f.DynamicClient, noxuDefinition) noxuResourceClient, err := newNamespacedCustomResourceClient(ns, f.DynamicClient, noxuDefinition)
framework.ExpectNoError(err, "creating custom resource client")
watchA, err := watchCRWithName(noxuResourceClient, watchCRNameA) watchA, err := watchCRWithName(noxuResourceClient, watchCRNameA)
framework.ExpectNoError(err, "failed to watch custom resource: %s", watchCRNameA) framework.ExpectNoError(err, "failed to watch custom resource: %s", watchCRNameA)
@ -124,7 +125,7 @@ func watchCRWithName(crdResourceClient dynamic.ResourceInterface, name string) (
) )
} }
func instantiateCustomResource(instanceToCreate *unstructured.Unstructured, client dynamic.ResourceInterface, definition *apiextensionsv1beta1.CustomResourceDefinition) (*unstructured.Unstructured, error) { func instantiateCustomResource(instanceToCreate *unstructured.Unstructured, client dynamic.ResourceInterface, definition *apiextensionsv1.CustomResourceDefinition) (*unstructured.Unstructured, error) {
createdInstance, err := client.Create(instanceToCreate, metav1.CreateOptions{}) createdInstance, err := client.Create(instanceToCreate, metav1.CreateOptions{})
if err != nil { if err != nil {
return nil, err return nil, err
@ -141,7 +142,10 @@ func instantiateCustomResource(instanceToCreate *unstructured.Unstructured, clie
if err != nil { if err != nil {
return nil, err return nil, err
} }
if e, a := definition.Spec.Group+"/"+definition.Spec.Version, createdTypeMeta.GetAPIVersion(); e != a { if len(definition.Spec.Versions) != 1 {
return nil, fmt.Errorf("expected exactly one version, got %v", definition.Spec.Versions)
}
if e, a := definition.Spec.Group+"/"+definition.Spec.Versions[0].Name, createdTypeMeta.GetAPIVersion(); e != a {
return nil, fmt.Errorf("expected %v, got %v", e, a) return nil, fmt.Errorf("expected %v, got %v", e, a)
} }
if e, a := definition.Spec.Names.Kind, createdTypeMeta.GetKind(); e != a { if e, a := definition.Spec.Names.Kind, createdTypeMeta.GetKind(); e != a {
@ -154,12 +158,15 @@ func deleteCustomResource(client dynamic.ResourceInterface, name string) error {
return client.Delete(name, &metav1.DeleteOptions{}) return client.Delete(name, &metav1.DeleteOptions{})
} }
func newNamespacedCustomResourceClient(ns string, client dynamic.Interface, crd *apiextensionsv1beta1.CustomResourceDefinition) dynamic.ResourceInterface { func newNamespacedCustomResourceClient(ns string, client dynamic.Interface, crd *apiextensionsv1.CustomResourceDefinition) (dynamic.ResourceInterface, error) {
gvr := schema.GroupVersionResource{Group: crd.Spec.Group, Version: crd.Spec.Version, Resource: crd.Spec.Names.Plural} if len(crd.Spec.Versions) != 1 {
return nil, fmt.Errorf("expected exactly one version, got %v", crd.Spec.Versions)
if crd.Spec.Scope != apiextensionsv1beta1.ClusterScoped {
return client.Resource(gvr).Namespace(ns)
} }
return client.Resource(gvr) gvr := schema.GroupVersionResource{Group: crd.Spec.Group, Version: crd.Spec.Versions[0].Name, Resource: crd.Spec.Names.Plural}
if crd.Spec.Scope != apiextensionsv1.ClusterScoped {
return client.Resource(gvr).Namespace(ns), nil
}
return client.Resource(gvr), nil
} }

View File

@ -28,7 +28,7 @@ import (
appsv1 "k8s.io/api/apps/v1" appsv1 "k8s.io/api/apps/v1"
v1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1" rbacv1 "k8s.io/api/rbac/v1"
apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
crdclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" crdclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
"k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -195,22 +195,24 @@ var _ = SIGDescribe("AdmissionWebhook", func() {
ginkgo.It("Should mutate custom resource with pruning", func() { ginkgo.It("Should mutate custom resource with pruning", func() {
const prune = true const prune = true
testcrd, err := createAdmissionWebhookMultiVersionTestCRDWithV1Storage(f, func(crd *apiextensionsv1beta1.CustomResourceDefinition) { testcrd, err := createAdmissionWebhookMultiVersionTestCRDWithV1Storage(f, func(crd *apiextensionsv1.CustomResourceDefinition) {
crd.Spec.PreserveUnknownFields = pointer.BoolPtr(false) crd.Spec.PreserveUnknownFields = false
crd.Spec.Validation = &apiextensionsv1beta1.CustomResourceValidation{ for i := range crd.Spec.Versions {
OpenAPIV3Schema: &apiextensionsv1beta1.JSONSchemaProps{ crd.Spec.Versions[i].Schema = &apiextensionsv1.CustomResourceValidation{
Type: "object", OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{
Properties: map[string]apiextensionsv1beta1.JSONSchemaProps{ Type: "object",
"data": { Properties: map[string]apiextensionsv1.JSONSchemaProps{
Type: "object", "data": {
Properties: map[string]apiextensionsv1beta1.JSONSchemaProps{ Type: "object",
"mutation-start": {Type: "string"}, Properties: map[string]apiextensionsv1.JSONSchemaProps{
"mutation-stage-1": {Type: "string"}, "mutation-start": {Type: "string"},
// mutation-stage-2 is intentionally missing such that it is pruned "mutation-stage-1": {Type: "string"},
// mutation-stage-2 is intentionally missing such that it is pruned
},
}, },
}, },
}, },
}, }
} }
}) })
if err != nil { if err != nil {
@ -733,7 +735,7 @@ func registerWebhookForAttachingPod(f *framework.Framework, configName string, c
CABundle: context.signingCert, CABundle: context.signingCert,
}, },
SideEffects: &sideEffectsNone, SideEffects: &sideEffectsNone,
AdmissionReviewVersions: []string{"v1beta1"}, AdmissionReviewVersions: []string{"v1", "v1beta1"},
// Scope the webhook to just this namespace // Scope the webhook to just this namespace
NamespaceSelector: &metav1.LabelSelector{ NamespaceSelector: &metav1.LabelSelector{
MatchLabels: map[string]string{f.UniqueName: "true"}, MatchLabels: map[string]string{f.UniqueName: "true"},
@ -821,7 +823,7 @@ func registerMutatingWebhookForPod(f *framework.Framework, configName string, co
CABundle: context.signingCert, CABundle: context.signingCert,
}, },
SideEffects: &sideEffectsNone, SideEffects: &sideEffectsNone,
AdmissionReviewVersions: []string{"v1beta1"}, AdmissionReviewVersions: []string{"v1", "v1beta1"},
// Scope the webhook to just this namespace // Scope the webhook to just this namespace
NamespaceSelector: &metav1.LabelSelector{ NamespaceSelector: &metav1.LabelSelector{
MatchLabels: map[string]string{f.UniqueName: "true"}, MatchLabels: map[string]string{f.UniqueName: "true"},
@ -1042,7 +1044,7 @@ func failingWebhook(namespace, name string) admissionregistrationv1.ValidatingWe
CABundle: nil, CABundle: nil,
}, },
SideEffects: &sideEffectsNone, SideEffects: &sideEffectsNone,
AdmissionReviewVersions: []string{"v1beta1"}, AdmissionReviewVersions: []string{"v1", "v1beta1"},
} }
} }
@ -1150,7 +1152,7 @@ func registerValidatingWebhookForWebhookConfigurations(f *framework.Framework, c
CABundle: context.signingCert, CABundle: context.signingCert,
}, },
SideEffects: &sideEffectsNone, SideEffects: &sideEffectsNone,
AdmissionReviewVersions: []string{"v1beta1"}, AdmissionReviewVersions: []string{"v1", "v1beta1"},
FailurePolicy: &failurePolicy, FailurePolicy: &failurePolicy,
// Scope the webhook to just this namespace // Scope the webhook to just this namespace
NamespaceSelector: &metav1.LabelSelector{ NamespaceSelector: &metav1.LabelSelector{
@ -1209,7 +1211,7 @@ func registerMutatingWebhookForWebhookConfigurations(f *framework.Framework, con
CABundle: context.signingCert, CABundle: context.signingCert,
}, },
SideEffects: &sideEffectsNone, SideEffects: &sideEffectsNone,
AdmissionReviewVersions: []string{"v1beta1"}, AdmissionReviewVersions: []string{"v1", "v1beta1"},
FailurePolicy: &failurePolicy, FailurePolicy: &failurePolicy,
// Scope the webhook to just this namespace // Scope the webhook to just this namespace
NamespaceSelector: &metav1.LabelSelector{ NamespaceSelector: &metav1.LabelSelector{
@ -1270,7 +1272,7 @@ func testWebhooksForWebhookConfigurations(f *framework.Framework, configName str
CABundle: nil, CABundle: nil,
}, },
SideEffects: &sideEffectsNone, SideEffects: &sideEffectsNone,
AdmissionReviewVersions: []string{"v1beta1"}, AdmissionReviewVersions: []string{"v1", "v1beta1"},
FailurePolicy: &failurePolicy, FailurePolicy: &failurePolicy,
// Scope the webhook to just this namespace // Scope the webhook to just this namespace
NamespaceSelector: &metav1.LabelSelector{ NamespaceSelector: &metav1.LabelSelector{
@ -1324,7 +1326,7 @@ func testWebhooksForWebhookConfigurations(f *framework.Framework, configName str
CABundle: nil, CABundle: nil,
}, },
SideEffects: &sideEffectsNone, SideEffects: &sideEffectsNone,
AdmissionReviewVersions: []string{"v1beta1"}, AdmissionReviewVersions: []string{"v1", "v1beta1"},
FailurePolicy: &failurePolicy, FailurePolicy: &failurePolicy,
// Scope the webhook to just this namespace // Scope the webhook to just this namespace
NamespaceSelector: &metav1.LabelSelector{ NamespaceSelector: &metav1.LabelSelector{
@ -1542,7 +1544,7 @@ func registerWebhookForCustomResource(f *framework.Framework, configName string,
CABundle: context.signingCert, CABundle: context.signingCert,
}, },
SideEffects: &sideEffectsNone, SideEffects: &sideEffectsNone,
AdmissionReviewVersions: []string{"v1beta1"}, AdmissionReviewVersions: []string{"v1", "v1beta1"},
// Scope the webhook to just this namespace // Scope the webhook to just this namespace
NamespaceSelector: &metav1.LabelSelector{ NamespaceSelector: &metav1.LabelSelector{
MatchLabels: map[string]string{f.UniqueName: "true"}, MatchLabels: map[string]string{f.UniqueName: "true"},
@ -1591,7 +1593,7 @@ func registerMutatingWebhookForCustomResource(f *framework.Framework, configName
CABundle: context.signingCert, CABundle: context.signingCert,
}, },
SideEffects: &sideEffectsNone, SideEffects: &sideEffectsNone,
AdmissionReviewVersions: []string{"v1beta1"}, AdmissionReviewVersions: []string{"v1", "v1beta1"},
// Scope the webhook to just this namespace // Scope the webhook to just this namespace
NamespaceSelector: &metav1.LabelSelector{ NamespaceSelector: &metav1.LabelSelector{
MatchLabels: map[string]string{f.UniqueName: "true"}, MatchLabels: map[string]string{f.UniqueName: "true"},
@ -1617,7 +1619,7 @@ func registerMutatingWebhookForCustomResource(f *framework.Framework, configName
CABundle: context.signingCert, CABundle: context.signingCert,
}, },
SideEffects: &sideEffectsNone, SideEffects: &sideEffectsNone,
AdmissionReviewVersions: []string{"v1beta1"}, AdmissionReviewVersions: []string{"v1", "v1beta1"},
// Scope the webhook to just this namespace // Scope the webhook to just this namespace
NamespaceSelector: &metav1.LabelSelector{ NamespaceSelector: &metav1.LabelSelector{
MatchLabels: map[string]string{f.UniqueName: "true"}, MatchLabels: map[string]string{f.UniqueName: "true"},
@ -1633,13 +1635,13 @@ func registerMutatingWebhookForCustomResource(f *framework.Framework, configName
return func() { client.AdmissionregistrationV1().MutatingWebhookConfigurations().Delete(configName, nil) } return func() { client.AdmissionregistrationV1().MutatingWebhookConfigurations().Delete(configName, nil) }
} }
func testCustomResourceWebhook(f *framework.Framework, crd *apiextensionsv1beta1.CustomResourceDefinition, customResourceClient dynamic.ResourceInterface) { func testCustomResourceWebhook(f *framework.Framework, crd *apiextensionsv1.CustomResourceDefinition, customResourceClient dynamic.ResourceInterface) {
ginkgo.By("Creating a custom resource that should be denied by the webhook") ginkgo.By("Creating a custom resource that should be denied by the webhook")
crInstanceName := "cr-instance-1" crInstanceName := "cr-instance-1"
crInstance := &unstructured.Unstructured{ crInstance := &unstructured.Unstructured{
Object: map[string]interface{}{ Object: map[string]interface{}{
"kind": crd.Spec.Names.Kind, "kind": crd.Spec.Names.Kind,
"apiVersion": crd.Spec.Group + "/" + crd.Spec.Version, "apiVersion": crd.Spec.Group + "/" + crd.Spec.Versions[0].Name,
"metadata": map[string]interface{}{ "metadata": map[string]interface{}{
"name": crInstanceName, "name": crInstanceName,
"namespace": f.Namespace.Name, "namespace": f.Namespace.Name,
@ -1657,13 +1659,13 @@ func testCustomResourceWebhook(f *framework.Framework, crd *apiextensionsv1beta1
} }
} }
func testBlockingCustomResourceDeletion(f *framework.Framework, crd *apiextensionsv1beta1.CustomResourceDefinition, customResourceClient dynamic.ResourceInterface) { func testBlockingCustomResourceDeletion(f *framework.Framework, crd *apiextensionsv1.CustomResourceDefinition, customResourceClient dynamic.ResourceInterface) {
ginkgo.By("Creating a custom resource whose deletion would be denied by the webhook") ginkgo.By("Creating a custom resource whose deletion would be denied by the webhook")
crInstanceName := "cr-instance-2" crInstanceName := "cr-instance-2"
crInstance := &unstructured.Unstructured{ crInstance := &unstructured.Unstructured{
Object: map[string]interface{}{ Object: map[string]interface{}{
"kind": crd.Spec.Names.Kind, "kind": crd.Spec.Names.Kind,
"apiVersion": crd.Spec.Group + "/" + crd.Spec.Version, "apiVersion": crd.Spec.Group + "/" + crd.Spec.Versions[0].Name,
"metadata": map[string]interface{}{ "metadata": map[string]interface{}{
"name": crInstanceName, "name": crInstanceName,
"namespace": f.Namespace.Name, "namespace": f.Namespace.Name,
@ -1701,13 +1703,13 @@ func testBlockingCustomResourceDeletion(f *framework.Framework, crd *apiextensio
} }
func testMutatingCustomResourceWebhook(f *framework.Framework, crd *apiextensionsv1beta1.CustomResourceDefinition, customResourceClient dynamic.ResourceInterface, prune bool) { func testMutatingCustomResourceWebhook(f *framework.Framework, crd *apiextensionsv1.CustomResourceDefinition, customResourceClient dynamic.ResourceInterface, prune bool) {
ginkgo.By("Creating a custom resource that should be mutated by the webhook") ginkgo.By("Creating a custom resource that should be mutated by the webhook")
crName := "cr-instance-1" crName := "cr-instance-1"
cr := &unstructured.Unstructured{ cr := &unstructured.Unstructured{
Object: map[string]interface{}{ Object: map[string]interface{}{
"kind": crd.Spec.Names.Kind, "kind": crd.Spec.Names.Kind,
"apiVersion": crd.Spec.Group + "/" + crd.Spec.Version, "apiVersion": crd.Spec.Group + "/" + crd.Spec.Versions[0].Name,
"metadata": map[string]interface{}{ "metadata": map[string]interface{}{
"name": crName, "name": crName,
"namespace": f.Namespace.Name, "namespace": f.Namespace.Name,
@ -1738,7 +1740,7 @@ func testMultiVersionCustomResourceWebhook(f *framework.Framework, testcrd *crd.
cr := &unstructured.Unstructured{ cr := &unstructured.Unstructured{
Object: map[string]interface{}{ Object: map[string]interface{}{
"kind": testcrd.Crd.Spec.Names.Kind, "kind": testcrd.Crd.Spec.Names.Kind,
"apiVersion": testcrd.Crd.Spec.Group + "/" + testcrd.Crd.Spec.Version, "apiVersion": testcrd.Crd.Spec.Group + "/" + testcrd.Crd.Spec.Versions[0].Name,
"metadata": map[string]interface{}{ "metadata": map[string]interface{}{
"name": crName, "name": crName,
"namespace": f.Namespace.Name, "namespace": f.Namespace.Name,
@ -1752,8 +1754,29 @@ func testMultiVersionCustomResourceWebhook(f *framework.Framework, testcrd *crd.
framework.ExpectNoError(err, "failed to create custom resource %s in namespace: %s", crName, f.Namespace.Name) framework.ExpectNoError(err, "failed to create custom resource %s in namespace: %s", crName, f.Namespace.Name)
ginkgo.By("Patching Custom Resource Definition to set v2 as storage") ginkgo.By("Patching Custom Resource Definition to set v2 as storage")
apiVersionWithV2StoragePatch := fmt.Sprint(`{"spec": {"versions": [{"name": "v1", "storage": false, "served": true},{"name": "v2", "storage": true, "served": true}]}}`) apiVersionWithV2StoragePatch := `{
_, err = testcrd.APIExtensionClient.ApiextensionsV1beta1().CustomResourceDefinitions().Patch(testcrd.Crd.Name, types.StrategicMergePatchType, []byte(apiVersionWithV2StoragePatch)) "spec": {
"versions": [
{
"name": "v1",
"storage": false,
"served": true,
"schema": {
"openAPIV3Schema": {"x-kubernetes-preserve-unknown-fields": true, "type": "object"}
}
},
{
"name": "v2",
"storage": true,
"served": true,
"schema": {
"openAPIV3Schema": {"x-kubernetes-preserve-unknown-fields": true, "type": "object"}
}
}
]
}
}`
_, err = testcrd.APIExtensionClient.ApiextensionsV1().CustomResourceDefinitions().Patch(testcrd.Crd.Name, types.StrategicMergePatchType, []byte(apiVersionWithV2StoragePatch))
framework.ExpectNoError(err, "failed to patch custom resource definition %s in namespace: %s", testcrd.Crd.Name, f.Namespace.Name) framework.ExpectNoError(err, "failed to patch custom resource definition %s in namespace: %s", testcrd.Crd.Name, f.Namespace.Name)
ginkgo.By("Patching the custom resource while v2 is storage version") ginkgo.By("Patching the custom resource while v2 is storage version")
@ -1798,7 +1821,7 @@ func registerValidatingWebhookForCRD(f *framework.Framework, configName string,
CABundle: context.signingCert, CABundle: context.signingCert,
}, },
SideEffects: &sideEffectsNone, SideEffects: &sideEffectsNone,
AdmissionReviewVersions: []string{"v1beta1"}, AdmissionReviewVersions: []string{"v1", "v1beta1"},
// Scope the webhook to just this namespace // Scope the webhook to just this namespace
NamespaceSelector: &metav1.LabelSelector{ NamespaceSelector: &metav1.LabelSelector{
MatchLabels: map[string]string{f.UniqueName: "true"}, MatchLabels: map[string]string{f.UniqueName: "true"},
@ -1819,12 +1842,18 @@ func testCRDDenyWebhook(f *framework.Framework) {
ginkgo.By("Creating a custom resource definition that should be denied by the webhook") ginkgo.By("Creating a custom resource definition that should be denied by the webhook")
name := fmt.Sprintf("e2e-test-%s-%s-crd", f.BaseName, "deny") name := fmt.Sprintf("e2e-test-%s-%s-crd", f.BaseName, "deny")
kind := fmt.Sprintf("E2e-test-%s-%s-crd", f.BaseName, "deny") kind := fmt.Sprintf("E2e-test-%s-%s-crd", f.BaseName, "deny")
group := fmt.Sprintf("%s-crd-test.k8s.io", f.BaseName) group := fmt.Sprintf("%s.example.com", f.BaseName)
apiVersions := []apiextensionsv1beta1.CustomResourceDefinitionVersion{ apiVersions := []apiextensionsv1.CustomResourceDefinitionVersion{
{ {
Name: "v1", Name: "v1",
Served: true, Served: true,
Storage: true, Storage: true,
Schema: &apiextensionsv1.CustomResourceValidation{
OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{
XPreserveUnknownFields: pointer.BoolPtr(true),
Type: "object",
},
},
}, },
} }
@ -1839,28 +1868,28 @@ func testCRDDenyWebhook(f *framework.Framework) {
e2elog.Failf("failed to initialize apiExtensionClient: %v", err) e2elog.Failf("failed to initialize apiExtensionClient: %v", err)
return return
} }
crd := &apiextensionsv1beta1.CustomResourceDefinition{ crd := &apiextensionsv1.CustomResourceDefinition{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: name + "s." + group, Name: name + "s." + group,
Labels: map[string]string{ Labels: map[string]string{
"webhook-e2e-test": "webhook-disallow", "webhook-e2e-test": "webhook-disallow",
}, },
}, },
Spec: apiextensionsv1beta1.CustomResourceDefinitionSpec{ Spec: apiextensionsv1.CustomResourceDefinitionSpec{
Group: group, Group: group,
Versions: apiVersions, Versions: apiVersions,
Names: apiextensionsv1beta1.CustomResourceDefinitionNames{ Names: apiextensionsv1.CustomResourceDefinitionNames{
Singular: name, Singular: name,
Kind: kind, Kind: kind,
ListKind: kind + "List", ListKind: kind + "List",
Plural: name + "s", Plural: name + "s",
}, },
Scope: apiextensionsv1beta1.NamespaceScoped, Scope: apiextensionsv1.NamespaceScoped,
}, },
} }
// create CRD // create CRD
_, err = apiExtensionClient.ApiextensionsV1beta1().CustomResourceDefinitions().Create(crd) _, err = apiExtensionClient.ApiextensionsV1().CustomResourceDefinitions().Create(crd)
framework.ExpectError(err, "create custom resource definition %s should be denied by webhook", crd.Name) framework.ExpectError(err, "create custom resource definition %s should be denied by webhook", crd.Name)
expectedErrMsg := "the crd contains unwanted label" expectedErrMsg := "the crd contains unwanted label"
if !strings.Contains(err.Error(), expectedErrMsg) { if !strings.Contains(err.Error(), expectedErrMsg) {
@ -1920,7 +1949,7 @@ func registerSlowWebhook(f *framework.Framework, configName string, context *cer
FailurePolicy: policy, FailurePolicy: policy,
TimeoutSeconds: timeout, TimeoutSeconds: timeout,
SideEffects: &sideEffectsNone, SideEffects: &sideEffectsNone,
AdmissionReviewVersions: []string{"v1beta1"}, AdmissionReviewVersions: []string{"v1", "v1beta1"},
}, },
}, },
}) })
@ -1958,25 +1987,37 @@ func testSlowWebhookTimeoutNoError(f *framework.Framework) {
// createAdmissionWebhookMultiVersionTestCRDWithV1Storage creates a new CRD specifically // createAdmissionWebhookMultiVersionTestCRDWithV1Storage creates a new CRD specifically
// for the admissin webhook calling test. // for the admissin webhook calling test.
func createAdmissionWebhookMultiVersionTestCRDWithV1Storage(f *framework.Framework, opts ...crd.Option) (*crd.TestCrd, error) { func createAdmissionWebhookMultiVersionTestCRDWithV1Storage(f *framework.Framework, opts ...crd.Option) (*crd.TestCrd, error) {
group := fmt.Sprintf("%s-multiversion-crd-test.k8s.io", f.BaseName) group := fmt.Sprintf("%s.example.com", f.BaseName)
return crd.CreateMultiVersionTestCRD(f, group, append([]crd.Option{func(crd *apiextensionsv1beta1.CustomResourceDefinition) { return crd.CreateMultiVersionTestCRD(f, group, append([]crd.Option{func(crd *apiextensionsv1.CustomResourceDefinition) {
crd.Spec.Versions = []apiextensionsv1beta1.CustomResourceDefinitionVersion{ crd.Spec.Versions = []apiextensionsv1.CustomResourceDefinitionVersion{
{ {
Name: "v1", Name: "v1",
Served: true, Served: true,
Storage: true, Storage: true,
Schema: &apiextensionsv1.CustomResourceValidation{
OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{
XPreserveUnknownFields: pointer.BoolPtr(true),
Type: "object",
},
},
}, },
{ {
Name: "v2", Name: "v2",
Served: true, Served: true,
Storage: false, Storage: false,
Schema: &apiextensionsv1.CustomResourceValidation{
OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{
XPreserveUnknownFields: pointer.BoolPtr(true),
Type: "object",
},
},
}, },
} }
}}, opts...)...) }}, opts...)...)
} }
// servedAPIVersions returns the API versions served by the CRD. // servedAPIVersions returns the API versions served by the CRD.
func servedAPIVersions(crd *apiextensionsv1beta1.CustomResourceDefinition) []string { func servedAPIVersions(crd *apiextensionsv1.CustomResourceDefinition) []string {
ret := []string{} ret := []string{}
for _, v := range crd.Spec.Versions { for _, v := range crd.Spec.Versions {
if v.Served { if v.Served {
@ -2038,7 +2079,7 @@ func newDenyPodWebhookFixture(f *framework.Framework, context *certContext) admi
CABundle: context.signingCert, CABundle: context.signingCert,
}, },
SideEffects: &sideEffectsNone, SideEffects: &sideEffectsNone,
AdmissionReviewVersions: []string{"v1beta1"}, AdmissionReviewVersions: []string{"v1", "v1beta1"},
// Scope the webhook to just this namespace // Scope the webhook to just this namespace
NamespaceSelector: &metav1.LabelSelector{ NamespaceSelector: &metav1.LabelSelector{
MatchLabels: map[string]string{f.UniqueName: "true"}, MatchLabels: map[string]string{f.UniqueName: "true"},
@ -2079,7 +2120,7 @@ func newDenyConfigMapWebhookFixture(f *framework.Framework, context *certContext
CABundle: context.signingCert, CABundle: context.signingCert,
}, },
SideEffects: &sideEffectsNone, SideEffects: &sideEffectsNone,
AdmissionReviewVersions: []string{"v1beta1"}, AdmissionReviewVersions: []string{"v1", "v1beta1"},
} }
} }
@ -2105,7 +2146,7 @@ func newMutateConfigMapWebhookFixture(f *framework.Framework, context *certConte
CABundle: context.signingCert, CABundle: context.signingCert,
}, },
SideEffects: &sideEffectsNone, SideEffects: &sideEffectsNone,
AdmissionReviewVersions: []string{"v1beta1"}, AdmissionReviewVersions: []string{"v1", "v1beta1"},
// Scope the webhook to just this namespace // Scope the webhook to just this namespace
NamespaceSelector: &metav1.LabelSelector{ NamespaceSelector: &metav1.LabelSelector{
MatchLabels: map[string]string{f.UniqueName: "true"}, MatchLabels: map[string]string{f.UniqueName: "true"},

View File

@ -17,7 +17,7 @@ go_library(
"//pkg/controller:go_default_library", "//pkg/controller:go_default_library",
"//staging/src/k8s.io/api/core/v1:go_default_library", "//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/api/rbac/v1:go_default_library", "//staging/src/k8s.io/api/rbac/v1:go_default_library",
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1:go_default_library", "//staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1: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/resource:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/api/resource: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",
@ -49,6 +49,7 @@ go_library(
"//vendor/github.com/onsi/gomega:go_default_library", "//vendor/github.com/onsi/gomega:go_default_library",
"//vendor/golang.org/x/net/websocket:go_default_library", "//vendor/golang.org/x/net/websocket:go_default_library",
"//vendor/k8s.io/utils/exec:go_default_library", "//vendor/k8s.io/utils/exec:go_default_library",
"//vendor/k8s.io/utils/pointer:go_default_library",
"//vendor/sigs.k8s.io/yaml:go_default_library", "//vendor/sigs.k8s.io/yaml:go_default_library",
], ],
) )

View File

@ -41,10 +41,13 @@ import (
"github.com/elazarl/goproxy" "github.com/elazarl/goproxy"
openapi_v2 "github.com/googleapis/gnostic/OpenAPIv2" openapi_v2 "github.com/googleapis/gnostic/OpenAPIv2"
uexec "k8s.io/utils/exec"
"k8s.io/utils/pointer"
"sigs.k8s.io/yaml"
v1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1" rbacv1 "k8s.io/api/rbac/v1"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
apierrs "k8s.io/apimachinery/pkg/api/errors" apierrs "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -69,8 +72,6 @@ import (
"k8s.io/kubernetes/test/e2e/scheduling" "k8s.io/kubernetes/test/e2e/scheduling"
testutils "k8s.io/kubernetes/test/utils" testutils "k8s.io/kubernetes/test/utils"
"k8s.io/kubernetes/test/utils/crd" "k8s.io/kubernetes/test/utils/crd"
uexec "k8s.io/utils/exec"
"sigs.k8s.io/yaml"
"github.com/onsi/ginkgo" "github.com/onsi/ginkgo"
"github.com/onsi/gomega" "github.com/onsi/gomega"
@ -927,12 +928,14 @@ metadata:
ginkgo.It("should create/apply a valid CR for CRD with validation schema", func() { ginkgo.It("should create/apply a valid CR for CRD with validation schema", func() {
ginkgo.By("prepare CRD with validation schema") ginkgo.By("prepare CRD with validation schema")
crd, err := crd.CreateTestCRD(f, func(crd *v1beta1.CustomResourceDefinition) { crd, err := crd.CreateTestCRD(f, func(crd *apiextensionsv1.CustomResourceDefinition) {
props := &v1beta1.JSONSchemaProps{} props := &apiextensionsv1.JSONSchemaProps{}
if err := yaml.Unmarshal(schemaFoo, props); err != nil { if err := yaml.Unmarshal(schemaFoo, props); err != nil {
e2elog.Failf("failed to unmarshal schema: %v", err) e2elog.Failf("failed to unmarshal schema: %v", err)
} }
crd.Spec.Validation = &v1beta1.CustomResourceValidation{OpenAPIV3Schema: props} for i := range crd.Spec.Versions {
crd.Spec.Versions[i].Schema = &apiextensionsv1.CustomResourceValidation{OpenAPIV3Schema: props}
}
}) })
if err != nil { if err != nil {
e2elog.Failf("failed to create test CRD: %v", err) e2elog.Failf("failed to create test CRD: %v", err)
@ -951,12 +954,16 @@ metadata:
ginkgo.It("should create/apply a valid CR with arbitrary-extra properties for CRD with partially-specified validation schema", func() { ginkgo.It("should create/apply a valid CR with arbitrary-extra properties for CRD with partially-specified validation schema", func() {
ginkgo.By("prepare CRD with partially-specified validation schema") ginkgo.By("prepare CRD with partially-specified validation schema")
crd, err := crd.CreateTestCRD(f, func(crd *v1beta1.CustomResourceDefinition) { crd, err := crd.CreateTestCRD(f, func(crd *apiextensionsv1.CustomResourceDefinition) {
props := &v1beta1.JSONSchemaProps{} props := &apiextensionsv1.JSONSchemaProps{}
if err := yaml.Unmarshal(schemaFoo, props); err != nil { if err := yaml.Unmarshal(schemaFoo, props); err != nil {
e2elog.Failf("failed to unmarshal schema: %v", err) e2elog.Failf("failed to unmarshal schema: %v", err)
} }
crd.Spec.Validation = &v1beta1.CustomResourceValidation{OpenAPIV3Schema: props} // Allow for arbitrary-extra properties.
props.XPreserveUnknownFields = pointer.BoolPtr(true)
for i := range crd.Spec.Versions {
crd.Spec.Versions[i].Schema = &apiextensionsv1.CustomResourceValidation{OpenAPIV3Schema: props}
}
}) })
if err != nil { if err != nil {
e2elog.Failf("failed to create test CRD: %v", err) e2elog.Failf("failed to create test CRD: %v", err)
@ -966,31 +973,14 @@ metadata:
ginkgo.By("sleep for 10s to wait for potential crd openapi publishing alpha feature") ginkgo.By("sleep for 10s to wait for potential crd openapi publishing alpha feature")
time.Sleep(10 * time.Second) time.Sleep(10 * time.Second)
publishedSchema := schemaForGVK(schema.GroupVersionKind{Group: crd.Crd.Spec.Group, Version: crd.Crd.Spec.Version, Kind: crd.Crd.Spec.Names.Kind}) schema := schemaForGVK(schema.GroupVersionKind{Group: crd.Crd.Spec.Group, Version: crd.Crd.Spec.Versions[0].Name, Kind: crd.Crd.Spec.Names.Kind})
expectSuccess := false framework.ExpectNotEqual(schema, nil, "retrieving a schema for the crd")
if publishedSchema == nil || publishedSchema.Properties == nil || publishedSchema.Properties.AdditionalProperties == nil || len(publishedSchema.Properties.AdditionalProperties) == 0 {
// expect success in the following cases:
// - no schema was published
// - a schema was published with no properties
expectSuccess = true
e2elog.Logf("no schema with properties found, expect apply with extra properties to succeed")
} else {
e2elog.Logf("schema with properties found, expect apply with extra properties to fail")
}
meta := fmt.Sprintf(metaPattern, crd.Crd.Spec.Names.Kind, crd.Crd.Spec.Group, crd.Crd.Spec.Versions[0].Name, "test-cr") meta := fmt.Sprintf(metaPattern, crd.Crd.Spec.Names.Kind, crd.Crd.Spec.Group, crd.Crd.Spec.Versions[0].Name, "test-cr")
validArbitraryCR := fmt.Sprintf(`{%s,"spec":{"bars":[{"name":"test-bar"}],"extraProperty":"arbitrary-value"}}`, meta) validArbitraryCR := fmt.Sprintf(`{%s,"spec":{"bars":[{"name":"test-bar"}],"extraProperty":"arbitrary-value"}}`, meta)
if err := createApplyCustomResource(validArbitraryCR, f.Namespace.Name, "test-cr", crd); err != nil { err = createApplyCustomResource(validArbitraryCR, f.Namespace.Name, "test-cr", crd)
if expectSuccess { framework.ExpectNoError(err, "creating custom resource")
e2elog.Failf("%v", err)
}
} else {
if !expectSuccess {
e2elog.Failf("expected error, got none")
}
}
}) })
}) })
framework.KubeDescribe("Kubectl cluster-info", func() { framework.KubeDescribe("Kubectl cluster-info", func() {

View File

@ -6,7 +6,7 @@ go_library(
importpath = "k8s.io/kubernetes/test/utils/crd", importpath = "k8s.io/kubernetes/test/utils/crd",
visibility = ["//visibility:public"], visibility = ["//visibility:public"],
deps = [ deps = [
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1:go_default_library", "//staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1:go_default_library",
"//staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset:go_default_library", "//staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset: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/apimachinery/pkg/apis/meta/v1:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
@ -14,6 +14,7 @@ go_library(
"//staging/src/k8s.io/client-go/dynamic:go_default_library", "//staging/src/k8s.io/client-go/dynamic:go_default_library",
"//test/e2e/framework:go_default_library", "//test/e2e/framework:go_default_library",
"//test/e2e/framework/log:go_default_library", "//test/e2e/framework/log:go_default_library",
"//vendor/k8s.io/utils/pointer:go_default_library",
], ],
) )

View File

@ -19,7 +19,9 @@ package crd
import ( import (
"fmt" "fmt"
apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" "k8s.io/utils/pointer"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
crdclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" crdclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
"k8s.io/apiextensions-apiserver/test/integration/fixtures" "k8s.io/apiextensions-apiserver/test/integration/fixtures"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -35,13 +37,13 @@ type CleanCrdFn func() error
// TestCrd holds all the pieces needed to test with the CRD // TestCrd holds all the pieces needed to test with the CRD
type TestCrd struct { type TestCrd struct {
APIExtensionClient *crdclientset.Clientset APIExtensionClient *crdclientset.Clientset
Crd *apiextensionsv1beta1.CustomResourceDefinition Crd *apiextensionsv1.CustomResourceDefinition
DynamicClients map[string]dynamic.ResourceInterface DynamicClients map[string]dynamic.ResourceInterface
CleanUp CleanCrdFn CleanUp CleanCrdFn
} }
// Option is a modifier for a CRD object used to customize CreateMultiVersionTestCRD and CreateTestCRD. // Option is a modifier for a CRD object used to customize CreateMultiVersionTestCRD and CreateTestCRD.
type Option func(crd *apiextensionsv1beta1.CustomResourceDefinition) type Option func(crd *apiextensionsv1.CustomResourceDefinition)
// CreateMultiVersionTestCRD creates a new CRD specifically for the calling test. // CreateMultiVersionTestCRD creates a new CRD specifically for the calling test.
func CreateMultiVersionTestCRD(f *framework.Framework, group string, opts ...Option) (*TestCrd, error) { func CreateMultiVersionTestCRD(f *framework.Framework, group string, opts ...Option) (*TestCrd, error) {
@ -67,28 +69,38 @@ func CreateMultiVersionTestCRD(f *framework.Framework, group string, opts ...Opt
return nil, err return nil, err
} }
crd := &apiextensionsv1beta1.CustomResourceDefinition{ crd := &apiextensionsv1.CustomResourceDefinition{
ObjectMeta: metav1.ObjectMeta{Name: name + "s." + group}, ObjectMeta: metav1.ObjectMeta{Name: name + "s." + group},
Spec: apiextensionsv1beta1.CustomResourceDefinitionSpec{ Spec: apiextensionsv1.CustomResourceDefinitionSpec{
Group: group, Group: group,
Names: apiextensionsv1beta1.CustomResourceDefinitionNames{ Names: apiextensionsv1.CustomResourceDefinitionNames{
Plural: name + "s", Plural: name + "s",
Singular: name, Singular: name,
Kind: kind, Kind: kind,
ListKind: kind + "List", ListKind: kind + "List",
}, },
Scope: apiextensionsv1beta1.NamespaceScoped, Scope: apiextensionsv1.NamespaceScoped,
}, },
} }
for _, opt := range opts { for _, opt := range opts {
opt(crd) opt(crd)
} }
if len(crd.Spec.Versions) == 0 && len(crd.Spec.Version) == 0 { if len(crd.Spec.Versions) == 0 {
crd.Spec.Version = "v1" crd.Spec.Versions = []apiextensionsv1.CustomResourceDefinitionVersion{{
Served: true,
Storage: true,
Name: "v1",
Schema: &apiextensionsv1.CustomResourceValidation{
OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{
XPreserveUnknownFields: pointer.BoolPtr(true),
Type: "object",
},
},
}}
} }
//create CRD and waits for the resource to be recognized and available. //create CRD and waits for the resource to be recognized and available.
crd, err = fixtures.CreateNewCustomResourceDefinitionWatchUnsafe(crd, apiExtensionClient) crd, err = fixtures.CreateNewV1CustomResourceDefinitionWatchUnsafe(crd, apiExtensionClient)
if err != nil { if err != nil {
e2elog.Failf("failed to create CustomResourceDefinition: %v", err) e2elog.Failf("failed to create CustomResourceDefinition: %v", err)
return nil, err return nil, err
@ -106,7 +118,7 @@ func CreateMultiVersionTestCRD(f *framework.Framework, group string, opts ...Opt
testcrd.Crd = crd testcrd.Crd = crd
testcrd.DynamicClients = resourceClients testcrd.DynamicClients = resourceClients
testcrd.CleanUp = func() error { testcrd.CleanUp = func() error {
err := fixtures.DeleteCustomResourceDefinition(crd, apiExtensionClient) err := fixtures.DeleteV1CustomResourceDefinition(crd, apiExtensionClient)
if err != nil { if err != nil {
e2elog.Failf("failed to delete CustomResourceDefinition(%s): %v", name, err) e2elog.Failf("failed to delete CustomResourceDefinition(%s): %v", name, err)
} }
@ -117,13 +129,19 @@ func CreateMultiVersionTestCRD(f *framework.Framework, group string, opts ...Opt
// CreateTestCRD creates a new CRD specifically for the calling test. // CreateTestCRD creates a new CRD specifically for the calling test.
func CreateTestCRD(f *framework.Framework, opts ...Option) (*TestCrd, error) { func CreateTestCRD(f *framework.Framework, opts ...Option) (*TestCrd, error) {
group := fmt.Sprintf("%s-crd-test.k8s.io", f.BaseName) group := fmt.Sprintf("%s.example.com", f.BaseName)
return CreateMultiVersionTestCRD(f, group, append([]Option{func(crd *apiextensionsv1beta1.CustomResourceDefinition) { return CreateMultiVersionTestCRD(f, group, append([]Option{func(crd *apiextensionsv1.CustomResourceDefinition) {
crd.Spec.Versions = []apiextensionsv1beta1.CustomResourceDefinitionVersion{ crd.Spec.Versions = []apiextensionsv1.CustomResourceDefinitionVersion{
{ {
Name: "v1", Name: "v1",
Served: true, Served: true,
Storage: true, Storage: true,
Schema: &apiextensionsv1.CustomResourceValidation{
OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{
XPreserveUnknownFields: pointer.BoolPtr(true),
Type: "object",
},
},
}, },
} }
}}, opts...)...) }}, opts...)...)

View File

@ -204,7 +204,7 @@ const (
func initImageConfigs() map[int]Config { func initImageConfigs() map[int]Config {
configs := map[int]Config{} configs := map[int]Config{}
configs[Agnhost] = Config{e2eRegistry, "agnhost", "2.4"} configs[Agnhost] = Config{e2eRegistry, "agnhost", "2.5"}
configs[Alpine] = Config{dockerLibraryRegistry, "alpine", "3.7"} configs[Alpine] = Config{dockerLibraryRegistry, "alpine", "3.7"}
configs[AuthenticatedAlpine] = Config{gcAuthenticatedRegistry, "alpine", "3.7"} configs[AuthenticatedAlpine] = Config{gcAuthenticatedRegistry, "alpine", "3.7"}
configs[AuthenticatedWindowsNanoServer] = Config{gcAuthenticatedRegistry, "windows-nanoserver", "v1"} configs[AuthenticatedWindowsNanoServer] = Config{gcAuthenticatedRegistry, "windows-nanoserver", "v1"}