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 PruneKeepRegex struct { // FIXME rename to KeepRegex
Type string `yaml:"type"` Type string `yaml:"type"`
Regex string `yaml:"regex"` Regex string `yaml:"regex"`
Negate bool `yaml:"negate,optional,default=false"`
} }
type LoggingOutletEnum struct { 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{ p := Pruner{
args: args{ args: args{

View File

@ -146,12 +146,22 @@ Policy ``regex``
- type: push - type: push
pruning: pruning:
keep_receiver: keep_receiver:
# keep all snapshots with prefix zrepl_ or manual_
- type: regex - type: regex
regex: "^(zrepl|manual)_.*" 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``. ``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>`_). 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 { type KeepRegex struct {
expr *regexp.Regexp expr *regexp.Regexp
negate bool
} }
var _ KeepRule = &KeepRegex{} var _ KeepRule = &KeepRegex{}
func NewKeepRegex(expr string) (*KeepRegex, error) { func NewKeepRegex(expr string, negate bool) (*KeepRegex, error) {
re, err := regexp.Compile(expr) re, err := regexp.Compile(expr)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &KeepRegex{re}, nil return &KeepRegex{re, negate}, nil
} }
func MustKeepRegex(expr string) *KeepRegex { func MustKeepRegex(expr string, negate bool) *KeepRegex {
k, err := NewKeepRegex(expr) k, err := NewKeepRegex(expr, negate)
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -28,6 +29,10 @@ func MustKeepRegex(expr string) *KeepRegex {
func (k *KeepRegex) KeepRule(snaps []Snapshot) []Snapshot { func (k *KeepRegex) KeepRule(snaps []Snapshot) []Snapshot {
return filterSnapList(snaps, func(s Snapshot) bool { 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 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: case *config.PruneKeepLastN:
return NewKeepLastN(v.Count) return NewKeepLastN(v.Count)
case *config.PruneKeepRegex: case *config.PruneKeepRegex:
return NewKeepRegex(v.Regex) return NewKeepRegex(v.Regex, v.Negate)
case *config.PruneGrid: case *config.PruneGrid:
return NewKeepGrid(v) return NewKeepGrid(v)
default: default:

View File

@ -24,6 +24,25 @@ type testCase struct {
expDestroyAlternatives []map[string]bool 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) { func testTable(tcs map[string]testCase, t *testing.T) {
mapEqual := func(a, b map[string]bool) bool { mapEqual := func(a, b map[string]bool) bool {
if len(a) != len(b) { if len(a) != len(b) {
@ -79,7 +98,7 @@ func TestPruneSnapshots(t *testing.T) {
"simple": { "simple": {
inputs: inputs["s1"], inputs: inputs["s1"],
rules: []KeepRule{ rules: []KeepRule{
MustKeepRegex("foo_"), MustKeepRegex("foo_", false),
}, },
expDestroy: map[string]bool{ expDestroy: map[string]bool{
"bar_123": true, "bar_123": true,
@ -88,16 +107,16 @@ func TestPruneSnapshots(t *testing.T) {
"multipleRules": { "multipleRules": {
inputs: inputs["s1"], inputs: inputs["s1"],
rules: []KeepRule{ rules: []KeepRule{
MustKeepRegex("foo_"), MustKeepRegex("foo_", false),
MustKeepRegex("bar_"), MustKeepRegex("bar_", false),
}, },
expDestroy: map[string]bool{}, expDestroy: map[string]bool{},
}, },
"onlyThoseRemovedByAllAreRemoved": { "onlyThoseRemovedByAllAreRemoved": {
inputs: inputs["s1"], inputs: inputs["s1"],
rules: []KeepRule{ rules: []KeepRule{
MustKeepRegex("notInS1"), // would remove all MustKeepRegex("notInS1", false), // would remove all
MustKeepRegex("bar_"), // would remove all but bar_, i.e. foo_.* MustKeepRegex("bar_", false), // would remove all but bar_, i.e. foo_.*
}, },
expDestroy: map[string]bool{ expDestroy: map[string]bool{
"foo_123": true, "foo_123": true,
@ -117,7 +136,7 @@ func TestPruneSnapshots(t *testing.T) {
"noSnaps": { "noSnaps": {
inputs: []Snapshot{}, inputs: []Snapshot{},
rules: []KeepRule{ rules: []KeepRule{
MustKeepRegex("foo_"), MustKeepRegex("foo_", false),
}, },
expDestroy: map[string]bool{}, expDestroy: map[string]bool{},
}, },