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

     1  package main
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"sort"
     7  	"strings"
     8  
     9  	"github.com/restic/restic/internal/errors"
    10  	"github.com/restic/restic/internal/restic"
    11  	"github.com/spf13/cobra"
    12  )
    13  
    14  var cmdForget = &cobra.Command{
    15  	Use:   "forget [flags] [snapshot ID] [...]",
    16  	Short: "Remove snapshots from the repository",
    17  	Long: `
    18  The "forget" command removes snapshots according to a policy. Please note that
    19  this command really only deletes the snapshot object in the repository, which
    20  is a reference to data stored there. In order to remove this (now unreferenced)
    21  data after 'forget' was run successfully, see the 'prune' command. `,
    22  	DisableAutoGenTag: true,
    23  	RunE: func(cmd *cobra.Command, args []string) error {
    24  		return runForget(forgetOptions, globalOptions, args)
    25  	},
    26  }
    27  
    28  // ForgetOptions collects all options for the forget command.
    29  type ForgetOptions struct {
    30  	Last     int
    31  	Hourly   int
    32  	Daily    int
    33  	Weekly   int
    34  	Monthly  int
    35  	Yearly   int
    36  	KeepTags restic.TagLists
    37  
    38  	Host    string
    39  	Tags    restic.TagLists
    40  	Paths   []string
    41  	Compact bool
    42  
    43  	// Grouping
    44  	GroupBy string
    45  	DryRun  bool
    46  	Prune   bool
    47  }
    48  
    49  var forgetOptions ForgetOptions
    50  
    51  func init() {
    52  	cmdRoot.AddCommand(cmdForget)
    53  
    54  	f := cmdForget.Flags()
    55  	f.IntVarP(&forgetOptions.Last, "keep-last", "l", 0, "keep the last `n` snapshots")
    56  	f.IntVarP(&forgetOptions.Hourly, "keep-hourly", "H", 0, "keep the last `n` hourly snapshots")
    57  	f.IntVarP(&forgetOptions.Daily, "keep-daily", "d", 0, "keep the last `n` daily snapshots")
    58  	f.IntVarP(&forgetOptions.Weekly, "keep-weekly", "w", 0, "keep the last `n` weekly snapshots")
    59  	f.IntVarP(&forgetOptions.Monthly, "keep-monthly", "m", 0, "keep the last `n` monthly snapshots")
    60  	f.IntVarP(&forgetOptions.Yearly, "keep-yearly", "y", 0, "keep the last `n` yearly snapshots")
    61  
    62  	f.Var(&forgetOptions.KeepTags, "keep-tag", "keep snapshots with this `taglist` (can be specified multiple times)")
    63  	// Sadly the commonly used shortcut `H` is already used.
    64  	f.StringVar(&forgetOptions.Host, "host", "", "only consider snapshots with the given `host`")
    65  	// Deprecated since 2017-03-07.
    66  	f.StringVar(&forgetOptions.Host, "hostname", "", "only consider snapshots with the given `hostname` (deprecated)")
    67  	f.Var(&forgetOptions.Tags, "tag", "only consider snapshots which include this `taglist` in the format `tag[,tag,...]` (can be specified multiple times)")
    68  	f.StringArrayVar(&forgetOptions.Paths, "path", nil, "only consider snapshots which include this (absolute) `path` (can be specified multiple times)")
    69  	f.BoolVarP(&forgetOptions.Compact, "compact", "c", false, "use compact format")
    70  
    71  	f.StringVarP(&forgetOptions.GroupBy, "group-by", "g", "host,paths", "string for grouping snapshots by host,paths,tags")
    72  	f.BoolVarP(&forgetOptions.DryRun, "dry-run", "n", false, "do not delete anything, just print what would be done")
    73  	f.BoolVar(&forgetOptions.Prune, "prune", false, "automatically run the 'prune' command if snapshots have been removed")
    74  
    75  	f.SortFlags = false
    76  }
    77  
    78  func runForget(opts ForgetOptions, gopts GlobalOptions, args []string) error {
    79  	repo, err := OpenRepository(gopts)
    80  	if err != nil {
    81  		return err
    82  	}
    83  
    84  	lock, err := lockRepoExclusive(repo)
    85  	defer unlockRepo(lock)
    86  	if err != nil {
    87  		return err
    88  	}
    89  
    90  	// group by hostname and dirs
    91  	type key struct {
    92  		Hostname string
    93  		Paths    []string
    94  		Tags     []string
    95  	}
    96  	snapshotGroups := make(map[string]restic.Snapshots)
    97  
    98  	var GroupByTag bool
    99  	var GroupByHost bool
   100  	var GroupByPath bool
   101  	var GroupOptionList []string
   102  
   103  	GroupOptionList = strings.Split(opts.GroupBy, ",")
   104  
   105  	for _, option := range GroupOptionList {
   106  		switch option {
   107  		case "host":
   108  			GroupByHost = true
   109  		case "paths":
   110  			GroupByPath = true
   111  		case "tags":
   112  			GroupByTag = true
   113  		case "":
   114  		default:
   115  			return errors.Fatal("unknown grouping option: '" + option + "'")
   116  		}
   117  	}
   118  
   119  	removeSnapshots := 0
   120  
   121  	ctx, cancel := context.WithCancel(gopts.ctx)
   122  	defer cancel()
   123  	for sn := range FindFilteredSnapshots(ctx, repo, opts.Host, opts.Tags, opts.Paths, args) {
   124  		if len(args) > 0 {
   125  			// When explicit snapshots args are given, remove them immediately.
   126  			if !opts.DryRun {
   127  				h := restic.Handle{Type: restic.SnapshotFile, Name: sn.ID().String()}
   128  				if err = repo.Backend().Remove(gopts.ctx, h); err != nil {
   129  					return err
   130  				}
   131  				Verbosef("removed snapshot %v\n", sn.ID().Str())
   132  				removeSnapshots++
   133  			} else {
   134  				Verbosef("would have removed snapshot %v\n", sn.ID().Str())
   135  			}
   136  		} else {
   137  			// Determining grouping-keys
   138  			var tags []string
   139  			var hostname string
   140  			var paths []string
   141  
   142  			if GroupByTag {
   143  				tags = sn.Tags
   144  				sort.StringSlice(tags).Sort()
   145  			}
   146  			if GroupByHost {
   147  				hostname = sn.Hostname
   148  			}
   149  			if GroupByPath {
   150  				paths = sn.Paths
   151  			}
   152  
   153  			sort.StringSlice(sn.Paths).Sort()
   154  			var k []byte
   155  			var err error
   156  
   157  			k, err = json.Marshal(key{Tags: tags, Hostname: hostname, Paths: paths})
   158  
   159  			if err != nil {
   160  				return err
   161  			}
   162  			snapshotGroups[string(k)] = append(snapshotGroups[string(k)], sn)
   163  		}
   164  	}
   165  	if len(args) > 0 {
   166  		return nil
   167  	}
   168  
   169  	policy := restic.ExpirePolicy{
   170  		Last:    opts.Last,
   171  		Hourly:  opts.Hourly,
   172  		Daily:   opts.Daily,
   173  		Weekly:  opts.Weekly,
   174  		Monthly: opts.Monthly,
   175  		Yearly:  opts.Yearly,
   176  		Tags:    opts.KeepTags,
   177  	}
   178  
   179  	if policy.Empty() {
   180  		Verbosef("no policy was specified, no snapshots will be removed\n")
   181  		return nil
   182  	}
   183  
   184  	for k, snapshotGroup := range snapshotGroups {
   185  		var key key
   186  		if json.Unmarshal([]byte(k), &key) != nil {
   187  			return err
   188  		}
   189  
   190  		// Info
   191  		Verbosef("snapshots")
   192  		var infoStrings []string
   193  		if GroupByTag {
   194  			infoStrings = append(infoStrings, "tags ["+strings.Join(key.Tags, ", ")+"]")
   195  		}
   196  		if GroupByHost {
   197  			infoStrings = append(infoStrings, "host ["+key.Hostname+"]")
   198  		}
   199  		if GroupByPath {
   200  			infoStrings = append(infoStrings, "paths ["+strings.Join(key.Paths, ", ")+"]")
   201  		}
   202  		if infoStrings != nil {
   203  			Verbosef(" for (" + strings.Join(infoStrings, ", ") + ")")
   204  		}
   205  		Verbosef(":\n\n")
   206  
   207  		keep, remove := restic.ApplyPolicy(snapshotGroup, policy)
   208  
   209  		if len(keep) != 0 && !gopts.Quiet {
   210  			Printf("keep %d snapshots:\n", len(keep))
   211  			PrintSnapshots(globalOptions.stdout, keep, opts.Compact)
   212  			Printf("\n")
   213  		}
   214  
   215  		if len(remove) != 0 && !gopts.Quiet {
   216  			Printf("remove %d snapshots:\n", len(remove))
   217  			PrintSnapshots(globalOptions.stdout, remove, opts.Compact)
   218  			Printf("\n")
   219  		}
   220  
   221  		removeSnapshots += len(remove)
   222  
   223  		if !opts.DryRun {
   224  			for _, sn := range remove {
   225  				h := restic.Handle{Type: restic.SnapshotFile, Name: sn.ID().String()}
   226  				err = repo.Backend().Remove(gopts.ctx, h)
   227  				if err != nil {
   228  					return err
   229  				}
   230  			}
   231  		}
   232  	}
   233  
   234  	if removeSnapshots > 0 && opts.Prune {
   235  		Verbosef("%d snapshots have been removed, running prune\n", removeSnapshots)
   236  		if !opts.DryRun {
   237  			return pruneRepository(gopts, repo)
   238  		}
   239  	}
   240  
   241  	return nil
   242  }