Support DryRun in cli-runtime REST Helper.

- Move TestSupportsDryRun to cli-runtime
- Move TestDryRunVerifier to cli-runtime
- Add OpenAPI schema for testdata to cli-runtime
- Use Helper.DryRun and DryRunVerifier for Apply
- Add WithOptions methods to Helper

Kubernetes-commit: 6bea0e469bcfe4967cbf2656a998d60e0b684747
This commit is contained in:
Julian V. Modesto 2019-12-18 22:00:34 -05:00 committed by Kubernetes Publisher
parent 4f155a6381
commit a3f7877cd1
11 changed files with 46 additions and 516 deletions

View File

@ -27,7 +27,6 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/cli-runtime/pkg/genericclioptions"
@ -207,10 +206,20 @@ func NewCmdApply(baseName string, f cmdutil.Factory, ioStreams genericclioptions
// Complete verifies if ApplyOptions are valid and without conflicts.
func (o *ApplyOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) error {
var err error
o.ServerSideApply = cmdutil.GetServerSideApplyFlag(cmd)
o.ForceConflicts = cmdutil.GetForceConflictsFlag(cmd)
o.FieldManager = cmdutil.GetFieldManagerFlag(cmd)
o.DryRun = cmdutil.GetDryRunFlag(cmd)
o.DynamicClient, err = f.DynamicClient()
if err != nil {
return err
}
o.DiscoveryClient, err = f.ToDiscoveryClient()
if err != nil {
return err
}
if o.ForceConflicts && !o.ServerSideApply {
return fmt.Errorf("--force-conflicts only works with --server-side")
@ -236,23 +245,13 @@ func (o *ApplyOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) error {
return o.PrintFlags.ToPrinter()
}
var err error
o.RecordFlags.Complete(cmd)
o.Recorder, err = o.RecordFlags.ToRecorder()
if err != nil {
return err
}
o.DiscoveryClient, err = f.ToDiscoveryClient()
if err != nil {
return err
}
dynamicClient, err := f.DynamicClient()
if err != nil {
return err
}
o.DeleteOptions = o.DeleteFlags.ToOptions(dynamicClient, o.IOStreams)
o.DeleteOptions = o.DeleteFlags.ToOptions(o.DynamicClient, o.IOStreams)
err = o.DeleteOptions.FilenameOptions.RequireFilenameOrKustomize()
if err != nil {
return err
@ -269,11 +268,6 @@ func (o *ApplyOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) error {
return err
}
o.DynamicClient, err = f.DynamicClient()
if err != nil {
return err
}
o.Namespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
if err != nil {
return err
@ -360,11 +354,6 @@ func (o *ApplyOptions) SetObjects(infos []*resource.Info) {
// Run executes the `apply` command.
func (o *ApplyOptions) Run() error {
dryRunVerifier := &DryRunVerifier{
Finder: cmdutil.NewCRDFinder(cmdutil.CRDFromDynamic(o.DynamicClient)),
OpenAPIGetter: o.DiscoveryClient,
}
if o.PreProcessorFn != nil {
klog.V(4).Infof("Running apply pre-processor function")
if err := o.PreProcessorFn(); err != nil {
@ -388,13 +377,6 @@ func (o *ApplyOptions) Run() error {
}
for _, info := range infos {
// If server-dry-run is requested but the type doesn't support it, fail right away.
if o.ServerDryRun {
if err := dryRunVerifier.HasSupport(info.Mapping.GroupVersionKind); err != nil {
return err
}
}
o.MarkNamespaceVisited(info)
if err := o.Recorder.Record(info.Object); err != nil {
@ -412,11 +394,15 @@ func (o *ApplyOptions) Run() error {
Force: &o.ForceConflicts,
FieldManager: o.FieldManager,
}
if o.ServerDryRun {
options.DryRun = []string{metav1.DryRunAll}
}
obj, err := resource.NewHelper(info.Client, info.Mapping).Patch(
helper := resource.NewHelper(info.Client, info.Mapping)
if o.ServerDryRun {
if err := resource.VerifyDryRun(info.Mapping.GroupVersionKind, o.DynamicClient, o.DiscoveryClient); err != nil {
return err
}
helper.DryRun(o.ServerDryRun)
}
obj, err := helper.Patch(
info.Namespace,
info.Name,
types.ApplyPatchType,
@ -486,11 +472,14 @@ See http://k8s.io/docs/reference/using-api/api-concepts/#conflicts`, err)
if !o.DryRun {
// Then create the resource and skip the three-way merge
options := metav1.CreateOptions{}
helper := resource.NewHelper(info.Client, info.Mapping)
if o.ServerDryRun {
options.DryRun = []string{metav1.DryRunAll}
if err := resource.VerifyDryRun(info.Mapping.GroupVersionKind, o.DynamicClient, o.DiscoveryClient); err != nil {
return cmdutil.AddSourceToErr("creating", info.Source, err)
}
helper.DryRun(o.ServerDryRun)
}
obj, err := resource.NewHelper(info.Client, info.Mapping).Create(info.Namespace, true, info.Object, &options)
obj, err := helper.Create(info.Namespace, true, info.Object)
if err != nil {
return cmdutil.AddSourceToErr("creating", info.Source, err)
}
@ -526,7 +515,10 @@ See http://k8s.io/docs/reference/using-api/api-concepts/#conflicts`, err)
fmt.Fprintf(o.ErrOut, warningNoLastAppliedConfigAnnotation, o.cmdBaseName)
}
patcher := newPatcher(o, info)
patcher, err := newPatcher(o, info)
if err != nil {
return err
}
patchBytes, patchedObject, err := patcher.Patch(info.Object, modified, info.Source, info.Namespace, info.Name, o.ErrOut)
if err != nil {
return cmdutil.AddSourceToErr(fmt.Sprintf("applying patch:\n%s\nto:\n%v\nfor:", patchBytes, info), info.Source, err)
@ -663,42 +655,3 @@ func (o *ApplyOptions) PrintAndPrunePostProcessor() func() error {
return nil
}
}
// 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
}

View File

@ -29,7 +29,6 @@ import (
"strings"
"testing"
"github.com/googleapis/gnostic/OpenAPIv2"
"github.com/spf13/cobra"
appsv1 "k8s.io/api/apps/v1"
@ -1389,75 +1388,3 @@ 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")
}
}

View File

@ -25,7 +25,6 @@ import (
"github.com/jonboulle/clockwork"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
@ -75,13 +74,19 @@ type Patcher struct {
OpenapiSchema openapi.Resources
}
func newPatcher(o *ApplyOptions, info *resource.Info) *Patcher {
func newPatcher(o *ApplyOptions, info *resource.Info) (*Patcher, error) {
var openapiSchema openapi.Resources
if o.OpenAPIPatch {
openapiSchema = o.OpenAPISchema
}
helper := resource.NewHelper(info.Client, info.Mapping)
if o.ServerDryRun {
if err := resource.VerifyDryRun(info.Mapping.GroupVersionKind, o.DynamicClient, o.DiscoveryClient); err != nil {
return nil, err
}
helper.DryRun(o.ServerDryRun)
}
return &Patcher{
Mapping: info.Mapping,
Helper: helper,
@ -95,7 +100,7 @@ func newPatcher(o *ApplyOptions, info *resource.Info) *Patcher {
ServerDryRun: o.ServerDryRun,
OpenapiSchema: openapiSchema,
Retries: maxPatchRetry,
}
}, nil
}
func (p *Patcher) delete(namespace, name string) error {
@ -180,12 +185,7 @@ func (p *Patcher) patchSimple(obj runtime.Object, modified []byte, source, names
}
}
options := metav1.PatchOptions{}
if p.ServerDryRun {
options.DryRun = []string{metav1.DryRunAll}
}
patchedObj, err := p.Helper.Patch(namespace, name, patchType, patch, &options)
patchedObj, err := p.Helper.Patch(namespace, name, patchType, patch, nil)
return patch, patchedObj, err
}
@ -230,15 +230,11 @@ func (p *Patcher) deleteAndCreate(original runtime.Object, modified []byte, name
if err != nil {
return modified, nil, err
}
options := metav1.CreateOptions{}
if p.ServerDryRun {
options.DryRun = []string{metav1.DryRunAll}
}
createdObject, err := p.Helper.Create(namespace, true, versionedObject, &options)
createdObject, err := p.Helper.Create(namespace, true, versionedObject)
if err != nil {
// restore the original object if we fail to create the new one
// but still propagate and advertise error to user
recreated, recreateErr := p.Helper.Create(namespace, true, original, &options)
recreated, recreateErr := p.Helper.Create(namespace, true, original)
if recreateErr != nil {
err = fmt.Errorf("An error occurred force-replacing the existing object with the newly provided one:\n\n%v.\n\nAdditionally, an error occurred attempting to restore the original object:\n\n%v", err, recreateErr)
} else {

View File

@ -299,7 +299,7 @@ func RunEditOnCreate(f cmdutil.Factory, printFlags *genericclioptions.PrintFlags
// createAndRefresh creates an object from input info and refreshes info with that object
func createAndRefresh(info *resource.Info) error {
obj, err := resource.NewHelper(info.Client, info.Mapping).Create(info.Namespace, true, info.Object, nil)
obj, err := resource.NewHelper(info.Client, info.Mapping).Create(info.Namespace, true, info.Object)
if err != nil {
return err
}

View File

@ -77,7 +77,7 @@ type DiffOptions struct {
OpenAPISchema openapi.Resources
DiscoveryClient discovery.DiscoveryInterface
DynamicClient dynamic.Interface
DryRunVerifier *apply.DryRunVerifier
DryRunVerifier *resource.DryRunVerifier
CmdNamespace string
EnforceNamespace bool
Builder *resource.Builder
@ -295,7 +295,7 @@ func (obj InfoObject) Merged() (runtime.Object, error) {
// Build the patcher, and then apply the patch with dry-run, unless the object doesn't exist, in which case we need to create it.
if obj.Live() == nil {
// Dry-run create if the object doesn't exist.
return resource.NewHelper(obj.Info.Client, obj.Info.Mapping).Create(
return resource.NewHelper(obj.Info.Client, obj.Info.Mapping).CreateWithOptions(
obj.Info.Namespace,
true,
obj.LocalObj,
@ -427,10 +427,7 @@ func (o *DiffOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) error {
return err
}
o.DryRunVerifier = &apply.DryRunVerifier{
Finder: cmdutil.NewCRDFinder(cmdutil.CRDFromDynamic(o.DynamicClient)),
OpenAPIGetter: o.DiscoveryClient,
}
o.DryRunVerifier = resource.NewDryRunVerifier(o.DynamicClient, o.DiscoveryClient)
o.CmdNamespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
if err != nil {

View File

@ -355,7 +355,7 @@ func (o *ReplaceOptions) forceReplace() error {
klog.V(4).Infof("error recording current command: %v", err)
}
obj, err := resource.NewHelper(info.Client, info.Mapping).Create(info.Namespace, true, info.Object, nil)
obj, err := resource.NewHelper(info.Client, info.Mapping).Create(info.Namespace, true, info.Object)
if err != nil {
return err
}

View File

@ -708,7 +708,7 @@ func (o *RunOptions) createGeneratedObject(f cmdutil.Factory, cmd *cobra.Command
if err != nil {
return nil, err
}
actualObj, err = resource.NewHelper(client, mapping).Create(namespace, false, obj, nil)
actualObj, err = resource.NewHelper(client, mapping).Create(namespace, false, obj)
if err != nil {
return nil, err
}

View File

@ -1,109 +0,0 @@
/*
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 (
"fmt"
"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: "customresourcedefinitions",
}).List(metav1.ListOptions{})
if err != nil {
return nil, fmt.Errorf("failed to list CRDs: %v", 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
}

View File

@ -1,89 +0,0 @@
/*
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/kubectl/pkg/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")
}
}

View File

@ -1,65 +0,0 @@
/*
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")
}

View File

@ -1,80 +0,0 @@
/*
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/kubectl/pkg/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,
)
}
}
}