Issue-271-mustfilter: add must-pass filters to selectors (#392)
* add must-pass filters to selectors Extends the selectors scope set to include Filters. This allows users to define all-pass matchers (filters), separate from any-pass matchers (includes), and global exclusions.
This commit is contained in:
parent
75e8317bf9
commit
350d27dcb4
@ -316,7 +316,7 @@ func detailsExchangeCmd(cmd *cobra.Command, args []string) error {
|
|||||||
emailFolder,
|
emailFolder,
|
||||||
event,
|
event,
|
||||||
user)
|
user)
|
||||||
includeExchangeBackupDetailInfoSelectors(
|
filterExchangeBackupDetailInfoSelectors(
|
||||||
sel,
|
sel,
|
||||||
emailReceivedAfter,
|
emailReceivedAfter,
|
||||||
emailReceivedBefore,
|
emailReceivedBefore,
|
||||||
@ -328,7 +328,7 @@ func detailsExchangeCmd(cmd *cobra.Command, args []string) error {
|
|||||||
sel.Include(sel.Users(selectors.Any()))
|
sel.Include(sel.Users(selectors.Any()))
|
||||||
}
|
}
|
||||||
|
|
||||||
ds := sel.FilterDetails(d)
|
ds := sel.Reduce(d)
|
||||||
print.Entries(ds.Entries)
|
print.Entries(ds.Entries)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -389,43 +389,43 @@ func includeExchangeEvents(sel *selectors.ExchangeRestore, users, events []strin
|
|||||||
sel.Include(sel.Events(users, events))
|
sel.Include(sel.Events(users, events))
|
||||||
}
|
}
|
||||||
|
|
||||||
// builds the info-selector inclusions for `backup details exchange`
|
// builds the info-selector filters for `backup details exchange`
|
||||||
func includeExchangeBackupDetailInfoSelectors(
|
func filterExchangeBackupDetailInfoSelectors(
|
||||||
sel *selectors.ExchangeRestore,
|
sel *selectors.ExchangeRestore,
|
||||||
emailReceivedAfter, emailReceivedBefore, emailSender, emailSubject []string,
|
emailReceivedAfter, emailReceivedBefore, emailSender, emailSubject []string,
|
||||||
) {
|
) {
|
||||||
includeExchangeInfoMailReceivedAfter(sel, emailReceivedAfter)
|
filterExchangeInfoMailReceivedAfter(sel, emailReceivedAfter)
|
||||||
includeExchangeInfoMailReceivedBefore(sel, emailReceivedBefore)
|
filterExchangeInfoMailReceivedBefore(sel, emailReceivedBefore)
|
||||||
includeExchangeInfoMailSender(sel, emailSender)
|
filterExchangeInfoMailSender(sel, emailSender)
|
||||||
includeExchangeInfoMailSubject(sel, emailSubject)
|
filterExchangeInfoMailSubject(sel, emailSubject)
|
||||||
}
|
}
|
||||||
|
|
||||||
func includeExchangeInfoMailReceivedAfter(sel *selectors.ExchangeRestore, receivedAfter []string) {
|
func filterExchangeInfoMailReceivedAfter(sel *selectors.ExchangeRestore, receivedAfter []string) {
|
||||||
if len(receivedAfter) == 0 {
|
if len(receivedAfter) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
sel.Include(sel.MailReceivedAfter(receivedAfter))
|
sel.Filter(sel.MailReceivedAfter(receivedAfter))
|
||||||
}
|
}
|
||||||
|
|
||||||
func includeExchangeInfoMailReceivedBefore(sel *selectors.ExchangeRestore, receivedBefore []string) {
|
func filterExchangeInfoMailReceivedBefore(sel *selectors.ExchangeRestore, receivedBefore []string) {
|
||||||
if len(receivedBefore) == 0 {
|
if len(receivedBefore) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
sel.Include(sel.MailReceivedBefore(receivedBefore))
|
sel.Filter(sel.MailReceivedBefore(receivedBefore))
|
||||||
}
|
}
|
||||||
|
|
||||||
func includeExchangeInfoMailSender(sel *selectors.ExchangeRestore, sender []string) {
|
func filterExchangeInfoMailSender(sel *selectors.ExchangeRestore, sender []string) {
|
||||||
if len(sender) == 0 {
|
if len(sender) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
sel.Include(sel.MailSender(sender))
|
sel.Filter(sel.MailSender(sender))
|
||||||
}
|
}
|
||||||
|
|
||||||
func includeExchangeInfoMailSubject(sel *selectors.ExchangeRestore, subject []string) {
|
func filterExchangeInfoMailSubject(sel *selectors.ExchangeRestore, subject []string) {
|
||||||
if len(subject) == 0 {
|
if len(subject) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
sel.Include(sel.MailSubject(subject))
|
sel.Filter(sel.MailSubject(subject))
|
||||||
}
|
}
|
||||||
|
|
||||||
// checks all flags for correctness and interdependencies
|
// checks all flags for correctness and interdependencies
|
||||||
|
|||||||
@ -480,78 +480,78 @@ func (suite *ExchangeSuite) TestIncludeExchangeBackupDetailDataSelectors() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *ExchangeSuite) TestIncludeExchangeBackupDetailInfoSelectors() {
|
func (suite *ExchangeSuite) TestFilterExchangeBackupDetailInfoSelectors() {
|
||||||
stub := []string{"id-stub"}
|
stub := []string{"id-stub"}
|
||||||
twoStubs := []string{"smarfs", "fnords"}
|
twoStubs := []string{"smarfs", "fnords"}
|
||||||
any := []string{utils.Wildcard}
|
any := []string{utils.Wildcard}
|
||||||
table := []struct {
|
table := []struct {
|
||||||
name string
|
name string
|
||||||
after, before, sender, subject []string
|
after, before, sender, subject []string
|
||||||
expectIncludeLen int
|
expectFilterLen int
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "no selectors",
|
name: "no selectors",
|
||||||
expectIncludeLen: 0,
|
expectFilterLen: 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "any receivedAfter",
|
name: "any receivedAfter",
|
||||||
after: any,
|
after: any,
|
||||||
expectIncludeLen: 1,
|
expectFilterLen: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "single receivedAfter",
|
name: "single receivedAfter",
|
||||||
after: stub,
|
after: stub,
|
||||||
expectIncludeLen: 1,
|
expectFilterLen: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "multiple receivedAfter",
|
name: "multiple receivedAfter",
|
||||||
after: twoStubs,
|
after: twoStubs,
|
||||||
expectIncludeLen: 1,
|
expectFilterLen: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "any receivedBefore",
|
name: "any receivedBefore",
|
||||||
before: any,
|
before: any,
|
||||||
expectIncludeLen: 1,
|
expectFilterLen: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "single receivedBefore",
|
name: "single receivedBefore",
|
||||||
before: stub,
|
before: stub,
|
||||||
expectIncludeLen: 1,
|
expectFilterLen: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "multiple receivedBefore",
|
name: "multiple receivedBefore",
|
||||||
before: twoStubs,
|
before: twoStubs,
|
||||||
expectIncludeLen: 1,
|
expectFilterLen: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "any sender",
|
name: "any sender",
|
||||||
sender: any,
|
sender: any,
|
||||||
expectIncludeLen: 1,
|
expectFilterLen: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "single sender",
|
name: "single sender",
|
||||||
sender: stub,
|
sender: stub,
|
||||||
expectIncludeLen: 1,
|
expectFilterLen: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "multiple senders",
|
name: "multiple senders",
|
||||||
sender: twoStubs,
|
sender: twoStubs,
|
||||||
expectIncludeLen: 1,
|
expectFilterLen: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "any subject",
|
name: "any subject",
|
||||||
subject: any,
|
subject: any,
|
||||||
expectIncludeLen: 1,
|
expectFilterLen: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "single subject",
|
name: "single subject",
|
||||||
subject: stub,
|
subject: stub,
|
||||||
expectIncludeLen: 1,
|
expectFilterLen: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "multiple subjects",
|
name: "multiple subjects",
|
||||||
subject: twoStubs,
|
subject: twoStubs,
|
||||||
expectIncludeLen: 1,
|
expectFilterLen: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "one of each",
|
name: "one of each",
|
||||||
@ -559,19 +559,19 @@ func (suite *ExchangeSuite) TestIncludeExchangeBackupDetailInfoSelectors() {
|
|||||||
before: stub,
|
before: stub,
|
||||||
sender: stub,
|
sender: stub,
|
||||||
subject: stub,
|
subject: stub,
|
||||||
expectIncludeLen: 4,
|
expectFilterLen: 4,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, test := range table {
|
for _, test := range table {
|
||||||
suite.T().Run(test.name, func(t *testing.T) {
|
suite.T().Run(test.name, func(t *testing.T) {
|
||||||
sel := selectors.NewExchangeRestore()
|
sel := selectors.NewExchangeRestore()
|
||||||
includeExchangeBackupDetailInfoSelectors(
|
filterExchangeBackupDetailInfoSelectors(
|
||||||
sel,
|
sel,
|
||||||
test.after,
|
test.after,
|
||||||
test.before,
|
test.before,
|
||||||
test.sender,
|
test.sender,
|
||||||
test.subject)
|
test.subject)
|
||||||
assert.Equal(t, test.expectIncludeLen, len(sel.Includes))
|
assert.Equal(t, test.expectFilterLen, len(sel.Filters))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -138,7 +138,7 @@ func restoreExchangeCmd(cmd *cobra.Command, args []string) error {
|
|||||||
emailFolder,
|
emailFolder,
|
||||||
event,
|
event,
|
||||||
user)
|
user)
|
||||||
includeExchangeRestoreInfoSelectors(
|
filterExchangeRestoreInfoSelectors(
|
||||||
sel,
|
sel,
|
||||||
emailReceivedAfter,
|
emailReceivedAfter,
|
||||||
emailReceivedBefore,
|
emailReceivedBefore,
|
||||||
@ -218,43 +218,43 @@ func includeExchangeEvents(sel *selectors.ExchangeRestore, users, events []strin
|
|||||||
sel.Include(sel.Events(users, events))
|
sel.Include(sel.Events(users, events))
|
||||||
}
|
}
|
||||||
|
|
||||||
// builds the info-selector inclusions for `restore exchange`
|
// builds the info-selector filters for `restore exchange`
|
||||||
func includeExchangeRestoreInfoSelectors(
|
func filterExchangeRestoreInfoSelectors(
|
||||||
sel *selectors.ExchangeRestore,
|
sel *selectors.ExchangeRestore,
|
||||||
emailReceivedAfter, emailReceivedBefore, emailSender, emailSubject []string,
|
emailReceivedAfter, emailReceivedBefore, emailSender, emailSubject []string,
|
||||||
) {
|
) {
|
||||||
includeExchangeInfoMailReceivedAfter(sel, emailReceivedAfter)
|
filterExchangeInfoMailReceivedAfter(sel, emailReceivedAfter)
|
||||||
includeExchangeInfoMailReceivedBefore(sel, emailReceivedBefore)
|
filterExchangeInfoMailReceivedBefore(sel, emailReceivedBefore)
|
||||||
includeExchangeInfoMailSender(sel, emailSender)
|
filterExchangeInfoMailSender(sel, emailSender)
|
||||||
includeExchangeInfoMailSubject(sel, emailSubject)
|
filterExchangeInfoMailSubject(sel, emailSubject)
|
||||||
}
|
}
|
||||||
|
|
||||||
func includeExchangeInfoMailReceivedAfter(sel *selectors.ExchangeRestore, receivedAfter []string) {
|
func filterExchangeInfoMailReceivedAfter(sel *selectors.ExchangeRestore, receivedAfter []string) {
|
||||||
if len(receivedAfter) == 0 {
|
if len(receivedAfter) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
sel.Include(sel.MailReceivedAfter(receivedAfter))
|
sel.Filter(sel.MailReceivedAfter(receivedAfter))
|
||||||
}
|
}
|
||||||
|
|
||||||
func includeExchangeInfoMailReceivedBefore(sel *selectors.ExchangeRestore, receivedBefore []string) {
|
func filterExchangeInfoMailReceivedBefore(sel *selectors.ExchangeRestore, receivedBefore []string) {
|
||||||
if len(receivedBefore) == 0 {
|
if len(receivedBefore) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
sel.Include(sel.MailReceivedBefore(receivedBefore))
|
sel.Filter(sel.MailReceivedBefore(receivedBefore))
|
||||||
}
|
}
|
||||||
|
|
||||||
func includeExchangeInfoMailSender(sel *selectors.ExchangeRestore, sender []string) {
|
func filterExchangeInfoMailSender(sel *selectors.ExchangeRestore, sender []string) {
|
||||||
if len(sender) == 0 {
|
if len(sender) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
sel.Include(sel.MailSender(sender))
|
sel.Filter(sel.MailSender(sender))
|
||||||
}
|
}
|
||||||
|
|
||||||
func includeExchangeInfoMailSubject(sel *selectors.ExchangeRestore, subject []string) {
|
func filterExchangeInfoMailSubject(sel *selectors.ExchangeRestore, subject []string) {
|
||||||
if len(subject) == 0 {
|
if len(subject) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
sel.Include(sel.MailSubject(subject))
|
sel.Filter(sel.MailSubject(subject))
|
||||||
}
|
}
|
||||||
|
|
||||||
// checks all flags for correctness and interdependencies
|
// checks all flags for correctness and interdependencies
|
||||||
|
|||||||
@ -316,78 +316,78 @@ func (suite *ExchangeSuite) TestIncludeExchangeRestoreDataSelectors() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *ExchangeSuite) TestIncludeExchangeRestoreInfoSelectors() {
|
func (suite *ExchangeSuite) TestFilterExchangeRestoreInfoSelectors() {
|
||||||
stub := []string{"id-stub"}
|
stub := []string{"id-stub"}
|
||||||
twoStubs := []string{"a-stub", "b-stub"}
|
twoStubs := []string{"a-stub", "b-stub"}
|
||||||
any := []string{utils.Wildcard}
|
any := []string{utils.Wildcard}
|
||||||
table := []struct {
|
table := []struct {
|
||||||
name string
|
name string
|
||||||
after, before, sender, subject []string
|
after, before, sender, subject []string
|
||||||
expectIncludeLen int
|
expectFilterLen int
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "no selectors",
|
name: "no selectors",
|
||||||
expectIncludeLen: 0,
|
expectFilterLen: 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "any receivedAfter",
|
name: "any receivedAfter",
|
||||||
after: any,
|
after: any,
|
||||||
expectIncludeLen: 1,
|
expectFilterLen: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "single receivedAfter",
|
name: "single receivedAfter",
|
||||||
after: stub,
|
after: stub,
|
||||||
expectIncludeLen: 1,
|
expectFilterLen: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "multiple receivedAfter",
|
name: "multiple receivedAfter",
|
||||||
after: twoStubs,
|
after: twoStubs,
|
||||||
expectIncludeLen: 1,
|
expectFilterLen: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "any receivedBefore",
|
name: "any receivedBefore",
|
||||||
before: any,
|
before: any,
|
||||||
expectIncludeLen: 1,
|
expectFilterLen: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "single receivedBefore",
|
name: "single receivedBefore",
|
||||||
before: stub,
|
before: stub,
|
||||||
expectIncludeLen: 1,
|
expectFilterLen: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "multiple receivedBefore",
|
name: "multiple receivedBefore",
|
||||||
before: twoStubs,
|
before: twoStubs,
|
||||||
expectIncludeLen: 1,
|
expectFilterLen: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "any senders",
|
name: "any senders",
|
||||||
sender: any,
|
sender: any,
|
||||||
expectIncludeLen: 1,
|
expectFilterLen: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "single sender",
|
name: "single sender",
|
||||||
sender: stub,
|
sender: stub,
|
||||||
expectIncludeLen: 1,
|
expectFilterLen: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "multiple senders",
|
name: "multiple senders",
|
||||||
sender: twoStubs,
|
sender: twoStubs,
|
||||||
expectIncludeLen: 1,
|
expectFilterLen: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "any subjects",
|
name: "any subjects",
|
||||||
subject: any,
|
subject: any,
|
||||||
expectIncludeLen: 1,
|
expectFilterLen: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "single subject",
|
name: "single subject",
|
||||||
subject: stub,
|
subject: stub,
|
||||||
expectIncludeLen: 1,
|
expectFilterLen: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "multiple subjects",
|
name: "multiple subjects",
|
||||||
subject: twoStubs,
|
subject: twoStubs,
|
||||||
expectIncludeLen: 1,
|
expectFilterLen: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "one of each",
|
name: "one of each",
|
||||||
@ -395,19 +395,19 @@ func (suite *ExchangeSuite) TestIncludeExchangeRestoreInfoSelectors() {
|
|||||||
before: stub,
|
before: stub,
|
||||||
sender: stub,
|
sender: stub,
|
||||||
subject: stub,
|
subject: stub,
|
||||||
expectIncludeLen: 4,
|
expectFilterLen: 4,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, test := range table {
|
for _, test := range table {
|
||||||
suite.T().Run(test.name, func(t *testing.T) {
|
suite.T().Run(test.name, func(t *testing.T) {
|
||||||
sel := selectors.NewExchangeRestore()
|
sel := selectors.NewExchangeRestore()
|
||||||
includeExchangeRestoreInfoSelectors(
|
filterExchangeRestoreInfoSelectors(
|
||||||
sel,
|
sel,
|
||||||
test.after,
|
test.after,
|
||||||
test.before,
|
test.before,
|
||||||
test.sender,
|
test.sender,
|
||||||
test.subject)
|
test.subject)
|
||||||
assert.Equal(t, test.expectIncludeLen, len(sel.Includes))
|
assert.Equal(t, test.expectFilterLen, len(sel.Filters))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -95,7 +95,7 @@ func (op *RestoreOperation) Run(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// format the details and retrieve the items from kopia
|
// format the details and retrieve the items from kopia
|
||||||
fds := er.FilterDetails(d)
|
fds := er.Reduce(d)
|
||||||
// todo: use path pkg for this
|
// todo: use path pkg for this
|
||||||
fdsPaths := fds.Paths()
|
fdsPaths := fds.Paths()
|
||||||
paths := make([][]string, len(fdsPaths))
|
paths := make([][]string, len(fdsPaths))
|
||||||
|
|||||||
@ -76,53 +76,63 @@ func (s Selector) ToExchangeRestore() (*ExchangeRestore, error) {
|
|||||||
// -------------------
|
// -------------------
|
||||||
// Exclude/Includes
|
// Exclude/Includes
|
||||||
|
|
||||||
// Include appends the provided scopes to the selector's inclusion set.
|
|
||||||
//
|
|
||||||
// All parts of the scope must match for data to be included.
|
|
||||||
// Ex: Mail(u1, f1, m1) => only includes mail if it is owned by user u1,
|
|
||||||
// located in folder f1, and ID'd as m1. Use selectors.Any() to wildcard
|
|
||||||
// a scope value. No value will match if selectors.None() is provided.
|
|
||||||
//
|
|
||||||
// Group-level scopes will automatically apply the Any() wildcard to child
|
|
||||||
// properties.
|
|
||||||
// ex: User(u1) is the same as Mail(u1, Any(), Any()).
|
|
||||||
func (s *exchange) Include(scopes ...[]ExchangeScope) {
|
|
||||||
if s.Includes == nil {
|
|
||||||
s.Includes = []map[string]string{}
|
|
||||||
}
|
|
||||||
concat := []ExchangeScope{}
|
|
||||||
for _, scopeSl := range scopes {
|
|
||||||
concat = append(concat, extendExchangeScopeValues(scopeSl)...)
|
|
||||||
}
|
|
||||||
for _, sc := range concat {
|
|
||||||
s.Includes = append(s.Includes, map[string]string(sc))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exclude appends the provided scopes to the selector's exclusion set.
|
// Exclude appends the provided scopes to the selector's exclusion set.
|
||||||
// Every Exclusion scope applies globally, affecting all inclusion scopes.
|
// Every Exclusion scope applies globally, affecting all inclusion scopes.
|
||||||
//
|
//
|
||||||
// All parts of the scope must match for data to be excluded.
|
// All parts of the scope must match for data to be exclucded.
|
||||||
// Ex: Mail(u1, f1, m1) => only excludes mail that is owned by user u1,
|
// Ex: Mail(u1, f1, m1) => only excludes mail if it is owned by user u1,
|
||||||
// located in folder f1, and ID'd as m1. Use selectors.Any() to wildcard
|
// located in folder f1, and ID'd as m1. MailSender(foo) => only excludes
|
||||||
// a scope value. No value will match if selectors.None() is provided.
|
// mail whose sender is foo. Use selectors.Any() to wildcard a scope value.
|
||||||
|
// No value will match if selectors.None() is provided.
|
||||||
//
|
//
|
||||||
// Group-level scopes will automatically apply the Any() wildcard to
|
// Group-level scopes will automatically apply the Any() wildcard to
|
||||||
// child properties.
|
// child properties.
|
||||||
// ex: User(u1) automatically includes all mail, events, and contacts,
|
// ex: User(u1) automatically cascades to all mail, events, and contacts,
|
||||||
// therefore it is the same as selecting all of the following:
|
// therefore it is the same as selecting all of the following:
|
||||||
// Mail(u1, Any(), Any()), Event(u1, Any()), Contacts(u1, Any(), Any())
|
// Mail(u1, Any(), Any()), Event(u1, Any()), Contacts(u1, Any(), Any())
|
||||||
func (s *exchange) Exclude(scopes ...[]ExchangeScope) {
|
func (s *exchange) Exclude(scopes ...[]ExchangeScope) {
|
||||||
if s.Excludes == nil {
|
appendExcludes(&s.Selector, extendExchangeScopeValues, scopes...)
|
||||||
s.Excludes = []map[string]string{}
|
|
||||||
}
|
}
|
||||||
concat := []ExchangeScope{}
|
|
||||||
for _, scopeSl := range scopes {
|
// Filter appends the provided scopes to the selector's filters set.
|
||||||
concat = append(concat, extendExchangeScopeValues(scopeSl)...)
|
// A selector with >0 filters and 0 inclusions will include any data
|
||||||
}
|
// that passes all filters.
|
||||||
for _, sc := range concat {
|
// A selector with >0 filters and >0 inclusions will reduce the
|
||||||
s.Excludes = append(s.Excludes, map[string]string(sc))
|
// inclusion set to only the data that passes all filters.
|
||||||
|
//
|
||||||
|
// All parts of the scope must match for data to pass the filter.
|
||||||
|
// Ex: Mail(u1, f1, m1) => only passes mail that is owned by user u1,
|
||||||
|
// located in folder f1, and ID'd as m1. MailSender(foo) => only passes
|
||||||
|
// mail whose sender is foo. Use selectors.Any() to wildcard a scope value.
|
||||||
|
// No value will match if selectors.None() is provided.
|
||||||
|
//
|
||||||
|
// Group-level scopes will automatically apply the Any() wildcard to
|
||||||
|
// child properties.
|
||||||
|
// ex: User(u1) automatically cascades to all mail, events, and contacts,
|
||||||
|
// therefore it is the same as selecting all of the following:
|
||||||
|
// Mail(u1, Any(), Any()), Event(u1, Any()), Contacts(u1, Any(), Any())
|
||||||
|
func (s *exchange) Filter(scopes ...[]ExchangeScope) {
|
||||||
|
appendFilters(&s.Selector, extendExchangeScopeValues, scopes...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Include appends the provided scopes to the selector's inclusion set.
|
||||||
|
// Data is included if it matches ANY inclusion.
|
||||||
|
// The inclusion set is later filtered (all included data must pass ALL
|
||||||
|
// filters) and excluded (all included data must not match ANY exclusion).
|
||||||
|
//
|
||||||
|
// All parts of the scope must match for data to be included.
|
||||||
|
// Ex: Mail(u1, f1, m1) => only includes mail if it is owned by user u1,
|
||||||
|
// located in folder f1, and ID'd as m1. MailSender(foo) => only includes
|
||||||
|
// mail whose sender is foo. Use selectors.Any() to wildcard a scope value.
|
||||||
|
// No value will match if selectors.None() is provided.
|
||||||
|
//
|
||||||
|
// Group-level scopes will automatically apply the Any() wildcard to
|
||||||
|
// child properties.
|
||||||
|
// ex: User(u1) automatically cascades to all mail, events, and contacts,
|
||||||
|
// therefore it is the same as selecting all of the following:
|
||||||
|
// Mail(u1, Any(), Any()), Event(u1, Any()), Contacts(u1, Any(), Any())
|
||||||
|
func (s *exchange) Include(scopes ...[]ExchangeScope) {
|
||||||
|
appendIncludes(&s.Selector, extendExchangeScopeValues, scopes...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// completes population for certain scope properties, according to the
|
// completes population for certain scope properties, according to the
|
||||||
@ -282,9 +292,8 @@ func makeExchangeFilterScope(cat, filterCat exchangeCategory, vs []string) Excha
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Produces one or more exchange contact info filter scopes.
|
// Produces one or more exchange mail received-after filter scopes.
|
||||||
// Matches any mail which was received after the timestring.
|
// Matches any mail which was received after the timestring.
|
||||||
// One scope is created per timeString entry.
|
|
||||||
// If any slice contains selectors.Any, that slice is reduced to [selectors.Any]
|
// If any slice contains selectors.Any, that slice is reduced to [selectors.Any]
|
||||||
// If any slice contains selectors.None, that slice is reduced to [selectors.None]
|
// If any slice contains selectors.None, that slice is reduced to [selectors.None]
|
||||||
// If any slice is empty, it defaults to [selectors.None]
|
// If any slice is empty, it defaults to [selectors.None]
|
||||||
@ -294,9 +303,8 @@ func (sr *ExchangeRestore) MailReceivedAfter(timeStrings []string) []ExchangeSco
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Produces one or more exchange mail subject filter scopes.
|
// Produces one or more exchange mail received-before filter scopes.
|
||||||
// Matches any mail whose mail subject contains one of the provided strings.
|
// Matches any mail whose mail subject contains one of the provided strings.
|
||||||
// One scope is created per subject entry.
|
|
||||||
// If any slice contains selectors.Any, that slice is reduced to [selectors.Any]
|
// If any slice contains selectors.Any, that slice is reduced to [selectors.Any]
|
||||||
// If any slice contains selectors.None, that slice is reduced to [selectors.None]
|
// If any slice contains selectors.None, that slice is reduced to [selectors.None]
|
||||||
// If any slice is empty, it defaults to [selectors.None]
|
// If any slice is empty, it defaults to [selectors.None]
|
||||||
@ -306,9 +314,8 @@ func (sr *ExchangeRestore) MailReceivedBefore(timeStrings []string) []ExchangeSc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Produces one or more exchange mail received-after filter scopes.
|
// Produces one or more exchange mail sender filter scopes.
|
||||||
// Matches any mail which was received after the timestring.
|
// Matches any mail which was received after the timestring.
|
||||||
// One scope is created per timeString entry.
|
|
||||||
// If any slice contains selectors.Any, that slice is reduced to [selectors.Any]
|
// If any slice contains selectors.Any, that slice is reduced to [selectors.Any]
|
||||||
// If any slice contains selectors.None, that slice is reduced to [selectors.None]
|
// If any slice contains selectors.None, that slice is reduced to [selectors.None]
|
||||||
// If any slice is empty, it defaults to [selectors.None]
|
// If any slice is empty, it defaults to [selectors.None]
|
||||||
@ -318,7 +325,7 @@ func (sr *ExchangeRestore) MailSender(senderIDs []string) []ExchangeScope {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Produces one or more exchange mail received-before filter scopes.
|
// Produces one or more exchange mail subject line filter scopes.
|
||||||
// Matches any mail which was received before the timestring.
|
// Matches any mail which was received before the timestring.
|
||||||
// If any slice contains selectors.Any, that slice is reduced to [selectors.Any]
|
// If any slice contains selectors.Any, that slice is reduced to [selectors.Any]
|
||||||
// If any slice contains selectors.None, that slice is reduced to [selectors.None]
|
// If any slice contains selectors.None, that slice is reduced to [selectors.None]
|
||||||
@ -627,15 +634,16 @@ func exchangeIDPath(cat exchangeCategory, path []string) map[exchangeCategory]st
|
|||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
// FilterDetails reduces the entries in a backupDetails struct to only
|
// Reduce reduces the entries in a backupDetails struct to only
|
||||||
// those that match the inclusions and exclusions in the selector.
|
// those that match the inclusions, filters, and exclusions in the selector.
|
||||||
func (s *ExchangeRestore) FilterDetails(deets *backup.Details) *backup.Details {
|
func (s *ExchangeRestore) Reduce(deets *backup.Details) *backup.Details {
|
||||||
if deets == nil {
|
if deets == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
entIncs := exchangeScopesByCategory(s.Includes)
|
|
||||||
entExcs := exchangeScopesByCategory(s.Excludes)
|
entExcs := exchangeScopesByCategory(s.Excludes)
|
||||||
|
entFilt := exchangeScopesByCategory(s.Filters)
|
||||||
|
entIncs := exchangeScopesByCategory(s.Includes)
|
||||||
|
|
||||||
ents := []backup.DetailsEntry{}
|
ents := []backup.DetailsEntry{}
|
||||||
|
|
||||||
@ -660,8 +668,9 @@ func (s *ExchangeRestore) FilterDetails(deets *backup.Details) *backup.Details {
|
|||||||
cat,
|
cat,
|
||||||
path,
|
path,
|
||||||
ent.Exchange,
|
ent.Exchange,
|
||||||
entIncs[cat.String()],
|
entExcs[cat.String()],
|
||||||
entExcs[cat.String()])
|
entFilt[cat.String()],
|
||||||
|
entIncs[cat.String()])
|
||||||
if matched {
|
if matched {
|
||||||
ents = append(ents, ent)
|
ents = append(ents, ent)
|
||||||
}
|
}
|
||||||
@ -695,13 +704,22 @@ func exchangeScopesByCategory(scopes []map[string]string) map[string][]ExchangeS
|
|||||||
}
|
}
|
||||||
|
|
||||||
// compare each path to the included and excluded exchange scopes. Returns true
|
// compare each path to the included and excluded exchange scopes. Returns true
|
||||||
// if the path is included, and not excluded.
|
// if the path is included, passes filters, and not excluded.
|
||||||
func matchExchangeEntry(
|
func matchExchangeEntry(
|
||||||
cat exchangeCategory,
|
cat exchangeCategory,
|
||||||
path []string,
|
path []string,
|
||||||
info *backup.ExchangeInfo,
|
info *backup.ExchangeInfo,
|
||||||
incs, excs []ExchangeScope,
|
excs, filts, incs []ExchangeScope,
|
||||||
) bool {
|
) bool {
|
||||||
|
// a passing match requires either a filter or an inclusion
|
||||||
|
if len(incs)+len(filts) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip this check if 0 inclusions were populated
|
||||||
|
// since filters act as the inclusion check in that case
|
||||||
|
if len(incs) > 0 {
|
||||||
|
// at least one inclusion must apply.
|
||||||
var included bool
|
var included bool
|
||||||
for _, inc := range incs {
|
for _, inc := range incs {
|
||||||
if inc.matches(cat, path, info) {
|
if inc.matches(cat, path, info) {
|
||||||
@ -712,14 +730,21 @@ func matchExchangeEntry(
|
|||||||
if !included {
|
if !included {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var excluded bool
|
// all filters must pass
|
||||||
|
for _, filt := range filts {
|
||||||
|
if !filt.matches(cat, path, info) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// any matching exclusion means failure
|
||||||
for _, exc := range excs {
|
for _, exc := range excs {
|
||||||
if exc.matches(cat, path, info) {
|
if exc.matches(cat, path, info) {
|
||||||
excluded = true
|
return false
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return !excluded
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
@ -488,7 +488,7 @@ func (suite *ExchangeSourceSuite) TestExchangeScope_Get() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *ExchangeSourceSuite) TestExchangeScope_Include_MatchesInfo() {
|
func (suite *ExchangeSourceSuite) TestExchangeScope_MatchesInfo() {
|
||||||
es := NewExchangeRestore()
|
es := NewExchangeRestore()
|
||||||
const (
|
const (
|
||||||
sender = "smarf@2many.cooks"
|
sender = "smarf@2many.cooks"
|
||||||
@ -628,7 +628,7 @@ func (suite *ExchangeSourceSuite) TestIdPath() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *ExchangeSourceSuite) TestExchangeRestore_FilterDetails() {
|
func (suite *ExchangeSourceSuite) TestExchangeRestore_Reduce() {
|
||||||
makeDeets := func(refs ...string) *backup.Details {
|
makeDeets := func(refs ...string) *backup.Details {
|
||||||
deets := &backup.Details{
|
deets := &backup.Details{
|
||||||
DetailsModel: backup.DetailsModel{
|
DetailsModel: backup.DetailsModel{
|
||||||
@ -773,7 +773,7 @@ func (suite *ExchangeSourceSuite) TestExchangeRestore_FilterDetails() {
|
|||||||
for _, test := range table {
|
for _, test := range table {
|
||||||
suite.T().Run(test.name, func(t *testing.T) {
|
suite.T().Run(test.name, func(t *testing.T) {
|
||||||
sel := test.makeSelector()
|
sel := test.makeSelector()
|
||||||
results := sel.FilterDetails(test.deets)
|
results := sel.Reduce(test.deets)
|
||||||
paths := results.Paths()
|
paths := results.Paths()
|
||||||
assert.Equal(t, test.expect, paths)
|
assert.Equal(t, test.expect, paths)
|
||||||
})
|
})
|
||||||
@ -825,44 +825,48 @@ func (suite *ExchangeSourceSuite) TestExchangeScopesByCategory() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (suite *ExchangeSourceSuite) TestMatchExchangeEntry() {
|
func (suite *ExchangeSourceSuite) TestMatchExchangeEntry() {
|
||||||
var TODO_EXCHANGE_INFO *backup.ExchangeInfo
|
var exchangeInfo *backup.ExchangeInfo
|
||||||
const (
|
const (
|
||||||
mail = "mailID"
|
mid = "mailID"
|
||||||
cat = ExchangeMail
|
cat = ExchangeMail
|
||||||
)
|
)
|
||||||
var (
|
var (
|
||||||
es = NewExchangeRestore()
|
es = NewExchangeRestore()
|
||||||
inAny = extendExchangeScopeValues(es.Users(Any()))
|
anyUser = extendExchangeScopeValues(es.Users(Any()))
|
||||||
inNone = extendExchangeScopeValues(es.Users(None()))
|
noUser = extendExchangeScopeValues(es.Users(None()))
|
||||||
inMail = extendExchangeScopeValues(es.Mails(Any(), Any(), []string{mail}))
|
mail = extendExchangeScopeValues(es.Mails(Any(), Any(), []string{mid}))
|
||||||
inOtherMail = extendExchangeScopeValues(es.Mails(Any(), Any(), []string{"smarf"}))
|
otherMail = extendExchangeScopeValues(es.Mails(Any(), Any(), []string{"smarf"}))
|
||||||
exAny = extendExchangeScopeValues(es.Users(Any()))
|
noMail = extendExchangeScopeValues(es.Mails(Any(), Any(), None()))
|
||||||
exNone = extendExchangeScopeValues(es.Users(None()))
|
path = []string{"tid", "user", "mail", "folder", mid}
|
||||||
exMail = extendExchangeScopeValues(es.Mails(Any(), Any(), []string{mail}))
|
|
||||||
exOtherMail = extendExchangeScopeValues(es.Mails(Any(), Any(), []string{"smarf"}))
|
|
||||||
path = []string{"tid", "user", "mail", "folder", mail}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
table := []struct {
|
table := []struct {
|
||||||
name string
|
name string
|
||||||
includes []ExchangeScope
|
excludes, filters, includes []ExchangeScope
|
||||||
excludes []ExchangeScope
|
|
||||||
expect assert.BoolAssertionFunc
|
expect assert.BoolAssertionFunc
|
||||||
}{
|
}{
|
||||||
{"empty", nil, nil, assert.False},
|
{"empty", nil, nil, nil, assert.False},
|
||||||
{"in all", inAny, nil, assert.True},
|
{"in Any", nil, nil, anyUser, assert.True},
|
||||||
{"in None", inNone, nil, assert.False},
|
{"in None", nil, nil, noUser, assert.False},
|
||||||
{"in Mail", inMail, nil, assert.True},
|
{"in Mail", nil, nil, mail, assert.True},
|
||||||
{"in Other", inOtherMail, nil, assert.False},
|
{"in Other", nil, nil, otherMail, assert.False},
|
||||||
{"ex all", inAny, exAny, assert.False},
|
{"in no Mail", nil, nil, noMail, assert.False},
|
||||||
{"ex None", inAny, exNone, assert.True},
|
{"ex Any", anyUser, nil, anyUser, assert.False},
|
||||||
{"in Mail", inAny, exMail, assert.False},
|
{"ex Any filter", anyUser, anyUser, nil, assert.False},
|
||||||
{"in Other", inAny, exOtherMail, assert.True},
|
{"ex None", noUser, nil, anyUser, assert.True},
|
||||||
{"in and ex Mail", inMail, exMail, assert.False},
|
{"ex None filter mail", noUser, mail, nil, assert.True},
|
||||||
|
{"ex None filter any user", noUser, anyUser, nil, assert.False},
|
||||||
|
{"ex Mail", mail, nil, anyUser, assert.False},
|
||||||
|
{"ex Other", otherMail, nil, anyUser, assert.True},
|
||||||
|
{"in and ex Mail", mail, nil, mail, assert.False},
|
||||||
|
{"filter Any", nil, anyUser, nil, assert.False},
|
||||||
|
{"filter None", nil, noUser, anyUser, assert.False},
|
||||||
|
{"filter Mail", nil, mail, anyUser, assert.True},
|
||||||
|
{"filter Other", nil, otherMail, anyUser, assert.False},
|
||||||
}
|
}
|
||||||
for _, test := range table {
|
for _, test := range table {
|
||||||
suite.T().Run(test.name, func(t *testing.T) {
|
suite.T().Run(test.name, func(t *testing.T) {
|
||||||
test.expect(t, matchExchangeEntry(cat, path, TODO_EXCHANGE_INFO, test.includes, test.excludes))
|
test.expect(t, matchExchangeEntry(cat, path, exchangeInfo, test.excludes, test.filters, test.includes))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -55,8 +55,9 @@ const (
|
|||||||
// Is only used to pass along more specific selector instances.
|
// Is only used to pass along more specific selector instances.
|
||||||
type Selector struct {
|
type Selector struct {
|
||||||
Service service `json:"service,omitempty"` // The service scope of the data. Exchange, Teams, Sharepoint, etc.
|
Service service `json:"service,omitempty"` // The service scope of the data. Exchange, Teams, Sharepoint, etc.
|
||||||
Excludes []map[string]string `json:"exclusions,omitempty"` // A slice of exclusions. Each exclusion applies to all inclusions.
|
Excludes []map[string]string `json:"exclusions,omitempty"` // A slice of exclusion scopes. Exclusions apply globally to all inclusions/filters, with any-match behavior.
|
||||||
Includes []map[string]string `json:"scopes,omitempty"` // A slice of inclusions. Expected to get cast to a service wrapper within each service handler.
|
Filters []map[string]string `json:"filters,omitempty"` // A slice of filter scopes. All inclusions must also match ALL filters.
|
||||||
|
Includes []map[string]string `json:"scopes,omitempty"` // A slice of inclusion scopes. Comparators must match either one of these, or all filters, to be included.
|
||||||
}
|
}
|
||||||
|
|
||||||
// helper for specific selector instance constructors.
|
// helper for specific selector instance constructors.
|
||||||
@ -81,6 +82,61 @@ func None() []string {
|
|||||||
return []string{NoneTgt}
|
return []string{NoneTgt}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type baseScope interface {
|
||||||
|
~map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
func appendExcludes[T baseScope](
|
||||||
|
s *Selector,
|
||||||
|
tform func([]T) []T,
|
||||||
|
scopes ...[]T,
|
||||||
|
) {
|
||||||
|
if s.Excludes == nil {
|
||||||
|
s.Excludes = []map[string]string{}
|
||||||
|
}
|
||||||
|
concat := []T{}
|
||||||
|
for _, scopeSl := range scopes {
|
||||||
|
concat = append(concat, tform(scopeSl)...)
|
||||||
|
}
|
||||||
|
for _, sc := range concat {
|
||||||
|
s.Excludes = append(s.Excludes, map[string]string(sc))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func appendFilters[T baseScope](
|
||||||
|
s *Selector,
|
||||||
|
tform func([]T) []T,
|
||||||
|
scopes ...[]T,
|
||||||
|
) {
|
||||||
|
if s.Filters == nil {
|
||||||
|
s.Filters = []map[string]string{}
|
||||||
|
}
|
||||||
|
concat := []T{}
|
||||||
|
for _, scopeSl := range scopes {
|
||||||
|
concat = append(concat, tform(scopeSl)...)
|
||||||
|
}
|
||||||
|
for _, sc := range concat {
|
||||||
|
s.Filters = append(s.Filters, map[string]string(sc))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func appendIncludes[T baseScope](
|
||||||
|
s *Selector,
|
||||||
|
tform func([]T) []T,
|
||||||
|
scopes ...[]T,
|
||||||
|
) {
|
||||||
|
if s.Includes == nil {
|
||||||
|
s.Includes = []map[string]string{}
|
||||||
|
}
|
||||||
|
concat := []T{}
|
||||||
|
for _, scopeSl := range scopes {
|
||||||
|
concat = append(concat, tform(scopeSl)...)
|
||||||
|
}
|
||||||
|
for _, sc := range concat {
|
||||||
|
s.Includes = append(s.Includes, map[string]string(sc))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Destination
|
// Destination
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user