Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 8 additions & 35 deletions cmd/pbm/delete.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package main

import (
"bufio"
"context"
"fmt"
"github.com/percona/percona-backup-mongodb/pbm/util"
Copy link
Preview

Copilot AI Aug 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The import should follow Go conventions by being grouped with other third-party imports and maintaining alphabetical order. Move this import to be grouped with other project imports.

Suggested change
"github.com/percona/percona-backup-mongodb/pbm/util"

Copilot uses AI. Check for mistakes.

"io"
"os"
"strings"
Expand Down Expand Up @@ -59,7 +59,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
Expand Down Expand Up @@ -109,7 +109,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
}
Expand Down Expand Up @@ -146,7 +146,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
}
}
Expand Down Expand Up @@ -219,8 +219,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
Expand Down Expand Up @@ -288,8 +288,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
Expand Down Expand Up @@ -414,33 +414,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,
Expand Down
31 changes: 21 additions & 10 deletions cmd/pbm/restore.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ type restoreOpts struct {
nsFrom string
nsTo string
usersAndRoles bool
confirmYes bool
Copy link
Preview

Copilot AI Aug 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The confirmYes field is added to the struct but there's no visible command-line flag definition or initialization code in this diff. Ensure that a corresponding CLI flag (like --yes or --confirm) is properly defined and wired to this field.

Copilot uses AI. Check for mistakes.

rsMap string
conf string
ts string
Expand Down Expand Up @@ -91,10 +92,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 ""
}
Expand Down Expand Up @@ -449,15 +449,10 @@ func doRestore(
}
err = yaml.UnmarshalStrict(buf, &cmd.Restore.ExtConf)
if err != nil {
return nil, errors.Wrap(err, "unable to unmarshal config file")
return nil, errors.Wrap(err, "unable to unmarshal config file")
}
}

err = sendCmd(ctx, conn, cmd)
if err != nil {
return nil, errors.Wrap(err, "send command")
}

if outf != outText {
return &restore.RestoreMeta{
Name: name,
Expand All @@ -477,7 +472,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")
Copy link
Preview

Copilot AI Aug 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The message "Starting restore" lacks context compared to the original message that included the backup name and other details. Consider including key restore information in this status message for better user experience.

Suggested change
fmt.Printf("Starting restore")
fmt.Printf("Starting restore: %s%s%s\n", name, pitrs, bcpName)

Copilot uses AI. Check for mistakes.


var (
fn getRestoreMetaFn
Expand Down
2 changes: 2 additions & 0 deletions pbm/errors/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
39 changes: 39 additions & 0 deletions pbm/util/cmd_prompt.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package util

import (
"bufio"
"fmt"
"os"
"runtime"
"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")
}

if runtime.GOOS == "linux" {
question = fmt.Sprintf("\033[1;33m%s\033[0m", question) // Yellow text for Linux terminals
Copy link
Preview

Copilot AI Aug 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The color formatting is only applied to Linux systems, but many other Unix-like systems (macOS, FreeBSD, etc.) also support ANSI color codes. Consider using a more inclusive condition like runtime.GOOS != "windows" or detecting terminal color support more robustly.

Suggested change
question = fmt.Sprintf("\033[1;33m%s\033[0m", question) // Yellow text for Linux terminals
if runtime.GOOS != "windows" {
question = fmt.Sprintf("\033[1;33m%s\033[0m", question) // Yellow text for Unix-like terminals

Copilot uses AI. Check for mistakes.

}
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
}