Merge pull request #86408 from julianvmodesto/kubectl-ss-dry-run-helper

Support server-side dry-run in cli-runtime REST Helper

Kubernetes-commit: c01451585e600017a5a58fb56ef04b5ae58e6abf
This commit is contained in:
Kubernetes Publisher 2020-01-21 09:21:37 -08:00
commit 36b559a5df
14 changed files with 50 additions and 520 deletions

2
Godeps/Godeps.json generated
View File

@ -576,7 +576,7 @@
},
{
"ImportPath": "k8s.io/cli-runtime",
"Rev": "b7dc99061fe0"
"Rev": "f770c8c66535"
},
{
"ImportPath": "k8s.io/client-go",

4
go.mod
View File

@ -38,7 +38,7 @@ require (
gotest.tools v2.2.0+incompatible // indirect
k8s.io/api v0.0.0-20200118233722-7aecbd569fd4
k8s.io/apimachinery v0.0.0-20200118233534-b615468efe04
k8s.io/cli-runtime v0.0.0-20200118235715-b7dc99061fe0
k8s.io/cli-runtime v0.0.0-20200121201526-f770c8c66535
k8s.io/client-go v0.0.0-20200118233946-a432bd9ba7da
k8s.io/component-base v0.0.0-20200118234435-5735a1699056
k8s.io/klog v1.0.0
@ -55,7 +55,7 @@ replace (
golang.org/x/tools => golang.org/x/tools v0.0.0-20190821162956-65e3620a7ae7 // pinned to release-branch.go1.13
k8s.io/api => k8s.io/api v0.0.0-20200118233722-7aecbd569fd4
k8s.io/apimachinery => k8s.io/apimachinery v0.0.0-20200118233534-b615468efe04
k8s.io/cli-runtime => k8s.io/cli-runtime v0.0.0-20200118235715-b7dc99061fe0
k8s.io/cli-runtime => k8s.io/cli-runtime v0.0.0-20200121201526-f770c8c66535
k8s.io/client-go => k8s.io/client-go v0.0.0-20200118233946-a432bd9ba7da
k8s.io/code-generator => k8s.io/code-generator v0.0.0-20200118233344-48228b4f418f
k8s.io/component-base => k8s.io/component-base v0.0.0-20200118234435-5735a1699056

2
go.sum
View File

@ -319,7 +319,7 @@ honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWh
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
k8s.io/api v0.0.0-20200118233722-7aecbd569fd4/go.mod h1:iTnrvplu5Iag9UUDHF5+igjjHk++6USiI3t2qaW2bX8=
k8s.io/apimachinery v0.0.0-20200118233534-b615468efe04/go.mod h1:j05cPXobKCShrrgR2gz+Wsw3flBA+nvUoLQLo1PdydY=
k8s.io/cli-runtime v0.0.0-20200118235715-b7dc99061fe0/go.mod h1:LV6a8Vt7HnVfrBjfyEkP0NnXrbPEr5zjR78CWms4iS0=
k8s.io/cli-runtime v0.0.0-20200121201526-f770c8c66535/go.mod h1:PUTKe0hOrak64cnVMmNTLvtWkWNOHNLDpta4O9xjmQc=
k8s.io/client-go v0.0.0-20200118233946-a432bd9ba7da/go.mod h1:q/VkAWSNaZSYCIh4i2xT6kRRgshtiFdeLxxVK/4Y+9w=
k8s.io/code-generator v0.0.0-20200118233344-48228b4f418f/go.mod h1:1Bsaqodc1TFZRb+hdtaQ1SJWc5RIkLIT9ZxThIv2S7M=
k8s.io/component-base v0.0.0-20200118234435-5735a1699056/go.mod h1:FkOXiiponoAxWNDDcjOwEgl4TVmMFI/SJx6TjeWfYHU=

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,
)
}
}
}