2
2
mirror of https://github.com/octoleo/restic.git synced 2024-12-22 10:58:55 +00:00

Merge pull request 2090 from plumbeo/within-hours

Increase the granularity of the "keep within" snapshot retention policy
This commit is contained in:
Alexander Neumann 2019-01-06 15:04:55 +01:00
commit 1f246c5309
10 changed files with 1697 additions and 8 deletions

View File

@ -0,0 +1,9 @@
Enhancement: increase granularity of the "keep within" retention policy
The `keep-within` option of the `forget` command now accepts time ranges with
an hourly granularity. For example, running `restic forget --keep-within 3d12h`
will keep all the snapshots made within three days and twelve hours from the
time of the latest snapshot.
https://github.com/restic/restic/issues/2089
https://github.com/restic/restic/pull/2090

View File

@ -59,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.VarP(&forgetOptions.Within, "keep-within", "", "keep snapshots that are older than `duration` (eg. 1y5m7d) relative to the latest snapshot") f.VarP(&forgetOptions.Within, "keep-within", "", "keep snapshots that are newer than `duration` (eg. 1y5m7d2h) relative to the latest snapshot")
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)")
f.StringVar(&forgetOptions.Host, "host", "", "only consider snapshots with the given `host`") f.StringVar(&forgetOptions.Host, "host", "", "only consider snapshots with the given `host`")

View File

@ -168,8 +168,9 @@ The ``forget`` command accepts the following parameters:
this option (can be specified multiple times). this option (can be specified multiple times).
- ``--keep-within duration`` keep all snapshots which have been made within - ``--keep-within duration`` keep all snapshots which have been made within
the duration of the latest snapshot. ``duration`` needs to be a number of the duration of the latest snapshot. ``duration`` needs to be a number of
years, months, and days, e.g. ``2y5m7d`` will keep all snapshots made in the years, months, days, and hours, e.g. ``2y5m7d3h`` will keep all snapshots
two years, five months, and seven days before the latest snapshot. made in the two years, five months, seven days, and three hours before the
latest snapshot.
Multiple policies will be ORed together so as to be as inclusive as possible Multiple policies will be ORed together so as to be as inclusive as possible
for keeping snapshots. for keeping snapshots.

View File

@ -10,9 +10,9 @@ import (
) )
// Duration is similar to time.Duration, except it only supports larger ranges // Duration is similar to time.Duration, except it only supports larger ranges
// like days, months, and years. // like hours, days, months, and years.
type Duration struct { type Duration struct {
Days, Months, Years int Hours, Days, Months, Years int
} }
func (d Duration) String() string { func (d Duration) String() string {
@ -29,6 +29,10 @@ func (d Duration) String() string {
s += fmt.Sprintf("%dd", d.Days) s += fmt.Sprintf("%dd", d.Days)
} }
if d.Hours != 0 {
s += fmt.Sprintf("%dh", d.Hours)
}
return s return s
} }
@ -73,7 +77,7 @@ func nextNumber(input string) (num int, rest string, err error) {
} }
// ParseDuration parses a duration from a string. The format is: // ParseDuration parses a duration from a string. The format is:
// 6y5m234d // 6y5m234d37h
func ParseDuration(s string) (Duration, error) { func ParseDuration(s string) (Duration, error) {
var ( var (
d Duration d Duration
@ -100,6 +104,8 @@ func ParseDuration(s string) (Duration, error) {
d.Months = num d.Months = num
case 'd': case 'd':
d.Days = num d.Days = num
case 'h':
d.Hours = num
} }
s = s[1:] s = s[1:]
@ -127,5 +133,5 @@ func (d Duration) Type() string {
// Zero returns true if the duration is empty (all values are set to zero). // Zero returns true if the duration is empty (all values are set to zero).
func (d Duration) Zero() bool { func (d Duration) Zero() bool {
return d.Years == 0 && d.Months == 0 && d.Days == 0 return d.Years == 0 && d.Months == 0 && d.Days == 0 && d.Hours == 0
} }

View File

@ -13,15 +13,24 @@ func TestNextNumber(t *testing.T) {
rest string rest string
err bool err bool
}{ }{
{
input: "12h", num: 12, rest: "h",
},
{ {
input: "3d", num: 3, rest: "d", input: "3d", num: 3, rest: "d",
}, },
{
input: "4d9h", num: 4, rest: "d9h",
},
{ {
input: "7m5d", num: 7, rest: "m5d", input: "7m5d", num: 7, rest: "m5d",
}, },
{ {
input: "-23y7m5d", num: -23, rest: "y7m5d", input: "-23y7m5d", num: -23, rest: "y7m5d",
}, },
{
input: "-13y5m11d12h", num: -13, rest: "y5m11d12h",
},
{ {
input: " 5d", num: 0, rest: " 5d", err: true, input: " 5d", num: 0, rest: " 5d", err: true,
}, },
@ -55,10 +64,15 @@ func TestParseDuration(t *testing.T) {
d Duration d Duration
output string output string
}{ }{
{"9h", Duration{Hours: 9}, "9h"},
{"3d", Duration{Days: 3}, "3d"}, {"3d", Duration{Days: 3}, "3d"},
{"4d2h", Duration{Days: 4, Hours: 2}, "4d2h"},
{"7m5d", Duration{Months: 7, Days: 5}, "7m5d"}, {"7m5d", Duration{Months: 7, Days: 5}, "7m5d"},
{"6m4d8h", Duration{Months: 6, Days: 4, Hours: 8}, "6m4d8h"},
{"5d7m", Duration{Months: 7, Days: 5}, "7m5d"}, {"5d7m", Duration{Months: 7, Days: 5}, "7m5d"},
{"4h3d9m", Duration{Months: 9, Days: 3, Hours: 4}, "9m3d4h"},
{"-7m5d", Duration{Months: -7, Days: 5}, "-7m5d"}, {"-7m5d", Duration{Months: -7, Days: 5}, "-7m5d"},
{"1y4m-5d-3h", Duration{Years: 1, Months: 4, Days: -5, Hours: -3}, "1y4m-5d-3h"},
{"2y7m-5d", Duration{Years: 2, Months: 7, Days: -5}, "2y7m-5d"}, {"2y7m-5d", Duration{Years: 2, Months: 7, Days: -5}, "2y7m-5d"},
} }

View File

@ -196,7 +196,7 @@ func ApplyPolicy(list Snapshots, p ExpirePolicy) (keep, remove Snapshots, reason
// If the timestamp of the snapshot is within the range, then keep it. // If the timestamp of the snapshot is within the range, then keep it.
if !p.Within.Zero() { if !p.Within.Zero() {
t := latest.AddDate(-p.Within.Years, -p.Within.Months, -p.Within.Days) t := latest.AddDate(-p.Within.Years, -p.Within.Months, -p.Within.Days).Add(time.Hour * time.Duration(-p.Within.Hours))
if cur.Time.After(t) { if cur.Time.After(t) {
keepSnap = true keepSnap = true
keepSnapReasons = append(keepSnapReasons, fmt.Sprintf("within %v", p.Within)) keepSnapReasons = append(keepSnapReasons, fmt.Sprintf("within %v", p.Within))

View File

@ -225,6 +225,9 @@ func TestApplyPolicy(t *testing.T) {
{Within: parseDuration("1m")}, {Within: parseDuration("1m")},
{Within: parseDuration("1m14d")}, {Within: parseDuration("1m14d")},
{Within: parseDuration("1y1d1m")}, {Within: parseDuration("1y1d1m")},
{Within: parseDuration("13d23h")},
{Within: parseDuration("2m2h")},
{Within: parseDuration("1y2m3d3h")},
} }
for i, p := range tests { for i, p := range tests {

View File

@ -0,0 +1,150 @@
{
"keep": [
{
"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
}
],
"reasons": [
{
"snapshot": {
"time": "2016-01-18T12:02:03Z",
"tree": null,
"paths": null
},
"matches": [
"within 13d23h"
],
"counters": {}
},
{
"snapshot": {
"time": "2016-01-12T21:08:03Z",
"tree": null,
"paths": null
},
"matches": [
"within 13d23h"
],
"counters": {}
},
{
"snapshot": {
"time": "2016-01-12T21:02:03Z",
"tree": null,
"paths": null
},
"matches": [
"within 13d23h"
],
"counters": {}
},
{
"snapshot": {
"time": "2016-01-09T21:02:03Z",
"tree": null,
"paths": null
},
"matches": [
"within 13d23h"
],
"counters": {}
},
{
"snapshot": {
"time": "2016-01-08T20:02:03Z",
"tree": null,
"paths": null
},
"matches": [
"within 13d23h"
],
"counters": {}
},
{
"snapshot": {
"time": "2016-01-07T10:02:03Z",
"tree": null,
"paths": null
},
"matches": [
"within 13d23h"
],
"counters": {}
},
{
"snapshot": {
"time": "2016-01-06T08:02:03Z",
"tree": null,
"paths": null
},
"matches": [
"within 13d23h"
],
"counters": {}
},
{
"snapshot": {
"time": "2016-01-05T09:02:03Z",
"tree": null,
"paths": null
},
"matches": [
"within 13d23h"
],
"counters": {}
},
{
"snapshot": {
"time": "2016-01-04T16:23:03Z",
"tree": null,
"paths": null
},
"matches": [
"within 13d23h"
],
"counters": {}
}
]
}

View File

@ -0,0 +1,374 @@
{
"keep": [
{
"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
},
{
"time": "2015-11-22T10:20:30Z",
"tree": null,
"paths": null
},
{
"time": "2015-11-21T10:20:30Z",
"tree": null,
"paths": null
},
{
"time": "2015-11-20T10:20:30Z",
"tree": null,
"paths": null
},
{
"time": "2015-11-18T10:20:30Z",
"tree": null,
"paths": null
}
],
"reasons": [
{
"snapshot": {
"time": "2016-01-18T12:02:03Z",
"tree": null,
"paths": null
},
"matches": [
"within 2m2h"
],
"counters": {}
},
{
"snapshot": {
"time": "2016-01-12T21:08:03Z",
"tree": null,
"paths": null
},
"matches": [
"within 2m2h"
],
"counters": {}
},
{
"snapshot": {
"time": "2016-01-12T21:02:03Z",
"tree": null,
"paths": null
},
"matches": [
"within 2m2h"
],
"counters": {}
},
{
"snapshot": {
"time": "2016-01-09T21:02:03Z",
"tree": null,
"paths": null
},
"matches": [
"within 2m2h"
],
"counters": {}
},
{
"snapshot": {
"time": "2016-01-08T20:02:03Z",
"tree": null,
"paths": null
},
"matches": [
"within 2m2h"
],
"counters": {}
},
{
"snapshot": {
"time": "2016-01-07T10:02:03Z",
"tree": null,
"paths": null
},
"matches": [
"within 2m2h"
],
"counters": {}
},
{
"snapshot": {
"time": "2016-01-06T08:02:03Z",
"tree": null,
"paths": null
},
"matches": [
"within 2m2h"
],
"counters": {}
},
{
"snapshot": {
"time": "2016-01-05T09:02:03Z",
"tree": null,
"paths": null
},
"matches": [
"within 2m2h"
],
"counters": {}
},
{
"snapshot": {
"time": "2016-01-04T16:23:03Z",
"tree": null,
"paths": null
},
"matches": [
"within 2m2h"
],
"counters": {}
},
{
"snapshot": {
"time": "2016-01-04T12:30:03Z",
"tree": null,
"paths": null
},
"matches": [
"within 2m2h"
],
"counters": {}
},
{
"snapshot": {
"time": "2016-01-04T12:28:03Z",
"tree": null,
"paths": null
},
"matches": [
"within 2m2h"
],
"counters": {}
},
{
"snapshot": {
"time": "2016-01-04T12:24:03Z",
"tree": null,
"paths": null
},
"matches": [
"within 2m2h"
],
"counters": {}
},
{
"snapshot": {
"time": "2016-01-04T12:23:03Z",
"tree": null,
"paths": null
},
"matches": [
"within 2m2h"
],
"counters": {}
},
{
"snapshot": {
"time": "2016-01-04T11:23:03Z",
"tree": null,
"paths": null
},
"matches": [
"within 2m2h"
],
"counters": {}
},
{
"snapshot": {
"time": "2016-01-04T10:23:03Z",
"tree": null,
"paths": null
},
"matches": [
"within 2m2h"
],
"counters": {}
},
{
"snapshot": {
"time": "2016-01-03T07:02:03Z",
"tree": null,
"paths": null
},
"matches": [
"within 2m2h"
],
"counters": {}
},
{
"snapshot": {
"time": "2016-01-01T07:08:03Z",
"tree": null,
"paths": null
},
"matches": [
"within 2m2h"
],
"counters": {}
},
{
"snapshot": {
"time": "2016-01-01T01:03:03Z",
"tree": null,
"paths": null
},
"matches": [
"within 2m2h"
],
"counters": {}
},
{
"snapshot": {
"time": "2016-01-01T01:02:03Z",
"tree": null,
"paths": null
},
"matches": [
"within 2m2h"
],
"counters": {}
},
{
"snapshot": {
"time": "2015-11-22T10:20:30Z",
"tree": null,
"paths": null
},
"matches": [
"within 2m2h"
],
"counters": {}
},
{
"snapshot": {
"time": "2015-11-21T10:20:30Z",
"tree": null,
"paths": null
},
"matches": [
"within 2m2h"
],
"counters": {}
},
{
"snapshot": {
"time": "2015-11-20T10:20:30Z",
"tree": null,
"paths": null
},
"matches": [
"within 2m2h"
],
"counters": {}
},
{
"snapshot": {
"time": "2015-11-18T10:20:30Z",
"tree": null,
"paths": null
},
"matches": [
"within 2m2h"
],
"counters": {}
}
]
}

File diff suppressed because it is too large Load Diff