2
2
mirror of https://github.com/octoleo/restic.git synced 2024-11-22 21:05:10 +00:00

Add support for keeping a range of snapshots

This commit is contained in:
Alexander Neumann 2018-05-13 11:06:17 +02:00
parent b52f2aa9a4
commit 5a0f0e3faa
7 changed files with 185 additions and 127 deletions

View File

@ -5,7 +5,6 @@ import (
"encoding/json" "encoding/json"
"sort" "sort"
"strings" "strings"
"time"
"github.com/restic/restic/internal/errors" "github.com/restic/restic/internal/errors"
"github.com/restic/restic/internal/restic" "github.com/restic/restic/internal/restic"
@ -28,14 +27,14 @@ data after 'forget' was run successfully, see the 'prune' command. `,
// ForgetOptions collects all options for the forget command. // ForgetOptions collects all options for the forget command.
type ForgetOptions struct { type ForgetOptions struct {
Last int Last int
Hourly int Hourly int
Daily int Daily int
Weekly int Weekly int
Monthly int Monthly int
Yearly int Yearly int
NewerThan time.Duration WithinDays int
KeepTags restic.TagLists KeepTags restic.TagLists
Host string Host string
Tags restic.TagLists Tags restic.TagLists
@ -60,7 +59,7 @@ func init() {
f.IntVarP(&forgetOptions.Weekly, "keep-weekly", "w", 0, "keep the last `n` weekly 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.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.IntVarP(&forgetOptions.Yearly, "keep-yearly", "y", 0, "keep the last `n` yearly snapshots")
f.DurationVar(&forgetOptions.NewerThan, "keep-newer-than", 0, "keep snapshots that were created within this timeframe") f.IntVar(&forgetOptions.WithinDays, "keep-within", 0, "keep snapshots that were created within `days` before the newest")
f.Var(&forgetOptions.KeepTags, "keep-tag", "keep snapshots with this `taglist` (can be specified multiple times)") 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. // Sadly the commonly used shortcut `H` is already used.
@ -166,20 +165,15 @@ func runForget(opts ForgetOptions, gopts GlobalOptions, args []string) error {
} }
} }
var ageCutoff time.Time
if opts.NewerThan > 0 {
ageCutoff = time.Now().Add(-opts.NewerThan)
}
policy := restic.ExpirePolicy{ policy := restic.ExpirePolicy{
Last: opts.Last, Last: opts.Last,
Hourly: opts.Hourly, Hourly: opts.Hourly,
Daily: opts.Daily, Daily: opts.Daily,
Weekly: opts.Weekly, Weekly: opts.Weekly,
Monthly: opts.Monthly, Monthly: opts.Monthly,
Yearly: opts.Yearly, Yearly: opts.Yearly,
NewerThan: ageCutoff, Within: opts.WithinDays,
Tags: opts.KeepTags, Tags: opts.KeepTags,
} }
if policy.Empty() && len(args) == 0 { if policy.Empty() && len(args) == 0 {

View File

@ -10,14 +10,14 @@ import (
// ExpirePolicy configures which snapshots should be automatically removed. // ExpirePolicy configures which snapshots should be automatically removed.
type ExpirePolicy struct { type ExpirePolicy struct {
Last int // keep the last n snapshots Last int // keep the last n snapshots
Hourly int // keep the last n hourly snapshots Hourly int // keep the last n hourly snapshots
Daily int // keep the last n daily snapshots Daily int // keep the last n daily snapshots
Weekly int // keep the last n weekly snapshots Weekly int // keep the last n weekly snapshots
Monthly int // keep the last n monthly snapshots Monthly int // keep the last n monthly snapshots
Yearly int // keep the last n yearly snapshots Yearly int // keep the last n yearly snapshots
NewerThan time.Time // keep snapshots newer than this time Within int // keep snapshots made within this number of days since the newest snapshot
Tags []TagList // keep all snapshots that include at least one of the tag lists. Tags []TagList // keep all snapshots that include at least one of the tag lists.
} }
func (e ExpirePolicy) String() (s string) { func (e ExpirePolicy) String() (s string) {
@ -40,8 +40,8 @@ func (e ExpirePolicy) String() (s string) {
if e.Yearly > 0 { if e.Yearly > 0 {
keeps = append(keeps, fmt.Sprintf("%d yearly", e.Yearly)) keeps = append(keeps, fmt.Sprintf("%d yearly", e.Yearly))
} }
if !e.NewerThan.IsZero() { if e.Within != 0 {
keeps = append(keeps, fmt.Sprintf("snapshots newer than %s", e.NewerThan)) keeps = append(keeps, fmt.Sprintf("snapshots within %d days of the newest snapshot", e.Within))
} }
return fmt.Sprintf("keep the last %s snapshots", strings.Join(keeps, ", ")) return fmt.Sprintf("keep the last %s snapshots", strings.Join(keeps, ", "))
@ -94,6 +94,22 @@ func always(d time.Time, nr int) int {
return nr return nr
} }
// findLatestTimestamp returns the time stamp for the newest snapshot.
func findLatestTimestamp(list Snapshots) time.Time {
if len(list) == 0 {
panic("list of snapshots is empty")
}
var latest time.Time
for _, sn := range list {
if sn.Time.After(latest) {
latest = sn.Time
}
}
return latest
}
// ApplyPolicy returns the snapshots from list that are to be kept and removed // ApplyPolicy returns the snapshots from list that are to be kept and removed
// according to the policy p. list is sorted in the process. // according to the policy p. list is sorted in the process.
func ApplyPolicy(list Snapshots, p ExpirePolicy) (keep, remove Snapshots) { func ApplyPolicy(list Snapshots, p ExpirePolicy) (keep, remove Snapshots) {
@ -120,6 +136,8 @@ func ApplyPolicy(list Snapshots, p ExpirePolicy) (keep, remove Snapshots) {
{p.Yearly, y, -1}, {p.Yearly, y, -1},
} }
latest := findLatestTimestamp(list)
for nr, cur := range list { for nr, cur := range list {
var keepSnap bool var keepSnap bool
@ -130,9 +148,12 @@ func ApplyPolicy(list Snapshots, p ExpirePolicy) (keep, remove Snapshots) {
} }
} }
// If a timestamp is specified, it's a hard cutoff for older snapshots. // If the timestamp of the snapshot is within the range, then keep it.
if !p.NewerThan.IsZero() && cur.Time.After(p.NewerThan) { if p.Within != 0 {
keepSnap = true t := latest.AddDate(0, 0, -p.Within)
if cur.Time.After(t) {
keepSnap = true
}
} }
// Now update the other buckets and see if they have some counts left. // Now update the other buckets and see if they have some counts left.

View File

@ -21,6 +21,15 @@ func parseTimeUTC(s string) time.Time {
return t.UTC() return t.UTC()
} }
func parseDuration(s string) time.Duration {
d, err := time.ParseDuration(s)
if err != nil {
panic(err)
}
return d
}
func TestExpireSnapshotOps(t *testing.T) { func TestExpireSnapshotOps(t *testing.T) {
data := []struct { data := []struct {
expectEmpty bool expectEmpty bool
@ -171,7 +180,10 @@ var expireTests = []restic.ExpirePolicy{
{Tags: []restic.TagList{{"foo"}}}, {Tags: []restic.TagList{{"foo"}}},
{Tags: []restic.TagList{{"foo", "bar"}}}, {Tags: []restic.TagList{{"foo", "bar"}}},
{Tags: []restic.TagList{{"foo"}, {"bar"}}}, {Tags: []restic.TagList{{"foo"}, {"bar"}}},
{NewerThan: parseTimeUTC("2016-01-01 01:00:00")}, {Within: 1},
{Within: 2},
{Within: 7},
{Within: 30},
} }
func TestApplyPolicy(t *testing.T) { func TestApplyPolicy(t *testing.T) {

View File

@ -3,95 +3,5 @@
"time": "2016-01-18T12:02:03Z", "time": "2016-01-18T12:02:03Z",
"tree": null, "tree": null,
"paths": null "paths": null
},
{
"time": "2016-01-12T21:08:03Z",
"tree": null,
"paths": null
},
{
"time": "2016-01-12T21:02:03Z",
"tree": null,
"paths": null
},
{
"time": "2016-01-09T21:02:03Z",
"tree": null,
"paths": null
},
{
"time": "2016-01-08T20:02:03Z",
"tree": null,
"paths": null
},
{
"time": "2016-01-07T10:02:03Z",
"tree": null,
"paths": null
},
{
"time": "2016-01-06T08:02:03Z",
"tree": null,
"paths": null
},
{
"time": "2016-01-05T09:02:03Z",
"tree": null,
"paths": null
},
{
"time": "2016-01-04T16:23:03Z",
"tree": null,
"paths": null
},
{
"time": "2016-01-04T12:30:03Z",
"tree": null,
"paths": null
},
{
"time": "2016-01-04T12:28:03Z",
"tree": null,
"paths": null
},
{
"time": "2016-01-04T12:24:03Z",
"tree": null,
"paths": null
},
{
"time": "2016-01-04T12:23:03Z",
"tree": null,
"paths": null
},
{
"time": "2016-01-04T11:23:03Z",
"tree": null,
"paths": null
},
{
"time": "2016-01-04T10:23:03Z",
"tree": null,
"paths": null
},
{
"time": "2016-01-03T07:02:03Z",
"tree": null,
"paths": null
},
{
"time": "2016-01-01T07:08:03Z",
"tree": null,
"paths": null
},
{
"time": "2016-01-01T01:03:03Z",
"tree": null,
"paths": null
},
{
"time": "2016-01-01T01:02:03Z",
"tree": null,
"paths": null
} }
] ]

View File

@ -0,0 +1,7 @@
[
{
"time": "2016-01-18T12:02:03Z",
"tree": null,
"paths": null
}
]

View File

@ -0,0 +1,17 @@
[
{
"time": "2016-01-18T12:02:03Z",
"tree": null,
"paths": null
},
{
"time": "2016-01-12T21:08:03Z",
"tree": null,
"paths": null
},
{
"time": "2016-01-12T21:02:03Z",
"tree": null,
"paths": null
}
]

View File

@ -0,0 +1,97 @@
[
{
"time": "2016-01-18T12:02:03Z",
"tree": null,
"paths": null
},
{
"time": "2016-01-12T21:08:03Z",
"tree": null,
"paths": null
},
{
"time": "2016-01-12T21:02:03Z",
"tree": null,
"paths": null
},
{
"time": "2016-01-09T21:02:03Z",
"tree": null,
"paths": null
},
{
"time": "2016-01-08T20:02:03Z",
"tree": null,
"paths": null
},
{
"time": "2016-01-07T10:02:03Z",
"tree": null,
"paths": null
},
{
"time": "2016-01-06T08:02:03Z",
"tree": null,
"paths": null
},
{
"time": "2016-01-05T09:02:03Z",
"tree": null,
"paths": null
},
{
"time": "2016-01-04T16:23:03Z",
"tree": null,
"paths": null
},
{
"time": "2016-01-04T12:30:03Z",
"tree": null,
"paths": null
},
{
"time": "2016-01-04T12:28:03Z",
"tree": null,
"paths": null
},
{
"time": "2016-01-04T12:24:03Z",
"tree": null,
"paths": null
},
{
"time": "2016-01-04T12:23:03Z",
"tree": null,
"paths": null
},
{
"time": "2016-01-04T11:23:03Z",
"tree": null,
"paths": null
},
{
"time": "2016-01-04T10:23:03Z",
"tree": null,
"paths": null
},
{
"time": "2016-01-03T07:02:03Z",
"tree": null,
"paths": null
},
{
"time": "2016-01-01T07:08:03Z",
"tree": null,
"paths": null
},
{
"time": "2016-01-01T01:03:03Z",
"tree": null,
"paths": null
},
{
"time": "2016-01-01T01:02:03Z",
"tree": null,
"paths": null
}
]