add support for bulk deletion to ark restore delete

refactor util methods into a common package

move utility methods into cli package instead of common

rename util.go to common.go

Signed-off-by: Shubheksha Jalan <jshubheksha@gmail.com>
pull/865/head
Shubheksha Jalan 2018-09-24 12:17:54 +05:30
parent a28327b47e
commit e3232b7eb6
3 changed files with 197 additions and 57 deletions

View File

@ -17,10 +17,7 @@ limitations under the License.
package backup
import (
"bufio"
"fmt"
"os"
"strings"
"github.com/pkg/errors"
"github.com/spf13/cobra"
@ -34,6 +31,7 @@ import (
"github.com/heptio/ark/pkg/backup"
"github.com/heptio/ark/pkg/client"
"github.com/heptio/ark/pkg/cmd"
"github.com/heptio/ark/pkg/cmd/cli"
"github.com/heptio/ark/pkg/cmd/util/flag"
clientset "github.com/heptio/ark/pkg/generated/clientset/versioned"
)
@ -117,31 +115,16 @@ func (o *DeleteOptions) Validate(c *cobra.Command, args []string, f client.Facto
hasSelector = o.Selector.LabelSelector != nil
)
if !xor(hasNames, hasAll, hasSelector) {
if !cli.Xor(hasNames, hasAll, hasSelector) {
return errors.New("you must specify exactly one of: specific backup name(s), the --all flag, or the --selector flag")
}
return nil
}
// xor returns true if exactly one of the provided values is true,
// or false otherwise.
func xor(val bool, vals ...bool) bool {
res := val
for _, v := range vals {
if res && v {
return false
}
res = res || v
}
return res
}
// Run performs the delete backup operation.
func (o *DeleteOptions) Run() error {
if !o.Confirm && !getConfirmation() {
if !o.Confirm && !cli.GetConfirmation() {
// Don't do anything unless we get confirmation
return nil
}
@ -197,28 +180,3 @@ func (o *DeleteOptions) Run() error {
return kubeerrs.NewAggregate(errs)
}
func getConfirmation() bool {
reader := bufio.NewReader(os.Stdin)
for {
fmt.Printf("Are you sure you want to continue (Y/N)? ")
confirmation, err := reader.ReadString('\n')
if err != nil {
fmt.Fprintf(os.Stderr, "error reading user input: %v\n", err)
return false
}
confirmation = strings.TrimSpace(confirmation)
if len(confirmation) != 1 {
continue
}
switch strings.ToLower(confirmation) {
case "y":
return true
case "n":
return false
}
}
}

64
pkg/cmd/cli/common.go Normal file
View File

@ -0,0 +1,64 @@
/*
Copyright 2018 the Heptio Ark contributors.
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 cli
import (
"bufio"
"fmt"
"os"
"strings"
)
// GetConfirmation ensures that the user confirms the action before proceeding.
func GetConfirmation() bool {
reader := bufio.NewReader(os.Stdin)
for {
fmt.Printf("Are you sure you want to continue (Y/N)? ")
confirmation, err := reader.ReadString('\n')
if err != nil {
fmt.Fprintf(os.Stderr, "error reading user input: %v\n", err)
return false
}
confirmation = strings.TrimSpace(confirmation)
if len(confirmation) != 1 {
continue
}
switch strings.ToLower(confirmation) {
case "y":
return true
case "n":
return false
}
}
}
// Xor returns true if exactly one of the provided values is true,
// or false otherwise.
func Xor(val bool, vals ...bool) bool {
res := val
for _, v := range vals {
if res && v {
return false
}
res = res || v
}
return res
}

View File

@ -19,29 +19,147 @@ package restore
import (
"fmt"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
kubeerrs "k8s.io/apimachinery/pkg/util/errors"
arkv1api "github.com/heptio/ark/pkg/apis/ark/v1"
"github.com/heptio/ark/pkg/client"
"github.com/heptio/ark/pkg/cmd"
"github.com/heptio/ark/pkg/cmd/cli"
"github.com/heptio/ark/pkg/cmd/util/flag"
clientset "github.com/heptio/ark/pkg/generated/clientset/versioned"
)
// NewDeleteCommand creates and returns a new cobra command for deleting restores.
func NewDeleteCommand(f client.Factory, use string) *cobra.Command {
o := &DeleteOptions{}
c := &cobra.Command{
Use: fmt.Sprintf("%s NAME", use),
Short: "Delete a restore",
Args: cobra.ExactArgs(1),
Use: fmt.Sprintf("%s [NAMES]", use),
Short: "Delete restores",
Example: ` # delete a restore named "restore-1"
ark restore delete restore-1
# delete a restore named "restore-1" without prompting for confirmation
ark restore delete restore-1 --confirm
# delete restores named "restore-1" and "restore-2"
ark restore delete restore-1 restore-2
# delete all restores labelled with foo=bar"
ark restore delete --selector foo=bar
# delete all restores
ark restore delete --all`,
Run: func(c *cobra.Command, args []string) {
arkClient, err := f.Client()
cmd.CheckError(err)
cmd.CheckError(o.Complete(f, args))
cmd.CheckError(o.Validate(c, f, args))
cmd.CheckError(o.Run())
name := args[0]
err = arkClient.ArkV1().Restores(f.Namespace()).Delete(name, nil)
cmd.CheckError(err)
fmt.Printf("Restore %q deleted\n", name)
},
}
o.BindFlags(c.Flags())
return c
}
// DeleteOptions contains parameters used for deleting a restore.
type DeleteOptions struct {
Names []string
All bool
Selector flag.LabelSelector
Confirm bool
client clientset.Interface
namespace string
}
// Complete fills in the correct values for all the options.
func (o *DeleteOptions) Complete(f client.Factory, args []string) error {
o.namespace = f.Namespace()
client, err := f.Client()
if err != nil {
return err
}
o.client = client
o.Names = args
return nil
}
// Validate validates the fields of the DeleteOptions struct.
func (o *DeleteOptions) Validate(c *cobra.Command, f client.Factory, args []string) error {
if o.client == nil {
return errors.New("Ark client is not set; unable to proceed")
}
var (
hasNames = len(o.Names) > 0
hasAll = o.All
hasSelector = o.Selector.LabelSelector != nil
)
if !cli.Xor(hasNames, hasAll, hasSelector) {
return errors.New("you must specify exactly one of: specific restore name(s), the --all flag, or the --selector flag")
}
return nil
}
// Run performs the deletion of restore(s).
func (o *DeleteOptions) Run() error {
if !o.Confirm && !cli.GetConfirmation() {
return nil
}
var (
restores []*arkv1api.Restore
errs []error
)
switch {
case len(o.Names) > 0:
for _, name := range o.Names {
restore, err := o.client.ArkV1().Restores(o.namespace).Get(name, metav1.GetOptions{})
if err != nil {
errs = append(errs, errors.WithStack(err))
continue
}
restores = append(restores, restore)
}
default:
selector := labels.Everything().String()
if o.Selector.LabelSelector != nil {
selector = o.Selector.String()
}
res, err := o.client.ArkV1().Restores(o.namespace).List(metav1.ListOptions{
LabelSelector: selector,
})
if err != nil {
errs = append(errs, errors.WithStack(err))
}
for i := range res.Items {
restores = append(restores, &res.Items[i])
}
}
if len(restores) == 0 {
fmt.Println("No restores found")
return nil
}
for _, r := range restores {
err := o.client.ArkV1().Restores(r.Namespace).Delete(r.Name, nil)
if err != nil {
errs = append(errs, errors.WithStack(err))
continue
}
fmt.Printf("Restore %q deleted\n", r.Name)
}
return kubeerrs.NewAggregate(errs)
}
// BindFlags binds the options for this command to the flags.
func (o *DeleteOptions) BindFlags(flags *pflag.FlagSet) {
flags.BoolVar(&o.Confirm, "confirm", o.Confirm, "Confirm deletion")
flags.BoolVar(&o.All, "all", o.All, "Delete all restores")
flags.VarP(&o.Selector, "selector", "l", "Delete all restores matching this label selector")
}