diff --git a/cmd/pbm/delete.go b/cmd/pbm/delete.go index d9f78ab25..b129b58b2 100644 --- a/cmd/pbm/delete.go +++ b/cmd/pbm/delete.go @@ -1,7 +1,6 @@ package main import ( - "bufio" "context" "fmt" "io" @@ -18,6 +17,7 @@ import ( "github.com/percona/percona-backup-mongodb/pbm/errors" "github.com/percona/percona-backup-mongodb/pbm/oplog" "github.com/percona/percona-backup-mongodb/pbm/storage" + "github.com/percona/percona-backup-mongodb/pbm/util" "github.com/percona/percona-backup-mongodb/sdk" ) @@ -65,7 +65,7 @@ func deleteBackup( cid, err = deleteManyBackup(ctx, pbm, d) } if err != nil { - if errors.Is(err, errUserCanceled) { + if errors.Is(err, errors.ErrUserCanceled) { return outMsg{err.Error()}, nil } return nil, err @@ -115,7 +115,7 @@ func deleteBackupByName(ctx context.Context, pbm *sdk.Client, d *deleteBcpOpts) return sdk.NoOpID, nil } if !d.yes { - err := askConfirmation("Are you sure you want to delete backup?") + err := util.AskConfirmation("Are you sure you want to delete this backup?") if err != nil { return sdk.NoOpID, err } @@ -152,7 +152,7 @@ func deleteManyBackup(ctx context.Context, pbm *sdk.Client, d *deleteBcpOpts) (s return sdk.NoOpID, nil } if !d.yes { - if err := askConfirmation("Are you sure you want to delete backups?"); err != nil { + if err := util.AskConfirmation("Are you sure you want to delete backups?"); err != nil { return sdk.NoOpID, err } } @@ -227,8 +227,8 @@ func deletePITR( if d.all { q = "Are you sure you want to delete ALL chunks?" } - if err := askConfirmation(q); err != nil { - if errors.Is(err, errUserCanceled) { + if err := util.AskConfirmation(q); err != nil { + if errors.Is(err, errors.ErrUserCanceled) { return outMsg{err.Error()}, nil } return nil, err @@ -300,8 +300,8 @@ func doCleanup(ctx context.Context, conn connect.Client, pbm *sdk.Client, d *cle return &outMsg{""}, nil } if !d.yes { - if err := askConfirmation("Are you sure you want to delete?"); err != nil { - if errors.Is(err, errUserCanceled) { + if err := util.AskConfirmation("Are you sure you want to delete?"); err != nil { + if errors.Is(err, errors.ErrUserCanceled) { return outMsg{err.Error()}, nil } return nil, err @@ -426,33 +426,6 @@ func printDeleteInfoTo(w io.Writer, backups []backup.BackupMeta, chunks []oplog. } } -var errUserCanceled = errors.New("canceled") - -func askConfirmation(question string) error { - fi, err := os.Stdin.Stat() - if err != nil { - return errors.Wrap(err, "stat stdin") - } - if (fi.Mode() & os.ModeCharDevice) == 0 { - return errors.New("no tty") - } - - fmt.Printf("%s [y/N] ", question) - - scanner := bufio.NewScanner(os.Stdin) - scanner.Scan() - if err := scanner.Err(); err != nil { - return errors.Wrap(err, "read stdin") - } - - switch strings.TrimSpace(scanner.Text()) { - case "yes", "Yes", "YES", "Y", "y": - return nil - } - - return errUserCanceled -} - func waitForDelete( ctx context.Context, conn connect.Client, diff --git a/cmd/pbm/main.go b/cmd/pbm/main.go index ac53b5a46..9453ebb2d 100644 --- a/cmd/pbm/main.go +++ b/cmd/pbm/main.go @@ -851,6 +851,10 @@ func (app *pbmApp) buildRestoreCmd() *cobra.Command { &restoreOptions.usersAndRoles, "with-users-and-roles", false, "Includes users and roles for selected database (--ns flag)", ) + restoreCmd.Flags().BoolVarP( + &restoreOptions.confirmYes, "confirm-yes", "y", false, + "Accept restore without confirmation prompt", + ) restoreCmd.Flags().BoolVarP( &restoreOptions.wait, "wait", "w", false, "Wait for the restore to finish", ) diff --git a/cmd/pbm/restore.go b/cmd/pbm/restore.go index 3af2c0f83..63f2cc0fa 100644 --- a/cmd/pbm/restore.go +++ b/cmd/pbm/restore.go @@ -49,6 +49,7 @@ type restoreOpts struct { nsFrom string nsTo string usersAndRoles bool + confirmYes bool rsMap string conf string ts string @@ -92,10 +93,9 @@ No other pbm command is available while the restore is running! `, r.Snapshot, r.Name) } - return fmt.Sprintf("Restore of the snapshot from '%s' has started", r.Snapshot) + return fmt.Sprintf("\nRestore of the snapshot from '%s' has started", r.Snapshot) case r.PITR != "": - return fmt.Sprintf("Restore to the point in time '%s' has started", r.PITR) - + return fmt.Sprintf("\nRestore to the point in time '%s' has started", r.PITR) default: return "" } @@ -494,11 +494,6 @@ func doRestore( } } - err = sendCmd(ctx, conn, cmd) - if err != nil { - return nil, errors.Wrap(err, "send command") - } - if outf != outText { return &restore.RestoreMeta{ Name: name, @@ -518,7 +513,23 @@ func doRestore( if o.pitr != "" { pitrs = fmt.Sprintf(" to point-in-time %s", o.pitr) } - fmt.Printf("Starting restore %s%s%s", name, pitrs, bcpName) + + fmt.Println("Restore:") + fmt.Printf(" - %s%s%s\n", name, pitrs, bcpName) + + if !o.confirmYes { + err := util.AskConfirmation("Are you sure you want to restore this backup?") + if err != nil { + return nil, err + } + } + + err = sendCmd(ctx, conn, cmd) + if err != nil { + return nil, errors.Wrap(err, "send command") + } + + fmt.Printf("Starting restore") var ( fn getRestoreMetaFn diff --git a/pbm/errors/errors.go b/pbm/errors/errors.go index 601026d3a..1229202d2 100644 --- a/pbm/errors/errors.go +++ b/pbm/errors/errors.go @@ -9,6 +9,8 @@ import ( // ErrNotFound - object not found var ErrNotFound = New("not found") +var ErrUserCanceled = New("user canceled") + func New(text string) error { return stderrors.New(text) //nolint:goerr113 } diff --git a/pbm/util/cmd_prompt.go b/pbm/util/cmd_prompt.go new file mode 100644 index 000000000..782133f9f --- /dev/null +++ b/pbm/util/cmd_prompt.go @@ -0,0 +1,35 @@ +package util + +import ( + "bufio" + "fmt" + "os" + "strings" + + "github.com/percona/percona-backup-mongodb/pbm/errors" +) + +func AskConfirmation(question string) error { + fi, err := os.Stdin.Stat() + if err != nil { + return errors.Wrap(err, "stat stdin") + } + if (fi.Mode() & os.ModeCharDevice) == 0 { + return errors.New("no tty") + } + + fmt.Printf("%s [y/N] ", question) + + scanner := bufio.NewScanner(os.Stdin) + scanner.Scan() + if err := scanner.Err(); err != nil { + return errors.Wrap(err, "read stdin") + } + + switch strings.TrimSpace(scanner.Text()) { + case "yes", "Yes", "YES", "Y", "y": + return nil + } + + return errors.ErrUserCanceled +}