diff --git a/pkg/cmd/cli/backup/delete.go b/pkg/cmd/cli/backup/delete.go index 913e3641ee..ad58ec1e0e 100644 --- a/pkg/cmd/cli/backup/delete.go +++ b/pkg/cmd/cli/backup/delete.go @@ -21,7 +21,6 @@ import ( "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" @@ -32,13 +31,11 @@ import ( "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 a new command that deletes a backup. func NewDeleteCommand(f client.Factory, use string) *cobra.Command { - o := &DeleteOptions{} + o := cli.NewDeleteOptions("backup") c := &cobra.Command{ Use: fmt.Sprintf("%s [NAMES]", use), @@ -60,8 +57,8 @@ func NewDeleteCommand(f client.Factory, use string) *cobra.Command { `, Run: func(c *cobra.Command, args []string) { cmd.CheckError(o.Complete(f, args)) - cmd.CheckError(o.Validate(c, args, f)) - cmd.CheckError(o.Run()) + cmd.CheckError(o.Validate(c, f, args)) + cmd.CheckError(Run(o)) }, } @@ -70,60 +67,8 @@ func NewDeleteCommand(f client.Factory, use string) *cobra.Command { return c } -// DeleteOptions contains parameters for deleting a backup. -type DeleteOptions struct { - Names []string - All bool - Selector flag.LabelSelector - Confirm bool - - client clientset.Interface - namespace string -} - -// BindFlags binds options for this command to 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 backups") - flags.VarP(&o.Selector, "selector", "l", "Delete all backups matching this label selector") -} - -// Complete fills out the remainder of the parameters based on user input. -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 ensures all of the parameters have been filled in correctly. -func (o *DeleteOptions) Validate(c *cobra.Command, args []string, f client.Factory) 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 backup name(s), the --all flag, or the --selector flag") - } - - return nil -} - // Run performs the delete backup operation. -func (o *DeleteOptions) Run() error { +func Run(o *cli.DeleteOptions) error { if !o.Confirm && !cli.GetConfirmation() { // Don't do anything unless we get confirmation return nil @@ -138,7 +83,7 @@ func (o *DeleteOptions) Run() error { switch { case len(o.Names) > 0: for _, name := range o.Names { - backup, err := o.client.ArkV1().Backups(o.namespace).Get(name, metav1.GetOptions{}) + backup, err := o.Client.ArkV1().Backups(o.Namespace).Get(name, metav1.GetOptions{}) if err != nil { errs = append(errs, errors.WithStack(err)) continue @@ -152,7 +97,7 @@ func (o *DeleteOptions) Run() error { selector = o.Selector.String() } - res, err := o.client.ArkV1().Backups(o.namespace).List(metav1.ListOptions{LabelSelector: selector}) + res, err := o.Client.ArkV1().Backups(o.Namespace).List(metav1.ListOptions{LabelSelector: selector}) if err != nil { return errors.WithStack(err) } @@ -170,7 +115,7 @@ func (o *DeleteOptions) Run() error { for _, b := range backups { deleteRequest := backup.NewDeleteBackupRequest(b.Name, string(b.UID)) - if _, err := o.client.ArkV1().DeleteBackupRequests(o.namespace).Create(deleteRequest); err != nil { + if _, err := o.Client.ArkV1().DeleteBackupRequests(o.Namespace).Create(deleteRequest); err != nil { errs = append(errs, err) continue } diff --git a/pkg/cmd/cli/common.go b/pkg/cmd/cli/common.go deleted file mode 100644 index 811b5417e5..0000000000 --- a/pkg/cmd/cli/common.go +++ /dev/null @@ -1,64 +0,0 @@ -/* -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 -} diff --git a/pkg/cmd/cli/delete_options.go b/pkg/cmd/cli/delete_options.go new file mode 100644 index 0000000000..a8a2d09655 --- /dev/null +++ b/pkg/cmd/cli/delete_options.go @@ -0,0 +1,125 @@ +/* +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" + "errors" + "fmt" + "os" + "strings" + + "github.com/heptio/ark/pkg/client" + "github.com/heptio/ark/pkg/cmd/util/flag" + clientset "github.com/heptio/ark/pkg/generated/clientset/versioned" + + "github.com/spf13/cobra" + "github.com/spf13/pflag" +) + +// 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 + singularTypeName string +} + +func NewDeleteOptions(singularTypeName string) *DeleteOptions { + o := &DeleteOptions{} + o.singularTypeName = singularTypeName + return o +} + +// 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 !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 +} + +// BindFlags binds options for this command to 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 "+o.singularTypeName+"s") + flags.VarP(&o.Selector, "selector", "l", "Delete all "+o.singularTypeName+"s matching this label selector") +} + +// 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 +} diff --git a/pkg/cmd/cli/restore/delete.go b/pkg/cmd/cli/restore/delete.go index c806ff8698..a8f6ac18bc 100644 --- a/pkg/cmd/cli/restore/delete.go +++ b/pkg/cmd/cli/restore/delete.go @@ -21,7 +21,6 @@ import ( "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" @@ -31,13 +30,12 @@ import ( "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{} + o := cli.NewDeleteOptions("restore") + c := &cobra.Command{ Use: fmt.Sprintf("%s [NAMES]", use), Short: "Delete restores", @@ -59,7 +57,7 @@ func NewDeleteCommand(f client.Factory, use string) *cobra.Command { Run: func(c *cobra.Command, args []string) { cmd.CheckError(o.Complete(f, args)) cmd.CheckError(o.Validate(c, f, args)) - cmd.CheckError(o.Run()) + cmd.CheckError(Run(o)) }, } @@ -67,47 +65,8 @@ func NewDeleteCommand(f client.Factory, use string) *cobra.Command { 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 { +func Run(o *cli.DeleteOptions) error { if !o.Confirm && !cli.GetConfirmation() { return nil } @@ -119,7 +78,7 @@ func (o *DeleteOptions) Run() error { switch { case len(o.Names) > 0: for _, name := range o.Names { - restore, err := o.client.ArkV1().Restores(o.namespace).Get(name, metav1.GetOptions{}) + restore, err := o.Client.ArkV1().Restores(o.Namespace).Get(name, metav1.GetOptions{}) if err != nil { errs = append(errs, errors.WithStack(err)) continue @@ -131,7 +90,7 @@ func (o *DeleteOptions) Run() error { if o.Selector.LabelSelector != nil { selector = o.Selector.String() } - res, err := o.client.ArkV1().Restores(o.namespace).List(metav1.ListOptions{ + res, err := o.Client.ArkV1().Restores(o.Namespace).List(metav1.ListOptions{ LabelSelector: selector, }) if err != nil { @@ -147,7 +106,7 @@ func (o *DeleteOptions) Run() error { return nil } for _, r := range restores { - err := o.client.ArkV1().Restores(r.Namespace).Delete(r.Name, nil) + err := o.Client.ArkV1().Restores(r.Namespace).Delete(r.Name, nil) if err != nil { errs = append(errs, errors.WithStack(err)) continue @@ -156,10 +115,3 @@ func (o *DeleteOptions) Run() error { } 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") -} diff --git a/pkg/cmd/cli/schedule/delete.go b/pkg/cmd/cli/schedule/delete.go index fe2f76f64a..31f5e8c46d 100644 --- a/pkg/cmd/cli/schedule/delete.go +++ b/pkg/cmd/cli/schedule/delete.go @@ -19,29 +19,99 @@ package schedule import ( "fmt" + "github.com/pkg/errors" "github.com/spf13/cobra" + 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" ) +// NewDeleteCommand creates and returns a new cobra command for deleting schedules. func NewDeleteCommand(f client.Factory, use string) *cobra.Command { + o := cli.NewDeleteOptions("schedule") + c := &cobra.Command{ - Use: fmt.Sprintf("%s NAME", use), - Short: "Delete a schedule", - Args: cobra.ExactArgs(1), + Use: fmt.Sprintf("%s [NAMES]", use), + Short: "Delete schedules", + Example: ` # delete a schedule named "schedule-1" + ark schedule delete schedule-1 + + # delete a schedule named "schedule-1" without prompting for confirmation + ark schedule delete schedule-1 --confirm + + # delete schedules named "schedule-1" and "schedule-2" + ark schedule delete schedule-1 schedule-2 + + # delete all schedules labelled with foo=bar" + ark schedule delete --selector foo=bar + + # delete all schedules + ark schedule 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(Run(o)) + }, + } - name := args[0] + o.BindFlags(c.Flags()) + return c +} - err = arkClient.ArkV1().Schedules(f.Namespace()).Delete(name, nil) - cmd.CheckError(err) +// Run performs the deletion of schedules. +func Run(o *cli.DeleteOptions) error { + if !o.Confirm && !cli.GetConfirmation() { + return nil + } + var ( + schedules []*arkv1api.Schedule + errs []error + ) + switch { + case len(o.Names) > 0: + for _, name := range o.Names { + schedule, err := o.Client.ArkV1().Schedules(o.Namespace).Get(name, metav1.GetOptions{}) + if err != nil { + errs = append(errs, errors.WithStack(err)) + continue + } + schedules = append(schedules, schedule) + } + default: + selector := labels.Everything().String() + if o.Selector.LabelSelector != nil { + selector = o.Selector.String() + } + res, err := o.Client.ArkV1().Schedules(o.Namespace).List(metav1.ListOptions{ + LabelSelector: selector, + }) + if err != nil { + errs = append(errs, errors.WithStack(err)) + } - fmt.Printf("Schedule %q deleted\n", name) - }, + for i := range res.Items { + schedules = append(schedules, &res.Items[i]) + } + } + if len(schedules) == 0 { + fmt.Println("No schedules found") + return nil } - return c + for _, s := range schedules { + err := o.Client.ArkV1().Schedules(s.Namespace).Delete(s.Name, nil) + if err != nil { + errs = append(errs, errors.WithStack(err)) + continue + } + fmt.Printf("Schedule deleted: %v/n", s.Name) + } + return kubeerrs.NewAggregate(errs) }