filter backup details by flags (#371)

* filter backup details by flags

`backup details` should have its output filtered by the flags provided by
the user.  In addition, the selector's FilterDetails should maintain
information (esp service info) about the entries, rather than slicing them
down to only the path reference.
This commit is contained in:
Keepers 2022-07-20 16:25:28 -06:00 committed by GitHub
parent a823231afd
commit 6224a92e7a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 191 additions and 36 deletions

View File

@ -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
}

View File

@ -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))
})
}
}

View File

@ -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

View File

@ -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 {

View File

@ -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())
})
}
}

View File

@ -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
}

View File

@ -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))
})
}
}