diff --git a/src/cli/backup/exchange.go b/src/cli/backup/exchange.go index ac1534c3a..8efba7feb 100644 --- a/src/cli/backup/exchange.go +++ b/src/cli/backup/exchange.go @@ -109,7 +109,8 @@ func createExchangeCmd(cmd *cobra.Command, args []string) error { if utils.HasNoFlagsAndShownHelp(cmd) { return nil } - if err := validateBackupCreateFlags(exchangeAll, user, exchangeData); err != nil { + + if err := validateExchangeBackupCreateFlags(exchangeAll, user, exchangeData); err != nil { return err } @@ -176,7 +177,7 @@ func exchangeBackupCreateSelectors(all bool, users, data []string) selectors.Sel return sel.Selector } -func validateBackupCreateFlags(all bool, users, data []string) error { +func validateExchangeBackupCreateFlags(all bool, users, data []string) error { if len(users) == 0 && !all { return errors.New("requries one or more --user ids, the wildcard --user *, or the --all flag.") } @@ -253,6 +254,22 @@ var exchangeDetailsCmd = &cobra.Command{ func detailsExchangeCmd(cmd *cobra.Command, args []string) error { ctx := cmd.Context() + if utils.HasNoFlagsAndShownHelp(cmd) { + return nil + } + + if err := validateExchangeBackupDetailFlags( + contact, + contactFolder, + email, + emailFolder, + event, + user, + backupID, + ); err != nil { + return err + } + s, acct, err := config.GetStorageAndAccount(true, nil) if err != nil { return err @@ -278,7 +295,14 @@ func detailsExchangeCmd(cmd *cobra.Command, args []string) error { return errors.Wrap(err, "Failed to get backup details in the repository") } - print.Entries(d.Entries) + sel := exchangeBackupDetailSelectors(contact, contactFolder, email, emailFolder, event, user) + erSel, err := sel.ToExchangeRestore() + if err != nil { + return err + } + + ds := erSel.FilterDetails(d) + print.Entries(ds.Entries) return nil } diff --git a/src/cli/backup/exchange_test.go b/src/cli/backup/exchange_test.go index 644effda0..c951b5b45 100644 --- a/src/cli/backup/exchange_test.go +++ b/src/cli/backup/exchange_test.go @@ -93,7 +93,7 @@ func (suite *ExchangeSuite) TestValidateBackupCreateFlags() { } for _, test := range table { suite.T().Run(test.name, func(t *testing.T) { - test.expect(t, validateBackupCreateFlags(test.all, test.user, test.data)) + test.expect(t, validateExchangeBackupCreateFlags(test.all, test.user, test.data)) }) } } diff --git a/src/internal/operations/restore.go b/src/internal/operations/restore.go index 20a053944..9aa568022 100644 --- a/src/internal/operations/restore.go +++ b/src/internal/operations/restore.go @@ -2,6 +2,7 @@ package operations import ( "context" + "strings" "time" "github.com/pkg/errors" @@ -95,7 +96,13 @@ func (op *RestoreOperation) Run(ctx context.Context) error { // format the details and retrieve the items from kopia fds := er.FilterDetails(d) - dcs, err := op.kopia.RestoreMultipleItems(ctx, b.SnapshotID, fds) + // todo: use path pkg for this + fdsPaths := fds.Paths() + paths := make([][]string, len(fdsPaths)) + for i := range fdsPaths { + paths[i] = strings.Split(fdsPaths[i], "/") + } + dcs, err := op.kopia.RestoreMultipleItems(ctx, b.SnapshotID, paths) if err != nil { stats.readErr = errors.Wrap(err, "retrieving service data") return stats.readErr diff --git a/src/pkg/backup/backup.go b/src/pkg/backup/backup.go index d85ae9c23..0bff0e077 100644 --- a/src/pkg/backup/backup.go +++ b/src/pkg/backup/backup.go @@ -77,6 +77,16 @@ type DetailsEntry struct { ItemInfo } +// Paths returns the list of Paths extracted from the Entriess slice. +func (dm DetailsModel) Paths() []string { + ents := dm.Entries + r := make([]string, len(ents)) + for i := range ents { + r[i] = ents[i].RepoRef + } + return r +} + // Headers returns the human-readable names of properties in a DetailsEntry // for printing out to a terminal in a columnar display. func (de DetailsEntry) Headers() []string { diff --git a/src/pkg/backup/backup_test.go b/src/pkg/backup/backup_test.go index 4cdeb32b1..69b5fb0b7 100644 --- a/src/pkg/backup/backup_test.go +++ b/src/pkg/backup/backup_test.go @@ -106,3 +106,42 @@ func (suite *BackupSuite) TestDetailsEntry_HeadersValues() { }) } } + +func (suite *BackupSuite) TestDetailsModel_Path() { + table := []struct { + name string + ents []backup.DetailsEntry + expect []string + }{ + { + name: "nil entries", + ents: nil, + expect: []string{}, + }, + { + name: "single entry", + ents: []backup.DetailsEntry{ + {RepoRef: "abcde"}, + }, + expect: []string{"abcde"}, + }, + { + name: "multiple entries", + ents: []backup.DetailsEntry{ + {RepoRef: "abcde"}, + {RepoRef: "12345"}, + }, + expect: []string{"abcde", "12345"}, + }, + } + for _, test := range table { + suite.T().Run(test.name, func(t *testing.T) { + d := backup.Details{ + DetailsModel: backup.DetailsModel{ + Entries: test.ents, + }, + } + assert.DeepEqual(t, test.expect, d.Paths()) + }) + } +} diff --git a/src/pkg/selectors/exchange.go b/src/pkg/selectors/exchange.go index 8d628ea72..eae1fe28c 100644 --- a/src/pkg/selectors/exchange.go +++ b/src/pkg/selectors/exchange.go @@ -391,7 +391,7 @@ var categoryPathSet = map[exchangeCategory][]exchangeCategory{ // includesPath returns true if all filters in the scope match the path. func (s exchangeScope) includesPath(cat exchangeCategory, path []string) bool { - ids := idPath(cat, path) + ids := exchangeIDPath(cat, path) for _, c := range categoryPathSet[cat] { target := s.Get(c) if len(target) == 0 { @@ -408,9 +408,18 @@ func (s exchangeScope) includesPath(cat exchangeCategory, path []string) bool { return true } +// includesInfo returns true if all filters in the scope match the info. +func (s exchangeScope) includesInfo(cat exchangeCategory, info *backup.ExchangeInfo) bool { + // todo: implement once filters used in scopes + if info == nil { + return false + } + return false +} + // excludesPath returns true if all filters in the scope match the path. func (s exchangeScope) excludesPath(cat exchangeCategory, path []string) bool { - ids := idPath(cat, path) + ids := exchangeIDPath(cat, path) for _, c := range categoryPathSet[cat] { target := s.Get(c) if len(target) == 0 { @@ -427,6 +436,15 @@ func (s exchangeScope) excludesPath(cat exchangeCategory, path []string) bool { return false } +// excludesInfo returns true if all filters in the scope matche the info. +func (s exchangeScope) excludesInfo(cat exchangeCategory, info *backup.ExchangeInfo) bool { + // todo: implement once filters used in scopes + if info == nil { + return false + } + return false +} + // temporary helper until filters replace string values for scopes. func contains(super []string, sub string) bool { for _, s := range super { @@ -446,7 +464,7 @@ func contains(super []string, sub string) bool { // Example: // [tenantID, userID, "mail", mailFolder, mailID] // => {exchUser: userID, exchMailFolder: mailFolder, exchMail: mailID} -func idPath(cat exchangeCategory, path []string) map[exchangeCategory]string { +func exchangeIDPath(cat exchangeCategory, path []string) map[exchangeCategory]string { m := map[exchangeCategory]string{} if len(path) == 0 { return m @@ -484,7 +502,7 @@ func idPath(cat exchangeCategory, path []string) map[exchangeCategory]string { // FilterDetails reduces the entries in a backupDetails struct to only // those that match the inclusions and exclusions in the selector. -func (s *ExchangeRestore) FilterDetails(deets *backup.Details) [][]string { +func (s *ExchangeRestore) FilterDetails(deets *backup.Details) *backup.Details { if deets == nil { return nil } @@ -492,9 +510,10 @@ func (s *ExchangeRestore) FilterDetails(deets *backup.Details) [][]string { entIncs := exchangeScopesByCategory(s.Includes) entExcs := exchangeScopesByCategory(s.Excludes) - refs := [][]string{} + ents := []backup.DetailsEntry{} for _, ent := range deets.Entries { + // todo: use Path pkg for this path := strings.Split(ent.RepoRef, "/") // not all paths will be len=3. Most should be longer. // This just protects us from panicing four lines later. @@ -513,14 +532,16 @@ func (s *ExchangeRestore) FilterDetails(deets *backup.Details) [][]string { matched := matchExchangeEntry( cat, path, + ent.Exchange, entIncs[cat.String()], entExcs[cat.String()]) if matched { - refs = append(refs, path) + ents = append(ents, ent) } } - return refs + deets.Entries = ents + return deets } // groups each scope by its category of data (contact, event, or mail). @@ -548,10 +569,15 @@ func exchangeScopesByCategory(scopes []map[string]string) map[string][]exchangeS // compare each path to the included and excluded exchange scopes. Returns true // if the path is included, and not excluded. -func matchExchangeEntry(cat exchangeCategory, path []string, incs, excs []exchangeScope) bool { +func matchExchangeEntry( + cat exchangeCategory, + path []string, + info *backup.ExchangeInfo, + incs, excs []exchangeScope, +) bool { var included bool for _, inc := range incs { - if inc.includesPath(cat, path) { + if inc.includesPath(cat, path) || inc.includesInfo(cat, info) { included = true break } @@ -562,7 +588,7 @@ func matchExchangeEntry(cat exchangeCategory, path []string, incs, excs []exchan var excluded bool for _, exc := range excs { - if exc.excludesPath(cat, path) { + if exc.excludesPath(cat, path) || exc.excludesInfo(cat, info) { excluded = true break } diff --git a/src/pkg/selectors/exchange_test.go b/src/pkg/selectors/exchange_test.go index d5b838459..d673552f9 100644 --- a/src/pkg/selectors/exchange_test.go +++ b/src/pkg/selectors/exchange_test.go @@ -1,7 +1,6 @@ package selectors import ( - "strings" "testing" "github.com/alcionai/corso/pkg/backup" @@ -475,6 +474,32 @@ func (suite *ExchangeSourceSuite) TestExchangeScope_Get() { } } +func (suite *ExchangeSourceSuite) TestExchangeScope_IncludesInfo() { + const ( + TODO = "this is a placeholder, awaiting implemenation of filters" + ) + var ( + es = NewExchangeRestore() + ) + + table := []struct { + name string + scope []exchangeScope + info *backup.ExchangeInfo + expect assert.BoolAssertionFunc + }{ + {"all user's items", es.Users(All()), nil, assert.False}, // false while a todo + } + for _, test := range table { + suite.T().Run(test.name, func(t *testing.T) { + scopes := extendExchangeScopeValues(All(), test.scope) + for _, scope := range scopes { + test.expect(t, scope.includesInfo(ExchangeMail, test.info)) + } + }) + } +} + func (suite *ExchangeSourceSuite) TestExchangeScope_IncludesPath() { const ( usr = "userID" @@ -517,6 +542,32 @@ func (suite *ExchangeSourceSuite) TestExchangeScope_IncludesPath() { } } +func (suite *ExchangeSourceSuite) TestExchangeScope_ExcludesInfo() { + const ( + TODO = "this is a placeholder, awaiting implemenation of filters" + ) + var ( + es = NewExchangeRestore() + ) + + table := []struct { + name string + scope []exchangeScope + info *backup.ExchangeInfo + expect assert.BoolAssertionFunc + }{ + {"all user's items", es.Users(All()), nil, assert.False}, // false while a todo + } + for _, test := range table { + suite.T().Run(test.name, func(t *testing.T) { + scopes := extendExchangeScopeValues(None(), test.scope) + for _, scope := range scopes { + test.expect(t, scope.excludesInfo(ExchangeMail, test.info)) + } + }) + } +} + func (suite *ExchangeSourceSuite) TestExchangeScope_ExcludesPath() { const ( usr = "userID" @@ -623,18 +674,14 @@ func (suite *ExchangeSourceSuite) TestExchangeRestore_FilterDetails() { event = "tid/uid/event/eid" mail = "tid/uid/mail/mfld/mid" ) - split := func(s ...string) [][]string { - r := [][]string{} - for _, ss := range s { - r = append(r, strings.Split(ss, "/")) - } - return r + arr := func(s ...string) []string { + return s } table := []struct { name string deets *backup.Details makeSelector func() *ExchangeRestore - expect [][]string + expect []string }{ { "no refs", @@ -644,7 +691,7 @@ func (suite *ExchangeSourceSuite) TestExchangeRestore_FilterDetails() { er.Include(er.Users(All())) return er }, - [][]string{}, + []string{}, }, { "contact only", @@ -654,7 +701,7 @@ func (suite *ExchangeSourceSuite) TestExchangeRestore_FilterDetails() { er.Include(er.Users(All())) return er }, - split(contact), + arr(contact), }, { "event only", @@ -664,7 +711,7 @@ func (suite *ExchangeSourceSuite) TestExchangeRestore_FilterDetails() { er.Include(er.Users(All())) return er }, - split(event), + arr(event), }, { "mail only", @@ -674,7 +721,7 @@ func (suite *ExchangeSourceSuite) TestExchangeRestore_FilterDetails() { er.Include(er.Users(All())) return er }, - split(mail), + arr(mail), }, { "all", @@ -684,7 +731,7 @@ func (suite *ExchangeSourceSuite) TestExchangeRestore_FilterDetails() { er.Include(er.Users(All())) return er }, - split(contact, event, mail), + arr(contact, event, mail), }, { "only match contact", @@ -694,7 +741,7 @@ func (suite *ExchangeSourceSuite) TestExchangeRestore_FilterDetails() { er.Include(er.Contacts([]string{"uid"}, []string{"cfld"}, []string{"cid"})) return er }, - split(contact), + arr(contact), }, { "only match event", @@ -704,7 +751,7 @@ func (suite *ExchangeSourceSuite) TestExchangeRestore_FilterDetails() { er.Include(er.Events([]string{"uid"}, []string{"eid"})) return er }, - split(event), + arr(event), }, { "only match mail", @@ -714,7 +761,7 @@ func (suite *ExchangeSourceSuite) TestExchangeRestore_FilterDetails() { er.Include(er.Mails([]string{"uid"}, []string{"mfld"}, []string{"mid"})) return er }, - split(mail), + arr(mail), }, { "exclude contact", @@ -725,7 +772,7 @@ func (suite *ExchangeSourceSuite) TestExchangeRestore_FilterDetails() { er.Exclude(er.Contacts([]string{"uid"}, []string{"cfld"}, []string{"cid"})) return er }, - split(event, mail), + arr(event, mail), }, { "exclude event", @@ -736,7 +783,7 @@ func (suite *ExchangeSourceSuite) TestExchangeRestore_FilterDetails() { er.Exclude(er.Events([]string{"uid"}, []string{"eid"})) return er }, - split(contact, mail), + arr(contact, mail), }, { "exclude mail", @@ -747,14 +794,15 @@ func (suite *ExchangeSourceSuite) TestExchangeRestore_FilterDetails() { er.Exclude(er.Mails([]string{"uid"}, []string{"mfld"}, []string{"mid"})) return er }, - split(contact, event), + arr(contact, event), }, } for _, test := range table { suite.T().Run(test.name, func(t *testing.T) { sel := test.makeSelector() results := sel.FilterDetails(test.deets) - assert.Equal(t, test.expect, results) + paths := results.Paths() + assert.Equal(t, test.expect, paths) }) } } @@ -804,6 +852,7 @@ func (suite *ExchangeSourceSuite) TestExchangeScopesByCategory() { } func (suite *ExchangeSourceSuite) TestMatchExchangeEntry() { + var TODO_EXCHANGE_INFO *backup.ExchangeInfo const ( mail = "mailID" cat = ExchangeMail @@ -846,7 +895,7 @@ func (suite *ExchangeSourceSuite) TestMatchExchangeEntry() { } for _, test := range table { suite.T().Run(test.name, func(t *testing.T) { - test.expect(t, matchExchangeEntry(cat, path, test.includes, test.excludes)) + test.expect(t, matchExchangeEntry(cat, path, TODO_EXCHANGE_INFO, test.includes, test.excludes)) }) } }