Skip to content

Commit

Permalink
Add --wait and --timeout flags (#72)
Browse files Browse the repository at this point in the history
  • Loading branch information
micnncim committed Sep 20, 2020
1 parent 940a294 commit 268701b
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 21 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,10 +174,12 @@ Flags:
-l, --selector string Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)
-s, --server string The address and port of the Kubernetes API server
--template string Template string or path to template file to use when -o=go-template, -o=go-template-file. The template format is golang templates [http://golang.org/pkg/text/template/#pkg-overview].
--timeout duration The length of time to wait before giving up on a delete, zero means determine a timeout from the size of the object
--tls-server-name string Server name to use for server certificate validation. If it is not provided, the hostname used to contact the server is used
--token string Bearer token for authentication to the API server
--user string The name of the kubeconfig user to use
-v, --version If true, show the version of this plugin
--wait If true, wait for resources to be gone before returning. This waits for finalizers.

```

Expand Down
3 changes: 3 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXP
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7 h1:5ZkaAPbicIKTF2I64qf5Fh8Aa83Q/dnOafMYV0OMwjA=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
Expand Down Expand Up @@ -174,6 +175,7 @@ github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OI
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
Expand All @@ -186,6 +188,7 @@ github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmg
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174 h1:WlZsjVhE8Af9IcZDGgJGQpNflI3+MJSBhsgT5PCtzBQ=
Expand Down
110 changes: 89 additions & 21 deletions pkg/cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,21 @@ import (
"errors"
"fmt"
"strings"
"time"

"github.com/spf13/cobra"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/cli-runtime/pkg/printers"
cliresource "k8s.io/cli-runtime/pkg/resource"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes/scheme"
_ "k8s.io/client-go/plugin/pkg/client/auth"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
cmdwait "k8s.io/kubectl/pkg/cmd/wait"

"github.com/micnncim/kubectl-prune/pkg/determiner"
"github.com/micnncim/kubectl-prune/pkg/prompt"
Expand Down Expand Up @@ -52,17 +57,21 @@ Delete unused resources. Supported resources:
printedOperationTypeDeleted = "deleted"
)

var timeWeek = 168 * time.Hour

type Options struct {
configFlags *genericclioptions.ConfigFlags
printFlags *genericclioptions.PrintFlags

namespace string
allNamespaces bool
chunkSize int64
labelSelector string
fieldSelector string
gracePeriod int
forceDeletion bool
namespace string
allNamespaces bool
chunkSize int64
labelSelector string
fieldSelector string
gracePeriod int
forceDeletion bool
waitForDeletion bool
timeout time.Duration

quiet bool
interactive bool
Expand All @@ -72,9 +81,10 @@ type Options struct {
dryRunStrategy cmdutil.DryRunStrategy
dryRunVerifier *cliresource.DryRunVerifier

determiner determiner.Determiner
printer printers.ResourcePrinter
result *cliresource.Result
determiner determiner.Determiner
dynamicClient dynamic.Interface
printer printers.ResourcePrinter
result *cliresource.Result

genericclioptions.IOStreams
}
Expand All @@ -97,7 +107,7 @@ func NewCmdPrune(streams genericclioptions.IOStreams) *cobra.Command {
Example: pruneExample,
Run: func(cmd *cobra.Command, args []string) {
if o.showVersion {
fmt.Fprintf(o.Out, "%s (%s)\n", version.Version, version.Revision)
o.Infof("%s (%s)\n", version.Version, version.Revision)
return
}

Expand All @@ -119,6 +129,8 @@ func NewCmdPrune(streams genericclioptions.IOStreams) *cobra.Command {
cmd.Flags().StringVar(&o.fieldSelector, "field-selector", "", "Selector (field query) to filter on, supports '=', '==', and '!='.(e.g. --field-selector key1=value1,key2=value2). The server only supports a limited number of field queries per type.")
cmd.Flags().IntVar(&o.gracePeriod, "grace-period", -1, "Period of time in seconds given to the resource to terminate gracefully. Ignored if negative. Set to 1 for immediate shutdown. Can only be set to 0 when --force is true (force deletion).")
cmd.Flags().BoolVar(&o.forceDeletion, "force", false, "If true, immediately remove resources from API and bypass graceful deletion. Note that immediate deletion of some resources may result in inconsistency or data loss and requires confirmation.")
cmd.Flags().BoolVar(&o.waitForDeletion, "wait", false, "If true, wait for resources to be gone before returning. This waits for finalizers.")
cmd.Flags().DurationVar(&o.timeout, "timeout", 0, "The length of time to wait before giving up on a delete, zero means determine a timeout from the size of the object")
cmd.Flags().BoolVarP(&o.quiet, "quiet", "q", false, "If true, no output is produced")
cmd.Flags().BoolVarP(&o.interactive, "interactive", "i", false, "If true, a prompt asks whether resources can be deleted")
cmd.Flags().BoolVarP(&o.showVersion, "version", "v", false, "If true, show the version of this plugin")
Expand Down Expand Up @@ -158,17 +170,17 @@ func (o *Options) Complete(f cmdutil.Factory, args []string, cmd *cobra.Command)
if err != nil {
return
}
dynamicClient, err := f.DynamicClient()
o.dynamicClient, err = f.DynamicClient()
if err != nil {
return
}
resourceClient := resource.NewClient(clientset, dynamicClient)
resourceClient := resource.NewClient(clientset, o.dynamicClient)

discoveryClient, err := f.ToDiscoveryClient()
if err != nil {
return err
}
o.dryRunVerifier = cliresource.NewDryRunVerifier(dynamicClient, discoveryClient)
o.dryRunVerifier = cliresource.NewDryRunVerifier(o.dynamicClient, discoveryClient)

namespace := o.namespace
if o.allNamespaces {
Expand Down Expand Up @@ -220,7 +232,7 @@ func (o *Options) Validate(args []string) error {

switch {
case o.forceDeletion && o.gracePeriod == 0:
fmt.Fprintf(o.ErrOut, "warning: Immediate deletion does not wait for confirmation that the running resource has been terminated. The resource may continue to run on the cluster indefinitely.\n")
o.Errorf("warning: Immediate deletion does not wait for confirmation that the running resource has been terminated. The resource may continue to run on the cluster indefinitely.\n")
case o.forceDeletion && o.gracePeriod > 0:
return fmt.Errorf("--force and --grace-period greater than 0 cannot be specified together")
}
Expand All @@ -229,6 +241,13 @@ func (o *Options) Validate(args []string) error {
}

func (o *Options) Run(ctx context.Context, f cmdutil.Factory) error {
deletedInfos := []*cliresource.Info{}
uidMap := cmdwait.UIDMap{}
deleteOpts := &metav1.DeleteOptions{}
if o.gracePeriod >= 0 {
deleteOpts = metav1.NewDeleteOptions(int64(o.gracePeriod))
}

if err := o.result.Visit(func(info *cliresource.Info, err error) error {
if info.Namespace == metav1.NamespaceSystem {
return nil // ignore resources in kube-system namespace
Expand All @@ -242,6 +261,8 @@ func (o *Options) Run(ctx context.Context, f cmdutil.Factory) error {
return nil // skip deletion
}

deletedInfos = append(deletedInfos, info)

if o.interactive {
kind := info.Object.GetObjectKind().GroupVersionKind().Kind
if ok := prompt.Confirm(fmt.Sprintf("Are you sure to delete %s/%s?", strings.ToLower(kind), info.Name)); !ok {
Expand All @@ -259,14 +280,10 @@ func (o *Options) Run(ctx context.Context, f cmdutil.Factory) error {
}
}

opts := &metav1.DeleteOptions{}
if o.gracePeriod >= 0 {
opts = metav1.NewDeleteOptions(int64(o.gracePeriod))
}
_, err = cliresource.
resp, err := cliresource.
NewHelper(info.Client, info.Mapping).
DryRun(o.dryRunStrategy == cmdutil.DryRunServer).
DeleteWithOptions(info.Namespace, info.Name, opts)
DeleteWithOptions(info.Namespace, info.Name, deleteOpts)
if err != nil {
return err
}
Expand All @@ -275,14 +292,65 @@ func (o *Options) Run(ctx context.Context, f cmdutil.Factory) error {
o.printObj(info.Object)
}

loc := cmdwait.ResourceLocation{
GroupResource: info.Mapping.Resource.GroupResource(),
Namespace: info.Namespace,
Name: info.Name,
}
if status, ok := resp.(*metav1.Status); ok && status.Details != nil {
uidMap[loc] = status.Details.UID
return nil
}

accessor, err := meta.Accessor(resp)
if err != nil {
// we don't have UID, but we didn't fail the delete, next best thing is just skipping the UID
o.Infof("%v\n", err)
return nil
}
uidMap[loc] = accessor.GetUID()

return nil
}); err != nil {
return err
}

if !o.waitForDeletion {
return nil
}

timeout := o.timeout
if timeout == 0 {
timeout = timeWeek
}
waitOpts := cmdwait.WaitOptions{
ResourceFinder: genericclioptions.ResourceFinderForResult(cliresource.InfoListVisitor(deletedInfos)),
UIDMap: uidMap,
DynamicClient: o.dynamicClient,
Timeout: timeout,
Printer: printers.NewDiscardingPrinter(),
ConditionFn: cmdwait.IsDeleted,
IOStreams: o.IOStreams,
}
err := waitOpts.RunWait()
if apierrors.IsForbidden(err) || apierrors.IsMethodNotSupported(err) {
// if we're forbidden from waiting, we shouldn't fail.
// if the resource doesn't support a verb we need, we shouldn't fail.
o.Errorf("%v\n", err)
return nil
}

return nil
}

func (o *Options) Infof(format string, a ...interface{}) {
fmt.Fprintf(o.Out, format, a...)
}

func (o *Options) Errorf(format string, a ...interface{}) {
fmt.Fprintf(o.ErrOut, format, a...)
}

func (o *Options) printObj(obj runtime.Object) error {
return o.printer.PrintObj(obj, o.Out)
}

0 comments on commit 268701b

Please sign in to comment.