restic/cmd/restic/cmd_forget.go

252 lines
7.0 KiB
Go
Raw Normal View History

2016-08-20 15:43:25 +00:00
package main
import (
"context"
"encoding/json"
"io"
2016-09-17 10:36:05 +00:00
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.
EXIT STATUS
===========
Exit status is 0 if the command was successful, and non-zero if there was any error.
`,
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 {
2018-05-13 10:02:21 +00:00
Last int
Hourly int
Daily int
Weekly int
Monthly int
Yearly int
Within restic.Duration
KeepTags restic.TagLists
2016-08-20 15:59:47 +00:00
Hosts []string
2017-09-22 23:32:59 +00:00
Tags restic.TagLists
Paths []string
Compact bool
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.VarP(&forgetOptions.Within, "keep-within", "", "keep snapshots that are newer than `duration` (eg. 1y5m7d2h) relative to the latest snapshot")
2016-09-29 18:39:55 +00:00
f.Var(&forgetOptions.KeepTags, "keep-tag", "keep snapshots with this `taglist` (can be specified multiple times)")
f.StringArrayVar(&forgetOptions.Hosts, "host", nil, "only consider snapshots with the given `host` (can be specified multiple times)")
f.StringArrayVar(&forgetOptions.Hosts, "hostname", nil, "only consider snapshots with the given `hostname` (can be specified multiple times)")
2021-01-30 15:48:09 +00:00
err := f.MarkDeprecated("hostname", "use --host")
if err != nil {
// MarkDeprecated only returns an error when the flag is not found
panic(err)
}
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)")
2020-10-02 13:55:56 +00:00
f.BoolVarP(&forgetOptions.Compact, "compact", "c", false, "use compact output format")
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
2020-07-19 05:55:14 +00:00
addPruneOptions(cmdForget)
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 {
2020-07-19 05:55:14 +00:00
err := verifyPruneOptions(&pruneOptions)
if err != nil {
return err
}
2016-09-17 10:36:05 +00:00
repo, err := OpenRepository(gopts)
2016-08-20 15:43:25 +00:00
if err != nil {
return err
}
2020-08-09 11:24:47 +00:00
lock, err := lockRepoExclusive(gopts.ctx, repo)
2016-08-20 15:43:25 +00:00
defer unlockRepo(lock)
if err != nil {
return err
}
ctx, cancel := context.WithCancel(gopts.ctx)
defer cancel()
var snapshots restic.Snapshots
2020-07-27 20:35:26 +00:00
removeSnIDs := restic.NewIDSet()
for sn := range FindFilteredSnapshots(ctx, repo, opts.Hosts, opts.Tags, opts.Paths, args) {
snapshots = append(snapshots, sn)
}
2020-07-27 20:35:26 +00:00
var jsonGroups []*ForgetGroup
if len(args) > 0 {
// When explicit snapshots args are given, remove them immediately.
for _, sn := range snapshots {
2020-07-27 20:35:26 +00:00
removeSnIDs.Insert(*sn.ID())
}
} else {
snapshotGroups, _, err := restic.GroupSnapshots(snapshots, opts.GroupBy)
if err != nil {
return err
}
policy := restic.ExpirePolicy{
Last: opts.Last,
Hourly: opts.Hourly,
Daily: opts.Daily,
Weekly: opts.Weekly,
Monthly: opts.Monthly,
Yearly: opts.Yearly,
Within: opts.Within,
Tags: opts.KeepTags,
}
if policy.Empty() && len(args) == 0 {
if !gopts.JSON {
Verbosef("no policy was specified, no snapshots will be removed\n")
}
2016-08-20 15:43:25 +00:00
}
if !policy.Empty() {
if !gopts.JSON {
Verbosef("Applying Policy: %v\n", policy)
}
2016-08-20 15:43:25 +00:00
for k, snapshotGroup := range snapshotGroups {
if gopts.Verbose >= 1 && !gopts.JSON {
err = PrintSnapshotGroupHeader(gopts.stdout, k)
if err != nil {
return err
}
}
2018-03-30 08:24:26 +00:00
var key restic.SnapshotGroupKey
if json.Unmarshal([]byte(k), &key) != nil {
return err
}
var fg ForgetGroup
fg.Tags = key.Tags
fg.Host = key.Hostname
fg.Paths = key.Paths
keep, remove, reasons := restic.ApplyPolicy(snapshotGroup, policy)
2016-08-20 15:43:25 +00:00
if len(keep) != 0 && !gopts.Quiet && !gopts.JSON {
Printf("keep %d snapshots:\n", len(keep))
PrintSnapshots(globalOptions.stdout, keep, reasons, opts.Compact)
Printf("\n")
}
addJSONSnapshots(&fg.Keep, keep)
2016-08-20 15:43:25 +00:00
if len(remove) != 0 && !gopts.Quiet && !gopts.JSON {
Printf("remove %d snapshots:\n", len(remove))
PrintSnapshots(globalOptions.stdout, remove, nil, opts.Compact)
Printf("\n")
}
addJSONSnapshots(&fg.Remove, remove)
fg.Reasons = reasons
jsonGroups = append(jsonGroups, &fg)
2016-08-20 15:43:25 +00:00
2020-07-27 20:35:26 +00:00
for _, sn := range remove {
removeSnIDs.Insert(*sn.ID())
2016-08-20 15:43:25 +00:00
}
}
2020-07-27 20:35:26 +00:00
}
}
2020-07-27 20:35:26 +00:00
if len(removeSnIDs) > 0 {
if !opts.DryRun {
err := DeleteFilesChecked(gopts, repo, removeSnIDs, restic.SnapshotFile)
if err != nil {
return err
}
} else {
if !gopts.JSON {
Printf("Would have removed the following snapshots:\n%v\n\n", removeSnIDs)
}
}
2016-08-20 15:43:25 +00:00
}
2020-07-27 20:35:26 +00:00
if gopts.JSON && len(jsonGroups) > 0 {
err = printJSONForget(gopts.stdout, jsonGroups)
if err != nil {
return err
2017-02-21 09:58:30 +00:00
}
}
if len(removeSnIDs) > 0 && opts.Prune {
2020-07-19 05:55:14 +00:00
if !gopts.JSON {
Verbosef("%d snapshots have been removed, running prune\n", len(removeSnIDs))
}
pruneOptions.DryRun = opts.DryRun
2020-07-19 05:13:41 +00:00
return runPruneWithRepo(pruneOptions, gopts, repo, removeSnIDs)
2020-07-27 20:35:26 +00:00
}
2016-08-20 15:43:25 +00:00
return nil
}
// ForgetGroup helps to print what is forgotten in JSON.
type ForgetGroup struct {
Tags []string `json:"tags"`
Host string `json:"host"`
Paths []string `json:"paths"`
Keep []Snapshot `json:"keep"`
Remove []Snapshot `json:"remove"`
Reasons []restic.KeepReason `json:"reasons"`
}
func addJSONSnapshots(js *[]Snapshot, list restic.Snapshots) {
for _, sn := range list {
k := Snapshot{
Snapshot: sn,
ID: sn.ID(),
ShortID: sn.ID().Str(),
}
*js = append(*js, k)
}
}
func printJSONForget(stdout io.Writer, forgets []*ForgetGroup) error {
return json.NewEncoder(stdout).Encode(forgets)
}