github.com/advanderveer/restic@v0.8.1-0.20171209104529-42a8c19aaea6/cmd/restic/cmd_restore.go (about)

     1  package main
     2  
     3  import (
     4  	"github.com/restic/restic/internal/debug"
     5  	"github.com/restic/restic/internal/errors"
     6  	"github.com/restic/restic/internal/filter"
     7  	"github.com/restic/restic/internal/restic"
     8  
     9  	"github.com/spf13/cobra"
    10  )
    11  
    12  var cmdRestore = &cobra.Command{
    13  	Use:   "restore [flags] snapshotID",
    14  	Short: "Extract the data from a snapshot",
    15  	Long: `
    16  The "restore" command extracts the data from a snapshot from the repository to
    17  a directory.
    18  
    19  The special snapshot "latest" can be used to restore the latest snapshot in the
    20  repository.
    21  `,
    22  	DisableAutoGenTag: true,
    23  	RunE: func(cmd *cobra.Command, args []string) error {
    24  		return runRestore(restoreOptions, globalOptions, args)
    25  	},
    26  }
    27  
    28  // RestoreOptions collects all options for the restore command.
    29  type RestoreOptions struct {
    30  	Exclude []string
    31  	Include []string
    32  	Target  string
    33  	Host    string
    34  	Paths   []string
    35  	Tags    restic.TagLists
    36  }
    37  
    38  var restoreOptions RestoreOptions
    39  
    40  func init() {
    41  	cmdRoot.AddCommand(cmdRestore)
    42  
    43  	flags := cmdRestore.Flags()
    44  	flags.StringArrayVarP(&restoreOptions.Exclude, "exclude", "e", nil, "exclude a `pattern` (can be specified multiple times)")
    45  	flags.StringArrayVarP(&restoreOptions.Include, "include", "i", nil, "include a `pattern`, exclude everything else (can be specified multiple times)")
    46  	flags.StringVarP(&restoreOptions.Target, "target", "t", "", "directory to extract data to")
    47  
    48  	flags.StringVarP(&restoreOptions.Host, "host", "H", "", `only consider snapshots for this host when the snapshot ID is "latest"`)
    49  	flags.Var(&restoreOptions.Tags, "tag", "only consider snapshots which include this `taglist` for snapshot ID \"latest\"")
    50  	flags.StringArrayVar(&restoreOptions.Paths, "path", nil, "only consider snapshots which include this (absolute) `path` for snapshot ID \"latest\"")
    51  }
    52  
    53  func runRestore(opts RestoreOptions, gopts GlobalOptions, args []string) error {
    54  	ctx := gopts.ctx
    55  
    56  	if len(args) != 1 {
    57  		return errors.Fatal("no snapshot ID specified")
    58  	}
    59  
    60  	if opts.Target == "" {
    61  		return errors.Fatal("please specify a directory to restore to (--target)")
    62  	}
    63  
    64  	if len(opts.Exclude) > 0 && len(opts.Include) > 0 {
    65  		return errors.Fatal("exclude and include patterns are mutually exclusive")
    66  	}
    67  
    68  	snapshotIDString := args[0]
    69  
    70  	debug.Log("restore %v to %v", snapshotIDString, opts.Target)
    71  
    72  	repo, err := OpenRepository(gopts)
    73  	if err != nil {
    74  		return err
    75  	}
    76  
    77  	if !gopts.NoLock {
    78  		lock, err := lockRepo(repo)
    79  		defer unlockRepo(lock)
    80  		if err != nil {
    81  			return err
    82  		}
    83  	}
    84  
    85  	err = repo.LoadIndex(ctx)
    86  	if err != nil {
    87  		return err
    88  	}
    89  
    90  	var id restic.ID
    91  
    92  	if snapshotIDString == "latest" {
    93  		id, err = restic.FindLatestSnapshot(ctx, repo, opts.Paths, opts.Tags, opts.Host)
    94  		if err != nil {
    95  			Exitf(1, "latest snapshot for criteria not found: %v Paths:%v Host:%v", err, opts.Paths, opts.Host)
    96  		}
    97  	} else {
    98  		id, err = restic.FindSnapshot(repo, snapshotIDString)
    99  		if err != nil {
   100  			Exitf(1, "invalid id %q: %v", snapshotIDString, err)
   101  		}
   102  	}
   103  
   104  	res, err := restic.NewRestorer(repo, id)
   105  	if err != nil {
   106  		Exitf(2, "creating restorer failed: %v\n", err)
   107  	}
   108  
   109  	totalErrors := 0
   110  	res.Error = func(dir string, node *restic.Node, err error) error {
   111  		Warnf("ignoring error for %s: %s\n", dir, err)
   112  		totalErrors++
   113  		return nil
   114  	}
   115  
   116  	selectExcludeFilter := func(item string, dstpath string, node *restic.Node) (selectedForRestore bool, childMayBeSelected bool) {
   117  		matched, _, err := filter.List(opts.Exclude, item)
   118  		if err != nil {
   119  			Warnf("error for exclude pattern: %v", err)
   120  		}
   121  
   122  		// An exclude filter is basically a 'wildcard but foo',
   123  		// so even if a childMayMatch, other children of a dir may not,
   124  		// therefore childMayMatch does not matter, but we should not go down
   125  		// unless the dir is selected for restore
   126  		selectedForRestore = !matched
   127  		childMayBeSelected = selectedForRestore && node.Type == "dir"
   128  
   129  		return selectedForRestore, childMayBeSelected
   130  	}
   131  
   132  	selectIncludeFilter := func(item string, dstpath string, node *restic.Node) (selectedForRestore bool, childMayBeSelected bool) {
   133  		matched, childMayMatch, err := filter.List(opts.Include, item)
   134  		if err != nil {
   135  			Warnf("error for include pattern: %v", err)
   136  		}
   137  
   138  		selectedForRestore = matched
   139  		childMayBeSelected = childMayMatch && node.Type == "dir"
   140  
   141  		return selectedForRestore, childMayBeSelected
   142  	}
   143  
   144  	if len(opts.Exclude) > 0 {
   145  		res.SelectFilter = selectExcludeFilter
   146  	} else if len(opts.Include) > 0 {
   147  		res.SelectFilter = selectIncludeFilter
   148  	}
   149  
   150  	Verbosef("restoring %s to %s\n", res.Snapshot(), opts.Target)
   151  
   152  	err = res.RestoreTo(ctx, opts.Target)
   153  	if totalErrors > 0 {
   154  		Printf("There were %d errors\n", totalErrors)
   155  	}
   156  	return err
   157  }