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:
parent
b52f2aa9a4
commit
5a0f0e3faa
@ -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"
|
||||||
@ -34,7 +33,7 @@ type ForgetOptions struct {
|
|||||||
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
|
||||||
@ -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,11 +165,6 @@ 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,
|
||||||
@ -178,7 +172,7 @@ func runForget(opts ForgetOptions, gopts GlobalOptions, args []string) error {
|
|||||||
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,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ type ExpirePolicy struct {
|
|||||||
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.
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -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,10 +148,13 @@ 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 {
|
||||||
|
t := latest.AddDate(0, 0, -p.Within)
|
||||||
|
if cur.Time.After(t) {
|
||||||
keepSnap = true
|
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.
|
||||||
for i, b := range buckets {
|
for i, b := range buckets {
|
||||||
|
@ -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) {
|
||||||
|
@ -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
|
|
||||||
}
|
}
|
||||||
]
|
]
|
7
internal/restic/testdata/policy_keep_snapshots_22
vendored
Normal file
7
internal/restic/testdata/policy_keep_snapshots_22
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"time": "2016-01-18T12:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
}
|
||||||
|
]
|
17
internal/restic/testdata/policy_keep_snapshots_23
vendored
Normal file
17
internal/restic/testdata/policy_keep_snapshots_23
vendored
Normal 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
|
||||||
|
}
|
||||||
|
]
|
97
internal/restic/testdata/policy_keep_snapshots_24
vendored
Normal file
97
internal/restic/testdata/policy_keep_snapshots_24
vendored
Normal 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
|
||||||
|
}
|
||||||
|
]
|
Loading…
Reference in New Issue
Block a user