key: convert to termstatus

This commit is contained in:
Michael Eischer
2025-09-14 10:53:08 +02:00
parent fd8f8d64f5
commit 51299b8ea7
5 changed files with 82 additions and 37 deletions

View File

@@ -6,6 +6,8 @@ import (
"github.com/restic/restic/internal/errors" "github.com/restic/restic/internal/errors"
"github.com/restic/restic/internal/repository" "github.com/restic/restic/internal/repository"
"github.com/restic/restic/internal/ui/progress"
"github.com/restic/restic/internal/ui/termstatus"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/pflag" "github.com/spf13/pflag"
) )
@@ -30,7 +32,9 @@ Exit status is 12 if the password is incorrect.
`, `,
DisableAutoGenTag: true, DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
return runKeyAdd(cmd.Context(), globalOptions, opts, args) term, cancel := setupTermstatus()
defer cancel()
return runKeyAdd(cmd.Context(), globalOptions, opts, args, term)
}, },
} }
@@ -52,21 +56,22 @@ func (opts *KeyAddOptions) Add(flags *pflag.FlagSet) {
flags.StringVarP(&opts.Hostname, "host", "", "", "the hostname for new key") flags.StringVarP(&opts.Hostname, "host", "", "", "the hostname for new key")
} }
func runKeyAdd(ctx context.Context, gopts GlobalOptions, opts KeyAddOptions, args []string) error { func runKeyAdd(ctx context.Context, gopts GlobalOptions, opts KeyAddOptions, args []string, term *termstatus.Terminal) error {
if len(args) > 0 { if len(args) > 0 {
return fmt.Errorf("the key add command expects no arguments, only options - please see `restic help key add` for usage and flags") return fmt.Errorf("the key add command expects no arguments, only options - please see `restic help key add` for usage and flags")
} }
printer := newTerminalProgressPrinter(gopts.JSON, gopts.verbosity, term)
ctx, repo, unlock, err := openWithAppendLock(ctx, gopts, false) ctx, repo, unlock, err := openWithAppendLock(ctx, gopts, false)
if err != nil { if err != nil {
return err return err
} }
defer unlock() defer unlock()
return addKey(ctx, repo, gopts, opts) return addKey(ctx, repo, gopts, opts, printer)
} }
func addKey(ctx context.Context, repo *repository.Repository, gopts GlobalOptions, opts KeyAddOptions) error { 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)
if err != nil { if err != nil {
return err return err
@@ -82,7 +87,7 @@ func addKey(ctx context.Context, repo *repository.Repository, gopts GlobalOption
return err return err
} }
Verbosef("saved new key with ID %s\n", id.ID()) printer.P("saved new key with ID %s", id.ID())
return nil return nil
} }

View File

@@ -12,11 +12,14 @@ import (
"github.com/restic/restic/internal/backend" "github.com/restic/restic/internal/backend"
"github.com/restic/restic/internal/repository" "github.com/restic/restic/internal/repository"
rtest "github.com/restic/restic/internal/test" rtest "github.com/restic/restic/internal/test"
"github.com/restic/restic/internal/ui/termstatus"
) )
func testRunKeyListOtherIDs(t testing.TB, gopts GlobalOptions) []string { func testRunKeyListOtherIDs(t testing.TB, gopts GlobalOptions) []string {
buf, err := withCaptureStdout(gopts, func(gopts GlobalOptions) error { buf, err := withCaptureStdout(gopts, func(gopts GlobalOptions) error {
return runKeyList(context.TODO(), gopts, []string{}) return withTermStatus(gopts, func(ctx context.Context, term *termstatus.Terminal) error {
return runKeyList(ctx, gopts, []string{}, term)
})
}) })
rtest.OK(t, err) rtest.OK(t, err)
@@ -39,7 +42,9 @@ func testRunKeyAddNewKey(t testing.TB, newPassword string, gopts GlobalOptions)
testKeyNewPassword = "" testKeyNewPassword = ""
}() }()
rtest.OK(t, runKeyAdd(context.TODO(), gopts, KeyAddOptions{}, []string{})) term, cancel := setupTermstatus()
defer cancel()
rtest.OK(t, runKeyAdd(context.TODO(), gopts, KeyAddOptions{}, []string{}, term))
} }
func testRunKeyAddNewKeyUserHost(t testing.TB, gopts GlobalOptions) { func testRunKeyAddNewKeyUserHost(t testing.TB, gopts GlobalOptions) {
@@ -49,10 +54,12 @@ func testRunKeyAddNewKeyUserHost(t testing.TB, gopts GlobalOptions) {
}() }()
t.Log("adding key for john@example.com") t.Log("adding key for john@example.com")
term, cancel := setupTermstatus()
defer cancel()
rtest.OK(t, runKeyAdd(context.TODO(), gopts, KeyAddOptions{ rtest.OK(t, runKeyAdd(context.TODO(), gopts, KeyAddOptions{
Username: "john", Username: "john",
Hostname: "example.com", Hostname: "example.com",
}, []string{})) }, []string{}, term))
repo, err := OpenRepository(context.TODO(), gopts) repo, err := OpenRepository(context.TODO(), gopts)
rtest.OK(t, err) rtest.OK(t, err)
@@ -69,13 +76,17 @@ func testRunKeyPasswd(t testing.TB, newPassword string, gopts GlobalOptions) {
testKeyNewPassword = "" testKeyNewPassword = ""
}() }()
rtest.OK(t, runKeyPasswd(context.TODO(), gopts, KeyPasswdOptions{}, []string{})) term, cancel := setupTermstatus()
defer cancel()
rtest.OK(t, runKeyPasswd(context.TODO(), gopts, KeyPasswdOptions{}, []string{}, term))
} }
func testRunKeyRemove(t testing.TB, gopts GlobalOptions, IDs []string) { func testRunKeyRemove(t testing.TB, gopts GlobalOptions, IDs []string) {
t.Logf("remove %d keys: %q\n", len(IDs), IDs) t.Logf("remove %d keys: %q\n", len(IDs), IDs)
term, cancel := setupTermstatus()
defer cancel()
for _, id := range IDs { for _, id := range IDs {
rtest.OK(t, runKeyRemove(context.TODO(), gopts, []string{id})) rtest.OK(t, runKeyRemove(context.TODO(), gopts, []string{id}, term))
} }
} }
@@ -105,7 +116,9 @@ func TestKeyAddRemove(t *testing.T) {
env.gopts.password = passwordList[len(passwordList)-1] env.gopts.password = passwordList[len(passwordList)-1]
t.Logf("testing access with last password %q\n", env.gopts.password) t.Logf("testing access with last password %q\n", env.gopts.password)
rtest.OK(t, runKeyList(context.TODO(), env.gopts, []string{})) term, cancel := setupTermstatus()
defer cancel()
rtest.OK(t, runKeyList(context.TODO(), env.gopts, []string{}, term))
testRunCheck(t, env.gopts) testRunCheck(t, env.gopts)
testRunKeyAddNewKeyUserHost(t, env.gopts) testRunKeyAddNewKeyUserHost(t, env.gopts)
@@ -116,10 +129,12 @@ func TestKeyAddInvalid(t *testing.T) {
defer cleanup() defer cleanup()
testRunInit(t, env.gopts) testRunInit(t, env.gopts)
term, cancel := setupTermstatus()
defer cancel()
err := runKeyAdd(context.TODO(), env.gopts, KeyAddOptions{ err := runKeyAdd(context.TODO(), env.gopts, KeyAddOptions{
NewPasswordFile: "some-file", NewPasswordFile: "some-file",
InsecureNoPassword: true, InsecureNoPassword: true,
}, []string{}) }, []string{}, term)
rtest.Assert(t, strings.Contains(err.Error(), "only either"), "unexpected error message, got %q", err) rtest.Assert(t, strings.Contains(err.Error(), "only either"), "unexpected error message, got %q", err)
pwfile := filepath.Join(t.TempDir(), "pwfile") pwfile := filepath.Join(t.TempDir(), "pwfile")
@@ -127,7 +142,7 @@ func TestKeyAddInvalid(t *testing.T) {
err = runKeyAdd(context.TODO(), env.gopts, KeyAddOptions{ err = runKeyAdd(context.TODO(), env.gopts, KeyAddOptions{
NewPasswordFile: pwfile, NewPasswordFile: pwfile,
}, []string{}) }, []string{}, term)
rtest.Assert(t, strings.Contains(err.Error(), "an empty password is not allowed by default"), "unexpected error message, got %q", err) rtest.Assert(t, strings.Contains(err.Error(), "an empty password is not allowed by default"), "unexpected error message, got %q", err)
} }
@@ -138,9 +153,11 @@ func TestKeyAddEmpty(t *testing.T) {
defer cleanup() defer cleanup()
testRunInit(t, env.gopts) testRunInit(t, env.gopts)
term, cancel := setupTermstatus()
defer cancel()
rtest.OK(t, runKeyAdd(context.TODO(), env.gopts, KeyAddOptions{ rtest.OK(t, runKeyAdd(context.TODO(), env.gopts, KeyAddOptions{
InsecureNoPassword: true, InsecureNoPassword: true,
}, []string{})) }, []string{}, term))
env.gopts.password = "" env.gopts.password = ""
env.gopts.InsecureNoPassword = true env.gopts.InsecureNoPassword = true
@@ -170,16 +187,20 @@ func TestKeyProblems(t *testing.T) {
testKeyNewPassword = "" testKeyNewPassword = ""
}() }()
err := runKeyPasswd(context.TODO(), env.gopts, KeyPasswdOptions{}, []string{}) term, cancel := setupTermstatus()
defer cancel()
err := runKeyPasswd(context.TODO(), env.gopts, KeyPasswdOptions{}, []string{}, term)
t.Log(err) t.Log(err)
rtest.Assert(t, err != nil, "expected passwd change to fail") rtest.Assert(t, err != nil, "expected passwd change to fail")
err = runKeyAdd(context.TODO(), env.gopts, KeyAddOptions{}, []string{}) err = runKeyAdd(context.TODO(), env.gopts, KeyAddOptions{}, []string{}, term)
t.Log(err) t.Log(err)
rtest.Assert(t, err != nil, "expected key adding to fail") rtest.Assert(t, err != nil, "expected key adding to fail")
t.Logf("testing access with initial password %q\n", env.gopts.password) t.Logf("testing access with initial password %q\n", env.gopts.password)
rtest.OK(t, runKeyList(context.TODO(), env.gopts, []string{})) term2, cancel2 := setupTermstatus()
defer cancel2()
rtest.OK(t, runKeyList(context.TODO(), env.gopts, []string{}, term2))
testRunCheck(t, env.gopts) testRunCheck(t, env.gopts)
} }
@@ -192,23 +213,27 @@ func TestKeyCommandInvalidArguments(t *testing.T) {
return &emptySaveBackend{r}, nil return &emptySaveBackend{r}, nil
} }
err := runKeyAdd(context.TODO(), env.gopts, KeyAddOptions{}, []string{"johndoe"}) term, cancel := setupTermstatus()
defer cancel()
err := runKeyAdd(context.TODO(), env.gopts, KeyAddOptions{}, []string{"johndoe"}, term)
t.Log(err) t.Log(err)
rtest.Assert(t, err != nil && strings.Contains(err.Error(), "no arguments"), "unexpected error for key add: %v", err) rtest.Assert(t, err != nil && strings.Contains(err.Error(), "no arguments"), "unexpected error for key add: %v", err)
err = runKeyPasswd(context.TODO(), env.gopts, KeyPasswdOptions{}, []string{"johndoe"}) err = runKeyPasswd(context.TODO(), env.gopts, KeyPasswdOptions{}, []string{"johndoe"}, term)
t.Log(err) t.Log(err)
rtest.Assert(t, err != nil && strings.Contains(err.Error(), "no arguments"), "unexpected error for key passwd: %v", err) rtest.Assert(t, err != nil && strings.Contains(err.Error(), "no arguments"), "unexpected error for key passwd: %v", err)
err = runKeyList(context.TODO(), env.gopts, []string{"johndoe"}) term3, cancel3 := setupTermstatus()
defer cancel3()
err = runKeyList(context.TODO(), env.gopts, []string{"johndoe"}, term3)
t.Log(err) t.Log(err)
rtest.Assert(t, err != nil && strings.Contains(err.Error(), "no arguments"), "unexpected error for key list: %v", err) rtest.Assert(t, err != nil && strings.Contains(err.Error(), "no arguments"), "unexpected error for key list: %v", err)
err = runKeyRemove(context.TODO(), env.gopts, []string{}) err = runKeyRemove(context.TODO(), env.gopts, []string{}, term)
t.Log(err) t.Log(err)
rtest.Assert(t, err != nil && strings.Contains(err.Error(), "one argument"), "unexpected error for key remove: %v", err) rtest.Assert(t, err != nil && strings.Contains(err.Error(), "one argument"), "unexpected error for key remove: %v", err)
err = runKeyRemove(context.TODO(), env.gopts, []string{"john", "doe"}) err = runKeyRemove(context.TODO(), env.gopts, []string{"john", "doe"}, term)
t.Log(err) t.Log(err)
rtest.Assert(t, err != nil && strings.Contains(err.Error(), "one argument"), "unexpected error for key remove: %v", err) rtest.Assert(t, err != nil && strings.Contains(err.Error(), "one argument"), "unexpected error for key remove: %v", err)
} }

View File

@@ -8,7 +8,9 @@ import (
"github.com/restic/restic/internal/repository" "github.com/restic/restic/internal/repository"
"github.com/restic/restic/internal/restic" "github.com/restic/restic/internal/restic"
"github.com/restic/restic/internal/ui/progress"
"github.com/restic/restic/internal/ui/table" "github.com/restic/restic/internal/ui/table"
"github.com/restic/restic/internal/ui/termstatus"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@@ -32,27 +34,30 @@ Exit status is 12 if the password is incorrect.
`, `,
DisableAutoGenTag: true, DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
return runKeyList(cmd.Context(), globalOptions, args) term, cancel := setupTermstatus()
defer cancel()
return runKeyList(cmd.Context(), globalOptions, args, term)
}, },
} }
return cmd return cmd
} }
func runKeyList(ctx context.Context, gopts GlobalOptions, args []string) error { func runKeyList(ctx context.Context, gopts GlobalOptions, args []string, term *termstatus.Terminal) error {
if len(args) > 0 { if len(args) > 0 {
return fmt.Errorf("the key list command expects no arguments, only options - please see `restic help key list` for usage and flags") return fmt.Errorf("the key list command expects no arguments, only options - please see `restic help key list` for usage and flags")
} }
printer := newTerminalProgressPrinter(gopts.JSON, gopts.verbosity, term)
ctx, repo, unlock, err := openWithReadLock(ctx, gopts, gopts.NoLock) ctx, repo, unlock, err := openWithReadLock(ctx, gopts, gopts.NoLock)
if err != nil { if err != nil {
return err return err
} }
defer unlock() defer unlock()
return listKeys(ctx, repo, gopts) return listKeys(ctx, repo, gopts, printer)
} }
func listKeys(ctx context.Context, s *repository.Repository, gopts GlobalOptions) error { func listKeys(ctx context.Context, s *repository.Repository, gopts GlobalOptions, printer progress.Printer) error {
type keyInfo struct { type keyInfo struct {
Current bool `json:"current"` Current bool `json:"current"`
ID string `json:"id"` ID string `json:"id"`
@@ -68,7 +73,7 @@ func listKeys(ctx context.Context, s *repository.Repository, gopts GlobalOptions
err := restic.ParallelList(ctx, s, restic.KeyFile, s.Connections(), func(ctx context.Context, id restic.ID, _ int64) error { err := restic.ParallelList(ctx, s, restic.KeyFile, s.Connections(), func(ctx context.Context, id restic.ID, _ int64) error {
k, err := repository.LoadKey(ctx, s, id) k, err := repository.LoadKey(ctx, s, id)
if err != nil { if err != nil {
Warnf("LoadKey() failed: %v\n", err) printer.E("LoadKey() failed: %v", err)
return nil return nil
} }

View File

@@ -6,6 +6,8 @@ import (
"github.com/restic/restic/internal/errors" "github.com/restic/restic/internal/errors"
"github.com/restic/restic/internal/repository" "github.com/restic/restic/internal/repository"
"github.com/restic/restic/internal/ui/progress"
"github.com/restic/restic/internal/ui/termstatus"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/pflag" "github.com/spf13/pflag"
) )
@@ -31,7 +33,9 @@ Exit status is 12 if the password is incorrect.
`, `,
DisableAutoGenTag: true, DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
return runKeyPasswd(cmd.Context(), globalOptions, opts, args) term, cancel := setupTermstatus()
defer cancel()
return runKeyPasswd(cmd.Context(), globalOptions, opts, args, term)
}, },
} }
@@ -47,21 +51,22 @@ func (opts *KeyPasswdOptions) AddFlags(flags *pflag.FlagSet) {
opts.KeyAddOptions.Add(flags) opts.KeyAddOptions.Add(flags)
} }
func runKeyPasswd(ctx context.Context, gopts GlobalOptions, opts KeyPasswdOptions, args []string) error { func runKeyPasswd(ctx context.Context, gopts GlobalOptions, opts KeyPasswdOptions, args []string, term *termstatus.Terminal) error {
if len(args) > 0 { if len(args) > 0 {
return fmt.Errorf("the key passwd command expects no arguments, only options - please see `restic help key passwd` for usage and flags") return fmt.Errorf("the key passwd command expects no arguments, only options - please see `restic help key passwd` for usage and flags")
} }
printer := newTerminalProgressPrinter(gopts.JSON, gopts.verbosity, term)
ctx, repo, unlock, err := openWithExclusiveLock(ctx, gopts, false) ctx, repo, unlock, err := openWithExclusiveLock(ctx, gopts, false)
if err != nil { if err != nil {
return err return err
} }
defer unlock() defer unlock()
return changePassword(ctx, repo, gopts, opts) return changePassword(ctx, repo, gopts, opts, printer)
} }
func changePassword(ctx context.Context, repo *repository.Repository, gopts GlobalOptions, opts KeyPasswdOptions) error { 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)
if err != nil { if err != nil {
return err return err
@@ -83,7 +88,7 @@ func changePassword(ctx context.Context, repo *repository.Repository, gopts Glob
return err return err
} }
Verbosef("saved new key as %s\n", id) printer.P("saved new key as %s", id)
return nil return nil
} }

View File

@@ -7,6 +7,8 @@ import (
"github.com/restic/restic/internal/errors" "github.com/restic/restic/internal/errors"
"github.com/restic/restic/internal/repository" "github.com/restic/restic/internal/repository"
"github.com/restic/restic/internal/restic" "github.com/restic/restic/internal/restic"
"github.com/restic/restic/internal/ui/progress"
"github.com/restic/restic/internal/ui/termstatus"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@@ -29,27 +31,30 @@ Exit status is 12 if the password is incorrect.
`, `,
DisableAutoGenTag: true, DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
return runKeyRemove(cmd.Context(), globalOptions, args) term, cancel := setupTermstatus()
defer cancel()
return runKeyRemove(cmd.Context(), globalOptions, args, term)
}, },
} }
return cmd return cmd
} }
func runKeyRemove(ctx context.Context, gopts GlobalOptions, args []string) error { func runKeyRemove(ctx context.Context, gopts GlobalOptions, args []string, term *termstatus.Terminal) error {
if len(args) != 1 { if len(args) != 1 {
return fmt.Errorf("key remove expects one argument as the key id") return fmt.Errorf("key remove expects one argument as the key id")
} }
printer := newTerminalProgressPrinter(gopts.JSON, gopts.verbosity, term)
ctx, repo, unlock, err := openWithExclusiveLock(ctx, gopts, false) ctx, repo, unlock, err := openWithExclusiveLock(ctx, gopts, false)
if err != nil { if err != nil {
return err return err
} }
defer unlock() defer unlock()
return deleteKey(ctx, repo, args[0]) return deleteKey(ctx, repo, args[0], gopts, printer)
} }
func deleteKey(ctx context.Context, repo *repository.Repository, idPrefix string) error { func deleteKey(ctx context.Context, repo *repository.Repository, idPrefix string, gopts GlobalOptions, printer progress.Printer) error {
id, err := restic.Find(ctx, repo, restic.KeyFile, idPrefix) id, err := restic.Find(ctx, repo, restic.KeyFile, idPrefix)
if err != nil { if err != nil {
return err return err
@@ -64,6 +69,6 @@ func deleteKey(ctx context.Context, repo *repository.Repository, idPrefix string
return err return err
} }
Verbosef("removed key %v\n", id) printer.P("removed key %v", id)
return nil return nil
} }