diff --git a/changelogs/unreleased/7566-kaovilai b/changelogs/unreleased/7566-kaovilai new file mode 100644 index 0000000000..acbf1f9b2c --- /dev/null +++ b/changelogs/unreleased/7566-kaovilai @@ -0,0 +1 @@ +Add confirm flag to velero plugin add \ No newline at end of file diff --git a/pkg/cmd/cli/backup/delete.go b/pkg/cmd/cli/backup/delete.go index 4c81477585..692b82dbf0 100644 --- a/pkg/cmd/cli/backup/delete.go +++ b/pkg/cmd/cli/backup/delete.go @@ -32,6 +32,7 @@ import ( "github.com/vmware-tanzu/velero/pkg/client" "github.com/vmware-tanzu/velero/pkg/cmd" "github.com/vmware-tanzu/velero/pkg/cmd/cli" + "github.com/vmware-tanzu/velero/pkg/cmd/util/confirm" "github.com/vmware-tanzu/velero/pkg/label" ) @@ -70,7 +71,7 @@ func NewDeleteCommand(f client.Factory, use string) *cobra.Command { // Run performs the delete backup operation. func Run(o *cli.DeleteOptions) error { - if !o.Confirm && !cli.GetConfirmation() { + if !o.Confirm && !confirm.GetConfirmation() { // Don't do anything unless we get confirmation return nil } diff --git a/pkg/cmd/cli/backuplocation/delete.go b/pkg/cmd/cli/backuplocation/delete.go index 922c32df99..f2c3bcc3df 100644 --- a/pkg/cmd/cli/backuplocation/delete.go +++ b/pkg/cmd/cli/backuplocation/delete.go @@ -31,6 +31,7 @@ import ( "github.com/vmware-tanzu/velero/pkg/client" "github.com/vmware-tanzu/velero/pkg/cmd" "github.com/vmware-tanzu/velero/pkg/cmd/cli" + "github.com/vmware-tanzu/velero/pkg/cmd/util/confirm" ) // NewDeleteCommand creates and returns a new cobra command for deleting backup-locations. @@ -67,7 +68,7 @@ func NewDeleteCommand(f client.Factory, use string) *cobra.Command { // Run performs the delete backup-location operation. func Run(f client.Factory, o *cli.DeleteOptions) error { - if !o.Confirm && !cli.GetConfirmation() { + if !o.Confirm && !confirm.GetConfirmation() { // Don't do anything unless we get confirmation return nil } diff --git a/pkg/cmd/cli/delete_options.go b/pkg/cmd/cli/delete_options.go index 1a5d711dc5..78c7ae3373 100644 --- a/pkg/cmd/cli/delete_options.go +++ b/pkg/cmd/cli/delete_options.go @@ -17,29 +17,27 @@ limitations under the License. package cli import ( - "bufio" "errors" - "fmt" - "os" - "strings" "github.com/spf13/cobra" "github.com/spf13/pflag" controllerclient "sigs.k8s.io/controller-runtime/pkg/client" "github.com/vmware-tanzu/velero/pkg/client" + "github.com/vmware-tanzu/velero/pkg/cmd/util/confirm" ) // DeleteOptions contains parameters used for deleting a restore. type DeleteOptions struct { *SelectOptions - Confirm bool + confirm.ConfirmOptions Client controllerclient.Client Namespace string } func NewDeleteOptions(singularTypeName string) *DeleteOptions { o := &DeleteOptions{} + o.ConfirmOptions = *confirm.NewConfirmOptionsWithDescription("Confirm deletion") o.SelectOptions = NewSelectOptions("delete", singularTypeName) return o } @@ -66,36 +64,10 @@ func (o *DeleteOptions) Validate(c *cobra.Command, f client.Factory, args []stri // BindFlags binds options for this command to flags. func (o *DeleteOptions) BindFlags(flags *pflag.FlagSet) { - flags.BoolVar(&o.Confirm, "confirm", o.Confirm, "Confirm deletion") + o.ConfirmOptions.BindFlags(flags) o.SelectOptions.BindFlags(flags) } -// 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 { diff --git a/pkg/cmd/cli/plugin/add.go b/pkg/cmd/cli/plugin/add.go index 7c0b98929c..c60bd70cae 100644 --- a/pkg/cmd/cli/plugin/add.go +++ b/pkg/cmd/cli/plugin/add.go @@ -32,6 +32,7 @@ import ( "github.com/vmware-tanzu/velero/pkg/builder" "github.com/vmware-tanzu/velero/pkg/client" "github.com/vmware-tanzu/velero/pkg/cmd" + "github.com/vmware-tanzu/velero/pkg/cmd/util/confirm" "github.com/vmware-tanzu/velero/pkg/cmd/util/flag" ) @@ -45,12 +46,17 @@ func NewAddCommand(f client.Factory) *cobra.Command { imagePullPolicies = []string{string(corev1api.PullAlways), string(corev1api.PullIfNotPresent), string(corev1api.PullNever)} imagePullPolicyFlag = flag.NewEnum(string(corev1api.PullIfNotPresent), imagePullPolicies...) ) - + o := confirm.NewConfirmOptionsWithDescription("Confirm add?, may cause the Velero server pod restart which will fail all ongoing jobs") c := &cobra.Command{ Use: "add IMAGE", Short: "Add a plugin", Args: cobra.ExactArgs(1), Run: func(c *cobra.Command, args []string) { + if !o.Confirm && !confirm.GetConfirmation("velero plugin add may cause the Velero server pod restart, so it is a dangerous operation", + "once Velero server restarts, all the ongoing jobs will fail.") { + // Don't do anything unless we get confirmation + return + } kubeClient, err := f.KubeClient() if err != nil { cmd.CheckError(err) @@ -122,6 +128,7 @@ func NewAddCommand(f client.Factory) *cobra.Command { } c.Flags().Var(imagePullPolicyFlag, "image-pull-policy", fmt.Sprintf("The imagePullPolicy for the plugin container. Valid values are %s.", strings.Join(imagePullPolicies, ", "))) + o.BindFlags(c.Flags()) return c } diff --git a/pkg/cmd/cli/restore/delete.go b/pkg/cmd/cli/restore/delete.go index e594654096..a2e70953db 100644 --- a/pkg/cmd/cli/restore/delete.go +++ b/pkg/cmd/cli/restore/delete.go @@ -31,6 +31,7 @@ import ( "github.com/vmware-tanzu/velero/pkg/client" "github.com/vmware-tanzu/velero/pkg/cmd" "github.com/vmware-tanzu/velero/pkg/cmd/cli" + "github.com/vmware-tanzu/velero/pkg/cmd/util/confirm" ) // NewDeleteCommand creates and returns a new cobra command for deleting restores. @@ -66,7 +67,7 @@ func NewDeleteCommand(f client.Factory, use string) *cobra.Command { // Run performs the deletion of restore(s). func Run(o *cli.DeleteOptions) error { - if !o.Confirm && !cli.GetConfirmation() { + if !o.Confirm && !confirm.GetConfirmation() { return nil } var ( diff --git a/pkg/cmd/cli/schedule/delete.go b/pkg/cmd/cli/schedule/delete.go index 883a3aaf32..77b0bf883c 100644 --- a/pkg/cmd/cli/schedule/delete.go +++ b/pkg/cmd/cli/schedule/delete.go @@ -31,6 +31,7 @@ import ( "github.com/vmware-tanzu/velero/pkg/client" "github.com/vmware-tanzu/velero/pkg/cmd" "github.com/vmware-tanzu/velero/pkg/cmd/cli" + "github.com/vmware-tanzu/velero/pkg/cmd/util/confirm" ) // NewDeleteCommand creates and returns a new cobra command for deleting schedules. @@ -67,7 +68,7 @@ func NewDeleteCommand(f client.Factory, use string) *cobra.Command { // Run performs the deletion of schedules. func Run(o *cli.DeleteOptions) error { - if !o.Confirm && !cli.GetConfirmation() { + if !o.Confirm && !confirm.GetConfirmation() { return nil } var ( diff --git a/pkg/cmd/cli/uninstall/uninstall.go b/pkg/cmd/cli/uninstall/uninstall.go index bc4501438a..70a02fb88a 100644 --- a/pkg/cmd/cli/uninstall/uninstall.go +++ b/pkg/cmd/cli/uninstall/uninstall.go @@ -45,7 +45,7 @@ import ( velerov2alpha1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v2alpha1" "github.com/vmware-tanzu/velero/pkg/client" "github.com/vmware-tanzu/velero/pkg/cmd" - "github.com/vmware-tanzu/velero/pkg/cmd/cli" + "github.com/vmware-tanzu/velero/pkg/cmd/util/confirm" "github.com/vmware-tanzu/velero/pkg/controller" "github.com/vmware-tanzu/velero/pkg/install" kubeutil "github.com/vmware-tanzu/velero/pkg/util/kube" @@ -88,7 +88,7 @@ Use '--force' to skip the prompt confirming if you want to uninstall Velero. // Confirm if not asked to force-skip confirmation if !o.force { fmt.Println("You are about to uninstall Velero.") - if !cli.GetConfirmation() { + if !confirm.GetConfirmation() { // Don't do anything unless we get confirmation return } diff --git a/pkg/cmd/util/confirm/confirm.go b/pkg/cmd/util/confirm/confirm.go new file mode 100644 index 0000000000..59e5e1ff6f --- /dev/null +++ b/pkg/cmd/util/confirm/confirm.go @@ -0,0 +1,57 @@ +package confirm + +import ( + "bufio" + "fmt" + "os" + "strings" + + "github.com/spf13/pflag" +) + +type ConfirmOptions struct { + Confirm bool + flagDescription string +} + +func NewConfirmOptions() *ConfirmOptions { + return &ConfirmOptions{flagDescription: "Confirm action"} +} + +func NewConfirmOptionsWithDescription(desc string) *ConfirmOptions { + return &ConfirmOptions{flagDescription: desc} +} + +// Bind confirm flags. +func (o *ConfirmOptions) BindFlags(flags *pflag.FlagSet) { + flags.BoolVar(&o.Confirm, "confirm", o.Confirm, o.flagDescription) +} + +// GetConfirmation ensures that the user confirms the action before proceeding. +func GetConfirmation(prompts ...string) bool { + reader := bufio.NewReader(os.Stdin) + + for { + for i := range prompts { + fmt.Println(prompts[i]) + } + 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 + } + } +}