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:
parent
4f155a6381
commit
a3f7877cd1
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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")
|
||||
}
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue