2016-08-20 15:43:25 +00:00
package main
import (
2021-10-31 22:08:13 +00:00
"context"
2017-03-07 13:19:36 +00:00
"encoding/json"
2019-02-23 04:25:41 +00:00
"io"
2023-07-28 16:59:57 +00:00
"strconv"
2016-09-17 10:36:05 +00:00
2022-01-08 11:58:06 +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 : `
2022-04-15 23:13:13 +00:00
The "forget" command removes snapshots according to a policy . All snapshots are
first divided into groups according to "--group-by" , and after that the policy
specified by the "--keep-*" options is applied to each group individually .
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 the
unreferenced data after "forget" was run successfully , see the "prune" command .
Please also read the documentation for "forget" to learn about some important
security considerations .
2019-11-05 06:03:38 +00:00
EXIT STATUS
== == == == == =
Exit status is 0 if the command was successful , and non - zero if there was any error .
` ,
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 {
2022-10-02 21:24:37 +00:00
return runForget ( cmd . Context ( ) , forgetOptions , globalOptions , args )
2016-09-17 10:36:05 +00:00
} ,
}
2016-08-20 15:43:25 +00:00
2023-07-28 16:59:57 +00:00
type ForgetPolicyCount int
var ErrNegativePolicyCount = errors . New ( "negative values not allowed, use 'unlimited' instead" )
func ( c * ForgetPolicyCount ) Set ( s string ) error {
switch s {
case "unlimited" :
* c = - 1
default :
val , err := strconv . ParseInt ( s , 10 , 0 )
if err != nil {
return err
}
if val < 0 {
return ErrNegativePolicyCount
}
* c = ForgetPolicyCount ( val )
}
return nil
}
func ( c * ForgetPolicyCount ) String ( ) string {
switch * c {
case - 1 :
return "unlimited"
default :
return strconv . FormatInt ( int64 ( * c ) , 10 )
}
}
func ( c * ForgetPolicyCount ) Type ( ) string {
return "n"
}
2016-09-17 10:36:05 +00:00
// ForgetOptions collects all options for the forget command.
type ForgetOptions struct {
2023-07-28 16:59:57 +00:00
Last ForgetPolicyCount
Hourly ForgetPolicyCount
Daily ForgetPolicyCount
Weekly ForgetPolicyCount
Monthly ForgetPolicyCount
Yearly ForgetPolicyCount
2021-07-24 12:29:16 +00:00
Within restic . Duration
WithinHourly restic . Duration
WithinDaily restic . Duration
WithinWeekly restic . Duration
WithinMonthly restic . Duration
WithinYearly restic . Duration
KeepTags restic . TagLists
2016-08-20 15:59:47 +00:00
2023-02-17 15:13:46 +00:00
restic . SnapshotFilter
2017-09-22 23:32:59 +00:00
Compact bool
2016-08-20 15:43:25 +00:00
2017-09-06 18:14:18 +00:00
// Grouping
2022-12-10 14:29:20 +00:00
GroupBy restic . SnapshotGroupByOptions
2017-09-09 16:19:19 +00:00
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 ( )
2023-07-28 16:59:57 +00:00
f . VarP ( & forgetOptions . Last , "keep-last" , "l" , "keep the last `n` snapshots (use 'unlimited' to keep all snapshots)" )
f . VarP ( & forgetOptions . Hourly , "keep-hourly" , "H" , "keep the last `n` hourly snapshots (use 'unlimited' to keep all hourly snapshots)" )
f . VarP ( & forgetOptions . Daily , "keep-daily" , "d" , "keep the last `n` daily snapshots (use 'unlimited' to keep all daily snapshots)" )
f . VarP ( & forgetOptions . Weekly , "keep-weekly" , "w" , "keep the last `n` weekly snapshots (use 'unlimited' to keep all weekly snapshots)" )
f . VarP ( & forgetOptions . Monthly , "keep-monthly" , "m" , "keep the last `n` monthly snapshots (use 'unlimited' to keep all monthly snapshots)" )
f . VarP ( & forgetOptions . Yearly , "keep-yearly" , "y" , "keep the last `n` yearly snapshots (use 'unlimited' to keep all yearly snapshots)" )
2018-11-14 16:24:59 +00:00
f . VarP ( & forgetOptions . Within , "keep-within" , "" , "keep snapshots that are newer than `duration` (eg. 1y5m7d2h) relative to the latest snapshot" )
2021-07-24 12:29:16 +00:00
f . VarP ( & forgetOptions . WithinHourly , "keep-within-hourly" , "" , "keep hourly snapshots that are newer than `duration` (eg. 1y5m7d2h) relative to the latest snapshot" )
f . VarP ( & forgetOptions . WithinDaily , "keep-within-daily" , "" , "keep daily snapshots that are newer than `duration` (eg. 1y5m7d2h) relative to the latest snapshot" )
f . VarP ( & forgetOptions . WithinWeekly , "keep-within-weekly" , "" , "keep weekly snapshots that are newer than `duration` (eg. 1y5m7d2h) relative to the latest snapshot" )
f . VarP ( & forgetOptions . WithinMonthly , "keep-within-monthly" , "" , "keep monthly snapshots that are newer than `duration` (eg. 1y5m7d2h) relative to the latest snapshot" )
f . VarP ( & forgetOptions . WithinYearly , "keep-within-yearly" , "" , "keep yearly snapshots that are newer than `duration` (eg. 1y5m7d2h) relative to the latest snapshot" )
2017-07-09 10:45:49 +00:00
f . Var ( & forgetOptions . KeepTags , "keep-tag" , "keep snapshots with this `taglist` (can be specified multiple times)" )
2022-09-02 22:19:19 +00:00
2023-02-17 15:13:46 +00:00
initMultiSnapshotFilter ( f , & forgetOptions . SnapshotFilter , false )
2020-02-26 21:17:59 +00:00
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 )
}
2018-10-03 12:05:53 +00:00
2020-10-02 13:55:56 +00:00
f . BoolVarP ( & forgetOptions . Compact , "compact" , "c" , false , "use compact output format" )
2022-12-10 14:29:20 +00:00
forgetOptions . GroupBy = restic . SnapshotGroupByOptions { Host : true , Path : true }
f . VarP ( & forgetOptions . GroupBy , "group-by" , "g" , "`group` snapshots by host, paths and/or tags, separated by comma (disable grouping with '')" )
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
}
2023-03-04 03:10:11 +00:00
func verifyForgetOptions ( opts * ForgetOptions ) error {
if opts . Last < - 1 || opts . Hourly < - 1 || opts . Daily < - 1 || opts . Weekly < - 1 ||
opts . Monthly < - 1 || opts . Yearly < - 1 {
2023-03-15 02:20:03 +00:00
return errors . Fatal ( "negative values other than -1 are not allowed for --keep-*" )
2023-03-04 03:10:11 +00:00
}
2023-03-15 02:20:03 +00:00
for _ , d := range [ ] restic . Duration { opts . Within , opts . WithinHourly , opts . WithinDaily ,
opts . WithinMonthly , opts . WithinWeekly , opts . WithinYearly } {
if d . Hours < 0 || d . Days < 0 || d . Months < 0 || d . Years < 0 {
return errors . Fatal ( "durations containing negative values are not allowed for --keep-within*" )
}
2023-03-04 03:10:11 +00:00
}
return nil
}
2021-10-31 22:08:13 +00:00
func runForget ( ctx context . Context , opts ForgetOptions , gopts GlobalOptions , args [ ] string ) error {
2023-03-04 03:10:11 +00:00
err := verifyForgetOptions ( & opts )
if err != nil {
return err
}
err = verifyPruneOptions ( & pruneOptions )
2020-07-19 05:55:14 +00:00
if err != nil {
return err
}
2021-10-31 22:08:13 +00:00
repo , err := OpenRepository ( ctx , gopts )
2016-08-20 15:43:25 +00:00
if err != nil {
return err
}
2022-01-08 11:58:06 +00:00
if gopts . NoLock && ! opts . DryRun {
return errors . Fatal ( "--no-lock is only applicable in combination with --dry-run for forget command" )
}
if ! opts . DryRun || ! gopts . NoLock {
2021-10-31 22:19:27 +00:00
var lock * restic . Lock
2023-02-16 15:58:36 +00:00
lock , ctx , err = lockRepoExclusive ( ctx , repo , gopts . RetryLock , gopts . JSON )
2022-01-08 11:58:06 +00:00
defer unlockRepo ( lock )
if err != nil {
return err
}
2016-08-20 15:43:25 +00:00
}
2019-01-04 18:24:03 +00:00
var snapshots restic . Snapshots
2020-07-27 20:35:26 +00:00
removeSnIDs := restic . NewIDSet ( )
2019-01-04 18:24:03 +00:00
2023-02-17 15:13:46 +00:00
for sn := range FindFilteredSnapshots ( ctx , repo . Backend ( ) , repo , & opts . SnapshotFilter , args ) {
2019-01-04 18:24:03 +00:00
snapshots = append ( snapshots , sn )
}
2020-07-27 20:35:26 +00:00
var jsonGroups [ ] * ForgetGroup
2019-01-04 18:24:03 +00:00
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 ( ) )
2019-01-04 18:24:03 +00:00
}
} else {
snapshotGroups , _ , err := restic . GroupSnapshots ( snapshots , opts . GroupBy )
if err != nil {
return err
}
2017-09-06 18:14:18 +00:00
2019-01-04 18:24:03 +00:00
policy := restic . ExpirePolicy {
2023-07-28 16:59:57 +00:00
Last : int ( opts . Last ) ,
Hourly : int ( opts . Hourly ) ,
Daily : int ( opts . Daily ) ,
Weekly : int ( opts . Weekly ) ,
Monthly : int ( opts . Monthly ) ,
Yearly : int ( opts . Yearly ) ,
2021-07-24 12:29:16 +00:00
Within : opts . Within ,
WithinHourly : opts . WithinHourly ,
WithinDaily : opts . WithinDaily ,
WithinWeekly : opts . WithinWeekly ,
WithinMonthly : opts . WithinMonthly ,
WithinYearly : opts . WithinYearly ,
Tags : opts . KeepTags ,
2019-01-04 18:24:03 +00:00
}
2017-09-06 18:14:18 +00:00
2019-01-04 18:24:03 +00:00
if policy . Empty ( ) && len ( args ) == 0 {
if ! gopts . JSON {
Verbosef ( "no policy was specified, no snapshots will be removed\n" )
2017-03-07 13:19:36 +00:00
}
2016-08-20 15:43:25 +00:00
}
2019-01-04 18:24:03 +00:00
if ! policy . Empty ( ) {
if ! gopts . JSON {
Verbosef ( "Applying Policy: %v\n" , policy )
}
2016-08-20 15:43:25 +00:00
2019-01-04 18:24:03 +00:00
for k , snapshotGroup := range snapshotGroups {
if gopts . Verbose >= 1 && ! gopts . JSON {
2023-05-07 20:21:56 +00:00
err = PrintSnapshotGroupHeader ( globalOptions . stdout , k )
2019-01-04 18:24:03 +00:00
if err != nil {
return err
}
}
2018-03-30 08:24:26 +00:00
2019-01-04 18:24:03 +00:00
var key restic . SnapshotGroupKey
if json . Unmarshal ( [ ] byte ( k ) , & key ) != nil {
return err
}
2017-09-06 18:14:18 +00:00
2019-01-04 18:24:03 +00:00
var fg ForgetGroup
2019-02-23 04:25:41 +00:00
fg . Tags = key . Tags
fg . Host = key . Hostname
fg . Paths = key . Paths
2017-09-06 18:14:18 +00:00
2019-01-04 18:24:03 +00:00
keep , remove , reasons := restic . ApplyPolicy ( snapshotGroup , policy )
2016-08-20 15:43:25 +00:00
2019-01-04 18:24:03 +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
2019-01-04 18:24:03 +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 )
2019-02-23 04:25:41 +00:00
2019-01-04 18:24:03 +00:00
fg . Reasons = reasons
2019-02-23 16:38:33 +00:00
2019-01-04 18:24:03 +00:00
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
}
}
2019-02-23 04:25:41 +00:00
2020-07-27 20:35:26 +00:00
if len ( removeSnIDs ) > 0 {
if ! opts . DryRun {
2021-10-31 22:08:13 +00:00
err := DeleteFilesChecked ( ctx , gopts , repo , removeSnIDs , restic . SnapshotFile )
2020-07-27 20:35:26 +00:00
if err != nil {
return err
}
} else {
if ! gopts . JSON {
Printf ( "Would have removed the following snapshots:\n%v\n\n" , removeSnIDs )
2019-02-23 04:25:41 +00:00
}
}
2016-08-20 15:43:25 +00:00
}
2020-07-27 20:35:26 +00:00
if gopts . JSON && len ( jsonGroups ) > 0 {
2023-05-07 20:21:56 +00:00
err = printJSONForget ( globalOptions . stdout , jsonGroups )
2020-07-27 20:35:26 +00:00
if err != nil {
return err
2017-02-21 09:58:30 +00:00
}
}
2020-12-22 19:58:02 +00:00
if len ( removeSnIDs ) > 0 && opts . Prune {
2020-07-19 05:55:14 +00:00
if ! gopts . JSON {
2022-12-30 15:15:03 +00:00
if opts . DryRun {
Verbosef ( "%d snapshots would be removed, running prune dry run\n" , len ( removeSnIDs ) )
} else {
Verbosef ( "%d snapshots have been removed, running prune\n" , len ( removeSnIDs ) )
}
2020-07-19 05:55:14 +00:00
}
pruneOptions . DryRun = opts . DryRun
2021-10-31 22:08:13 +00:00
return runPruneWithRepo ( ctx , pruneOptions , gopts , repo , removeSnIDs )
2020-07-27 20:35:26 +00:00
}
2016-08-20 15:43:25 +00:00
return nil
}
2019-02-23 04:25:41 +00:00
// ForgetGroup helps to print what is forgotten in JSON.
type ForgetGroup struct {
2019-02-23 16:38:33 +00: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-23 04:25:41 +00: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 )
}