2
2
mirror of https://github.com/octoleo/restic.git synced 2024-11-21 20:35:12 +00:00

Remove all usages of the global command-specific options

Now, every command uses an options struct, which is passed to the run*
function by the command.RunE method.
This commit is contained in:
Michael Eischer 2024-01-21 17:43:13 +01:00
parent 6696195f38
commit 66103aea3d
13 changed files with 93 additions and 79 deletions

View File

@ -633,7 +633,7 @@ func runBackup(ctx context.Context, opts BackupOptions, gopts GlobalOptions, ter
wg.Go(func() error { return sc.Scan(cancelCtx, targets) })
}
arch := archiver.New(repo, targetFS, archiver.Options{ReadConcurrency: backupOptions.ReadConcurrency})
arch := archiver.New(repo, targetFS, archiver.Options{ReadConcurrency: opts.ReadConcurrency})
arch.SelectByName = selectByNameFilter
arch.Select = selectFilter
arch.WithAtime = opts.WithAtime

View File

@ -52,19 +52,23 @@ Exit status is 0 if the command was successful, and non-zero if there was any er
},
}
var tryRepair bool
var repairByte bool
var extractPack bool
var reuploadBlobs bool
type DebugExamineOptions struct {
TryRepair bool
RepairByte bool
ExtractPack bool
ReuploadBlobs bool
}
var debugExamineOpts DebugExamineOptions
func init() {
cmdRoot.AddCommand(cmdDebug)
cmdDebug.AddCommand(cmdDebugDump)
cmdDebug.AddCommand(cmdDebugExamine)
cmdDebugExamine.Flags().BoolVar(&extractPack, "extract-pack", false, "write blobs to the current directory")
cmdDebugExamine.Flags().BoolVar(&reuploadBlobs, "reupload-blobs", false, "reupload blobs to the repository")
cmdDebugExamine.Flags().BoolVar(&tryRepair, "try-repair", false, "try to repair broken blobs with single bit flips")
cmdDebugExamine.Flags().BoolVar(&repairByte, "repair-byte", false, "try to repair broken blobs by trying bytes")
cmdDebugExamine.Flags().BoolVar(&debugExamineOpts.ExtractPack, "extract-pack", false, "write blobs to the current directory")
cmdDebugExamine.Flags().BoolVar(&debugExamineOpts.ReuploadBlobs, "reupload-blobs", false, "reupload blobs to the repository")
cmdDebugExamine.Flags().BoolVar(&debugExamineOpts.TryRepair, "try-repair", false, "try to repair broken blobs with single bit flips")
cmdDebugExamine.Flags().BoolVar(&debugExamineOpts.RepairByte, "repair-byte", false, "try to repair broken blobs by trying bytes")
}
func prettyPrintJSON(wr io.Writer, item interface{}) error {
@ -196,7 +200,7 @@ var cmdDebugExamine = &cobra.Command{
Short: "Examine a pack file",
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
return runDebugExamine(cmd.Context(), globalOptions, args)
return runDebugExamine(cmd.Context(), globalOptions, debugExamineOpts, args)
},
}
@ -315,7 +319,7 @@ func decryptUnsigned(ctx context.Context, k *crypto.Key, buf []byte) []byte {
return out
}
func loadBlobs(ctx context.Context, repo restic.Repository, packID restic.ID, list []restic.Blob) error {
func loadBlobs(ctx context.Context, opts DebugExamineOptions, repo restic.Repository, packID restic.ID, list []restic.Blob) error {
dec, err := zstd.NewReader(nil)
if err != nil {
panic(err)
@ -328,7 +332,7 @@ func loadBlobs(ctx context.Context, repo restic.Repository, packID restic.ID, li
wg, ctx := errgroup.WithContext(ctx)
if reuploadBlobs {
if opts.ReuploadBlobs {
repo.StartPackUploader(ctx, wg)
}
@ -356,8 +360,8 @@ func loadBlobs(ctx context.Context, repo restic.Repository, packID restic.ID, li
filePrefix := ""
if err != nil {
Warnf("error decrypting blob: %v\n", err)
if tryRepair || repairByte {
plaintext = tryRepairWithBitflip(ctx, key, buf, repairByte)
if opts.TryRepair || opts.RepairByte {
plaintext = tryRepairWithBitflip(ctx, key, buf, opts.RepairByte)
}
if plaintext != nil {
outputPrefix = "repaired "
@ -391,13 +395,13 @@ func loadBlobs(ctx context.Context, repo restic.Repository, packID restic.ID, li
Printf(" successfully %vdecrypted blob (length %v), hash is %v, ID matches\n", outputPrefix, len(plaintext), id)
prefix = "correct-"
}
if extractPack {
if opts.ExtractPack {
err = storePlainBlob(id, filePrefix+prefix, plaintext)
if err != nil {
return err
}
}
if reuploadBlobs {
if opts.ReuploadBlobs {
_, _, _, err := repo.SaveBlob(ctx, blob.Type, plaintext, id, true)
if err != nil {
return err
@ -406,7 +410,7 @@ func loadBlobs(ctx context.Context, repo restic.Repository, packID restic.ID, li
}
}
if reuploadBlobs {
if opts.ReuploadBlobs {
return repo.Flush(ctx)
}
return nil
@ -437,7 +441,7 @@ func storePlainBlob(id restic.ID, prefix string, plain []byte) error {
return nil
}
func runDebugExamine(ctx context.Context, gopts GlobalOptions, args []string) error {
func runDebugExamine(ctx context.Context, gopts GlobalOptions, opts DebugExamineOptions, args []string) error {
repo, err := OpenRepository(ctx, gopts)
if err != nil {
return err
@ -476,7 +480,7 @@ func runDebugExamine(ctx context.Context, gopts GlobalOptions, args []string) er
}
for _, id := range ids {
err := examinePack(ctx, repo, id)
err := examinePack(ctx, opts, repo, id)
if err != nil {
Warnf("error: %v\n", err)
}
@ -487,7 +491,7 @@ func runDebugExamine(ctx context.Context, gopts GlobalOptions, args []string) er
return nil
}
func examinePack(ctx context.Context, repo restic.Repository, id restic.ID) error {
func examinePack(ctx context.Context, opts DebugExamineOptions, repo restic.Repository, id restic.ID) error {
Printf("examine %v\n", id)
h := backend.Handle{
@ -524,7 +528,7 @@ func examinePack(ctx context.Context, repo restic.Repository, id restic.ID) erro
checkPackSize(blobs, fi.Size)
err = loadBlobs(ctx, repo, id, blobs)
err = loadBlobs(ctx, opts, repo, id, blobs)
if err != nil {
Warnf("error: %v\n", err)
} else {
@ -542,7 +546,7 @@ func examinePack(ctx context.Context, repo restic.Repository, id restic.ID) erro
checkPackSize(blobs, fi.Size)
if !blobsLoaded {
return loadBlobs(ctx, repo, id, blobs)
return loadBlobs(ctx, opts, repo, id, blobs)
}
return nil
}

View File

@ -401,7 +401,7 @@ func runDiff(ctx context.Context, opts DiffOptions, gopts GlobalOptions, args []
c := &Comparer{
repo: repo,
opts: diffOptions,
opts: opts,
printChange: func(change *Change) {
Printf("%-5s%v\n", change.Modifier, change.Path)
},

View File

@ -33,7 +33,7 @@ Exit status is 0 if the command was successful, and non-zero if there was any er
`,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
return runForget(cmd.Context(), forgetOptions, globalOptions, args)
return runForget(cmd.Context(), forgetOptions, forgetPruneOptions, globalOptions, args)
},
}
@ -98,6 +98,7 @@ type ForgetOptions struct {
}
var forgetOptions ForgetOptions
var forgetPruneOptions PruneOptions
func init() {
cmdRoot.AddCommand(cmdForget)
@ -132,7 +133,7 @@ func init() {
f.BoolVar(&forgetOptions.Prune, "prune", false, "automatically run the 'prune' command if snapshots have been removed")
f.SortFlags = false
addPruneOptions(cmdForget)
addPruneOptions(cmdForget, &forgetPruneOptions)
}
func verifyForgetOptions(opts *ForgetOptions) error {
@ -151,7 +152,7 @@ func verifyForgetOptions(opts *ForgetOptions) error {
return nil
}
func runForget(ctx context.Context, opts ForgetOptions, gopts GlobalOptions, args []string) error {
func runForget(ctx context.Context, opts ForgetOptions, pruneOptions PruneOptions, gopts GlobalOptions, args []string) error {
err := verifyForgetOptions(&opts)
if err != nil {
return err

View File

@ -9,5 +9,8 @@ import (
func testRunForget(t testing.TB, gopts GlobalOptions, args ...string) {
opts := ForgetOptions{}
rtest.OK(t, runForget(context.TODO(), opts, gopts, args))
pruneOpts := PruneOptions{
MaxUnused: "5%",
}
rtest.OK(t, runForget(context.TODO(), opts, pruneOpts, gopts, args))
}

View File

@ -21,7 +21,9 @@ EXIT STATUS
Exit status is 0 if the command was successful, and non-zero if there was any error.
`,
DisableAutoGenTag: true,
RunE: runGenerate,
RunE: func(cmd *cobra.Command, args []string) error {
return runGenerate(genOpts, args)
},
}
type generateOptions struct {
@ -90,48 +92,48 @@ func writePowerShellCompletion(file string) error {
return cmdRoot.GenPowerShellCompletionFile(file)
}
func runGenerate(_ *cobra.Command, args []string) error {
func runGenerate(opts generateOptions, args []string) error {
if len(args) > 0 {
return errors.Fatal("the generate command expects no arguments, only options - please see `restic help generate` for usage and flags")
}
if genOpts.ManDir != "" {
err := writeManpages(genOpts.ManDir)
if opts.ManDir != "" {
err := writeManpages(opts.ManDir)
if err != nil {
return err
}
}
if genOpts.BashCompletionFile != "" {
err := writeBashCompletion(genOpts.BashCompletionFile)
if opts.BashCompletionFile != "" {
err := writeBashCompletion(opts.BashCompletionFile)
if err != nil {
return err
}
}
if genOpts.FishCompletionFile != "" {
err := writeFishCompletion(genOpts.FishCompletionFile)
if opts.FishCompletionFile != "" {
err := writeFishCompletion(opts.FishCompletionFile)
if err != nil {
return err
}
}
if genOpts.ZSHCompletionFile != "" {
err := writeZSHCompletion(genOpts.ZSHCompletionFile)
if opts.ZSHCompletionFile != "" {
err := writeZSHCompletion(opts.ZSHCompletionFile)
if err != nil {
return err
}
}
if genOpts.PowerShellCompletionFile != "" {
err := writePowerShellCompletion(genOpts.PowerShellCompletionFile)
if opts.PowerShellCompletionFile != "" {
err := writePowerShellCompletion(opts.PowerShellCompletionFile)
if err != nil {
return err
}
}
var empty generateOptions
if genOpts == empty {
if opts == empty {
return errors.Fatal("nothing to do, please specify at least one output file/dir")
}

View File

@ -29,23 +29,25 @@ Exit status is 0 if the command was successful, and non-zero if there was any er
`,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
return runKey(cmd.Context(), globalOptions, args)
return runKey(cmd.Context(), globalOptions, keyOpts, args)
},
}
var (
newPasswordFile string
keyUsername string
keyHostname string
)
type KeyOptions struct {
NewPasswordFile string
Username string
Hostname string
}
var keyOpts KeyOptions
func init() {
cmdRoot.AddCommand(cmdKey)
flags := cmdKey.Flags()
flags.StringVarP(&newPasswordFile, "new-password-file", "", "", "`file` from which to read the new password")
flags.StringVarP(&keyUsername, "user", "", "", "the username for new keys")
flags.StringVarP(&keyHostname, "host", "", "", "the hostname for new keys")
flags.StringVarP(&keyOpts.NewPasswordFile, "new-password-file", "", "", "`file` from which to read the new password")
flags.StringVarP(&keyOpts.Username, "user", "", "", "the username for new keys")
flags.StringVarP(&keyOpts.Hostname, "host", "", "", "the hostname for new keys")
}
func listKeys(ctx context.Context, s *repository.Repository, gopts GlobalOptions) error {
@ -105,7 +107,7 @@ func listKeys(ctx context.Context, s *repository.Repository, gopts GlobalOptions
// testKeyNewPassword is used to set a new password during integration testing.
var testKeyNewPassword string
func getNewPassword(gopts GlobalOptions) (string, error) {
func getNewPassword(gopts GlobalOptions, newPasswordFile string) (string, error) {
if testKeyNewPassword != "" {
return testKeyNewPassword, nil
}
@ -124,13 +126,13 @@ func getNewPassword(gopts GlobalOptions) (string, error) {
"enter password again: ")
}
func addKey(ctx context.Context, repo *repository.Repository, gopts GlobalOptions) error {
pw, err := getNewPassword(gopts)
func addKey(ctx context.Context, repo *repository.Repository, gopts GlobalOptions, opts KeyOptions) error {
pw, err := getNewPassword(gopts, opts.NewPasswordFile)
if err != nil {
return err
}
id, err := repository.AddKey(ctx, repo, pw, keyUsername, keyHostname, repo.Key())
id, err := repository.AddKey(ctx, repo, pw, opts.Username, opts.Hostname, repo.Key())
if err != nil {
return errors.Fatalf("creating new key failed: %v\n", err)
}
@ -160,8 +162,8 @@ func deleteKey(ctx context.Context, repo *repository.Repository, id restic.ID) e
return nil
}
func changePassword(ctx context.Context, repo *repository.Repository, gopts GlobalOptions) error {
pw, err := getNewPassword(gopts)
func changePassword(ctx context.Context, repo *repository.Repository, gopts GlobalOptions, newPasswordFile string) error {
pw, err := getNewPassword(gopts, newPasswordFile)
if err != nil {
return err
}
@ -201,7 +203,7 @@ func switchToNewKeyAndRemoveIfBroken(ctx context.Context, repo *repository.Repos
return nil
}
func runKey(ctx context.Context, gopts GlobalOptions, args []string) error {
func runKey(ctx context.Context, gopts GlobalOptions, opts KeyOptions, args []string) error {
if len(args) < 1 || (args[0] == "remove" && len(args) != 2) || (args[0] != "remove" && len(args) != 1) {
return errors.Fatal("wrong number of arguments")
}
@ -230,7 +232,7 @@ func runKey(ctx context.Context, gopts GlobalOptions, args []string) error {
return err
}
return addKey(ctx, repo, gopts)
return addKey(ctx, repo, gopts, opts)
case "remove":
lock, ctx, err := lockRepoExclusive(ctx, repo, gopts.RetryLock, gopts.JSON)
defer unlockRepo(lock)
@ -251,7 +253,7 @@ func runKey(ctx context.Context, gopts GlobalOptions, args []string) error {
return err
}
return changePassword(ctx, repo, gopts)
return changePassword(ctx, repo, gopts, opts.NewPasswordFile)
}
return nil

View File

@ -13,7 +13,7 @@ import (
func testRunKeyListOtherIDs(t testing.TB, gopts GlobalOptions) []string {
buf, err := withCaptureStdout(func() error {
return runKey(context.TODO(), gopts, []string{"list"})
return runKey(context.TODO(), gopts, KeyOptions{}, []string{"list"})
})
rtest.OK(t, err)
@ -36,21 +36,20 @@ func testRunKeyAddNewKey(t testing.TB, newPassword string, gopts GlobalOptions)
testKeyNewPassword = ""
}()
rtest.OK(t, runKey(context.TODO(), gopts, []string{"add"}))
rtest.OK(t, runKey(context.TODO(), gopts, KeyOptions{}, []string{"add"}))
}
func testRunKeyAddNewKeyUserHost(t testing.TB, gopts GlobalOptions) {
testKeyNewPassword = "john's geheimnis"
defer func() {
testKeyNewPassword = ""
keyUsername = ""
keyHostname = ""
}()
rtest.OK(t, cmdKey.Flags().Parse([]string{"--user=john", "--host=example.com"}))
t.Log("adding key for john@example.com")
rtest.OK(t, runKey(context.TODO(), gopts, []string{"add"}))
rtest.OK(t, runKey(context.TODO(), gopts, KeyOptions{
Username: "john",
Hostname: "example.com",
}, []string{"add"}))
repo, err := OpenRepository(context.TODO(), gopts)
rtest.OK(t, err)
@ -67,13 +66,13 @@ func testRunKeyPasswd(t testing.TB, newPassword string, gopts GlobalOptions) {
testKeyNewPassword = ""
}()
rtest.OK(t, runKey(context.TODO(), gopts, []string{"passwd"}))
rtest.OK(t, runKey(context.TODO(), gopts, KeyOptions{}, []string{"passwd"}))
}
func testRunKeyRemove(t testing.TB, gopts GlobalOptions, IDs []string) {
t.Logf("remove %d keys: %q\n", len(IDs), IDs)
for _, id := range IDs {
rtest.OK(t, runKey(context.TODO(), gopts, []string{"remove", id}))
rtest.OK(t, runKey(context.TODO(), gopts, KeyOptions{}, []string{"remove", id}))
}
}
@ -103,7 +102,7 @@ func TestKeyAddRemove(t *testing.T) {
env.gopts.password = passwordList[len(passwordList)-1]
t.Logf("testing access with last password %q\n", env.gopts.password)
rtest.OK(t, runKey(context.TODO(), env.gopts, []string{"list"}))
rtest.OK(t, runKey(context.TODO(), env.gopts, KeyOptions{}, []string{"list"}))
testRunCheck(t, env.gopts)
testRunKeyAddNewKeyUserHost(t, env.gopts)
@ -131,15 +130,15 @@ func TestKeyProblems(t *testing.T) {
testKeyNewPassword = ""
}()
err := runKey(context.TODO(), env.gopts, []string{"passwd"})
err := runKey(context.TODO(), env.gopts, KeyOptions{}, []string{"passwd"})
t.Log(err)
rtest.Assert(t, err != nil, "expected passwd change to fail")
err = runKey(context.TODO(), env.gopts, []string{"add"})
err = runKey(context.TODO(), env.gopts, KeyOptions{}, []string{"add"})
t.Log(err)
rtest.Assert(t, err != nil, "expected key adding to fail")
t.Logf("testing access with initial password %q\n", env.gopts.password)
rtest.OK(t, runKey(context.TODO(), env.gopts, []string{"list"}))
rtest.OK(t, runKey(context.TODO(), env.gopts, KeyOptions{}, []string{"list"}))
testRunCheck(t, env.gopts)
}

View File

@ -23,7 +23,7 @@ Exit status is 0 if the command was successful, and non-zero if there was any er
`,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
return runList(cmd.Context(), cmd, globalOptions, args)
return runList(cmd.Context(), globalOptions, args)
},
}
@ -31,9 +31,9 @@ func init() {
cmdRoot.AddCommand(cmdList)
}
func runList(ctx context.Context, cmd *cobra.Command, gopts GlobalOptions, args []string) error {
func runList(ctx context.Context, gopts GlobalOptions, args []string) error {
if len(args) != 1 {
return errors.Fatal("type not specified, usage: " + cmd.Use)
return errors.Fatal("type not specified")
}
repo, err := OpenRepository(ctx, gopts)

View File

@ -12,7 +12,7 @@ import (
func testRunList(t testing.TB, tpe string, opts GlobalOptions) restic.IDs {
buf, err := withCaptureStdout(func() error {
return runList(context.TODO(), cmdList, opts, []string{tpe})
return runList(context.TODO(), opts, []string{tpe})
})
rtest.OK(t, err)
return parseIDsFromReader(t, buf)

View File

@ -210,7 +210,7 @@ func runLs(ctx context.Context, opts LsOptions, gopts GlobalOptions, args []stri
Verbosef("%v filtered by %v:\n", sn, dirs)
}
printNode = func(path string, node *restic.Node) {
Printf("%s\n", formatNode(path, node, lsOptions.ListLong, lsOptions.HumanReadable))
Printf("%s\n", formatNode(path, node, opts.ListLong, opts.HumanReadable))
}
}

View File

@ -66,10 +66,10 @@ func init() {
f := cmdPrune.Flags()
f.BoolVarP(&pruneOptions.DryRun, "dry-run", "n", false, "do not modify the repository, just print what would be done")
f.StringVarP(&pruneOptions.UnsafeNoSpaceRecovery, "unsafe-recover-no-free-space", "", "", "UNSAFE, READ THE DOCUMENTATION BEFORE USING! Try to recover a repository stuck with no free space. Do not use without trying out 'prune --max-repack-size 0' first.")
addPruneOptions(cmdPrune)
addPruneOptions(cmdPrune, &pruneOptions)
}
func addPruneOptions(c *cobra.Command) {
func addPruneOptions(c *cobra.Command, pruneOptions *PruneOptions) {
f := c.Flags()
f.StringVar(&pruneOptions.MaxUnused, "max-unused", "5%", "tolerate given `limit` of unused data (absolute value in bytes with suffixes k/K, m/M, g/G, t/T, a value in % or the word 'unlimited')")
f.StringVar(&pruneOptions.MaxRepackSize, "max-repack-size", "", "maximum `size` to repack (allowed suffixes: k/K, m/M, g/G, t/T)")

View File

@ -81,7 +81,10 @@ func testRunForgetJSON(t testing.TB, gopts GlobalOptions, args ...string) {
DryRun: true,
Last: 1,
}
return runForget(context.TODO(), opts, gopts, args)
pruneOpts := PruneOptions{
MaxUnused: "5%",
}
return runForget(context.TODO(), opts, pruneOpts, gopts, args)
})
rtest.OK(t, err)