adds calendars as a folder structure to events (#714)

## Description

Exchange events were amended to use calendars
as a folder structure.  This updates the selector
to treat events as having folders similar to mail and
contacts.

## Type of change

Please check the type of change your PR introduces:
- [x] 🌻 Feature

## Issue(s)

#501

## Test Plan

- [ ] 💪 Manual
- [x]  Unit test
- [ ] 💚 E2E
This commit is contained in:
Keepers 2022-09-01 09:39:14 -06:00 committed by GitHub
parent 127b6d061a
commit c29d93e655
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 114 additions and 67 deletions

View File

@ -1,8 +1,6 @@
package backup package backup
import ( import (
"fmt"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/pflag" "github.com/spf13/pflag"
@ -222,7 +220,7 @@ func exchangeBackupCreateSelectors(all bool, users, data []string) selectors.Sel
if len(data) == 0 { if len(data) == 0 {
sel.Include(sel.ContactFolders(user, selectors.Any())) sel.Include(sel.ContactFolders(user, selectors.Any()))
sel.Include(sel.MailFolders(user, selectors.Any())) sel.Include(sel.MailFolders(user, selectors.Any()))
sel.Include(sel.Events(user, selectors.Any())) sel.Include(sel.EventCalendars(user, selectors.Any()))
} }
for _, d := range data { for _, d := range data {
@ -232,7 +230,7 @@ func exchangeBackupCreateSelectors(all bool, users, data []string) selectors.Sel
case dataEmail: case dataEmail:
sel.Include(sel.MailFolders(users, selectors.Any())) sel.Include(sel.MailFolders(users, selectors.Any()))
case dataEvents: case dataEvents:
sel.Include(sel.Events(users, selectors.Any())) sel.Include(sel.EventCalendars(users, selectors.Any()))
} }
} }
@ -441,9 +439,7 @@ func includeExchangeEvents(sel *selectors.ExchangeRestore, users, eventCalendars
events = selectors.Any() events = selectors.Any()
} }
fmt.Println("logging eventCalendars so the linter sees it 'used'. Will remove asap", eventCalendars) sel.Include(sel.Events(users, eventCalendars, events))
sel.Include(sel.Events(users, events))
} }
// builds the info-selector filters for `backup details exchange` // builds the info-selector filters for `backup details exchange`

View File

@ -1,8 +1,6 @@
package restore package restore
import ( import (
"fmt"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/pflag" "github.com/spf13/pflag"
@ -240,9 +238,7 @@ func includeExchangeEvents(sel *selectors.ExchangeRestore, users, eventCalendars
events = selectors.Any() events = selectors.Any()
} }
fmt.Println("logging eventCalendars so the linter sees it 'used'. Will remove asap", eventCalendars) sel.Include(sel.Events(users, eventCalendars, events))
sel.Include(sel.Events(users, events))
} }
// builds the info-selector filters for `restore exchange` // builds the info-selector filters for `restore exchange`

View File

@ -213,7 +213,7 @@ func (suite *GraphConnectorIntegrationSuite) TestEventsSerializationRegression()
t := suite.T() t := suite.T()
connector := loadConnector(t) connector := loadConnector(t)
sel := selectors.NewExchangeBackup() sel := selectors.NewExchangeBackup()
sel.Include(sel.Events([]string{suite.user}, selectors.Any())) sel.Include(sel.EventCalendars([]string{suite.user}, selectors.Any()))
scopes := sel.Scopes() scopes := sel.Scopes()
suite.Equal(len(scopes), 1) suite.Equal(len(scopes), 1)
collections, err := connector.createCollections(context.Background(), scopes[0]) collections, err := connector.createCollections(context.Background(), scopes[0])

View File

@ -159,7 +159,7 @@ func (suite *BackupOpIntegrationSuite) TestBackup_Run() {
name: "Integration Exchange.Events", name: "Integration Exchange.Events",
selectFunc: func() *selectors.Selector { selectFunc: func() *selectors.Selector {
sel := selectors.NewExchangeBackup() sel := selectors.NewExchangeBackup()
sel.Include(sel.Events([]string{m365UserID}, selectors.Any())) sel.Include(sel.EventCalendars([]string{m365UserID}, selectors.Any()))
return &sel.Selector return &sel.Selector
}, },

View File

@ -160,8 +160,7 @@ func (s *exchange) DiscreteScopes(userPNs []string) []ExchangeScope {
// ------------------- // -------------------
// Scope Factories // Scope Factories
// Produces one or more exchange contact scopes. // Contacts produces one or more exchange contact scopes.
// One scope is created per combination of users,folders,contacts.
// 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]
@ -177,8 +176,7 @@ func (s *exchange) Contacts(users, folders, contacts []string) []ExchangeScope {
return scopes return scopes
} }
// Produces one or more exchange contact folder scopes. // Contactfolders produces one or more exchange contact folder scopes.
// One scope is created per combination of users,folders.
// 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]
@ -193,24 +191,39 @@ func (s *exchange) ContactFolders(users, folders []string) []ExchangeScope {
return scopes return scopes
} }
// Produces one or more exchange event scopes. // Events produces one or more exchange event scopes.
// One scope is created per combination of users,events.
// 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]
func (s *exchange) Events(users, events []string) []ExchangeScope { func (s *exchange) Events(users, calendars, events []string) []ExchangeScope {
scopes := []ExchangeScope{} scopes := []ExchangeScope{}
scopes = append( scopes = append(
scopes, scopes,
makeScope[ExchangeScope](Item, ExchangeEvent, users, events), makeScope[ExchangeScope](Item, ExchangeEvent, users, events).
set(ExchangeEventCalendar, calendars),
)
return scopes
}
// EventCalendars produces one or more exchange event calendar scopes.
// Calendars act as folders to contain Events
// 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 is empty, it defaults to [selectors.None]
func (s *exchange) EventCalendars(users, events []string) []ExchangeScope {
scopes := []ExchangeScope{}
scopes = append(
scopes,
makeScope[ExchangeScope](Item, ExchangeEventCalendar, users, events),
) )
return scopes return scopes
} }
// Produces one or more mail scopes. // Produces one or more mail scopes.
// One scope is created per combination of users,folders,mails.
// 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]
@ -227,7 +240,6 @@ func (s *exchange) Mails(users, folders, mails []string) []ExchangeScope {
} }
// Produces one or more exchange mail folder scopes. // Produces one or more exchange mail folder scopes.
// One scope is created per combination of users,folders.
// 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]
@ -243,7 +255,7 @@ func (s *exchange) MailFolders(users, folders []string) []ExchangeScope {
} }
// Produces one or more exchange contact user scopes. // Produces one or more exchange contact user scopes.
// One scope is created per user entry. // Each user id generates three scopes, one for each data type: contact, event, and mail.
// 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]
@ -252,7 +264,7 @@ func (s *exchange) Users(users []string) []ExchangeScope {
scopes = append(scopes, scopes = append(scopes,
makeScope[ExchangeScope](Group, ExchangeContactFolder, users, Any()), makeScope[ExchangeScope](Group, ExchangeContactFolder, users, Any()),
makeScope[ExchangeScope](Item, ExchangeEvent, users, Any()), makeScope[ExchangeScope](Item, ExchangeEventCalendar, users, Any()),
makeScope[ExchangeScope](Group, ExchangeMailFolder, users, Any()), makeScope[ExchangeScope](Group, ExchangeMailFolder, users, Any()),
) )
@ -448,6 +460,7 @@ const (
ExchangeContact exchangeCategory = "ExchangeContact" ExchangeContact exchangeCategory = "ExchangeContact"
ExchangeContactFolder exchangeCategory = "ExchangeContactFolder" ExchangeContactFolder exchangeCategory = "ExchangeContactFolder"
ExchangeEvent exchangeCategory = "ExchangeEvent" ExchangeEvent exchangeCategory = "ExchangeEvent"
ExchangeEventCalendar exchangeCategory = "ExchangeEventCalendar"
ExchangeMail exchangeCategory = "ExchangeMail" ExchangeMail exchangeCategory = "ExchangeMail"
ExchangeMailFolder exchangeCategory = "ExchangeFolder" ExchangeMailFolder exchangeCategory = "ExchangeFolder"
ExchangeUser exchangeCategory = "ExchangeUser" ExchangeUser exchangeCategory = "ExchangeUser"
@ -471,7 +484,7 @@ const (
// these types appear in the canonical Path for each type. // these types appear in the canonical Path for each type.
var exchangePathSet = map[categorizer][]categorizer{ var exchangePathSet = map[categorizer][]categorizer{
ExchangeContact: {ExchangeUser, ExchangeContactFolder, ExchangeContact}, ExchangeContact: {ExchangeUser, ExchangeContactFolder, ExchangeContact},
ExchangeEvent: {ExchangeUser, ExchangeEvent}, ExchangeEvent: {ExchangeUser, ExchangeEventCalendar, ExchangeEvent},
ExchangeMail: {ExchangeUser, ExchangeMailFolder, ExchangeMail}, ExchangeMail: {ExchangeUser, ExchangeMailFolder, ExchangeMail},
ExchangeUser: {ExchangeUser}, // the root category must be represented ExchangeUser: {ExchangeUser}, // the root category must be represented
} }
@ -492,9 +505,11 @@ func (ec exchangeCategory) leafCat() categorizer {
switch ec { switch ec {
case ExchangeContact, ExchangeContactFolder, ExchangeFilterContactName: case ExchangeContact, ExchangeContactFolder, ExchangeFilterContactName:
return ExchangeContact return ExchangeContact
case ExchangeEvent, ExchangeFilterEventRecurs, ExchangeFilterEventStartsAfter,
ExchangeFilterEventStartsBefore, ExchangeFilterEventSubject: case ExchangeEvent, ExchangeEventCalendar, ExchangeFilterEventRecurs,
ExchangeFilterEventStartsAfter, ExchangeFilterEventStartsBefore, ExchangeFilterEventSubject:
return ExchangeEvent return ExchangeEvent
case ExchangeMail, ExchangeMailFolder, ExchangeFilterMailReceivedAfter, case ExchangeMail, ExchangeMailFolder, ExchangeFilterMailReceivedAfter,
ExchangeFilterMailReceivedBefore, ExchangeFilterMailSender, ExchangeFilterMailSubject: ExchangeFilterMailReceivedBefore, ExchangeFilterMailSender, ExchangeFilterMailSubject:
return ExchangeMail return ExchangeMail
@ -524,40 +539,22 @@ func (ec exchangeCategory) unknownCat() categorizer {
// => {exchUser: userPN, exchMailFolder: mailFolder, exchMail: mailID} // => {exchUser: userPN, exchMailFolder: mailFolder, exchMail: mailID}
func (ec exchangeCategory) pathValues(path []string) map[categorizer]string { func (ec exchangeCategory) pathValues(path []string) map[categorizer]string {
m := map[categorizer]string{} m := map[categorizer]string{}
if len(path) < 2 { if len(path) < 5 {
return m return m
} }
m[ExchangeUser] = path[1] m[ExchangeUser] = path[1]
/*
TODO/Notice:
Mail and Contacts contain folder structures, identified
in this code as being at index 3. This assumes a single
folder, while in reality users can express subfolder
hierarchies of arbirary depth. Subfolder handling is coming
at a later time.
*/
switch ec { switch ec {
case ExchangeContact: case ExchangeContact:
if len(path) < 5 {
return m
}
m[ExchangeContactFolder] = path[3] m[ExchangeContactFolder] = path[3]
m[ExchangeContact] = path[4] m[ExchangeContact] = path[4]
case ExchangeEvent: case ExchangeEvent:
if len(path) < 4 { m[ExchangeEventCalendar] = path[3]
return m m[ExchangeEvent] = path[4]
}
m[ExchangeEvent] = path[3]
case ExchangeMail: case ExchangeMail:
if len(path) < 5 {
return m
}
m[ExchangeMailFolder] = path[3] m[ExchangeMailFolder] = path[3]
m[ExchangeMail] = path[4] m[ExchangeMail] = path[4]
} }
@ -641,8 +638,13 @@ func (s ExchangeScope) setDefaults() {
switch s.Category() { switch s.Category() {
case ExchangeContactFolder: case ExchangeContactFolder:
s[ExchangeContact.String()] = passAny s[ExchangeContact.String()] = passAny
case ExchangeEventCalendar:
s[ExchangeEvent.String()] = passAny
case ExchangeMailFolder: case ExchangeMailFolder:
s[ExchangeMail.String()] = passAny s[ExchangeMail.String()] = passAny
case ExchangeUser: case ExchangeUser:
s[ExchangeContactFolder.String()] = passAny s[ExchangeContactFolder.String()] = passAny
s[ExchangeContact.String()] = passAny s[ExchangeContact.String()] = passAny

View File

@ -169,9 +169,10 @@ func (suite *ExchangeSelectorSuite) TestExchangeSelector_Exclude_Events() {
user = "user" user = "user"
e1 = "e1" e1 = "e1"
e2 = "e2" e2 = "e2"
c1 = "c1"
) )
sel.Exclude(sel.Events([]string{user}, []string{e1, e2})) sel.Exclude(sel.Events([]string{user}, []string{c1}, []string{e1, e2}))
scopes := sel.Excludes scopes := sel.Excludes
require.Len(t, scopes, 1) require.Len(t, scopes, 1)
@ -180,11 +181,37 @@ func (suite *ExchangeSelectorSuite) TestExchangeSelector_Exclude_Events() {
ExchangeScope(scopes[0]), ExchangeScope(scopes[0]),
map[categorizer]string{ map[categorizer]string{
ExchangeUser: user, ExchangeUser: user,
ExchangeEventCalendar: c1,
ExchangeEvent: join(e1, e2), ExchangeEvent: join(e1, e2),
}, },
) )
} }
func (suite *ExchangeSelectorSuite) TestExchangeSelector_Exclude_EventCalendars() {
t := suite.T()
sel := NewExchangeBackup()
const (
user = "user"
c1 = "c1"
c2 = "c2"
)
sel.Exclude(sel.EventCalendars([]string{user}, []string{c1, c2}))
scopes := sel.Excludes
require.Len(t, scopes, 1)
scopeMustHave(
t,
ExchangeScope(scopes[0]),
map[categorizer]string{
ExchangeUser: user,
ExchangeEventCalendar: join(c1, c2),
ExchangeEvent: AnyTgt,
},
)
}
func (suite *ExchangeSelectorSuite) TestExchangeSelector_Include_Events() { func (suite *ExchangeSelectorSuite) TestExchangeSelector_Include_Events() {
t := suite.T() t := suite.T()
sel := NewExchangeBackup() sel := NewExchangeBackup()
@ -193,9 +220,10 @@ func (suite *ExchangeSelectorSuite) TestExchangeSelector_Include_Events() {
user = "user" user = "user"
e1 = "e1" e1 = "e1"
e2 = "e2" e2 = "e2"
c1 = "c1"
) )
sel.Include(sel.Events([]string{user}, []string{e1, e2})) sel.Include(sel.Events([]string{user}, []string{c1}, []string{e1, e2}))
scopes := sel.Includes scopes := sel.Includes
require.Len(t, scopes, 1) require.Len(t, scopes, 1)
@ -204,11 +232,35 @@ func (suite *ExchangeSelectorSuite) TestExchangeSelector_Include_Events() {
ExchangeScope(scopes[0]), ExchangeScope(scopes[0]),
map[categorizer]string{ map[categorizer]string{
ExchangeUser: user, ExchangeUser: user,
ExchangeEventCalendar: c1,
ExchangeEvent: join(e1, e2), ExchangeEvent: join(e1, e2),
}, },
) )
}
assert.Equal(t, sel.Scopes()[0].Category(), ExchangeEvent) func (suite *ExchangeSelectorSuite) TestExchangeSelector_Include_EventCalendars() {
t := suite.T()
sel := NewExchangeBackup()
const (
user = "user"
c1 = "c1"
c2 = "c2"
)
sel.Include(sel.EventCalendars([]string{user}, []string{c1, c2}))
scopes := sel.Includes
require.Len(t, scopes, 1)
scopeMustHave(
t,
ExchangeScope(scopes[0]),
map[categorizer]string{
ExchangeUser: user,
ExchangeEventCalendar: join(c1, c2),
ExchangeEvent: AnyTgt,
},
)
} }
func (suite *ExchangeSelectorSuite) TestExchangeSelector_Exclude_Mails() { func (suite *ExchangeSelectorSuite) TestExchangeSelector_Exclude_Mails() {
@ -824,7 +876,7 @@ func (suite *ExchangeSelectorSuite) TestExchangeRestore_Reduce() {
const ( const (
contact = "tid/uid/contact/cfld/cid" contact = "tid/uid/contact/cfld/cid"
event = "tid/uid/event/eid" event = "tid/uid/event/ecld/eid"
mail = "tid/uid/mail/mfld/mid" mail = "tid/uid/mail/mfld/mid"
) )
@ -903,7 +955,7 @@ func (suite *ExchangeSelectorSuite) TestExchangeRestore_Reduce() {
makeDeets(contact, event, mail), makeDeets(contact, event, mail),
func() *ExchangeRestore { func() *ExchangeRestore {
er := NewExchangeRestore() er := NewExchangeRestore()
er.Include(er.Events([]string{"uid"}, []string{"eid"})) er.Include(er.Events([]string{"uid"}, []string{"ecld"}, []string{"eid"}))
return er return er
}, },
arr(event), arr(event),
@ -935,7 +987,7 @@ func (suite *ExchangeSelectorSuite) TestExchangeRestore_Reduce() {
func() *ExchangeRestore { func() *ExchangeRestore {
er := NewExchangeRestore() er := NewExchangeRestore()
er.Include(er.Users(Any())) er.Include(er.Users(Any()))
er.Exclude(er.Events([]string{"uid"}, []string{"eid"})) er.Exclude(er.Events([]string{"uid"}, []string{"ecld"}, []string{"eid"}))
return er return er
}, },
arr(contact, mail), arr(contact, mail),
@ -967,7 +1019,7 @@ func (suite *ExchangeSelectorSuite) TestScopesByCategory() {
es = NewExchangeRestore() es = NewExchangeRestore()
users = es.Users(Any()) users = es.Users(Any())
contacts = es.ContactFolders(Any(), Any()) contacts = es.ContactFolders(Any(), Any())
events = es.Events(Any(), Any()) events = es.Events(Any(), Any(), Any())
mail = es.MailFolders(Any(), Any()) mail = es.MailFolders(Any(), Any())
) )
@ -1174,10 +1226,11 @@ func (suite *ExchangeSelectorSuite) TestExchangeCategory_PathValues() {
ExchangeContactFolder: contactPath[3], ExchangeContactFolder: contactPath[3],
ExchangeContact: contactPath[4], ExchangeContact: contactPath[4],
} }
eventPath := []string{"ten", "user", "event", "eventitem"} eventPath := []string{"ten", "user", "event", "ecalendar", "eventitem"}
eventMap := map[categorizer]string{ eventMap := map[categorizer]string{
ExchangeUser: eventPath[1], ExchangeUser: eventPath[1],
ExchangeEvent: eventPath[3], ExchangeEventCalendar: eventPath[3],
ExchangeEvent: eventPath[4],
} }
mailPath := []string{"ten", "user", "mail", "mfolder", "mailitem"} mailPath := []string{"ten", "user", "mail", "mfolder", "mailitem"}
mailMap := map[categorizer]string{ mailMap := map[categorizer]string{
@ -1206,7 +1259,7 @@ func (suite *ExchangeSelectorSuite) TestExchangeCategory_PathValues() {
func (suite *ExchangeSelectorSuite) TestExchangeCategory_PathKeys() { func (suite *ExchangeSelectorSuite) TestExchangeCategory_PathKeys() {
contact := []categorizer{ExchangeUser, ExchangeContactFolder, ExchangeContact} contact := []categorizer{ExchangeUser, ExchangeContactFolder, ExchangeContact}
event := []categorizer{ExchangeUser, ExchangeEvent} event := []categorizer{ExchangeUser, ExchangeEventCalendar, ExchangeEvent}
mail := []categorizer{ExchangeUser, ExchangeMailFolder, ExchangeMail} mail := []categorizer{ExchangeUser, ExchangeMailFolder, ExchangeMail}
user := []categorizer{ExchangeUser} user := []categorizer{ExchangeUser}

View File

@ -136,7 +136,7 @@ func setScopesToDefault[T scopeT](ts []T) []T {
func scopeMustHave[T scopeT](t *testing.T, sc T, m map[categorizer]string) { func scopeMustHave[T scopeT](t *testing.T, sc T, m map[categorizer]string) {
for k, v := range m { for k, v := range m {
t.Run(k.String(), func(t *testing.T) { t.Run(k.String(), func(t *testing.T) {
assert.Equal(t, getCatValue(sc, k), split(v)) assert.Equal(t, getCatValue(sc, k), split(v), "Key: %s", k)
}) })
} }
} }