Merge pull request #74832 from roycaihw/kubectl-crd-test

kubectl e2e: test client-side validation behavior on CustomResources
pull/564/head
Kubernetes Prow Robot 2019-03-06 22:55:21 -08:00 committed by GitHub
commit 9ce5966ff0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 150 additions and 0 deletions

View File

@ -24,6 +24,8 @@ import (
"k8s.io/apiextensions-apiserver/test/integration/fixtures"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
utilyaml "k8s.io/apimachinery/pkg/util/yaml"
"k8s.io/client-go/dynamic"
)
@ -185,3 +187,14 @@ func (c *TestCrd) GetAPIVersions() []string {
func (c *TestCrd) GetV1DynamicClient() dynamic.ResourceInterface {
return c.DynamicClients["v1"]
}
// PatchSchema takes validation schema in YAML and patches it to given CRD
func (c *TestCrd) PatchSchema(schema []byte) error {
s, err := utilyaml.ToJSON(schema)
if err != nil {
return fmt.Errorf("failed to create json patch: %v", err)
}
patch := []byte(fmt.Sprintf(`{"spec":{"validation":{"openAPIV3Schema":%s}}}`, string(s)))
c.Crd, err = c.ApiExtensionClient.ApiextensionsV1beta1().CustomResourceDefinitions().Patch(c.GetMetaName(), types.MergePatchType, patch)
return err
}

View File

@ -2371,6 +2371,11 @@ func RunKubectlOrDieInput(data string, args ...string) string {
return NewKubectlCommand(args...).WithStdinData(data).ExecOrDie()
}
// RunKubectlInput is a convenience wrapper over kubectlBuilder that takes input to stdin
func RunKubectlInput(data string, args ...string) (string, error) {
return NewKubectlCommand(args...).WithStdinData(data).Exec()
}
// RunKubemciWithKubeconfig is a convenience wrapper over RunKubemciCmd
func RunKubemciWithKubeconfig(args ...string) (string, error) {
if TestContext.KubeConfig != "" {

View File

@ -88,6 +88,7 @@ const (
nginxDeployment1Filename = "nginx-deployment1.yaml.in"
nginxDeployment2Filename = "nginx-deployment2.yaml.in"
nginxDeployment3Filename = "nginx-deployment3.yaml.in"
metaPattern = `"kind":"%s","apiVersion":"%s/%s","metadata":{"name":"%s"}`
)
var (
@ -819,6 +820,69 @@ metadata:
})
})
framework.KubeDescribe("Kubectl client-side validation", func() {
ginkgo.It("should create/apply a CR with unknown fields for CRD with no validation schema", func() {
ginkgo.By("create CRD with no validation schema")
crd, err := framework.CreateTestCRD(f)
if err != nil {
framework.Failf("failed to create test CRD: %v", err)
}
defer crd.CleanUp()
ginkgo.By("sleep for 10s to wait for potential crd openapi publishing alpha feature")
time.Sleep(10 * time.Second)
meta := fmt.Sprintf(metaPattern, crd.Kind, crd.ApiGroup, crd.Versions[0].Name, "test-cr")
randomCR := fmt.Sprintf(`{%s,"a":{"b":[{"c":"d"}]}}`, meta)
if err := createApplyCustomResource(randomCR, f.Namespace.Name, "test-cr", crd); err != nil {
framework.Failf("%v", err)
}
})
ginkgo.It("should create/apply a valid CR for CRD with validation schema", func() {
ginkgo.By("prepare CRD with validation schema")
crd, err := framework.CreateTestCRD(f)
if err != nil {
framework.Failf("failed to create test CRD: %v", err)
}
defer crd.CleanUp()
if err := crd.PatchSchema(schemaFoo); err != nil {
framework.Failf("%v", err)
}
ginkgo.By("sleep for 10s to wait for potential crd openapi publishing alpha feature")
time.Sleep(10 * time.Second)
meta := fmt.Sprintf(metaPattern, crd.Kind, crd.ApiGroup, crd.Versions[0].Name, "test-cr")
validCR := fmt.Sprintf(`{%s,"spec":{"bars":[{"name":"test-bar"}]}}`, meta)
if err := createApplyCustomResource(validCR, f.Namespace.Name, "test-cr", crd); err != nil {
framework.Failf("%v", err)
}
})
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")
crd, err := framework.CreateTestCRD(f)
if err != nil {
framework.Failf("failed to create test CRD: %v", err)
}
defer crd.CleanUp()
if err := crd.PatchSchema(schemaFoo); err != nil {
framework.Failf("%v", err)
}
ginkgo.By("sleep for 10s to wait for potential crd openapi publishing alpha feature")
time.Sleep(10 * time.Second)
meta := fmt.Sprintf(metaPattern, crd.Kind, crd.ApiGroup, crd.Versions[0].Name, "test-cr")
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 {
framework.Failf("%v", err)
}
})
})
framework.KubeDescribe("Kubectl cluster-info", func() {
/*
Release : v1.9
@ -2159,3 +2223,71 @@ func startLocalProxy() (srv *httptest.Server, logs *bytes.Buffer) {
p.Logger = log.New(logs, "", 0)
return httptest.NewServer(p), logs
}
// createApplyCustomResource asserts that given CustomResource be created and applied
// without being rejected by client-side validation
func createApplyCustomResource(resource, namespace, name string, crd *framework.TestCrd) error {
ns := fmt.Sprintf("--namespace=%v", namespace)
ginkgo.By("successfully create CR")
if _, err := framework.RunKubectlInput(resource, ns, "create", "-f", "-"); err != nil {
return fmt.Errorf("failed to create CR %s in namespace %s: %v", resource, ns, err)
}
if _, err := framework.RunKubectl(ns, "delete", crd.GetPluralName(), name); err != nil {
return fmt.Errorf("failed to delete CR %s: %v", name, err)
}
ginkgo.By("successfully apply CR")
if _, err := framework.RunKubectlInput(resource, ns, "apply", "-f", "-"); err != nil {
return fmt.Errorf("failed to apply CR %s in namespace %s: %v", resource, ns, err)
}
if _, err := framework.RunKubectl(ns, "delete", crd.GetPluralName(), name); err != nil {
return fmt.Errorf("failed to delete CR %s: %v", name, err)
}
return nil
}
var schemaFoo = []byte(`description: Foo CRD for Testing
type: object
properties:
spec:
type: object
description: Specification of Foo
properties:
bars:
description: List of Bars and their specs.
type: array
items:
type: object
required:
- name
properties:
name:
description: Name of Bar.
type: string
age:
description: Age of Bar.
type: string
bazs:
description: List of Bazs.
items:
type: string
type: array
status:
description: Status of Foo
type: object
properties:
bars:
description: List of Bars and their statuses.
type: array
items:
type: object
properties:
name:
description: Name of Bar.
type: string
available:
description: Whether the Bar is installed.
type: boolean
quxType:
description: Indicates to external qux type.
pattern: in-tree|out-of-tree
type: string`)