restic/cmd/restic/cmd_forget.go

239 lines
6.4 KiB
Go
Raw Normal View History

2016-08-20 15:43:25 +00:00
package main
import (
"context"
"encoding/json"
"sort"
2016-08-20 15:59:10 +00:00
"strings"
2016-09-17 10:36:05 +00:00
"github.com/restic/restic/internal/errors"
2017-09-09 16:19:19 +00:00
"github.com/restic/restic/internal/restic"
2016-09-17 10:36:05 +00:00
"github.com/spf13/cobra"
2016-08-20 15:43:25 +00:00
)
2016-09-17 10:36:05 +00:00
var cmdForget = &cobra.Command{
Use: "forget [flags] [snapshot ID] [...]",
Short: "remove snapshots from the repository",
2016-09-17 10:36:05 +00:00
Long: `
The "forget" command removes snapshots according to a policy. Please note that
this command really only deletes the snapshot object in the repository, which
is a reference to data stored there. In order to remove this (now unreferenced)
data after 'forget' was run successfully, see the 'prune' command. `,
DisableAutoGenTag: true,
2016-09-17 10:36:05 +00:00
RunE: func(cmd *cobra.Command, args []string) error {
return runForget(forgetOptions, globalOptions, args)
},
}
2016-08-20 15:43:25 +00:00
2016-09-17 10:36:05 +00:00
// ForgetOptions collects all options for the forget command.
type ForgetOptions struct {
Last int
Hourly int
Daily int
Weekly int
Monthly int
Yearly int
KeepTags restic.TagLists
2016-08-20 15:59:47 +00:00
Host string
Tags restic.TagLists
Paths []string
2016-08-20 15:43:25 +00:00
// Grouping
2017-09-09 16:19:19 +00:00
GroupBy string
DryRun bool
Prune bool
2016-08-20 15:43:25 +00:00
}
2016-09-17 10:36:05 +00:00
var forgetOptions ForgetOptions
2016-08-20 15:43:25 +00:00
func init() {
2016-09-17 10:36:05 +00:00
cmdRoot.AddCommand(cmdForget)
f := cmdForget.Flags()
2016-09-29 18:39:55 +00:00
f.IntVarP(&forgetOptions.Last, "keep-last", "l", 0, "keep the last `n` snapshots")
f.IntVarP(&forgetOptions.Hourly, "keep-hourly", "H", 0, "keep the last `n` hourly snapshots")
f.IntVarP(&forgetOptions.Daily, "keep-daily", "d", 0, "keep the last `n` daily snapshots")
f.IntVarP(&forgetOptions.Weekly, "keep-weekly", "w", 0, "keep the last `n` weekly snapshots")
f.IntVarP(&forgetOptions.Monthly, "keep-monthly", "m", 0, "keep the last `n` monthly snapshots")
f.IntVarP(&forgetOptions.Yearly, "keep-yearly", "y", 0, "keep the last `n` yearly snapshots")
f.Var(&forgetOptions.KeepTags, "keep-tag", "keep snapshots with this `taglist` (can be specified multiple times)")
// Sadly the commonly used shortcut `H` is already used.
f.StringVar(&forgetOptions.Host, "host", "", "only consider snapshots with the given `host`")
// Deprecated since 2017-03-07.
f.StringVar(&forgetOptions.Host, "hostname", "", "only consider snapshots with the given `hostname` (deprecated)")
2017-07-16 13:25:28 +00:00
f.Var(&forgetOptions.Tags, "tag", "only consider snapshots which include this `taglist` in the format `tag[,tag,...]` (can be specified multiple times)")
f.StringArrayVar(&forgetOptions.Paths, "path", nil, "only consider snapshots which include this (absolute) `path` (can be specified multiple times)")
2016-08-20 15:43:25 +00:00
f.StringVarP(&forgetOptions.GroupBy, "group-by", "g", "host,paths", "string for grouping snapshots by host,paths,tags")
2016-09-17 10:36:05 +00:00
f.BoolVarP(&forgetOptions.DryRun, "dry-run", "n", false, "do not delete anything, just print what would be done")
2017-02-21 09:58:30 +00:00
f.BoolVar(&forgetOptions.Prune, "prune", false, "automatically run the 'prune' command if snapshots have been removed")
2017-04-21 17:25:21 +00:00
f.SortFlags = false
2016-08-20 15:43:25 +00:00
}
2016-09-17 10:36:05 +00:00
func runForget(opts ForgetOptions, gopts GlobalOptions, args []string) error {
repo, err := OpenRepository(gopts)
2016-08-20 15:43:25 +00:00
if err != nil {
return err
}
lock, err := lockRepoExclusive(repo)
defer unlockRepo(lock)
if err != nil {
return err
}
// group by hostname and dirs
type key struct {
Hostname string
Paths []string
Tags []string
}
snapshotGroups := make(map[string]restic.Snapshots)
2016-08-20 15:43:25 +00:00
2017-09-09 16:19:19 +00:00
var GroupByTag bool
var GroupByHost bool
var GroupByPath bool
var GroupOptionList []string
2017-09-09 16:19:19 +00:00
GroupOptionList = strings.Split(opts.GroupBy, ",")
for _, option := range GroupOptionList {
2017-09-09 16:19:19 +00:00
switch option {
case "host":
GroupByHost = true
case "paths":
GroupByPath = true
case "tags":
GroupByTag = true
case "":
default:
return errors.Fatal("unknown grouping option: '" + option + "'")
}
}
ctx, cancel := context.WithCancel(gopts.ctx)
defer cancel()
for sn := range FindFilteredSnapshots(ctx, repo, opts.Host, opts.Tags, opts.Paths, args) {
if len(args) > 0 {
// When explicit snapshots args are given, remove them immediately.
if !opts.DryRun {
h := restic.Handle{Type: restic.SnapshotFile, Name: sn.ID().String()}
2017-06-04 09:16:55 +00:00
if err = repo.Backend().Remove(context.TODO(), h); err != nil {
return err
}
Verbosef("removed snapshot %v\n", sn.ID().Str())
} else {
Verbosef("would have removed snapshot %v\n", sn.ID().Str())
}
} else {
// Determing grouping-keys
var tags []string
var hostname string
var paths []string
if GroupByTag {
tags = sn.Tags
sort.StringSlice(tags).Sort()
}
if GroupByHost {
hostname = sn.Hostname
}
if GroupByPath {
paths = sn.Paths
}
sort.StringSlice(sn.Paths).Sort()
2017-08-29 16:30:13 +00:00
var k []byte
var err error
k, err = json.Marshal(key{Tags: tags, Hostname: hostname, Paths: paths})
if err != nil {
return err
}
snapshotGroups[string(k)] = append(snapshotGroups[string(k)], sn)
2016-08-20 15:43:25 +00:00
}
}
if len(args) > 0 {
return nil
}
policy := restic.ExpirePolicy{
2016-09-17 10:36:05 +00:00
Last: opts.Last,
Hourly: opts.Hourly,
Daily: opts.Daily,
Weekly: opts.Weekly,
Monthly: opts.Monthly,
Yearly: opts.Yearly,
Tags: opts.KeepTags,
}
2016-08-20 15:43:25 +00:00
if policy.Empty() {
Verbosef("no policy was specified, no snapshots will be removed\n")
return nil
2016-08-20 15:43:25 +00:00
}
2017-02-21 09:58:30 +00:00
removeSnapshots := 0
for k, snapshotGroup := range snapshotGroups {
var key key
if json.Unmarshal([]byte(k), &key) != nil {
return err
}
// Info
2017-09-09 16:19:19 +00:00
Verbosef("snapshots")
var infoStrings []string
if GroupByTag {
2017-09-09 16:19:19 +00:00
infoStrings = append(infoStrings, "tags ["+strings.Join(key.Tags, ", ")+"]")
}
if GroupByHost {
2017-09-09 16:19:19 +00:00
infoStrings = append(infoStrings, "host ["+key.Hostname+"]")
}
if GroupByPath {
2017-09-09 16:19:19 +00:00
infoStrings = append(infoStrings, "paths ["+strings.Join(key.Paths, ", ")+"]")
}
if infoStrings != nil {
2017-09-09 16:19:19 +00:00
Verbosef(" for (" + strings.Join(infoStrings, ", ") + ")")
}
2017-09-09 16:19:19 +00:00
Verbosef(":\n\n")
2016-08-20 15:43:25 +00:00
keep, remove := restic.ApplyPolicy(snapshotGroup, policy)
if len(keep) != 0 && !gopts.Quiet {
Printf("keep %d snapshots:\n", len(keep))
PrintSnapshots(globalOptions.stdout, keep)
Printf("\n")
}
2016-08-20 15:43:25 +00:00
if len(remove) != 0 && !gopts.Quiet {
Printf("remove %d snapshots:\n", len(remove))
PrintSnapshots(globalOptions.stdout, remove)
Printf("\n")
}
2016-08-20 15:43:25 +00:00
2017-02-21 09:58:30 +00:00
removeSnapshots += len(remove)
2016-09-17 10:36:05 +00:00
if !opts.DryRun {
2016-08-20 15:43:25 +00:00
for _, sn := range remove {
h := restic.Handle{Type: restic.SnapshotFile, Name: sn.ID().String()}
2017-06-04 09:16:55 +00:00
err = repo.Backend().Remove(context.TODO(), h)
2016-08-20 15:43:25 +00:00
if err != nil {
return err
}
}
}
}
2017-02-21 09:58:30 +00:00
if removeSnapshots > 0 && opts.Prune {
Verbosef("%d snapshots have been removed, running prune\n", removeSnapshots)
2017-02-21 09:58:30 +00:00
if !opts.DryRun {
return pruneRepository(gopts, repo)
}
}
2016-08-20 15:43:25 +00:00
return nil
}