2016-08-20 15:43:25 +00:00
package main
import (
2017-03-08 19:24:58 +00:00
"context"
2017-03-07 13:19:36 +00:00
"encoding/json"
"sort"
2016-08-20 15:59:10 +00:00
"strings"
2016-09-17 10:36:05 +00:00
2017-09-09 11:04:12 +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] [...]" ,
2017-09-11 16:32:44 +00:00
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 . ` ,
2017-08-06 19:02:16 +00:00
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 {
2017-03-08 19:24:58 +00:00
Last int
Hourly int
Daily int
Weekly int
Monthly int
Yearly int
2017-07-09 10:45:49 +00:00
KeepTags restic . TagLists
2016-08-20 15:59:47 +00:00
2017-09-22 23:32:59 +00:00
Host string
Tags restic . TagLists
Paths [ ] string
Compact bool
2016-08-20 15:43:25 +00:00
2017-09-06 18:14:18 +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" )
2017-07-09 10:45:49 +00:00
f . Var ( & forgetOptions . KeepTags , "keep-tag" , "keep snapshots with this `taglist` (can be specified multiple times)" )
2017-03-07 13:19:36 +00:00
// 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)" )
2017-07-07 01:19:06 +00:00
f . StringArrayVar ( & forgetOptions . Paths , "path" , nil , "only consider snapshots which include this (absolute) `path` (can be specified multiple times)" )
2017-09-22 23:32:59 +00:00
f . BoolVarP ( & forgetOptions . Compact , "compact" , "c" , false , "use compact format" )
2016-08-20 15:43:25 +00:00
2017-09-06 18:14:18 +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
}
2017-03-08 19:24:58 +00:00
// 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
2017-09-09 11:04:12 +00:00
var GroupOptionList [ ] string
2017-09-09 16:19:19 +00:00
GroupOptionList = strings . Split ( opts . GroupBy , "," )
2017-09-09 11:04:12 +00:00
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 + "'" )
2017-09-09 11:04:12 +00:00
}
}
2017-09-06 18:14:18 +00:00
2017-10-03 09:56:13 +00:00
removeSnapshots := 0
2017-05-04 14:35:35 +00:00
ctx , cancel := context . WithCancel ( gopts . ctx )
2017-03-08 19:24:58 +00:00
defer cancel ( )
2017-07-09 10:45:49 +00:00
for sn := range FindFilteredSnapshots ( ctx , repo , opts . Host , opts . Tags , opts . Paths , args ) {
2017-03-08 19:24:58 +00:00
if len ( args ) > 0 {
// When explicit snapshots args are given, remove them immediately.
2017-03-07 13:19:36 +00:00
if ! opts . DryRun {
2017-03-08 19:24:58 +00:00
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 {
2017-03-07 13:19:36 +00:00
return err
}
2017-03-08 19:24:58 +00:00
Verbosef ( "removed snapshot %v\n" , sn . ID ( ) . Str ( ) )
2017-10-03 09:56:13 +00:00
removeSnapshots ++
2017-03-07 13:19:36 +00:00
} else {
2017-03-08 19:24:58 +00:00
Verbosef ( "would have removed snapshot %v\n" , sn . ID ( ) . Str ( ) )
}
} else {
2017-10-02 16:19:22 +00:00
// Determining grouping-keys
2017-03-08 19:24:58 +00:00
var tags [ ] string
2017-09-06 18:14:18 +00:00
var hostname string
var paths [ ] string
if GroupByTag {
2017-03-08 19:24:58 +00:00
tags = sn . Tags
sort . StringSlice ( tags ) . Sort ( )
}
2017-09-06 18:14:18 +00:00
if GroupByHost {
hostname = sn . Hostname
}
if GroupByPath {
paths = sn . Paths
}
2017-03-08 19:24:58 +00:00
sort . StringSlice ( sn . Paths ) . Sort ( )
2017-08-29 16:30:13 +00:00
var k [ ] byte
var err error
2017-09-06 18:14:18 +00:00
k , err = json . Marshal ( key { Tags : tags , Hostname : hostname , Paths : paths } )
2017-03-08 19:24:58 +00:00
if err != nil {
return err
2017-03-07 13:19:36 +00:00
}
2017-03-08 19:24:58 +00:00
snapshotGroups [ string ( k ) ] = append ( snapshotGroups [ string ( k ) ] , sn )
2016-08-20 15:43:25 +00:00
}
2017-03-08 19:24:58 +00:00
}
if len ( args ) > 0 {
2017-03-07 13:19:36 +00:00
return nil
2016-08-20 15:53:03 +00:00
}
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 ,
2017-07-09 10:45:49 +00:00
Tags : opts . KeepTags ,
2016-08-20 15:53:03 +00:00
}
2016-08-20 15:43:25 +00:00
2017-03-07 13:19:36 +00:00
if policy . Empty ( ) {
Verbosef ( "no policy was specified, no snapshots will be removed\n" )
2017-03-08 19:24:58 +00:00
return nil
2016-08-20 15:43:25 +00:00
}
2017-03-07 13:19:36 +00:00
for k , snapshotGroup := range snapshotGroups {
var key key
2017-03-08 19:24:58 +00:00
if json . Unmarshal ( [ ] byte ( k ) , & key ) != nil {
return err
}
2017-09-06 18:14:18 +00:00
// Info
2017-09-09 16:19:19 +00:00
Verbosef ( "snapshots" )
2017-09-06 18:14:18 +00:00
var infoStrings [ ] string
if GroupByTag {
2017-09-09 16:19:19 +00:00
infoStrings = append ( infoStrings , "tags [" + strings . Join ( key . Tags , ", " ) + "]" )
2017-03-07 13:19:36 +00:00
}
2017-09-06 18:14:18 +00:00
if GroupByHost {
2017-09-09 16:19:19 +00:00
infoStrings = append ( infoStrings , "host [" + key . Hostname + "]" )
2017-09-06 18:14:18 +00:00
}
if GroupByPath {
2017-09-09 16:19:19 +00:00
infoStrings = append ( infoStrings , "paths [" + strings . Join ( key . Paths , ", " ) + "]" )
2017-09-06 18:14:18 +00:00
}
if infoStrings != nil {
2017-09-09 16:19:19 +00:00
Verbosef ( " for (" + strings . Join ( infoStrings , ", " ) + ")" )
2017-09-06 18:14:18 +00:00
}
2017-09-09 16:19:19 +00:00
Verbosef ( ":\n\n" )
2017-09-06 18:14:18 +00:00
2016-08-20 15:43:25 +00:00
keep , remove := restic . ApplyPolicy ( snapshotGroup , policy )
2017-03-09 19:18:17 +00:00
if len ( keep ) != 0 && ! gopts . Quiet {
2017-03-07 13:19:36 +00:00
Printf ( "keep %d snapshots:\n" , len ( keep ) )
2017-09-22 23:32:59 +00:00
PrintSnapshots ( globalOptions . stdout , keep , opts . Compact )
2017-03-07 13:19:36 +00:00
Printf ( "\n" )
}
2016-08-20 15:43:25 +00:00
2017-03-09 19:18:17 +00:00
if len ( remove ) != 0 && ! gopts . Quiet {
2017-03-07 13:19:36 +00:00
Printf ( "remove %d snapshots:\n" , len ( remove ) )
2017-09-22 23:32:59 +00:00
PrintSnapshots ( globalOptions . stdout , remove , opts . Compact )
2017-03-07 13:19:36 +00:00
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 {
2017-01-25 16:48:35 +00:00
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 {
2017-03-09 19:18:17 +00:00
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
}