diff --git a/cmd/config_mapfilter.go b/cmd/config_mapfilter.go index 2e600f6..b39cc51 100644 --- a/cmd/config_mapfilter.go +++ b/cmd/config_mapfilter.go @@ -4,8 +4,9 @@ import ( "fmt" "strings" - "github.com/zrepl/zrepl/zfs" "github.com/mitchellh/mapstructure" + "github.com/pkg/errors" + "github.com/zrepl/zrepl/zfs" ) type DatasetMapFilter struct { @@ -144,14 +145,46 @@ func (m DatasetMapFilter) Filter(p *zfs.DatasetPath) (pass bool, err error) { return } +// Construct a new filter-only DatasetMapFilter from a mapping +// The new filter allows excactly those paths that were not forbidden by the mapping. +func (m DatasetMapFilter) InvertedFilter() (inv *DatasetMapFilter, err error) { + + if m.filterOnly { + err = errors.Errorf("can only invert mappings") + return + } + + inv = &DatasetMapFilter{ + make([]datasetMapFilterEntry, len(m.entries)), + true, + } + + for i, e := range m.entries { + inv.entries[i].path, err = zfs.NewDatasetPath(e.mapping) + if err != nil { + err = errors.Wrapf(err, "mapping cannot be inverted: '%s' is not a dataset path: %s", e.mapping) + return + } + inv.entries[i].mapping = MapFilterResultOk + inv.entries[i].subtreeMatch = e.subtreeMatch + } + + return inv, nil +} + +const ( + MapFilterResultOk string = "ok" + MapFilterResultOmit string = "omit" +) + // Parse a dataset filter result func parseDatasetFilterResult(result string) (pass bool, err error) { l := strings.ToLower(result) switch strings.ToLower(l) { - case "ok": + case MapFilterResultOk: pass = true return - case "omit": + case MapFilterResultOmit: return default: err = fmt.Errorf("'%s' is not a valid filter result", result) diff --git a/cmd/config_test.go b/cmd/config_test.go index 09b3826..58865ce 100644 --- a/cmd/config_test.go +++ b/cmd/config_test.go @@ -4,6 +4,7 @@ import ( "testing" "time" + "github.com/kr/pretty" "github.com/stretchr/testify/assert" "github.com/zrepl/zrepl/util" "github.com/zrepl/zrepl/zfs" @@ -135,3 +136,36 @@ func TestDatasetMapFilter(t *testing.T) { expectFilter(filter2, "foo", false) // default to omit } + +func TestDatasetMapFilter_InvertedFilter(t *testing.T) { + mapspec := map[string]string{ + "a/b": "1/2", + "a/b/c<": "3", + "a/b/c/d<": "1/2/a", + } + + m, err := parseDatasetMapFilter(mapspec, false) + assert.Nil(t, err) + + inv, err := m.InvertedFilter() + assert.Nil(t, err) + + t.Log(pretty.Sprint(inv)) + + expectMapping := func(m *DatasetMapFilter, ps string, expRes bool) { + p, err := zfs.NewDatasetPath(ps) + assert.Nil(t, err) + r, err := m.Filter(p) + assert.Nil(t, err) + assert.Equal(t, expRes, r) + } + + expectMapping(inv, "4", false) + expectMapping(inv, "3", true) + expectMapping(inv, "3/x", true) + expectMapping(inv, "1", false) + expectMapping(inv, "1/2", true) + expectMapping(inv, "1/2/3", false) + expectMapping(inv, "1/2/a/b", true) + +}