diff --git a/pkg/kubectl/cmd/apply/BUILD b/pkg/kubectl/cmd/apply/BUILD index a29c7e77e0..0b16d0c8e9 100644 --- a/pkg/kubectl/cmd/apply/BUILD +++ b/pkg/kubectl/cmd/apply/BUILD @@ -36,6 +36,7 @@ go_library( "//staging/src/k8s.io/cli-runtime/pkg/genericclioptions:go_default_library", "//staging/src/k8s.io/cli-runtime/pkg/genericclioptions/printers:go_default_library", "//staging/src/k8s.io/cli-runtime/pkg/genericclioptions/resource:go_default_library", + "//staging/src/k8s.io/client-go/discovery:go_default_library", "//staging/src/k8s.io/client-go/dynamic:go_default_library", "//vendor/github.com/ghodss/yaml:go_default_library", "//vendor/github.com/golang/glog:go_default_library", @@ -49,6 +50,7 @@ go_test( name = "go_default_test", srcs = ["apply_test.go"], data = [ + "//api/openapi-spec:swagger-spec", "//test/fixtures", ], embed = [":go_default_library"], @@ -71,6 +73,7 @@ go_test( "//staging/src/k8s.io/client-go/rest:go_default_library", "//staging/src/k8s.io/client-go/rest/fake:go_default_library", "//staging/src/k8s.io/client-go/testing:go_default_library", + "//vendor/github.com/googleapis/gnostic/OpenAPIv2:go_default_library", "//vendor/github.com/spf13/cobra:go_default_library", ], ) diff --git a/pkg/kubectl/cmd/apply/apply.go b/pkg/kubectl/cmd/apply/apply.go index e1ee8366ac..8b13130f72 100644 --- a/pkg/kubectl/cmd/apply/apply.go +++ b/pkg/kubectl/cmd/apply/apply.go @@ -42,6 +42,7 @@ import ( "k8s.io/cli-runtime/pkg/genericclioptions" "k8s.io/cli-runtime/pkg/genericclioptions/printers" "k8s.io/cli-runtime/pkg/genericclioptions/resource" + "k8s.io/client-go/discovery" "k8s.io/client-go/dynamic" oapi "k8s.io/kube-openapi/pkg/util/proto" "k8s.io/kubernetes/pkg/kubectl" @@ -76,11 +77,12 @@ type ApplyOptions struct { PruneWhitelist []string ShouldIncludeUninitialized bool - Validator validation.Schema - Builder *resource.Builder - Mapper meta.RESTMapper - DynamicClient dynamic.Interface - OpenAPISchema openapi.Resources + Validator validation.Schema + Builder *resource.Builder + Mapper meta.RESTMapper + DynamicClient dynamic.Interface + DiscoveryClient discovery.DiscoveryInterface + OpenAPISchema openapi.Resources Namespace string EnforceNamespace bool @@ -211,6 +213,11 @@ func (o *ApplyOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) error { return err } + o.DiscoveryClient, err = f.ToDiscoveryClient() + if err != nil { + return err + } + dynamicClient, err := f.DynamicClient() if err != nil { return err @@ -403,19 +410,25 @@ func (o *ApplyOptions) Run() error { fmt.Fprintf(o.ErrOut, warningNoLastAppliedConfigAnnotation, o.cmdBaseName) } + dryRunVerifier := &DryRunVerifier{ + Finder: cmdutil.NewCRDFinder(cmdutil.CRDFromDynamic(o.DynamicClient)), + OpenAPIGetter: o.DiscoveryClient, + } + helper := resource.NewHelper(info.Client, info.Mapping) patcher := &Patcher{ - Mapping: info.Mapping, - Helper: helper, - DynamicClient: o.DynamicClient, - Overwrite: o.Overwrite, - BackOff: clockwork.NewRealClock(), - Force: o.DeleteOptions.ForceDeletion, - Cascade: o.DeleteOptions.Cascade, - Timeout: o.DeleteOptions.Timeout, - GracePeriod: o.DeleteOptions.GracePeriod, - ServerDryRun: o.ServerDryRun, - OpenapiSchema: openapiSchema, + Mapping: info.Mapping, + Helper: helper, + DynamicClient: o.DynamicClient, + DryRunVerifier: dryRunVerifier, + Overwrite: o.Overwrite, + BackOff: clockwork.NewRealClock(), + Force: o.DeleteOptions.ForceDeletion, + Cascade: o.DeleteOptions.Cascade, + Timeout: o.DeleteOptions.Timeout, + GracePeriod: o.DeleteOptions.GracePeriod, + ServerDryRun: o.ServerDryRun, + OpenapiSchema: openapiSchema, } patchBytes, patchedObject, err := patcher.Patch(info.Object, modified, info.Source, info.Namespace, info.Name, o.ErrOut) @@ -668,9 +681,10 @@ func (p *Patcher) delete(namespace, name string) error { } type Patcher struct { - Mapping *meta.RESTMapping - Helper *resource.Helper - DynamicClient dynamic.Interface + Mapping *meta.RESTMapping + Helper *resource.Helper + DynamicClient dynamic.Interface + DryRunVerifier *DryRunVerifier Overwrite bool BackOff clockwork.Clock @@ -684,7 +698,52 @@ type Patcher struct { OpenapiSchema openapi.Resources } +// DryRunVerifier verifies if a given group-version-kind supports DryRun +// against the current server. Sending dryRun requests to apiserver that +// don't support it will result in objects being unwillingly persisted. +// +// It reads the OpenAPI to see if the given GVK supports dryRun. If the +// GVK can not be found, we assume that CRDs will have the same level of +// support as "namespaces", and non-CRDs will not be supported. We +// delay the check for CRDs as much as possible though, since it +// requires an extra round-trip to the server. +type DryRunVerifier struct { + Finder cmdutil.CRDFinder + OpenAPIGetter discovery.OpenAPISchemaInterface +} + +// HasSupport verifies if the given gvk supports DryRun. An error is +// returned if it doesn't. +func (v *DryRunVerifier) HasSupport(gvk schema.GroupVersionKind) error { + oapi, err := v.OpenAPIGetter.OpenAPISchema() + if err != nil { + return fmt.Errorf("failed to download openapi: %v", err) + } + supports, err := openapi.SupportsDryRun(oapi, gvk) + if err != nil { + // We assume that we couldn't find the type, then check for namespace: + supports, _ = openapi.SupportsDryRun(oapi, schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Namespace"}) + // If namespace supports dryRun, then we will support dryRun for CRDs only. + if supports { + supports, err = v.Finder.HasCRD(gvk.GroupKind()) + if err != nil { + return fmt.Errorf("failed to check CRD: %v", err) + } + } + } + if !supports { + return fmt.Errorf("%v doesn't support dry-run", gvk) + } + return nil +} + func (p *Patcher) patchSimple(obj runtime.Object, modified []byte, source, namespace, name string, errOut io.Writer) ([]byte, runtime.Object, error) { + if p.ServerDryRun { + if err := p.DryRunVerifier.HasSupport(p.Mapping.GroupVersionKind); err != nil { + return nil, nil, err + } + } + // Serialize the current configuration of the object from the server. current, err := runtime.Encode(unstructured.UnstructuredJSONScheme, obj) if err != nil { diff --git a/pkg/kubectl/cmd/apply/apply_test.go b/pkg/kubectl/cmd/apply/apply_test.go index 7611640b8c..8c1631f684 100644 --- a/pkg/kubectl/cmd/apply/apply_test.go +++ b/pkg/kubectl/cmd/apply/apply_test.go @@ -29,6 +29,7 @@ import ( "strings" "testing" + "github.com/googleapis/gnostic/OpenAPIv2" "github.com/spf13/cobra" corev1 "k8s.io/api/core/v1" @@ -52,7 +53,7 @@ import ( ) var ( - fakeSchema = sptest.Fake{Path: filepath.Join("..", "..", "..", "api", "openapi-spec", "swagger.json")} + fakeSchema = sptest.Fake{Path: filepath.Join("..", "..", "..", "..", "api", "openapi-spec", "swagger.json")} testingOpenAPISchemaFns = []func() (openapi.Resources, error){nil, AlwaysErrorOpenAPISchemaFn, openAPISchemaFn} AlwaysErrorOpenAPISchemaFn = func() (openapi.Resources, error) { return nil, errors.New("cannot get openapi spec") @@ -1317,3 +1318,75 @@ func TestForceApply(t *testing.T) { }) } } + +func TestDryRunVerifier(t *testing.T) { + dryRunVerifier := DryRunVerifier{ + Finder: cmdutil.NewCRDFinder(func() ([]schema.GroupKind, error) { + return []schema.GroupKind{ + { + Group: "crd.com", + Kind: "MyCRD", + }, + { + Group: "crd.com", + Kind: "MyNewCRD", + }, + }, nil + }), + OpenAPIGetter: &fakeSchema, + } + + err := dryRunVerifier.HasSupport(schema.GroupVersionKind{Group: "", Version: "v1", Kind: "NodeProxyOptions"}) + if err == nil { + t.Fatalf("NodeProxyOptions doesn't support dry-run, yet no error found") + } + + err = dryRunVerifier.HasSupport(schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Pod"}) + if err != nil { + t.Fatalf("Pod should support dry-run: %v", err) + } + + err = dryRunVerifier.HasSupport(schema.GroupVersionKind{Group: "crd.com", Version: "v1", Kind: "MyCRD"}) + if err != nil { + t.Fatalf("MyCRD should support dry-run: %v", err) + } + + err = dryRunVerifier.HasSupport(schema.GroupVersionKind{Group: "crd.com", Version: "v1", Kind: "Random"}) + if err == nil { + t.Fatalf("Random doesn't support dry-run, yet no error found") + } +} + +type EmptyOpenAPI struct{} + +func (EmptyOpenAPI) OpenAPISchema() (*openapi_v2.Document, error) { + return &openapi_v2.Document{}, nil +} + +func TestDryRunVerifierNoOpenAPI(t *testing.T) { + dryRunVerifier := DryRunVerifier{ + Finder: cmdutil.NewCRDFinder(func() ([]schema.GroupKind, error) { + return []schema.GroupKind{ + { + Group: "crd.com", + Kind: "MyCRD", + }, + { + Group: "crd.com", + Kind: "MyNewCRD", + }, + }, nil + }), + OpenAPIGetter: EmptyOpenAPI{}, + } + + err := dryRunVerifier.HasSupport(schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Pod"}) + if err == nil { + t.Fatalf("Pod doesn't support dry-run, yet no error found") + } + + err = dryRunVerifier.HasSupport(schema.GroupVersionKind{Group: "crd.com", Version: "v1", Kind: "MyCRD"}) + if err == nil { + t.Fatalf("MyCRD doesn't support dry-run, yet no error found") + } +} diff --git a/pkg/kubectl/cmd/diff/diff.go b/pkg/kubectl/cmd/diff/diff.go index 5f1ffc0fa6..5a58d6a8a2 100644 --- a/pkg/kubectl/cmd/diff/diff.go +++ b/pkg/kubectl/cmd/diff/diff.go @@ -226,10 +226,11 @@ type Object interface { // InfoObject is an implementation of the Object interface. It gets all // the information from the Info object. type InfoObject struct { - LocalObj runtime.Object - Info *resource.Info - Encoder runtime.Encoder - OpenAPI openapi.Resources + LocalObj runtime.Object + Info *resource.Info + Encoder runtime.Encoder + OpenAPI openapi.Resources + DryRunVerifier *apply.DryRunVerifier } var _ Object = &InfoObject{} @@ -261,12 +262,13 @@ func (obj InfoObject) Merged() (runtime.Object, error) { // This is using the patcher from apply, to keep the same behavior. // We plan on replacing this with server-side apply when it becomes available. patcher := &apply.Patcher{ - Mapping: obj.Info.Mapping, - Helper: resource.NewHelper(obj.Info.Client, obj.Info.Mapping), - Overwrite: true, - BackOff: clockwork.NewRealClock(), - ServerDryRun: true, - OpenapiSchema: obj.OpenAPI, + DryRunVerifier: obj.DryRunVerifier, + Mapping: obj.Info.Mapping, + Helper: resource.NewHelper(obj.Info.Client, obj.Info.Mapping), + Overwrite: true, + BackOff: clockwork.NewRealClock(), + ServerDryRun: true, + OpenapiSchema: obj.OpenAPI, } _, result, err := patcher.Patch(obj.Info.Object, modified, obj.Info.Source, obj.Info.Namespace, obj.Info.Name, nil) @@ -330,6 +332,21 @@ func RunDiff(f cmdutil.Factory, diff *DiffProgram, options *DiffOptions) error { return err } + discovery, err := f.ToDiscoveryClient() + if err != nil { + return err + } + + dynamic, err := f.DynamicClient() + if err != nil { + return err + } + + dryRunVerifier := &apply.DryRunVerifier{ + Finder: cmdutil.NewCRDFinder(cmdutil.CRDFromDynamic(dynamic)), + OpenAPIGetter: discovery, + } + differ, err := NewDiffer("LIVE", "MERGED") if err != nil { return err @@ -367,10 +384,11 @@ func RunDiff(f cmdutil.Factory, diff *DiffProgram, options *DiffOptions) error { } obj := InfoObject{ - LocalObj: local, - Info: info, - Encoder: scheme.DefaultJSONEncoder(), - OpenAPI: schema, + LocalObj: local, + Info: info, + Encoder: scheme.DefaultJSONEncoder(), + OpenAPI: schema, + DryRunVerifier: dryRunVerifier, } return differ.Diff(obj, printer) diff --git a/pkg/kubectl/cmd/util/BUILD b/pkg/kubectl/cmd/util/BUILD index 124d14148a..b57b84e2f9 100644 --- a/pkg/kubectl/cmd/util/BUILD +++ b/pkg/kubectl/cmd/util/BUILD @@ -4,6 +4,7 @@ go_library( name = "go_default_library", srcs = [ "conversion.go", + "crdfinder.go", "factory.go", "factory_client_access.go", "generator.go", @@ -34,6 +35,7 @@ go_library( "//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/api/meta:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/util/errors:go_default_library", @@ -57,7 +59,10 @@ go_library( go_test( name = "go_default_test", - srcs = ["helpers_test.go"], + srcs = [ + "crdfinder_test.go", + "helpers_test.go", + ], embed = [":go_default_library"], deps = [ "//pkg/kubectl/scheme:go_default_library", diff --git a/pkg/kubectl/cmd/util/crdfinder.go b/pkg/kubectl/cmd/util/crdfinder.go new file mode 100644 index 0000000000..d3674ae160 --- /dev/null +++ b/pkg/kubectl/cmd/util/crdfinder.go @@ -0,0 +1,108 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package util + +import ( + "reflect" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/dynamic" +) + +// CRDGetter is a function that can download the list of GVK for all +// CRDs. +type CRDGetter func() ([]schema.GroupKind, error) + +func CRDFromDynamic(client dynamic.Interface) CRDGetter { + return func() ([]schema.GroupKind, error) { + list, err := client.Resource(schema.GroupVersionResource{ + Group: "apiextensions.k8s.io", + Version: "v1beta1", + Resource: "curstomresourcedefinitions", + }).List(metav1.ListOptions{}) + if err != nil { + return nil, err + } + if list == nil { + return nil, nil + } + + gks := []schema.GroupKind{} + + // We need to parse the list to get the gvk, I guess that's fine. + for _, crd := range (*list).Items { + // Look for group, version, and kind + group, _, _ := unstructured.NestedString(crd.Object, "spec", "group") + kind, _, _ := unstructured.NestedString(crd.Object, "spec", "names", "kind") + + gks = append(gks, schema.GroupKind{ + Group: group, + Kind: kind, + }) + } + + return gks, nil + } +} + +// CRDFinder keeps a cache of known CRDs and finds a given GVK in the +// list. +type CRDFinder interface { + HasCRD(gvk schema.GroupKind) (bool, error) +} + +func NewCRDFinder(getter CRDGetter) CRDFinder { + return &crdFinder{ + getter: getter, + } +} + +type crdFinder struct { + getter CRDGetter + cache *[]schema.GroupKind +} + +func (f *crdFinder) cacheCRDs() error { + if f.cache != nil { + return nil + } + + list, err := f.getter() + if err != nil { + return err + } + f.cache = &list + return nil +} + +func (f *crdFinder) findCRD(gvk schema.GroupKind) bool { + for _, crd := range *f.cache { + if reflect.DeepEqual(gvk, crd) { + return true + } + } + return false +} + +func (f *crdFinder) HasCRD(gvk schema.GroupKind) (bool, error) { + if err := f.cacheCRDs(); err != nil { + return false, err + } + return f.findCRD(gvk), nil +} diff --git a/pkg/kubectl/cmd/util/crdfinder_test.go b/pkg/kubectl/cmd/util/crdfinder_test.go new file mode 100644 index 0000000000..5c90a0c256 --- /dev/null +++ b/pkg/kubectl/cmd/util/crdfinder_test.go @@ -0,0 +1,89 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package util_test + +import ( + "errors" + "testing" + + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/kubernetes/pkg/kubectl/cmd/util" +) + +func TestCacheCRDFinder(t *testing.T) { + called := 0 + getter := func() ([]schema.GroupKind, error) { + called += 1 + return nil, nil + } + finder := util.NewCRDFinder(getter) + if called != 0 { + t.Fatalf("Creating the finder shouldn't call the getter, has called = %v", called) + } + _, err := finder.HasCRD(schema.GroupKind{Group: "", Kind: "Pod"}) + if err != nil { + t.Fatalf("Failed to call HasCRD: %v", err) + } + if called != 1 { + t.Fatalf("First call should call the getter, has called = %v", called) + } + + _, err = finder.HasCRD(schema.GroupKind{Group: "", Kind: "Pod"}) + if err != nil { + t.Fatalf("Failed to call HasCRD: %v", err) + } + if called != 1 { + t.Fatalf("Second call should NOT call the getter, has called = %v", called) + } +} + +func TestCRDFinderErrors(t *testing.T) { + getter := func() ([]schema.GroupKind, error) { + return nil, errors.New("not working") + } + finder := util.NewCRDFinder(getter) + found, err := finder.HasCRD(schema.GroupKind{Group: "", Kind: "Pod"}) + if found == true { + t.Fatalf("Found the CRD with non-working getter function") + } + if err == nil { + t.Fatalf("Error in getter should be reported") + } +} + +func TestCRDFinder(t *testing.T) { + getter := func() ([]schema.GroupKind, error) { + return []schema.GroupKind{ + { + Group: "crd.com", + Kind: "MyCRD", + }, + { + Group: "crd.com", + Kind: "MyNewCRD", + }, + }, nil + } + finder := util.NewCRDFinder(getter) + + if found, _ := finder.HasCRD(schema.GroupKind{Group: "crd.com", Kind: "MyCRD"}); !found { + t.Fatalf("Failed to find CRD MyCRD") + } + if found, _ := finder.HasCRD(schema.GroupKind{Group: "crd.com", Kind: "Random"}); found { + t.Fatalf("Found crd Random that doesn't exist") + } +} diff --git a/pkg/kubectl/cmd/util/openapi/BUILD b/pkg/kubectl/cmd/util/openapi/BUILD index 398af66eec..5958bec309 100644 --- a/pkg/kubectl/cmd/util/openapi/BUILD +++ b/pkg/kubectl/cmd/util/openapi/BUILD @@ -10,6 +10,7 @@ go_library( name = "go_default_library", srcs = [ "doc.go", + "dryrun.go", "extensions.go", "openapi.go", "openapi_getter.go", @@ -20,6 +21,7 @@ go_library( "//staging/src/k8s.io/client-go/discovery:go_default_library", "//vendor/github.com/go-openapi/spec:go_default_library", "//vendor/github.com/googleapis/gnostic/OpenAPIv2:go_default_library", + "//vendor/gopkg.in/yaml.v2:go_default_library", "//vendor/k8s.io/kube-openapi/pkg/util/proto:go_default_library", ], ) @@ -28,6 +30,7 @@ go_test( name = "go_default_test", size = "small", srcs = [ + "dryrun_test.go", "openapi_getter_test.go", "openapi_suite_test.go", "openapi_test.go", diff --git a/pkg/kubectl/cmd/util/openapi/dryrun.go b/pkg/kubectl/cmd/util/openapi/dryrun.go new file mode 100644 index 0000000000..33cf9e9e5c --- /dev/null +++ b/pkg/kubectl/cmd/util/openapi/dryrun.go @@ -0,0 +1,65 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package openapi + +import ( + "errors" + + openapi_v2 "github.com/googleapis/gnostic/OpenAPIv2" + yaml "gopkg.in/yaml.v2" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +func hasGVKExtension(extensions []*openapi_v2.NamedAny, gvk schema.GroupVersionKind) bool { + for _, extension := range extensions { + if extension.GetValue().GetYaml() == "" || + extension.GetName() != "x-kubernetes-group-version-kind" { + continue + } + var value map[string]string + err := yaml.Unmarshal([]byte(extension.GetValue().GetYaml()), &value) + if err != nil { + continue + } + + if value["group"] == gvk.Group && value["kind"] == gvk.Kind && value["version"] == gvk.Version { + return true + } + return false + } + return false +} + +// SupportsDryRun is a method that let's us look in the OpenAPI if the +// specific group-version-kind supports the dryRun query parameter for +// the PATCH end-point. +func SupportsDryRun(doc *openapi_v2.Document, gvk schema.GroupVersionKind) (bool, error) { + for _, path := range doc.GetPaths().GetPath() { + // Is this describing the gvk we're looking for? + if !hasGVKExtension(path.GetValue().GetPatch().GetVendorExtension(), gvk) { + continue + } + for _, param := range path.GetValue().GetPatch().GetParameters() { + if param.GetParameter().GetNonBodyParameter().GetQueryParameterSubSchema().GetName() == "dryRun" { + return true, nil + } + } + return false, nil + } + + return false, errors.New("couldn't find GVK in openapi") +} diff --git a/pkg/kubectl/cmd/util/openapi/dryrun_test.go b/pkg/kubectl/cmd/util/openapi/dryrun_test.go new file mode 100644 index 0000000000..e9398945b3 --- /dev/null +++ b/pkg/kubectl/cmd/util/openapi/dryrun_test.go @@ -0,0 +1,80 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package openapi_test + +import ( + "testing" + + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi" +) + +func TestSupportsDryRun(t *testing.T) { + doc, err := fakeSchema.OpenAPISchema() + if err != nil { + t.Fatalf("Failed to get OpenAPI Schema: %v", err) + } + + tests := []struct { + gvk schema.GroupVersionKind + success bool + supports bool + }{ + { + gvk: schema.GroupVersionKind{ + Group: "", + Version: "v1", + Kind: "Pod", + }, + success: true, + supports: true, + }, + { + gvk: schema.GroupVersionKind{ + Group: "", + Version: "v1", + Kind: "UnknownKind", + }, + success: false, + supports: false, + }, + { + gvk: schema.GroupVersionKind{ + Group: "", + Version: "v1", + Kind: "NodeProxyOptions", + }, + success: true, + supports: false, + }, + } + + for _, test := range tests { + supports, err := openapi.SupportsDryRun(doc, test.gvk) + if supports != test.supports || ((err == nil) != test.success) { + errStr := "nil" + if test.success == false { + errStr = "err" + } + t.Errorf("SupportsDryRun(doc, %v) = (%v, %v), expected (%v, %v)", + test.gvk, + supports, err, + test.supports, errStr, + ) + } + } +}