add --all and --data to backup create (#342)

* add --all and --data to backup create

Adds flags for backing up all exchange data, and
for isolating the data in the backup by data type.
Introduces validation and selector creation in
backup create.  Switches the --user flag variable
type from a string to a []string.
This commit is contained in:
Keepers 2022-07-15 10:27:20 -06:00 committed by GitHub
parent bd3b2a8096
commit efaa2da1bb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 253 additions and 10 deletions

View File

@ -15,8 +15,16 @@ import (
// exchange bucket info from flags
var (
user string
backupDetailsID string
exchangeAll bool
exchangeData []string
user []string
)
const (
dataContacts = "contacts"
dataEmail = "email"
dataEvents = "events"
)
// called by backup.go to map parent subcommands to provider-specific handling.
@ -28,12 +36,18 @@ func addExchangeCommands(parent *cobra.Command) *cobra.Command {
switch parent.Use {
case createCommand:
c, fs = utils.AddCommand(parent, exchangeCreateCmd)
fs.StringVar(&user, "user", "", "ID of the user whose Exchange data is to be backed up.")
fs.StringArrayVar(&user, "user", nil, "Back up Exchange data by user ID; accepts "+utils.Wildcard+" to select all users")
fs.BoolVar(&exchangeAll, "all", false, "Back up all Exchange data for all users")
fs.StringArrayVar(
&exchangeData,
"data",
nil,
"Select one or more types of data to backup: "+dataEmail+", "+dataContacts+", or "+dataEvents)
case listCommand:
c, _ = utils.AddCommand(parent, exchangeListCmd)
case detailsCommand:
c, fs = utils.AddCommand(parent, exchangeDetailsCmd)
fs.StringVar(&backupDetailsID, "backup-details", "", "ID of the backup details to be shown.")
fs.StringVar(&backupDetailsID, "backup-details", "", "ID of the backup details to be shown")
cobra.CheckErr(c.MarkFlagRequired("backup-details"))
}
return c
@ -56,6 +70,9 @@ func createExchangeCmd(cmd *cobra.Command, args []string) error {
if utils.HasNoFlagsAndShownHelp(cmd) {
return nil
}
if err := validateBackupCreateFlags(exchangeAll, user, exchangeData); err != nil {
return err
}
s, acct, err := config.GetStorageAndAccount(true, nil)
if err != nil {
@ -79,10 +96,9 @@ func createExchangeCmd(cmd *cobra.Command, args []string) error {
}
defer utils.CloseRepo(ctx, r)
sel := selectors.NewExchangeBackup()
sel.Include(sel.Users(user))
sel := exchangeBackupCreateSelectors(exchangeAll, user, exchangeData)
bo, err := r.NewBackup(ctx, sel.Selector)
bo, err := r.NewBackup(ctx, sel)
if err != nil {
return errors.Wrap(err, "Failed to initialize Exchange backup")
}
@ -97,6 +113,65 @@ func createExchangeCmd(cmd *cobra.Command, args []string) error {
return nil
}
func exchangeBackupCreateSelectors(all bool, users, data []string) selectors.Selector {
sel := selectors.NewExchangeBackup()
if all {
sel.Include(sel.Users(selectors.All))
return sel.Selector
}
if len(data) == 0 {
for _, user := range users {
if user == utils.Wildcard {
user = selectors.All
}
sel.Include(sel.ContactFolders(user, selectors.All))
sel.Include(sel.MailFolders(user, selectors.All))
sel.Include(sel.Events(user, selectors.All))
}
}
for _, d := range data {
switch d {
case dataContacts:
for _, user := range users {
if user == utils.Wildcard {
user = selectors.All
}
sel.Include(sel.ContactFolders(user, selectors.All))
}
case dataEmail:
for _, user := range users {
if user == utils.Wildcard {
user = selectors.All
}
sel.Include(sel.MailFolders(user, selectors.All))
}
case dataEvents:
for _, user := range users {
if user == utils.Wildcard {
user = selectors.All
}
sel.Include(sel.Events(user, selectors.All))
}
}
}
return sel.Selector
}
func validateBackupCreateFlags(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.")
}
if len(data) > 0 && all {
return errors.New("--all backs up all data, and cannot be reduced with --data")
}
for _, d := range data {
if d != dataContacts && d != dataEmail && d != dataEvents {
return errors.New(d + " is an unrecognized data type; must be one of " + dataContacts + ", " + dataEmail + ", or " + dataEvents)
}
}
return nil
}
// `corso backup list exchange [<flag>...]`
var exchangeListCmd = &cobra.Command{
Use: exchangeServiceCommand,

View File

@ -8,6 +8,7 @@ import (
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"github.com/alcionai/corso/cli/utils"
ctesting "github.com/alcionai/corso/internal/testing"
)
@ -49,3 +50,165 @@ func (suite *ExchangeSuite) TestAddExchangeCommands() {
})
}
}
func (suite *ExchangeSuite) TestValidateBackupCreateFlags() {
table := []struct {
name string
all bool
user, data []string
expect assert.ErrorAssertionFunc
}{
{
name: "no users, not all",
expect: assert.Error,
},
{
name: "all and data",
all: true,
data: []string{dataEmail},
expect: assert.Error,
},
{
name: "unrecognized data",
user: []string{"fnord"},
data: []string{"smurfs"},
expect: assert.Error,
},
{
name: "users, not all",
user: []string{"fnord"},
expect: assert.NoError,
},
{
name: "no users, all",
all: true,
expect: assert.NoError,
},
{
name: "users, all",
all: true,
user: []string{"fnord"},
expect: assert.NoError,
},
}
for _, test := range table {
suite.T().Run(test.name, func(t *testing.T) {
test.expect(t, validateBackupCreateFlags(test.all, test.user, test.data))
})
}
}
func (suite *ExchangeSuite) TestExchangeBackupCreateSelectors() {
table := []struct {
name string
all bool
user, data []string
expectIncludeLen int
}{
{
name: "all",
all: true,
expectIncludeLen: 1,
},
{
name: "all users, no data",
user: []string{utils.Wildcard},
expectIncludeLen: 3,
},
{
name: "single user, no data",
user: []string{"u1"},
expectIncludeLen: 3,
},
{
name: "all users, contacts",
user: []string{utils.Wildcard},
data: []string{dataContacts},
expectIncludeLen: 1,
},
{
name: "single user, contacts",
user: []string{"u1"},
data: []string{dataContacts},
expectIncludeLen: 1,
},
{
name: "all users, email",
user: []string{utils.Wildcard},
data: []string{dataEmail},
expectIncludeLen: 1,
},
{
name: "single user, email",
user: []string{"u1"},
data: []string{dataEmail},
expectIncludeLen: 1,
},
{
name: "all users, events",
user: []string{utils.Wildcard},
data: []string{dataEvents},
expectIncludeLen: 1,
},
{
name: "single user, events",
user: []string{"u1"},
data: []string{dataEvents},
expectIncludeLen: 1,
},
{
name: "all users, contacts + email",
user: []string{utils.Wildcard},
data: []string{dataContacts, dataEmail},
expectIncludeLen: 2,
},
{
name: "single user, contacts + email",
user: []string{"u1"},
data: []string{dataContacts, dataEmail},
expectIncludeLen: 2,
},
{
name: "all users, email + events",
user: []string{utils.Wildcard},
data: []string{dataEmail, dataEvents},
expectIncludeLen: 2,
},
{
name: "single user, email + events",
user: []string{"u1"},
data: []string{dataEmail, dataEvents},
expectIncludeLen: 2,
},
{
name: "all users, events + contacts",
user: []string{utils.Wildcard},
data: []string{dataEvents, dataContacts},
expectIncludeLen: 2,
},
{
name: "single user, events + contacts",
user: []string{"u1"},
data: []string{dataEvents, dataContacts},
expectIncludeLen: 2,
},
{
name: "many users, events",
user: []string{"fnord", "smarf"},
data: []string{dataEvents},
expectIncludeLen: 2,
},
{
name: "many users, events + contacts",
user: []string{"fnord", "smarf"},
data: []string{dataEvents, dataContacts},
expectIncludeLen: 4,
},
}
for _, test := range table {
suite.T().Run(test.name, func(t *testing.T) {
sel := exchangeBackupCreateSelectors(test.all, test.user, test.data)
assert.Equal(t, test.expectIncludeLen, len(sel.Includes))
})
}
}

View File

@ -127,10 +127,10 @@ func validateRestoreFlags(u, f, m, rpid string) error {
return errors.New("a restore point ID is requried")
}
lu, lf, lm := len(u), len(f), len(m)
if (lu == 0 || u == "*") && (lf+lm > 0) {
if (lu == 0 || u == utils.Wildcard) && (lf+lm > 0) {
return errors.New("a specific --user must be provided if --folder or --mail is specified")
}
if (lf == 0 || f == "*") && lm > 0 {
if (lf == 0 || f == utils.Wildcard) && lm > 0 {
return errors.New("a specific --folder must be provided if a --mail is specified")
}
return nil

View File

@ -8,6 +8,7 @@ import (
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"github.com/alcionai/corso/cli/utils"
ctesting "github.com/alcionai/corso/internal/testing"
)
@ -27,10 +28,10 @@ func (suite *ExchangeSuite) TestValidateRestoreFlags() {
}{
{"all populated", "u", "f", "m", "rpid", assert.NoError},
{"folder missing user", "", "f", "m", "rpid", assert.Error},
{"folder with wildcard user", "*", "f", "m", "rpid", assert.Error},
{"folder with wildcard user", utils.Wildcard, "f", "m", "rpid", assert.Error},
{"mail missing user", "", "", "m", "rpid", assert.Error},
{"mail missing folder", "u", "", "m", "rpid", assert.Error},
{"mail with wildcard folder", "u", "*", "m", "rpid", assert.Error},
{"mail with wildcard folder", "u", utils.Wildcard, "m", "rpid", assert.Error},
{"missing backup id", "u", "f", "m", "", assert.Error},
{"all missing", "", "", "", "rpid", assert.NoError},
}

View File

@ -10,6 +10,10 @@ import (
"github.com/spf13/pflag"
)
const (
Wildcard = "*"
)
// RequireProps validates the existence of the properties
// in the map. Expects the format map[propName]propVal.
func RequireProps(props map[string]string) error {