mirror of
https://github.com/zrepl/zrepl.git
synced 2025-06-20 09:47:50 +02:00
parent
428a60870a
commit
b1f8cdf385
@ -324,6 +324,7 @@ type PruneKeepNotReplicated struct {
|
|||||||
type PruneKeepLastN struct {
|
type PruneKeepLastN struct {
|
||||||
Type string `yaml:"type"`
|
Type string `yaml:"type"`
|
||||||
Count int `yaml:"count"`
|
Count int `yaml:"count"`
|
||||||
|
Regex string `yaml:"regex,optional"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type PruneKeepRegex struct { // FIXME rename to KeepRegex
|
type PruneKeepRegex struct { // FIXME rename to KeepRegex
|
||||||
|
@ -175,9 +175,11 @@ Policy ``last_n``
|
|||||||
keep_receiver:
|
keep_receiver:
|
||||||
- type: last_n
|
- type: last_n
|
||||||
count: 10
|
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:
|
.. _prune-keep-regex:
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package pruning
|
package pruning
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@ -8,14 +9,27 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type KeepLastN struct {
|
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 {
|
if n <= 0 {
|
||||||
return nil, errors.Errorf("must specify positive number as 'keep last count', got %d", n)
|
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) {
|
func (k KeepLastN) KeepRule(snaps []Snapshot) (destroyList []Snapshot) {
|
||||||
@ -24,17 +38,25 @@ func (k KeepLastN) KeepRule(snaps []Snapshot) (destroyList []Snapshot) {
|
|||||||
return []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)
|
// by date (youngest first)
|
||||||
id, jd := res[i].Date(), res[j].Date()
|
id, jd := matching[i].Date(), matching[j].Date()
|
||||||
if !id.Equal(jd) {
|
if !id.Equal(jd) {
|
||||||
return id.After(jd)
|
return id.After(jd)
|
||||||
}
|
}
|
||||||
// then lexicographically descending (e.g. b, a)
|
// 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
|
||||||
})
|
})
|
||||||
|
destroyList = append(destroyList, matching[k.n:]...)
|
||||||
return res[k.n:]
|
return destroyList
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestKeepLastN(t *testing.T) {
|
func TestKeepLastN(t *testing.T) {
|
||||||
@ -28,7 +29,7 @@ func TestKeepLastN(t *testing.T) {
|
|||||||
"keep2": {
|
"keep2": {
|
||||||
inputs: inputs["s1"],
|
inputs: inputs["s1"],
|
||||||
rules: []KeepRule{
|
rules: []KeepRule{
|
||||||
KeepLastN{2},
|
MustKeepLastN(2, ""),
|
||||||
},
|
},
|
||||||
expDestroy: map[string]bool{
|
expDestroy: map[string]bool{
|
||||||
"1": true, "2": true, "3": true,
|
"1": true, "2": true, "3": true,
|
||||||
@ -37,34 +38,66 @@ func TestKeepLastN(t *testing.T) {
|
|||||||
"keep1OfTwoWithSameTime": { // Keep one of two with same time
|
"keep1OfTwoWithSameTime": { // Keep one of two with same time
|
||||||
inputs: inputs["s1"],
|
inputs: inputs["s1"],
|
||||||
rules: []KeepRule{
|
rules: []KeepRule{
|
||||||
KeepLastN{1},
|
MustKeepLastN(1, ""),
|
||||||
},
|
},
|
||||||
expDestroy: map[string]bool{"1": true, "2": true, "3": true, "4": true},
|
expDestroy: map[string]bool{"1": true, "2": true, "3": true, "4": true},
|
||||||
},
|
},
|
||||||
"keepMany": {
|
"keepMany": {
|
||||||
inputs: inputs["s1"],
|
inputs: inputs["s1"],
|
||||||
rules: []KeepRule{
|
rules: []KeepRule{
|
||||||
KeepLastN{100},
|
MustKeepLastN(100, ""),
|
||||||
},
|
},
|
||||||
expDestroy: map[string]bool{},
|
expDestroy: map[string]bool{},
|
||||||
},
|
},
|
||||||
"empty": {
|
"empty_input": {
|
||||||
inputs: inputs["s2"],
|
inputs: inputs["s2"],
|
||||||
rules: []KeepRule{
|
rules: []KeepRule{
|
||||||
KeepLastN{100},
|
MustKeepLastN(100, ""),
|
||||||
},
|
},
|
||||||
expDestroy: map[string]bool{},
|
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)
|
testTable(tcs, t)
|
||||||
|
|
||||||
t.Run("mustBePositive", func(t *testing.T) {
|
t.Run("mustBePositive", func(t *testing.T) {
|
||||||
var err error
|
var err error
|
||||||
_, err = NewKeepLastN(0)
|
_, err = NewKeepLastN(0, "foo")
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
_, err = NewKeepLastN(-5)
|
_, err = NewKeepLastN(-5, "foo")
|
||||||
assert.Error(t, err)
|
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:
|
case *config.PruneKeepNotReplicated:
|
||||||
return NewKeepNotReplicated(), nil
|
return NewKeepNotReplicated(), nil
|
||||||
case *config.PruneKeepLastN:
|
case *config.PruneKeepLastN:
|
||||||
return NewKeepLastN(v.Count)
|
return NewKeepLastN(v.Count, v.Regex)
|
||||||
case *config.PruneKeepRegex:
|
case *config.PruneKeepRegex:
|
||||||
return NewKeepRegex(v.Regex, v.Negate)
|
return NewKeepRegex(v.Regex, v.Negate)
|
||||||
case *config.PruneGrid:
|
case *config.PruneGrid:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user