mirror of
https://github.com/zrepl/zrepl.git
synced 2024-11-22 00:13:52 +01:00
parent
428a60870a
commit
b1f8cdf385
@ -324,6 +324,7 @@ type PruneKeepNotReplicated struct {
|
||||
type PruneKeepLastN struct {
|
||||
Type string `yaml:"type"`
|
||||
Count int `yaml:"count"`
|
||||
Regex string `yaml:"regex,optional"`
|
||||
}
|
||||
|
||||
type PruneKeepRegex struct { // FIXME rename to KeepRegex
|
||||
|
@ -175,9 +175,11 @@ Policy ``last_n``
|
||||
keep_receiver:
|
||||
- type: last_n
|
||||
count: 10
|
||||
regex: ^zrepl_.*$ # optional
|
||||
...
|
||||
|
||||
``last_n`` keeps the last ``count`` snapshots (last = youngest = most recent creation date).
|
||||
``last_n`` filters the snapshot list by ``regex``, then keeps the last ``count`` snapshots in that list (last = youngest = most recent creation date)
|
||||
All snapshots that don't match ``regex`` or exceed ``count`` in the filtered list are destroyed unless matched by other rules.
|
||||
|
||||
.. _prune-keep-regex:
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
package pruning
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
@ -8,14 +9,27 @@ import (
|
||||
)
|
||||
|
||||
type KeepLastN struct {
|
||||
n int
|
||||
n int
|
||||
re *regexp.Regexp
|
||||
}
|
||||
|
||||
func NewKeepLastN(n int) (*KeepLastN, error) {
|
||||
func MustKeepLastN(n int, regex string) *KeepLastN {
|
||||
k, err := NewKeepLastN(n, regex)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return k
|
||||
}
|
||||
|
||||
func NewKeepLastN(n int, regex string) (*KeepLastN, error) {
|
||||
if n <= 0 {
|
||||
return nil, errors.Errorf("must specify positive number as 'keep last count', got %d", n)
|
||||
}
|
||||
return &KeepLastN{n}, nil
|
||||
re, err := regexp.Compile(regex)
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("invalid regex %q: %s", regex, err)
|
||||
}
|
||||
return &KeepLastN{n, re}, nil
|
||||
}
|
||||
|
||||
func (k KeepLastN) KeepRule(snaps []Snapshot) (destroyList []Snapshot) {
|
||||
@ -24,17 +38,25 @@ func (k KeepLastN) KeepRule(snaps []Snapshot) (destroyList []Snapshot) {
|
||||
return []Snapshot{}
|
||||
}
|
||||
|
||||
res := shallowCopySnapList(snaps)
|
||||
matching, notMatching := partitionSnapList(snaps, func(snapshot Snapshot) bool {
|
||||
return k.re.MatchString(snapshot.Name())
|
||||
})
|
||||
// snaps that don't match the regex are not kept by this rule
|
||||
destroyList = append(destroyList, notMatching...)
|
||||
|
||||
sort.Slice(res, func(i, j int) bool {
|
||||
if len(matching) == 0 {
|
||||
return destroyList
|
||||
}
|
||||
|
||||
sort.Slice(matching, func(i, j int) bool {
|
||||
// by date (youngest first)
|
||||
id, jd := res[i].Date(), res[j].Date()
|
||||
id, jd := matching[i].Date(), matching[j].Date()
|
||||
if !id.Equal(jd) {
|
||||
return id.After(jd)
|
||||
}
|
||||
// then lexicographically descending (e.g. b, a)
|
||||
return strings.Compare(res[i].Name(), res[j].Name()) == 1
|
||||
return strings.Compare(matching[i].Name(), matching[j].Name()) == 1
|
||||
})
|
||||
|
||||
return res[k.n:]
|
||||
destroyList = append(destroyList, matching[k.n:]...)
|
||||
return destroyList
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestKeepLastN(t *testing.T) {
|
||||
@ -28,7 +29,7 @@ func TestKeepLastN(t *testing.T) {
|
||||
"keep2": {
|
||||
inputs: inputs["s1"],
|
||||
rules: []KeepRule{
|
||||
KeepLastN{2},
|
||||
MustKeepLastN(2, ""),
|
||||
},
|
||||
expDestroy: map[string]bool{
|
||||
"1": true, "2": true, "3": true,
|
||||
@ -37,34 +38,66 @@ func TestKeepLastN(t *testing.T) {
|
||||
"keep1OfTwoWithSameTime": { // Keep one of two with same time
|
||||
inputs: inputs["s1"],
|
||||
rules: []KeepRule{
|
||||
KeepLastN{1},
|
||||
MustKeepLastN(1, ""),
|
||||
},
|
||||
expDestroy: map[string]bool{"1": true, "2": true, "3": true, "4": true},
|
||||
},
|
||||
"keepMany": {
|
||||
inputs: inputs["s1"],
|
||||
rules: []KeepRule{
|
||||
KeepLastN{100},
|
||||
MustKeepLastN(100, ""),
|
||||
},
|
||||
expDestroy: map[string]bool{},
|
||||
},
|
||||
"empty": {
|
||||
"empty_input": {
|
||||
inputs: inputs["s2"],
|
||||
rules: []KeepRule{
|
||||
KeepLastN{100},
|
||||
MustKeepLastN(100, ""),
|
||||
},
|
||||
expDestroy: map[string]bool{},
|
||||
},
|
||||
"empty_regex": {
|
||||
inputs: inputs["s1"],
|
||||
rules: []KeepRule{
|
||||
MustKeepLastN(4, ""),
|
||||
},
|
||||
expDestroy: map[string]bool{
|
||||
"1": true,
|
||||
},
|
||||
},
|
||||
"multiple_regexes": {
|
||||
inputs: []Snapshot{
|
||||
stubSnap{"a1", false, o(10)},
|
||||
stubSnap{"b1", false, o(11)},
|
||||
stubSnap{"a2", false, o(20)},
|
||||
stubSnap{"b2", false, o(21)},
|
||||
stubSnap{"a3", false, o(30)},
|
||||
stubSnap{"b3", false, o(31)},
|
||||
},
|
||||
rules: []KeepRule{
|
||||
MustKeepLastN(2, "^a"),
|
||||
MustKeepLastN(2, "^b"),
|
||||
},
|
||||
expDestroy: map[string]bool{
|
||||
"a1": true,
|
||||
"b1": true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
testTable(tcs, t)
|
||||
|
||||
t.Run("mustBePositive", func(t *testing.T) {
|
||||
var err error
|
||||
_, err = NewKeepLastN(0)
|
||||
_, err = NewKeepLastN(0, "foo")
|
||||
assert.Error(t, err)
|
||||
_, err = NewKeepLastN(-5)
|
||||
_, err = NewKeepLastN(-5, "foo")
|
||||
assert.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("emptyRegexAllowed", func(t *testing.T) {
|
||||
_, err := NewKeepLastN(23, "")
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
}
|
||||
|
@ -60,7 +60,7 @@ func RuleFromConfig(in config.PruningEnum) (KeepRule, error) {
|
||||
case *config.PruneKeepNotReplicated:
|
||||
return NewKeepNotReplicated(), nil
|
||||
case *config.PruneKeepLastN:
|
||||
return NewKeepLastN(v.Count)
|
||||
return NewKeepLastN(v.Count, v.Regex)
|
||||
case *config.PruneKeepRegex:
|
||||
return NewKeepRegex(v.Regex, v.Negate)
|
||||
case *config.PruneGrid:
|
||||
|
Loading…
Reference in New Issue
Block a user