convert repository open/create to use termstatus

This commit is contained in:
Michael Eischer
2025-09-14 14:26:29 +02:00
parent c14cf48776
commit 320fb5fb98
12 changed files with 58 additions and 50 deletions

View File

@@ -70,7 +70,8 @@ func (opts *CopyOptions) AddFlags(f *pflag.FlagSet) {
}
func runCopy(ctx context.Context, opts CopyOptions, gopts GlobalOptions, args []string, term *termstatus.Terminal) error {
secondaryGopts, isFromRepo, err := fillSecondaryGlobalOpts(ctx, opts.secondaryRepoOptions, gopts, "destination")
printer := newTerminalProgressPrinter(gopts.JSON, gopts.verbosity, term)
secondaryGopts, isFromRepo, err := fillSecondaryGlobalOpts(ctx, opts.secondaryRepoOptions, gopts, "destination", printer)
if err != nil {
return err
}
@@ -79,8 +80,6 @@ func runCopy(ctx context.Context, opts CopyOptions, gopts GlobalOptions, args []
gopts, secondaryGopts = secondaryGopts, gopts
}
printer := newTerminalProgressPrinter(gopts.JSON, gopts.verbosity, term)
ctx, srcRepo, unlock, err := openWithReadLock(ctx, gopts, gopts.NoLock, printer)
if err != nil {
return err

View File

@@ -10,6 +10,7 @@ import (
"github.com/restic/restic/internal/errors"
"github.com/restic/restic/internal/repository"
"github.com/restic/restic/internal/restic"
"github.com/restic/restic/internal/ui/progress"
"github.com/restic/restic/internal/ui/termstatus"
"github.com/spf13/cobra"
@@ -79,7 +80,7 @@ func runInit(ctx context.Context, opts InitOptions, gopts GlobalOptions, args []
return errors.Fatalf("only repository versions between %v and %v are allowed", restic.MinRepoVersion, restic.MaxRepoVersion)
}
chunkerPolynomial, err := maybeReadChunkerPolynomial(ctx, opts, gopts)
chunkerPolynomial, err := maybeReadChunkerPolynomial(ctx, opts, gopts, printer)
if err != nil {
return err
}
@@ -91,12 +92,13 @@ func runInit(ctx context.Context, opts InitOptions, gopts GlobalOptions, args []
gopts.password, err = ReadPasswordTwice(ctx, gopts,
"enter password for new repository: ",
"enter password again: ")
"enter password again: ",
printer)
if err != nil {
return err
}
be, err := create(ctx, gopts.Repo, gopts, gopts.extended)
be, err := create(ctx, gopts.Repo, gopts, gopts.extended, printer)
if err != nil {
return errors.Fatalf("create repository at %s failed: %v\n", location.StripPassword(gopts.backends, gopts.Repo), err)
}
@@ -136,14 +138,14 @@ func runInit(ctx context.Context, opts InitOptions, gopts GlobalOptions, args []
return nil
}
func maybeReadChunkerPolynomial(ctx context.Context, opts InitOptions, gopts GlobalOptions) (*chunker.Pol, error) {
func maybeReadChunkerPolynomial(ctx context.Context, opts InitOptions, gopts GlobalOptions, printer progress.Printer) (*chunker.Pol, error) {
if opts.CopyChunkerParameters {
otherGopts, _, err := fillSecondaryGlobalOpts(ctx, opts.secondaryRepoOptions, gopts, "secondary")
otherGopts, _, err := fillSecondaryGlobalOpts(ctx, opts.secondaryRepoOptions, gopts, "secondary", printer)
if err != nil {
return nil, err
}
otherRepo, err := OpenRepository(ctx, otherGopts)
otherRepo, err := OpenRepository(ctx, otherGopts, printer)
if err != nil {
return nil, err
}

View File

@@ -9,6 +9,7 @@ import (
"github.com/restic/restic/internal/repository"
"github.com/restic/restic/internal/restic"
rtest "github.com/restic/restic/internal/test"
"github.com/restic/restic/internal/ui/progress"
"github.com/restic/restic/internal/ui/termstatus"
)
@@ -54,10 +55,10 @@ func TestInitCopyChunkerParams(t *testing.T) {
})
rtest.OK(t, err)
repo, err := OpenRepository(context.TODO(), env.gopts)
repo, err := OpenRepository(context.TODO(), env.gopts, &progress.NoopPrinter{})
rtest.OK(t, err)
otherRepo, err := OpenRepository(context.TODO(), env2.gopts)
otherRepo, err := OpenRepository(context.TODO(), env2.gopts, &progress.NoopPrinter{})
rtest.OK(t, err)
rtest.Assert(t, repo.Config().ChunkerPolynomial == otherRepo.Config().ChunkerPolynomial,

View File

@@ -72,7 +72,7 @@ func runKeyAdd(ctx context.Context, gopts GlobalOptions, opts KeyAddOptions, arg
}
func addKey(ctx context.Context, repo *repository.Repository, gopts GlobalOptions, opts KeyAddOptions, printer progress.Printer) error {
pw, err := getNewPassword(ctx, gopts, opts.NewPasswordFile, opts.InsecureNoPassword)
pw, err := getNewPassword(ctx, gopts, opts.NewPasswordFile, opts.InsecureNoPassword, printer)
if err != nil {
return err
}
@@ -95,7 +95,7 @@ func addKey(ctx context.Context, repo *repository.Repository, gopts GlobalOption
// testKeyNewPassword is used to set a new password during integration testing.
var testKeyNewPassword string
func getNewPassword(ctx context.Context, gopts GlobalOptions, newPasswordFile string, insecureNoPassword bool) (string, error) {
func getNewPassword(ctx context.Context, gopts GlobalOptions, newPasswordFile string, insecureNoPassword bool, printer progress.Printer) (string, error) {
if testKeyNewPassword != "" {
return testKeyNewPassword, nil
}
@@ -127,7 +127,8 @@ func getNewPassword(ctx context.Context, gopts GlobalOptions, newPasswordFile st
return ReadPasswordTwice(ctx, newopts,
"enter new password: ",
"enter password again: ")
"enter password again: ",
printer)
}
func switchToNewKeyAndRemoveIfBroken(ctx context.Context, repo *repository.Repository, key *repository.Key, pw string) error {

View File

@@ -12,6 +12,7 @@ import (
"github.com/restic/restic/internal/backend"
"github.com/restic/restic/internal/repository"
rtest "github.com/restic/restic/internal/test"
"github.com/restic/restic/internal/ui/progress"
"github.com/restic/restic/internal/ui/termstatus"
)
@@ -61,7 +62,7 @@ func testRunKeyAddNewKeyUserHost(t testing.TB, gopts GlobalOptions) {
Hostname: "example.com",
}, []string{}, term))
repo, err := OpenRepository(context.TODO(), gopts)
repo, err := OpenRepository(context.TODO(), gopts, &progress.NoopPrinter{})
rtest.OK(t, err)
key, err := repository.SearchKey(context.TODO(), repo, testKeyNewPassword, 2, "")
rtest.OK(t, err)

View File

@@ -67,7 +67,7 @@ func runKeyPasswd(ctx context.Context, gopts GlobalOptions, opts KeyPasswdOption
}
func changePassword(ctx context.Context, repo *repository.Repository, gopts GlobalOptions, opts KeyPasswdOptions, printer progress.Printer) error {
pw, err := getNewPassword(ctx, gopts, opts.NewPasswordFile, opts.InsecureNoPassword)
pw, err := getNewPassword(ctx, gopts, opts.NewPasswordFile, opts.InsecureNoPassword, printer)
if err != nil {
return err
}

View File

@@ -47,7 +47,7 @@ func (opts *UnlockOptions) AddFlags(f *pflag.FlagSet) {
func runUnlock(ctx context.Context, opts UnlockOptions, gopts GlobalOptions, term *termstatus.Terminal) error {
printer := newTerminalProgressPrinter(gopts.JSON, gopts.verbosity, term)
repo, err := OpenRepository(ctx, gopts)
repo, err := OpenRepository(ctx, gopts, printer)
if err != nil {
return err
}

View File

@@ -34,6 +34,7 @@ import (
"github.com/restic/restic/internal/restic"
"github.com/restic/restic/internal/terminal"
"github.com/restic/restic/internal/textfile"
"github.com/restic/restic/internal/ui/progress"
"github.com/spf13/pflag"
"github.com/restic/restic/internal/errors"
@@ -293,7 +294,7 @@ func readPassword(in io.Reader) (password string, err error) {
// ReadPassword reads the password from a password file, the environment
// variable RESTIC_PASSWORD or prompts the user. If the context is canceled,
// the function leaks the password reading goroutine.
func ReadPassword(ctx context.Context, opts GlobalOptions, prompt string) (string, error) {
func ReadPassword(ctx context.Context, opts GlobalOptions, prompt string, printer progress.Printer) (string, error) {
if opts.InsecureNoPassword {
if opts.password != "" {
return "", errors.Fatal("--insecure-no-password must not be specified together with providing a password via a cli option or environment variable")
@@ -313,10 +314,10 @@ func ReadPassword(ctx context.Context, opts GlobalOptions, prompt string) (strin
if terminal.StdinIsTerminal() {
password, err = terminal.ReadPassword(ctx, os.Stdin, os.Stderr, prompt)
} else {
password, err = readPassword(os.Stdin)
if terminal.StdoutIsTerminal() {
Verbosef("reading repository password from stdin\n")
printer.P("reading repository password from stdin")
}
password, err = readPassword(os.Stdin)
}
if err != nil {
@@ -333,13 +334,13 @@ func ReadPassword(ctx context.Context, opts GlobalOptions, prompt string) (strin
// ReadPasswordTwice calls ReadPassword two times and returns an error when the
// passwords don't match. If the context is canceled, the function leaks the
// password reading goroutine.
func ReadPasswordTwice(ctx context.Context, gopts GlobalOptions, prompt1, prompt2 string) (string, error) {
pw1, err := ReadPassword(ctx, gopts, prompt1)
func ReadPasswordTwice(ctx context.Context, gopts GlobalOptions, prompt1, prompt2 string, printer progress.Printer) (string, error) {
pw1, err := ReadPassword(ctx, gopts, prompt1, printer)
if err != nil {
return "", err
}
if terminal.StdinIsTerminal() {
pw2, err := ReadPassword(ctx, gopts, prompt2)
pw2, err := ReadPassword(ctx, gopts, prompt2, printer)
if err != nil {
return "", err
}
@@ -380,13 +381,13 @@ func ReadRepo(opts GlobalOptions) (string, error) {
const maxKeys = 20
// OpenRepository reads the password and opens the repository.
func OpenRepository(ctx context.Context, opts GlobalOptions) (*repository.Repository, error) {
func OpenRepository(ctx context.Context, opts GlobalOptions, printer progress.Printer) (*repository.Repository, error) {
repo, err := ReadRepo(opts)
if err != nil {
return nil, err
}
be, err := open(ctx, repo, opts, opts.extended)
be, err := open(ctx, repo, opts, opts.extended, printer)
if err != nil {
return nil, err
}
@@ -406,13 +407,13 @@ func OpenRepository(ctx context.Context, opts GlobalOptions) (*repository.Reposi
}
for ; passwordTriesLeft > 0; passwordTriesLeft-- {
opts.password, err = ReadPassword(ctx, opts, "enter password for repository: ")
opts.password, err = ReadPassword(ctx, opts, "enter password for repository: ", printer)
if ctx.Err() != nil {
return nil, ctx.Err()
}
if err != nil && passwordTriesLeft > 1 {
opts.password = ""
fmt.Printf("%s. Try again\n", err)
printer.E("%s. Try again", err)
}
if err != nil {
continue
@@ -421,7 +422,7 @@ func OpenRepository(ctx context.Context, opts GlobalOptions) (*repository.Reposi
err = s.SearchKey(ctx, opts.password, maxKeys, opts.KeyHint)
if err != nil && passwordTriesLeft > 1 {
opts.password = ""
fmt.Fprintf(os.Stderr, "%s. Try again\n", err)
printer.E("%s. Try again", err)
}
}
if err != nil {
@@ -441,7 +442,7 @@ func OpenRepository(ctx context.Context, opts GlobalOptions) (*repository.Reposi
if s.Config().Version >= 2 {
extra = ", compression level " + opts.Compression.String()
}
Verbosef("repository %v opened (version %v%s)\n", id, s.Config().Version, extra)
printer.P("repository %v opened (version %v%s)", id, s.Config().Version, extra)
}
}
@@ -451,12 +452,12 @@ func OpenRepository(ctx context.Context, opts GlobalOptions) (*repository.Reposi
c, err := cache.New(s.Config().ID, opts.CacheDir)
if err != nil {
Warnf("unable to open cache: %v\n", err)
printer.E("unable to open cache: %v", err)
return s, nil
}
if c.Created && !opts.JSON && terminal.StdoutIsTerminal() {
Verbosef("created new cache in %v\n", c.Base)
printer.P("created new cache in %v", c.Base)
}
// start using the cache
@@ -464,7 +465,7 @@ func OpenRepository(ctx context.Context, opts GlobalOptions) (*repository.Reposi
oldCacheDirs, err := cache.Old(c.Base)
if err != nil {
Warnf("unable to find old cache directories: %v", err)
printer.E("unable to find old cache directories: %v", err)
}
// nothing more to do if no old cache dirs could be found
@@ -475,18 +476,18 @@ func OpenRepository(ctx context.Context, opts GlobalOptions) (*repository.Reposi
// cleanup old cache dirs if instructed to do so
if opts.CleanupCache {
if terminal.StdoutIsTerminal() && !opts.JSON {
Verbosef("removing %d old cache dirs from %v\n", len(oldCacheDirs), c.Base)
printer.P("removing %d old cache dirs from %v", len(oldCacheDirs), c.Base)
}
for _, item := range oldCacheDirs {
dir := filepath.Join(c.Base, item.Name())
err = os.RemoveAll(dir)
if err != nil {
Warnf("unable to remove %v: %v\n", dir, err)
printer.E("unable to remove %v: %v", dir, err)
}
}
} else {
if terminal.StdoutIsTerminal() {
Verbosef("found %d old cache directories in %v, run `restic cache --cleanup` to remove them\n",
printer.P("found %d old cache directories in %v, run `restic cache --cleanup` to remove them",
len(oldCacheDirs), c.Base)
}
}
@@ -510,7 +511,7 @@ func parseConfig(loc location.Location, opts options.Options) (interface{}, erro
return cfg, nil
}
func innerOpen(ctx context.Context, s string, gopts GlobalOptions, opts options.Options, create bool) (backend.Backend, error) {
func innerOpen(ctx context.Context, s string, gopts GlobalOptions, opts options.Options, create bool, printer progress.Printer) (backend.Backend, error) {
debug.Log("parsing location %v", location.StripPassword(gopts.backends, s))
loc, err := location.Parse(gopts.backends, s)
if err != nil {
@@ -563,13 +564,13 @@ func innerOpen(ctx context.Context, s string, gopts GlobalOptions, opts options.
report := func(msg string, err error, d time.Duration) {
if d >= 0 {
Warnf("%v returned error, retrying after %v: %v\n", msg, d, err)
printer.E("%v returned error, retrying after %v: %v", msg, d, err)
} else {
Warnf("%v failed: %v\n", msg, err)
printer.E("%v failed: %v", msg, err)
}
}
success := func(msg string, retries int) {
Warnf("%v operation successful after %d retries\n", msg, retries)
printer.E("%v operation successful after %d retries", msg, retries)
}
be = retry.New(be, 15*time.Minute, report, success)
@@ -585,8 +586,8 @@ func innerOpen(ctx context.Context, s string, gopts GlobalOptions, opts options.
}
// Open the backend specified by a location config.
func open(ctx context.Context, s string, gopts GlobalOptions, opts options.Options) (backend.Backend, error) {
be, err := innerOpen(ctx, s, gopts, opts, false)
func open(ctx context.Context, s string, gopts GlobalOptions, opts options.Options, printer progress.Printer) (backend.Backend, error) {
be, err := innerOpen(ctx, s, gopts, opts, false, printer)
if err != nil {
return nil, err
}
@@ -608,6 +609,6 @@ func open(ctx context.Context, s string, gopts GlobalOptions, opts options.Optio
}
// Create the backend specified by URI.
func create(ctx context.Context, s string, gopts GlobalOptions, opts options.Options) (backend.Backend, error) {
return innerOpen(ctx, s, gopts, opts, true)
func create(ctx context.Context, s string, gopts GlobalOptions, opts options.Options, printer progress.Printer) (backend.Backend, error) {
return innerOpen(ctx, s, gopts, opts, true, printer)
}

View File

@@ -9,6 +9,7 @@ import (
"github.com/restic/restic/internal/errors"
rtest "github.com/restic/restic/internal/test"
"github.com/restic/restic/internal/ui/progress"
)
func Test_PrintFunctionsRespectsGlobalStdout(t *testing.T) {
@@ -66,11 +67,11 @@ func TestReadRepo(t *testing.T) {
func TestReadEmptyPassword(t *testing.T) {
opts := GlobalOptions{InsecureNoPassword: true}
password, err := ReadPassword(context.TODO(), opts, "test")
password, err := ReadPassword(context.TODO(), opts, "test", &progress.NoopPrinter{})
rtest.OK(t, err)
rtest.Equals(t, "", password, "got unexpected password")
opts.password = "invalid"
_, err = ReadPassword(context.TODO(), opts, "test")
_, err = ReadPassword(context.TODO(), opts, "test", &progress.NoopPrinter{})
rtest.Assert(t, strings.Contains(err.Error(), "must not be specified together with providing a password via a cli option or environment variable"), "unexpected error message, got %v", err)
}

View File

@@ -8,7 +8,7 @@ import (
)
func internalOpenWithLocked(ctx context.Context, gopts GlobalOptions, dryRun bool, exclusive bool, printer progress.Printer) (context.Context, *repository.Repository, func(), error) {
repo, err := OpenRepository(ctx, gopts)
repo, err := OpenRepository(ctx, gopts, printer)
if err != nil {
return nil, nil, nil, err
}

View File

@@ -5,6 +5,7 @@ import (
"os"
"github.com/restic/restic/internal/errors"
"github.com/restic/restic/internal/ui/progress"
"github.com/spf13/pflag"
)
@@ -59,7 +60,7 @@ func (opts *secondaryRepoOptions) AddFlags(f *pflag.FlagSet, repoPrefix string,
opts.PasswordCommand = os.Getenv("RESTIC_FROM_PASSWORD_COMMAND")
}
func fillSecondaryGlobalOpts(ctx context.Context, opts secondaryRepoOptions, gopts GlobalOptions, repoPrefix string) (GlobalOptions, bool, error) {
func fillSecondaryGlobalOpts(ctx context.Context, opts secondaryRepoOptions, gopts GlobalOptions, repoPrefix string, printer progress.Printer) (GlobalOptions, bool, error) {
if opts.Repo == "" && opts.RepositoryFile == "" && opts.LegacyRepo == "" && opts.LegacyRepositoryFile == "" {
return GlobalOptions{}, false, errors.Fatal("Please specify a source repository location (--from-repo or --from-repository-file)")
}
@@ -115,7 +116,7 @@ func fillSecondaryGlobalOpts(ctx context.Context, opts secondaryRepoOptions, gop
return GlobalOptions{}, false, err
}
}
dstGopts.password, err = ReadPassword(ctx, dstGopts, "enter password for "+repoPrefix+" repository: ")
dstGopts.password, err = ReadPassword(ctx, dstGopts, "enter password for "+repoPrefix+" repository: ", printer)
if err != nil {
return GlobalOptions{}, false, err
}

View File

@@ -7,6 +7,7 @@ import (
"testing"
rtest "github.com/restic/restic/internal/test"
"github.com/restic/restic/internal/ui/progress"
)
// TestFillSecondaryGlobalOpts tests valid and invalid data on fillSecondaryGlobalOpts-function
@@ -171,7 +172,7 @@ func TestFillSecondaryGlobalOpts(t *testing.T) {
// Test all valid cases
for _, testCase := range validSecondaryRepoTestCases {
DstGOpts, isFromRepo, err := fillSecondaryGlobalOpts(context.TODO(), testCase.Opts, gOpts, "destination")
DstGOpts, isFromRepo, err := fillSecondaryGlobalOpts(context.TODO(), testCase.Opts, gOpts, "destination", &progress.NoopPrinter{})
rtest.OK(t, err)
rtest.Equals(t, DstGOpts, testCase.DstGOpts)
rtest.Equals(t, isFromRepo, testCase.FromRepo)
@@ -179,7 +180,7 @@ func TestFillSecondaryGlobalOpts(t *testing.T) {
// Test all invalid cases
for _, testCase := range invalidSecondaryRepoTestCases {
_, _, err := fillSecondaryGlobalOpts(context.TODO(), testCase.Opts, gOpts, "destination")
_, _, err := fillSecondaryGlobalOpts(context.TODO(), testCase.Opts, gOpts, "destination", &progress.NoopPrinter{})
rtest.Assert(t, err != nil, "Expected error, but function did not return an error")
}
}