2016-08-20 17:43:25 +02:00
package main
import (
2017-03-08 20:24:58 +01:00
"context"
2017-03-07 14:19:36 +01:00
"encoding/json"
2019-02-22 21:25:41 -07:00
"io"
2016-09-17 12:36:05 +02:00
2017-09-09 18:19:19 +02:00
"github.com/restic/restic/internal/restic"
2016-09-17 12:36:05 +02:00
"github.com/spf13/cobra"
2016-08-20 17:43:25 +02:00
)
2016-09-17 12:36:05 +02:00
var cmdForget = & cobra . Command {
Use : "forget [flags] [snapshot ID] [...]" ,
2017-09-11 09:32:44 -07:00
Short : "Remove snapshots from the repository" ,
2016-09-17 12:36:05 +02: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 . ` ,
2017-08-06 21:02:16 +02:00
DisableAutoGenTag : true ,
2016-09-17 12:36:05 +02:00
RunE : func ( cmd * cobra . Command , args [ ] string ) error {
return runForget ( forgetOptions , globalOptions , args )
} ,
}
2016-08-20 17:43:25 +02:00
2016-09-17 12:36:05 +02:00
// ForgetOptions collects all options for the forget command.
type ForgetOptions struct {
2018-05-13 12:02:21 +02:00
Last int
Hourly int
Daily int
Weekly int
Monthly int
Yearly int
Within restic . Duration
KeepTags restic . TagLists
2016-08-20 17:59:47 +02:00
2017-09-22 16:32:59 -07:00
Host string
Tags restic . TagLists
Paths [ ] string
Compact bool
2016-08-20 17:43:25 +02:00
2017-09-06 20:14:18 +02:00
// Grouping
2017-09-09 18:19:19 +02:00
GroupBy string
DryRun bool
Prune bool
2016-08-20 17:43:25 +02:00
}
2016-09-17 12:36:05 +02:00
var forgetOptions ForgetOptions
2016-08-20 17:43:25 +02:00
func init ( ) {
2016-09-17 12:36:05 +02:00
cmdRoot . AddCommand ( cmdForget )
f := cmdForget . Flags ( )
2016-09-29 20:39:55 +02: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" )
2018-11-14 17:24:59 +01:00
f . VarP ( & forgetOptions . Within , "keep-within" , "" , "keep snapshots that are newer than `duration` (eg. 1y5m7d2h) relative to the latest snapshot" )
2016-09-29 20:39:55 +02:00
2017-07-09 12:45:49 +02:00
f . Var ( & forgetOptions . KeepTags , "keep-tag" , "keep snapshots with this `taglist` (can be specified multiple times)" )
2017-03-07 14:19:36 +01:00
f . StringVar ( & forgetOptions . Host , "host" , "" , "only consider snapshots with the given `host`" )
2018-10-03 14:05:53 +02:00
f . StringVar ( & forgetOptions . Host , "hostname" , "" , "only consider snapshots with the given `hostname`" )
f . MarkDeprecated ( "hostname" , "use --host" )
2017-07-16 15:25:28 +02:00
f . Var ( & forgetOptions . Tags , "tag" , "only consider snapshots which include this `taglist` in the format `tag[,tag,...]` (can be specified multiple times)" )
2018-10-03 14:05:53 +02:00
2017-07-07 03:19:06 +02:00
f . StringArrayVar ( & forgetOptions . Paths , "path" , nil , "only consider snapshots which include this (absolute) `path` (can be specified multiple times)" )
2017-09-22 16:32:59 -07:00
f . BoolVarP ( & forgetOptions . Compact , "compact" , "c" , false , "use compact format" )
2016-08-20 17:43:25 +02:00
2017-09-06 20:14:18 +02:00
f . StringVarP ( & forgetOptions . GroupBy , "group-by" , "g" , "host,paths" , "string for grouping snapshots by host,paths,tags" )
2016-09-17 12:36:05 +02:00
f . BoolVarP ( & forgetOptions . DryRun , "dry-run" , "n" , false , "do not delete anything, just print what would be done" )
2017-02-21 10:58:30 +01:00
f . BoolVar ( & forgetOptions . Prune , "prune" , false , "automatically run the 'prune' command if snapshots have been removed" )
2017-04-21 19:25:21 +02:00
f . SortFlags = false
2016-08-20 17:43:25 +02:00
}
2016-09-17 12:36:05 +02:00
func runForget ( opts ForgetOptions , gopts GlobalOptions , args [ ] string ) error {
repo , err := OpenRepository ( gopts )
2016-08-20 17:43:25 +02:00
if err != nil {
return err
}
lock , err := lockRepoExclusive ( repo )
defer unlockRepo ( lock )
if err != nil {
return err
}
2017-10-03 11:56:13 +02:00
removeSnapshots := 0
2017-05-04 16:35:35 +02:00
ctx , cancel := context . WithCancel ( gopts . ctx )
2017-03-08 20:24:58 +01:00
defer cancel ( )
2019-01-04 19:24:03 +01:00
var snapshots restic . Snapshots
2017-07-09 12:45:49 +02:00
for sn := range FindFilteredSnapshots ( ctx , repo , opts . Host , opts . Tags , opts . Paths , args ) {
2019-01-04 19:24:03 +01:00
snapshots = append ( snapshots , sn )
}
if len ( args ) > 0 {
// When explicit snapshots args are given, remove them immediately.
for _ , sn := range snapshots {
2017-03-07 14:19:36 +01:00
if ! opts . DryRun {
2017-03-08 20:24:58 +01:00
h := restic . Handle { Type : restic . SnapshotFile , Name : sn . ID ( ) . String ( ) }
use global context for check, debug, dump, find, forget, init, key,
list, mount, tag, unlock commands
gh-1434
2017-12-06 07:02:55 -05:00
if err = repo . Backend ( ) . Remove ( gopts . ctx , h ) ; err != nil {
2017-03-07 14:19:36 +01:00
return err
}
2019-01-04 19:24:03 +01:00
if ! gopts . JSON {
Verbosef ( "removed snapshot %v\n" , sn . ID ( ) . Str ( ) )
}
2017-10-03 11:56:13 +02:00
removeSnapshots ++
2017-03-07 14:19:36 +01:00
} else {
2019-01-04 19:24:03 +01:00
if ! gopts . JSON {
Verbosef ( "would have removed snapshot %v\n" , sn . ID ( ) . Str ( ) )
}
2017-09-06 20:14:18 +02:00
}
2019-01-04 19:24:03 +01:00
}
} else {
snapshotGroups , _ , err := restic . GroupSnapshots ( snapshots , opts . GroupBy )
if err != nil {
return err
}
2017-09-06 20:14:18 +02:00
2019-01-04 19:24:03 +01:00
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 ,
}
2017-09-06 20:14:18 +02:00
2019-01-04 19:24:03 +01:00
if policy . Empty ( ) && len ( args ) == 0 {
if ! gopts . JSON {
Verbosef ( "no policy was specified, no snapshots will be removed\n" )
2017-03-07 14:19:36 +01:00
}
2016-08-20 17:43:25 +02:00
}
2019-01-04 19:24:03 +01:00
if ! policy . Empty ( ) {
if ! gopts . JSON {
Verbosef ( "Applying Policy: %v\n" , policy )
}
2016-08-20 17:43:25 +02:00
2019-01-04 19:24:03 +01:00
var jsonGroups [ ] * ForgetGroup
2019-02-22 21:25:41 -07:00
2019-01-04 19:24:03 +01: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 10:24:26 +02:00
2019-01-04 19:24:03 +01:00
var key restic . SnapshotGroupKey
if json . Unmarshal ( [ ] byte ( k ) , & key ) != nil {
return err
}
2017-09-06 20:14:18 +02:00
2019-01-04 19:24:03 +01:00
var fg ForgetGroup
2019-02-22 21:25:41 -07:00
fg . Tags = key . Tags
fg . Host = key . Hostname
fg . Paths = key . Paths
2017-09-06 20:14:18 +02:00
2019-01-04 19:24:03 +01:00
keep , remove , reasons := restic . ApplyPolicy ( snapshotGroup , policy )
2016-08-20 17:43:25 +02:00
2019-01-04 19:24:03 +01: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 17:43:25 +02:00
2019-01-04 19:24:03 +01: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 )
2019-02-22 21:25:41 -07:00
2019-01-04 19:24:03 +01:00
fg . Reasons = reasons
2019-02-23 09:38:33 -07:00
2019-01-04 19:24:03 +01:00
jsonGroups = append ( jsonGroups , & fg )
2016-08-20 17:43:25 +02:00
2019-01-04 19:24:03 +01:00
removeSnapshots += len ( remove )
2017-02-21 10:58:30 +01:00
2019-01-04 19:24:03 +01:00
if ! opts . DryRun {
for _ , sn := range remove {
h := restic . Handle { Type : restic . SnapshotFile , Name : sn . ID ( ) . String ( ) }
err = repo . Backend ( ) . Remove ( gopts . ctx , h )
if err != nil {
return err
}
2018-01-01 21:27:40 +01:00
}
2016-08-20 17:43:25 +02:00
}
}
2019-02-22 21:25:41 -07:00
2019-01-04 19:24:03 +01:00
if gopts . JSON {
err = printJSONForget ( gopts . stdout , jsonGroups )
if err != nil {
return err
}
2019-02-22 21:25:41 -07:00
}
}
2016-08-20 17:43:25 +02:00
}
2017-02-21 10:58:30 +01:00
if removeSnapshots > 0 && opts . Prune {
2019-01-04 19:24:03 +01:00
if ! gopts . JSON {
Verbosef ( "%d snapshots have been removed, running prune\n" , removeSnapshots )
}
2017-02-21 10:58:30 +01:00
if ! opts . DryRun {
return pruneRepository ( gopts , repo )
}
}
2016-08-20 17:43:25 +02:00
return nil
}
2019-02-22 21:25:41 -07:00
// ForgetGroup helps to print what is forgotten in JSON.
type ForgetGroup struct {
2019-02-23 09:38:33 -07:00
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" `
2019-02-22 21:25:41 -07:00
}
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 )
}