pruning: add 'Negate' option to KeepRegex and expose it in config

This commit is contained in:
Christian Schwarz 2018-11-16 11:32:24 +01:00
parent 2db3977408
commit 5e1ea21f85
7 changed files with 82 additions and 15 deletions

View File

@ -250,6 +250,7 @@ type PruneKeepLastN struct {
type PruneKeepRegex struct { // FIXME rename to KeepRegex
Type string `yaml:"type"`
Regex string `yaml:"regex"`
Negate bool `yaml:"negate,optional,default=false"`
}
type LoggingOutletEnum struct {

View File

@ -179,7 +179,7 @@ func TestPruner_Prune(t *testing.T) {
},
}
keepRules := []pruning.KeepRule{pruning.MustKeepRegex("^keep")}
keepRules := []pruning.KeepRule{pruning.MustKeepRegex("^keep", false)}
p := Pruner{
args: args{

View File

@ -146,12 +146,22 @@ Policy ``regex``
- type: push
pruning:
keep_receiver:
# keep all snapshots with prefix zrepl_ or manual_
- type: regex
regex: "^(zrepl|manual)_.*"
...
- type: push
snapshotting:
prefix: zrepl_
pruning:
keep_sender:
# keep all snapshots that were not created by zrepl
- type: regex
negate: true
regex: "^zrepl_.*"
``regex`` keeps all snapshots whose names are matched by the regular expressionin ``regex``.
Like all other regular expression fields in prune policies, zrepl uses Go's `regexp.Regexp <https://golang.org/pkg/regexp/#Compile>`_ Perl-compatible regular expressions (`Syntax <https://golang.org/pkg/regexp/syntax>`_).
The optional `negate` boolean field inverts the semantics: Use it if you want to keep all snapshots that *do not* match the given regex.

View File

@ -6,20 +6,21 @@ import (
type KeepRegex struct {
expr *regexp.Regexp
negate bool
}
var _ KeepRule = &KeepRegex{}
func NewKeepRegex(expr string) (*KeepRegex, error) {
func NewKeepRegex(expr string, negate bool) (*KeepRegex, error) {
re, err := regexp.Compile(expr)
if err != nil {
return nil, err
}
return &KeepRegex{re}, nil
return &KeepRegex{re, negate}, nil
}
func MustKeepRegex(expr string) *KeepRegex {
k, err := NewKeepRegex(expr)
func MustKeepRegex(expr string, negate bool) *KeepRegex {
k, err := NewKeepRegex(expr, negate)
if err != nil {
panic(err)
}
@ -28,6 +29,10 @@ func MustKeepRegex(expr string) *KeepRegex {
func (k *KeepRegex) KeepRule(snaps []Snapshot) []Snapshot {
return filterSnapList(snaps, func(s Snapshot) bool {
if k.negate {
return k.expr.FindStringIndex(s.Name()) != nil
} else {
return k.expr.FindStringIndex(s.Name()) == nil
}
})
}

View File

@ -0,0 +1,32 @@
package pruning
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestKeepRegexNegation(t *testing.T) {
noneg := MustKeepRegex("^zrepl_", false)
neg := MustKeepRegex("^zrepl_", true)
snaps := []Snapshot{
stubSnap{name: "zrepl_foobar"},
stubSnap{name: "zrepl"},
stubSnap{name: "barfoo"},
}
destroyNonNeg := snapshotList(noneg.KeepRule(snaps))
t.Logf("non-negated rule destroys: %#v", destroyNonNeg.NameList())
assert.True(t, destroyNonNeg.ContainsName("zrepl"))
assert.True(t, destroyNonNeg.ContainsName("barfoo"))
assert.False(t, destroyNonNeg.ContainsName("zrepl_foobar"))
destroyNeg := snapshotList(neg.KeepRule(snaps))
t.Logf("negated rule destroys: %#v", destroyNeg.NameList())
assert.False(t, destroyNeg.ContainsName("zrepl"))
assert.False(t, destroyNeg.ContainsName("barfoo"))
assert.True(t, destroyNeg.ContainsName("zrepl_foobar"))
}

View File

@ -60,7 +60,7 @@ func RuleFromConfig(in config.PruningEnum) (KeepRule, error) {
case *config.PruneKeepLastN:
return NewKeepLastN(v.Count)
case *config.PruneKeepRegex:
return NewKeepRegex(v.Regex)
return NewKeepRegex(v.Regex, v.Negate)
case *config.PruneGrid:
return NewKeepGrid(v)
default:

View File

@ -24,6 +24,25 @@ type testCase struct {
expDestroyAlternatives []map[string]bool
}
type snapshotList []Snapshot
func (l snapshotList) ContainsName(n string) bool {
for _, s := range l {
if s.Name() == n {
return true
}
}
return false
}
func (l snapshotList) NameList() []string {
res := make([]string, len(l))
for i, s := range l {
res[i] = s.Name()
}
return res
}
func testTable(tcs map[string]testCase, t *testing.T) {
mapEqual := func(a, b map[string]bool) bool {
if len(a) != len(b) {
@ -79,7 +98,7 @@ func TestPruneSnapshots(t *testing.T) {
"simple": {
inputs: inputs["s1"],
rules: []KeepRule{
MustKeepRegex("foo_"),
MustKeepRegex("foo_", false),
},
expDestroy: map[string]bool{
"bar_123": true,
@ -88,16 +107,16 @@ func TestPruneSnapshots(t *testing.T) {
"multipleRules": {
inputs: inputs["s1"],
rules: []KeepRule{
MustKeepRegex("foo_"),
MustKeepRegex("bar_"),
MustKeepRegex("foo_", false),
MustKeepRegex("bar_", false),
},
expDestroy: map[string]bool{},
},
"onlyThoseRemovedByAllAreRemoved": {
inputs: inputs["s1"],
rules: []KeepRule{
MustKeepRegex("notInS1"), // would remove all
MustKeepRegex("bar_"), // would remove all but bar_, i.e. foo_.*
MustKeepRegex("notInS1", false), // would remove all
MustKeepRegex("bar_", false), // would remove all but bar_, i.e. foo_.*
},
expDestroy: map[string]bool{
"foo_123": true,
@ -117,7 +136,7 @@ func TestPruneSnapshots(t *testing.T) {
"noSnaps": {
inputs: []Snapshot{},
rules: []KeepRule{
MustKeepRegex("foo_"),
MustKeepRegex("foo_", false),
},
expDestroy: map[string]bool{},
},