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:
parent
a823231afd
commit
6224a92e7a
@ -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
|
||||
}
|
||||
|
||||
@ -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))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user