allow delete of multiple backup IDs (#4335)

<!-- PR description-->

allow deletion of multiple IDs in single command

#### Does this PR need a docs update or release note?

- [ ]  Yes, it's included
- [ ] 🕐 Yes, but in a later PR
- [ ]  No

#### Type of change

<!--- Please check the type of change your PR introduces: --->
- [ ] 🌻 Feature

#### Issue(s)

<!-- Can reference multiple issues. Use one of the following "magic words" - "closes, fixes" to auto-close the Github issue. -->
* https://github.com/alcionai/corso/issues/4119

#### Test Plan

<!-- How will this be tested prior to merging.-->
- [ ] 💪 Manual
- [ ] 💚 E2E
This commit is contained in:
neha_gupta 2023-09-26 14:23:14 +05:30 committed by GitHub
parent 5c4419fdc0
commit adbc7fe03e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 346 additions and 44 deletions

View File

@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Enables local or network-attached storage for Corso repositories. - Enables local or network-attached storage for Corso repositories.
- Reduce backup runtime for OneDrive and SharePoint incremental backups that have no file changes. - Reduce backup runtime for OneDrive and SharePoint incremental backups that have no file changes.
- Increase Exchange backup performance by lazily fetching data only for items whose content changed. - Increase Exchange backup performance by lazily fetching data only for items whose content changed.
- Added `--backups` flag to delete multiple backups in `corso backup delete` command.
## [v0.13.0] (beta) - 2023-09-18 ## [v0.13.0] (beta) - 2023-09-18

View File

@ -252,8 +252,8 @@ func runBackups(
func genericDeleteCommand( func genericDeleteCommand(
cmd *cobra.Command, cmd *cobra.Command,
pst path.ServiceType, pst path.ServiceType,
bID, designation string, designation string,
args []string, bID, args []string,
) error { ) error {
if utils.HasNoFlagsAndShownHelp(cmd) { if utils.HasNoFlagsAndShownHelp(cmd) {
return nil return nil
@ -275,11 +275,11 @@ func genericDeleteCommand(
defer utils.CloseRepo(ctx, r) defer utils.CloseRepo(ctx, r)
if err := r.DeleteBackups(ctx, true, bID); err != nil { if err := r.DeleteBackups(ctx, true, bID...); err != nil {
return Only(ctx, clues.Wrap(err, "Deleting backup "+bID)) return Only(ctx, clues.Wrap(err, fmt.Sprintf("Deleting backup %v", bID)))
} }
Infof(ctx, "Deleted %s backup %s", designation, bID) Infof(ctx, "Deleted %s backup %v", designation, bID)
return nil return nil
} }

View File

@ -32,7 +32,7 @@ const (
const ( const (
exchangeServiceCommand = "exchange" exchangeServiceCommand = "exchange"
exchangeServiceCommandCreateUseSuffix = "--mailbox <email> | '" + flags.Wildcard + "'" exchangeServiceCommandCreateUseSuffix = "--mailbox <email> | '" + flags.Wildcard + "'"
exchangeServiceCommandDeleteUseSuffix = "--backup <backupId>" exchangeServiceCommandDeleteUseSuffix = "--backups <backupId>"
exchangeServiceCommandDetailsUseSuffix = "--backup <backupId>" exchangeServiceCommandDetailsUseSuffix = "--backup <backupId>"
) )
@ -46,8 +46,9 @@ corso backup create exchange --mailbox alice@example.com,bob@example.com --data
# Backup all Exchange data for all M365 users # Backup all Exchange data for all M365 users
corso backup create exchange --mailbox '*'` corso backup create exchange --mailbox '*'`
exchangeServiceCommandDeleteExamples = `# Delete Exchange backup with ID 1234abcd-12ab-cd34-56de-1234abcd exchangeServiceCommandDeleteExamples = `# Delete Exchange backup with IDs 1234abcd-12ab-cd34-56de-1234abcd \
corso backup delete exchange --backup 1234abcd-12ab-cd34-56de-1234abcd` and 1234abcd-12ab-cd34-56de-1234abce
corso backup delete exchange --backups 1234abcd-12ab-cd34-56de-1234abcd,1234abcd-12ab-cd34-56de-1234abce`
exchangeServiceCommandDetailsExamples = `# Explore items in Alice's latest backup (1234abcd...) exchangeServiceCommandDetailsExamples = `# Explore items in Alice's latest backup (1234abcd...)
corso backup details exchange --backup 1234abcd-12ab-cd34-56de-1234abcd corso backup details exchange --backup 1234abcd-12ab-cd34-56de-1234abcd
@ -121,7 +122,8 @@ func addExchangeCommands(cmd *cobra.Command) *cobra.Command {
c.Use = c.Use + " " + exchangeServiceCommandDeleteUseSuffix c.Use = c.Use + " " + exchangeServiceCommandDeleteUseSuffix
c.Example = exchangeServiceCommandDeleteExamples c.Example = exchangeServiceCommandDeleteExamples
flags.AddBackupIDFlag(c, true) flags.AddMultipleBackupIDsFlag(c, false)
flags.AddBackupIDFlag(c, false)
} }
return c return c
@ -352,5 +354,15 @@ func exchangeDeleteCmd() *cobra.Command {
// deletes an exchange service backup. // deletes an exchange service backup.
func deleteExchangeCmd(cmd *cobra.Command, args []string) error { func deleteExchangeCmd(cmd *cobra.Command, args []string) error {
return genericDeleteCommand(cmd, path.ExchangeService, flags.BackupIDFV, "Exchange", args) var backupIDValue []string
if len(flags.BackupIDsFV) > 0 {
backupIDValue = flags.BackupIDsFV
} else if len(flags.BackupIDFV) > 0 {
backupIDValue = append(backupIDValue, flags.BackupIDFV)
} else {
return clues.New("either --backup or --backups flag is required")
}
return genericDeleteCommand(cmd, path.ExchangeService, "Exchange", backupIDValue, args)
} }

View File

@ -561,8 +561,9 @@ func runExchangeDetailsCmdTest(suite *PreparedBackupExchangeE2ESuite, category p
type BackupDeleteExchangeE2ESuite struct { type BackupDeleteExchangeE2ESuite struct {
tester.Suite tester.Suite
dpnd dependencies dpnd dependencies
backupOp operations.BackupOperation backupOp operations.BackupOperation
secondaryBackupOp operations.BackupOperation
} }
func TestBackupDeleteExchangeE2ESuite(t *testing.T) { func TestBackupDeleteExchangeE2ESuite(t *testing.T) {
@ -595,6 +596,14 @@ func (suite *BackupDeleteExchangeE2ESuite) SetupSuite() {
err = suite.backupOp.Run(ctx) err = suite.backupOp.Run(ctx)
require.NoError(t, err, clues.ToCore(err)) require.NoError(t, err, clues.ToCore(err))
backupOp2, err := suite.dpnd.repo.NewBackup(ctx, sel.Selector)
require.NoError(t, err, clues.ToCore(err))
suite.secondaryBackupOp = backupOp2
err = suite.secondaryBackupOp.Run(ctx)
require.NoError(t, err, clues.ToCore(err))
} }
func (suite *BackupDeleteExchangeE2ESuite) TestExchangeBackupDeleteCmd() { func (suite *BackupDeleteExchangeE2ESuite) TestExchangeBackupDeleteCmd() {
@ -608,7 +617,10 @@ func (suite *BackupDeleteExchangeE2ESuite) TestExchangeBackupDeleteCmd() {
cmd := cliTD.StubRootCmd( cmd := cliTD.StubRootCmd(
"backup", "delete", "exchange", "backup", "delete", "exchange",
"--config-file", suite.dpnd.configFilePath, "--config-file", suite.dpnd.configFilePath,
"--"+flags.BackupFN, string(suite.backupOp.Results.BackupID)) "--"+flags.BackupIDsFN,
fmt.Sprintf("%s,%s",
string(suite.backupOp.Results.BackupID),
string(suite.secondaryBackupOp.Results.BackupID)))
cli.BuildCommandTree(cmd) cli.BuildCommandTree(cmd)
// run the command // run the command
@ -624,6 +636,46 @@ func (suite *BackupDeleteExchangeE2ESuite) TestExchangeBackupDeleteCmd() {
err = cmd.ExecuteContext(ctx) err = cmd.ExecuteContext(ctx)
require.Error(t, err, clues.ToCore(err)) require.Error(t, err, clues.ToCore(err))
// a follow-up details call should fail, due to the backup ID being deleted
cmd = cliTD.StubRootCmd(
"backup", "details", "exchange",
"--config-file", suite.dpnd.configFilePath,
"--backup", string(suite.secondaryBackupOp.Results.BackupID))
cli.BuildCommandTree(cmd)
err = cmd.ExecuteContext(ctx)
require.Error(t, err, clues.ToCore(err))
}
func (suite *BackupDeleteExchangeE2ESuite) TestExchangeBackupDeleteCmd_SingleID() {
t := suite.T()
ctx, flush := tester.NewContext(t)
ctx = config.SetViper(ctx, suite.dpnd.vpr)
defer flush()
cmd := cliTD.StubRootCmd(
"backup", "delete", "exchange",
"--config-file", suite.dpnd.configFilePath,
"--"+flags.BackupFN,
string(suite.backupOp.Results.BackupID))
cli.BuildCommandTree(cmd)
// run the command
err := cmd.ExecuteContext(ctx)
require.NoError(t, err, clues.ToCore(err))
// a follow-up details call should fail, due to the backup ID being deleted
cmd = cliTD.StubRootCmd(
"backup", "details", "exchange",
"--config-file", suite.dpnd.configFilePath,
"--backup", string(suite.secondaryBackupOp.Results.BackupID))
cli.BuildCommandTree(cmd)
err = cmd.ExecuteContext(ctx)
require.Error(t, err, clues.ToCore(err))
} }
func (suite *BackupDeleteExchangeE2ESuite) TestExchangeBackupDeleteCmd_UnknownID() { func (suite *BackupDeleteExchangeE2ESuite) TestExchangeBackupDeleteCmd_UnknownID() {
@ -637,10 +689,28 @@ func (suite *BackupDeleteExchangeE2ESuite) TestExchangeBackupDeleteCmd_UnknownID
cmd := cliTD.StubRootCmd( cmd := cliTD.StubRootCmd(
"backup", "delete", "exchange", "backup", "delete", "exchange",
"--config-file", suite.dpnd.configFilePath, "--config-file", suite.dpnd.configFilePath,
"--"+flags.BackupFN, uuid.NewString()) "--"+flags.BackupIDsFN, uuid.NewString())
cli.BuildCommandTree(cmd) cli.BuildCommandTree(cmd)
// unknown backupIDs should error since the modelStore can't find the backup // unknown backupIDs should error since the modelStore can't find the backup
err := cmd.ExecuteContext(ctx) err := cmd.ExecuteContext(ctx)
require.Error(t, err, clues.ToCore(err)) require.Error(t, err, clues.ToCore(err))
} }
func (suite *BackupDeleteExchangeE2ESuite) TestExchangeBackupDeleteCmd_NoBackupID() {
t := suite.T()
ctx, flush := tester.NewContext(t)
ctx = config.SetViper(ctx, suite.dpnd.vpr)
defer flush()
cmd := cliTD.StubRootCmd(
"backup", "delete", "exchange",
"--config-file", suite.dpnd.configFilePath)
cli.BuildCommandTree(cmd)
// empty backupIDs should error since no data provided
err := cmd.ExecuteContext(ctx)
require.Error(t, err, clues.ToCore(err))
}

View File

@ -32,7 +32,7 @@ const (
groupsServiceCommand = "groups" groupsServiceCommand = "groups"
teamsServiceCommand = "teams" teamsServiceCommand = "teams"
groupsServiceCommandCreateUseSuffix = "--group <groupName> | '" + flags.Wildcard + "'" groupsServiceCommandCreateUseSuffix = "--group <groupName> | '" + flags.Wildcard + "'"
groupsServiceCommandDeleteUseSuffix = "--backup <backupId>" groupsServiceCommandDeleteUseSuffix = "--backups <backupId>"
groupsServiceCommandDetailsUseSuffix = "--backup <backupId>" groupsServiceCommandDetailsUseSuffix = "--backup <backupId>"
) )
@ -46,8 +46,9 @@ corso backup create groups --group Marketing --data messages
# Backup all Groups and Teams data for all groups # Backup all Groups and Teams data for all groups
corso backup create groups --group '*'` corso backup create groups --group '*'`
groupsServiceCommandDeleteExamples = `# Delete Groups backup with ID 1234abcd-12ab-cd34-56de-1234abcd groupsServiceCommandDeleteExamples = `# Delete Groups backup with ID 1234abcd-12ab-cd34-56de-1234abcd \
corso backup delete groups --backup 1234abcd-12ab-cd34-56de-1234abcd` and 1234abcd-12ab-cd34-56de-1234abce
corso backup delete groups --backups 1234abcd-12ab-cd34-56de-1234abcd,1234abcd-12ab-cd34-56de-1234abce`
groupsServiceCommandDetailsExamples = `# Explore items in Marketing's latest backup (1234abcd...) groupsServiceCommandDetailsExamples = `# Explore items in Marketing's latest backup (1234abcd...)
corso backup details groups --backup 1234abcd-12ab-cd34-56de-1234abcd corso backup details groups --backup 1234abcd-12ab-cd34-56de-1234abcd
@ -110,7 +111,8 @@ func addGroupsCommands(cmd *cobra.Command) *cobra.Command {
c.Use = c.Use + " " + groupsServiceCommandDeleteUseSuffix c.Use = c.Use + " " + groupsServiceCommandDeleteUseSuffix
c.Example = groupsServiceCommandDeleteExamples c.Example = groupsServiceCommandDeleteExamples
flags.AddBackupIDFlag(c, true) flags.AddMultipleBackupIDsFlag(c, false)
flags.AddBackupIDFlag(c, false)
} }
return c return c
@ -305,7 +307,17 @@ func groupsDeleteCmd() *cobra.Command {
// deletes an groups service backup. // deletes an groups service backup.
func deleteGroupsCmd(cmd *cobra.Command, args []string) error { func deleteGroupsCmd(cmd *cobra.Command, args []string) error {
return genericDeleteCommand(cmd, path.GroupsService, flags.BackupIDFV, "Groups", args) backupIDValue := []string{}
if len(flags.BackupIDsFV) > 0 {
backupIDValue = flags.BackupIDsFV
} else if len(flags.BackupIDFV) > 0 {
backupIDValue = append(backupIDValue, flags.BackupIDFV)
} else {
return clues.New("either --backup or --backups flag is required")
}
return genericDeleteCommand(cmd, path.GroupsService, "Groups", backupIDValue, args)
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------

View File

@ -497,8 +497,9 @@ func runGroupsDetailsCmdTest(suite *PreparedBackupGroupsE2ESuite, category path.
type BackupDeleteGroupsE2ESuite struct { type BackupDeleteGroupsE2ESuite struct {
tester.Suite tester.Suite
dpnd dependencies dpnd dependencies
backupOp operations.BackupOperation backupOp operations.BackupOperation
secondaryBackupOp operations.BackupOperation
} }
func TestBackupDeleteGroupsE2ESuite(t *testing.T) { func TestBackupDeleteGroupsE2ESuite(t *testing.T) {
@ -531,6 +532,15 @@ func (suite *BackupDeleteGroupsE2ESuite) SetupSuite() {
err = suite.backupOp.Run(ctx) err = suite.backupOp.Run(ctx)
require.NoError(t, err, clues.ToCore(err)) require.NoError(t, err, clues.ToCore(err))
// secondary backup
secondaryBackupOp, err := suite.dpnd.repo.NewBackup(ctx, sel.Selector)
require.NoError(t, err, clues.ToCore(err))
suite.secondaryBackupOp = secondaryBackupOp
err = suite.secondaryBackupOp.Run(ctx)
require.NoError(t, err, clues.ToCore(err))
} }
func (suite *BackupDeleteGroupsE2ESuite) TestGroupsBackupDeleteCmd() { func (suite *BackupDeleteGroupsE2ESuite) TestGroupsBackupDeleteCmd() {
@ -544,7 +554,40 @@ func (suite *BackupDeleteGroupsE2ESuite) TestGroupsBackupDeleteCmd() {
cmd := cliTD.StubRootCmd( cmd := cliTD.StubRootCmd(
"backup", "delete", "groups", "backup", "delete", "groups",
"--config-file", suite.dpnd.configFilePath, "--config-file", suite.dpnd.configFilePath,
"--"+flags.BackupFN, string(suite.backupOp.Results.BackupID)) "--"+flags.BackupIDsFN,
fmt.Sprintf("%s,%s",
string(suite.backupOp.Results.BackupID),
string(suite.secondaryBackupOp.Results.BackupID)))
cli.BuildCommandTree(cmd)
// run the command
err := cmd.ExecuteContext(ctx)
require.NoError(t, err, clues.ToCore(err))
// a follow-up details call should fail, due to the backup ID being deleted
cmd = cliTD.StubRootCmd(
"backup", "details", "groups",
"--config-file", suite.dpnd.configFilePath,
"--backups", string(suite.backupOp.Results.BackupID))
cli.BuildCommandTree(cmd)
err = cmd.ExecuteContext(ctx)
require.Error(t, err, clues.ToCore(err))
}
func (suite *BackupDeleteGroupsE2ESuite) TestGroupsBackupDeleteCmd_SingleID() {
t := suite.T()
ctx, flush := tester.NewContext(t)
ctx = config.SetViper(ctx, suite.dpnd.vpr)
defer flush()
cmd := cliTD.StubRootCmd(
"backup", "delete", "groups",
"--config-file", suite.dpnd.configFilePath,
"--"+flags.BackupFN,
string(suite.backupOp.Results.BackupID))
cli.BuildCommandTree(cmd) cli.BuildCommandTree(cmd)
// run the command // run the command
@ -573,7 +616,7 @@ func (suite *BackupDeleteGroupsE2ESuite) TestGroupsBackupDeleteCmd_UnknownID() {
cmd := cliTD.StubRootCmd( cmd := cliTD.StubRootCmd(
"backup", "delete", "groups", "backup", "delete", "groups",
"--config-file", suite.dpnd.configFilePath, "--config-file", suite.dpnd.configFilePath,
"--"+flags.BackupFN, uuid.NewString()) "--"+flags.BackupIDsFN, uuid.NewString())
cli.BuildCommandTree(cmd) cli.BuildCommandTree(cmd)
// unknown backupIDs should error since the modelStore can't find the backup // unknown backupIDs should error since the modelStore can't find the backup
@ -581,6 +624,24 @@ func (suite *BackupDeleteGroupsE2ESuite) TestGroupsBackupDeleteCmd_UnknownID() {
require.Error(t, err, clues.ToCore(err)) require.Error(t, err, clues.ToCore(err))
} }
func (suite *BackupDeleteGroupsE2ESuite) TestGroupsBackupDeleteCmd_NoBackupID() {
t := suite.T()
ctx, flush := tester.NewContext(t)
ctx = config.SetViper(ctx, suite.dpnd.vpr)
defer flush()
cmd := cliTD.StubRootCmd(
"backup", "delete", "groups",
"--config-file", suite.dpnd.configFilePath)
cli.BuildCommandTree(cmd)
// empty backupIDs should error since no data provided
err := cmd.ExecuteContext(ctx)
require.Error(t, err, clues.ToCore(err))
}
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// helpers // helpers
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------

View File

@ -26,7 +26,7 @@ import (
const ( const (
oneDriveServiceCommand = "onedrive" oneDriveServiceCommand = "onedrive"
oneDriveServiceCommandCreateUseSuffix = "--user <email> | '" + flags.Wildcard + "'" oneDriveServiceCommandCreateUseSuffix = "--user <email> | '" + flags.Wildcard + "'"
oneDriveServiceCommandDeleteUseSuffix = "--backup <backupId>" oneDriveServiceCommandDeleteUseSuffix = "--backups <backupId>"
oneDriveServiceCommandDetailsUseSuffix = "--backup <backupId>" oneDriveServiceCommandDetailsUseSuffix = "--backup <backupId>"
) )
@ -40,8 +40,9 @@ corso backup create onedrive --user alice@example.com,bob@example.com
# Backup all OneDrive data for all M365 users # Backup all OneDrive data for all M365 users
corso backup create onedrive --user '*'` corso backup create onedrive --user '*'`
oneDriveServiceCommandDeleteExamples = `# Delete OneDrive backup with ID 1234abcd-12ab-cd34-56de-1234abcd oneDriveServiceCommandDeleteExamples = `# Delete OneDrive backup with ID 1234abcd-12ab-cd34-56de-1234abcd \
corso backup delete onedrive --backup 1234abcd-12ab-cd34-56de-1234abcd` and 1234abcd-12ab-cd34-56de-1234abce
corso backup delete onedrive --backups 1234abcd-12ab-cd34-56de-1234abcd,1234abcd-12ab-cd34-56de-1234abce`
oneDriveServiceCommandDetailsExamples = `# Explore items in Bob's latest backup (1234abcd...) oneDriveServiceCommandDetailsExamples = `# Explore items in Bob's latest backup (1234abcd...)
corso backup details onedrive --backup 1234abcd-12ab-cd34-56de-1234abcd corso backup details onedrive --backup 1234abcd-12ab-cd34-56de-1234abcd
@ -100,7 +101,8 @@ func addOneDriveCommands(cmd *cobra.Command) *cobra.Command {
c.Use = c.Use + " " + oneDriveServiceCommandDeleteUseSuffix c.Use = c.Use + " " + oneDriveServiceCommandDeleteUseSuffix
c.Example = oneDriveServiceCommandDeleteExamples c.Example = oneDriveServiceCommandDeleteExamples
flags.AddBackupIDFlag(c, true) flags.AddMultipleBackupIDsFlag(c, false)
flags.AddBackupIDFlag(c, false)
} }
return c return c
@ -306,5 +308,15 @@ func oneDriveDeleteCmd() *cobra.Command {
// deletes a oneDrive service backup. // deletes a oneDrive service backup.
func deleteOneDriveCmd(cmd *cobra.Command, args []string) error { func deleteOneDriveCmd(cmd *cobra.Command, args []string) error {
return genericDeleteCommand(cmd, path.OneDriveService, flags.BackupIDFV, "OneDrive", args) backupIDValue := []string{}
if len(flags.BackupIDsFV) > 0 {
backupIDValue = flags.BackupIDsFV
} else if len(flags.BackupIDFV) > 0 {
backupIDValue = append(backupIDValue, flags.BackupIDFV)
} else {
return clues.New("either --backup or --backups flag is required")
}
return genericDeleteCommand(cmd, path.OneDriveService, "OneDrive", backupIDValue, args)
} }

View File

@ -121,8 +121,9 @@ func (suite *NoBackupOneDriveE2ESuite) TestOneDriveBackupCmd_userNotInTenant() {
type BackupDeleteOneDriveE2ESuite struct { type BackupDeleteOneDriveE2ESuite struct {
tester.Suite tester.Suite
dpnd dependencies dpnd dependencies
backupOp operations.BackupOperation backupOp operations.BackupOperation
secondaryBackupOp operations.BackupOperation
} }
func TestBackupDeleteOneDriveE2ESuite(t *testing.T) { func TestBackupDeleteOneDriveE2ESuite(t *testing.T) {
@ -158,6 +159,15 @@ func (suite *BackupDeleteOneDriveE2ESuite) SetupSuite() {
err = suite.backupOp.Run(ctx) err = suite.backupOp.Run(ctx)
require.NoError(t, err, clues.ToCore(err)) require.NoError(t, err, clues.ToCore(err))
// secondary backup
secondaryBackupOp, err := suite.dpnd.repo.NewBackupWithLookup(ctx, sel.Selector, ins)
require.NoError(t, err, clues.ToCore(err))
suite.secondaryBackupOp = secondaryBackupOp
err = suite.secondaryBackupOp.Run(ctx)
require.NoError(t, err, clues.ToCore(err))
} }
func (suite *BackupDeleteOneDriveE2ESuite) TestOneDriveBackupDeleteCmd() { func (suite *BackupDeleteOneDriveE2ESuite) TestOneDriveBackupDeleteCmd() {
@ -173,7 +183,10 @@ func (suite *BackupDeleteOneDriveE2ESuite) TestOneDriveBackupDeleteCmd() {
cmd := cliTD.StubRootCmd( cmd := cliTD.StubRootCmd(
"backup", "delete", "onedrive", "backup", "delete", "onedrive",
"--config-file", suite.dpnd.configFilePath, "--config-file", suite.dpnd.configFilePath,
"--"+flags.BackupFN, string(suite.backupOp.Results.BackupID)) "--"+flags.BackupIDsFN,
fmt.Sprintf("%s,%s",
string(suite.backupOp.Results.BackupID),
string(suite.secondaryBackupOp.Results.BackupID)))
cli.BuildCommandTree(cmd) cli.BuildCommandTree(cmd)
cmd.SetErr(&suite.dpnd.recorder) cmd.SetErr(&suite.dpnd.recorder)
@ -187,7 +200,51 @@ func (suite *BackupDeleteOneDriveE2ESuite) TestOneDriveBackupDeleteCmd() {
assert.True(t, assert.True(t,
strings.HasSuffix( strings.HasSuffix(
result, result,
fmt.Sprintf("Deleted OneDrive backup %s\n", string(suite.backupOp.Results.BackupID)))) fmt.Sprintf("Deleted OneDrive backup [%s %s]\n",
string(suite.backupOp.Results.BackupID),
string(suite.secondaryBackupOp.Results.BackupID))))
// a follow-up details call should fail, due to the backup ID being deleted
cmd = cliTD.StubRootCmd(
"backup", "details", "onedrive",
"--config-file", suite.dpnd.configFilePath,
"--backups", string(suite.backupOp.Results.BackupID))
cli.BuildCommandTree(cmd)
err = cmd.ExecuteContext(ctx)
require.Error(t, err, clues.ToCore(err))
}
func (suite *BackupDeleteOneDriveE2ESuite) TestOneDriveBackupDeleteCmd_SingleID() {
t := suite.T()
ctx, flush := tester.NewContext(t)
ctx = config.SetViper(ctx, suite.dpnd.vpr)
defer flush()
suite.dpnd.recorder.Reset()
cmd := cliTD.StubRootCmd(
"backup", "delete", "onedrive",
"--config-file", suite.dpnd.configFilePath,
"--"+flags.BackupFN,
string(suite.backupOp.Results.BackupID))
cli.BuildCommandTree(cmd)
cmd.SetErr(&suite.dpnd.recorder)
ctx = print.SetRootCmd(ctx, cmd)
// run the command
err := cmd.ExecuteContext(ctx)
require.NoError(t, err, clues.ToCore(err))
result := suite.dpnd.recorder.String()
assert.True(t,
strings.HasSuffix(
result,
fmt.Sprintf("Deleted OneDrive backup [%s]\n",
string(suite.backupOp.Results.BackupID))))
// a follow-up details call should fail, due to the backup ID being deleted // a follow-up details call should fail, due to the backup ID being deleted
cmd = cliTD.StubRootCmd( cmd = cliTD.StubRootCmd(
@ -211,10 +268,28 @@ func (suite *BackupDeleteOneDriveE2ESuite) TestOneDriveBackupDeleteCmd_unknownID
cmd := cliTD.StubRootCmd( cmd := cliTD.StubRootCmd(
"backup", "delete", "onedrive", "backup", "delete", "onedrive",
"--config-file", suite.dpnd.configFilePath, "--config-file", suite.dpnd.configFilePath,
"--"+flags.BackupFN, uuid.NewString()) "--"+flags.BackupIDsFN, uuid.NewString())
cli.BuildCommandTree(cmd) cli.BuildCommandTree(cmd)
// unknown backupIDs should error since the modelStore can't find the backup // unknown backupIDs should error since the modelStore can't find the backup
err := cmd.ExecuteContext(ctx) err := cmd.ExecuteContext(ctx)
require.Error(t, err, clues.ToCore(err)) require.Error(t, err, clues.ToCore(err))
} }
func (suite *BackupDeleteOneDriveE2ESuite) TestOneDriveBackupDeleteCmd_NoBackupID() {
t := suite.T()
ctx, flush := tester.NewContext(t)
ctx = config.SetViper(ctx, suite.dpnd.vpr)
defer flush()
cmd := cliTD.StubRootCmd(
"backup", "delete", "onedrive",
"--config-file", suite.dpnd.configFilePath)
cli.BuildCommandTree(cmd)
// empty backupIDs should error since no data provided
err := cmd.ExecuteContext(ctx)
require.Error(t, err, clues.ToCore(err))
}

View File

@ -30,7 +30,7 @@ import (
const ( const (
sharePointServiceCommand = "sharepoint" sharePointServiceCommand = "sharepoint"
sharePointServiceCommandCreateUseSuffix = "--site <siteURL> | '" + flags.Wildcard + "'" sharePointServiceCommandCreateUseSuffix = "--site <siteURL> | '" + flags.Wildcard + "'"
sharePointServiceCommandDeleteUseSuffix = "--backup <backupId>" sharePointServiceCommandDeleteUseSuffix = "--backups <backupId>"
sharePointServiceCommandDetailsUseSuffix = "--backup <backupId>" sharePointServiceCommandDetailsUseSuffix = "--backup <backupId>"
) )
@ -44,8 +44,9 @@ corso backup create sharepoint --site https://example.com/hr,https://example.com
# Backup all SharePoint data for all Sites # Backup all SharePoint data for all Sites
corso backup create sharepoint --site '*'` corso backup create sharepoint --site '*'`
sharePointServiceCommandDeleteExamples = `# Delete SharePoint backup with ID 1234abcd-12ab-cd34-56de-1234abcd sharePointServiceCommandDeleteExamples = `# Delete SharePoint backup with ID 1234abcd-12ab-cd34-56de-1234abcd \
corso backup delete sharepoint --backup 1234abcd-12ab-cd34-56de-1234abcd` and 1234abcd-12ab-cd34-56de-1234abce
corso backup delete sharepoint --backups 1234abcd-12ab-cd34-56de-1234abcd,1234abcd-12ab-cd34-56de-1234abce`
sharePointServiceCommandDetailsExamples = `# Explore items in the HR site's latest backup (1234abcd...) sharePointServiceCommandDetailsExamples = `# Explore items in the HR site's latest backup (1234abcd...)
corso backup details sharepoint --backup 1234abcd-12ab-cd34-56de-1234abcd corso backup details sharepoint --backup 1234abcd-12ab-cd34-56de-1234abcd
@ -111,7 +112,8 @@ func addSharePointCommands(cmd *cobra.Command) *cobra.Command {
c.Use = c.Use + " " + sharePointServiceCommandDeleteUseSuffix c.Use = c.Use + " " + sharePointServiceCommandDeleteUseSuffix
c.Example = sharePointServiceCommandDeleteExamples c.Example = sharePointServiceCommandDeleteExamples
flags.AddBackupIDFlag(c, true) flags.AddMultipleBackupIDsFlag(c, false)
flags.AddBackupIDFlag(c, false)
} }
return c return c
@ -284,7 +286,17 @@ func sharePointDeleteCmd() *cobra.Command {
// deletes a sharePoint service backup. // deletes a sharePoint service backup.
func deleteSharePointCmd(cmd *cobra.Command, args []string) error { func deleteSharePointCmd(cmd *cobra.Command, args []string) error {
return genericDeleteCommand(cmd, path.SharePointService, flags.BackupIDFV, "SharePoint", args) backupIDValue := []string{}
if len(flags.BackupIDsFV) > 0 {
backupIDValue = flags.BackupIDsFV
} else if len(flags.BackupIDFV) > 0 {
backupIDValue = append(backupIDValue, flags.BackupIDFV)
} else {
return clues.New("either --backup or --backups flag is required")
}
return genericDeleteCommand(cmd, path.SharePointService, "SharePoint", backupIDValue, args)
} }
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------

View File

@ -84,8 +84,9 @@ func (suite *NoBackupSharePointE2ESuite) TestSharePointBackupListCmd_empty() {
type BackupDeleteSharePointE2ESuite struct { type BackupDeleteSharePointE2ESuite struct {
tester.Suite tester.Suite
dpnd dependencies dpnd dependencies
backupOp operations.BackupOperation backupOp operations.BackupOperation
secondaryBackupOp operations.BackupOperation
} }
func TestBackupDeleteSharePointE2ESuite(t *testing.T) { func TestBackupDeleteSharePointE2ESuite(t *testing.T) {
@ -121,6 +122,15 @@ func (suite *BackupDeleteSharePointE2ESuite) SetupSuite() {
err = suite.backupOp.Run(ctx) err = suite.backupOp.Run(ctx)
require.NoError(t, err, clues.ToCore(err)) require.NoError(t, err, clues.ToCore(err))
// secondary backup
secondaryBackupOp, err := suite.dpnd.repo.NewBackupWithLookup(ctx, sel.Selector, ins)
require.NoError(t, err, clues.ToCore(err))
suite.secondaryBackupOp = secondaryBackupOp
err = suite.secondaryBackupOp.Run(ctx)
require.NoError(t, err, clues.ToCore(err))
} }
func (suite *BackupDeleteSharePointE2ESuite) TestSharePointBackupDeleteCmd() { func (suite *BackupDeleteSharePointE2ESuite) TestSharePointBackupDeleteCmd() {
@ -136,7 +146,10 @@ func (suite *BackupDeleteSharePointE2ESuite) TestSharePointBackupDeleteCmd() {
cmd := cliTD.StubRootCmd( cmd := cliTD.StubRootCmd(
"backup", "delete", "sharepoint", "backup", "delete", "sharepoint",
"--config-file", suite.dpnd.configFilePath, "--config-file", suite.dpnd.configFilePath,
"--"+flags.BackupFN, string(suite.backupOp.Results.BackupID)) "--"+flags.BackupIDsFN,
fmt.Sprintf("%s,%s",
string(suite.backupOp.Results.BackupID),
string(suite.secondaryBackupOp.Results.BackupID)))
cli.BuildCommandTree(cmd) cli.BuildCommandTree(cmd)
cmd.SetErr(&suite.dpnd.recorder) cmd.SetErr(&suite.dpnd.recorder)
@ -150,7 +163,9 @@ func (suite *BackupDeleteSharePointE2ESuite) TestSharePointBackupDeleteCmd() {
assert.True(t, assert.True(t,
strings.HasSuffix( strings.HasSuffix(
result, result,
fmt.Sprintf("Deleted SharePoint backup %s\n", string(suite.backupOp.Results.BackupID)))) fmt.Sprintf("Deleted SharePoint backup [%s %s]\n",
string(suite.backupOp.Results.BackupID),
string(suite.secondaryBackupOp.Results.BackupID))))
} }
// moved out of the func above to make the linter happy // moved out of the func above to make the linter happy
@ -175,10 +190,28 @@ func (suite *BackupDeleteSharePointE2ESuite) TestSharePointBackupDeleteCmd_unkno
cmd := cliTD.StubRootCmd( cmd := cliTD.StubRootCmd(
"backup", "delete", "sharepoint", "backup", "delete", "sharepoint",
"--config-file", suite.dpnd.configFilePath, "--config-file", suite.dpnd.configFilePath,
"--"+flags.BackupFN, uuid.NewString()) "--"+flags.BackupIDsFN, uuid.NewString())
cli.BuildCommandTree(cmd) cli.BuildCommandTree(cmd)
// unknown backupIDs should error since the modelStore can't find the backup // unknown backupIDs should error since the modelStore can't find the backup
err := cmd.ExecuteContext(ctx) err := cmd.ExecuteContext(ctx)
require.Error(t, err, clues.ToCore(err)) require.Error(t, err, clues.ToCore(err))
} }
func (suite *BackupDeleteSharePointE2ESuite) TestSharePointBackupDeleteCmd_NoBackupID() {
t := suite.T()
ctx, flush := tester.NewContext(t)
ctx = config.SetViper(ctx, suite.dpnd.vpr)
defer flush()
cmd := cliTD.StubRootCmd(
"backup", "delete", "groups",
"--config-file", suite.dpnd.configFilePath)
cli.BuildCommandTree(cmd)
// empty backupIDs should error since no data provided
err := cmd.ExecuteContext(ctx)
require.Error(t, err, clues.ToCore(err))
}

View File

@ -6,6 +6,7 @@ import (
const ( const (
BackupFN = "backup" BackupFN = "backup"
BackupIDsFN = "backups"
AWSAccessKeyFN = "aws-access-key" AWSAccessKeyFN = "aws-access-key"
AWSSecretAccessKeyFN = "aws-secret-access-key" AWSSecretAccessKeyFN = "aws-secret-access-key"
AWSSessionTokenFN = "aws-session-token" AWSSessionTokenFN = "aws-session-token"
@ -17,6 +18,7 @@ const (
var ( var (
BackupIDFV string BackupIDFV string
BackupIDsFV []string
AWSAccessKeyFV string AWSAccessKeyFV string
AWSSecretAccessKeyFV string AWSSecretAccessKeyFV string
AWSSessionTokenFV string AWSSessionTokenFV string
@ -24,6 +26,18 @@ var (
SucceedIfExistsFV bool SucceedIfExistsFV bool
) )
// AddMultipleBackupIDsFlag adds the --backups flag.
func AddMultipleBackupIDsFlag(cmd *cobra.Command, require bool) {
cmd.Flags().StringSliceVar(
&BackupIDsFV,
BackupIDsFN, nil,
"',' separated IDs of the backup to retrieve")
if require {
cobra.CheckErr(cmd.MarkFlagRequired(BackupIDsFN))
}
}
// AddBackupIDFlag adds the --backup flag. // AddBackupIDFlag adds the --backup flag.
func AddBackupIDFlag(cmd *cobra.Command, require bool) { func AddBackupIDFlag(cmd *cobra.Command, require bool) {
cmd.Flags().StringVar(&BackupIDFV, BackupFN, "", "ID of the backup to retrieve.") cmd.Flags().StringVar(&BackupIDFV, BackupFN, "", "ID of the backup to retrieve.")

View File

@ -46,7 +46,7 @@ const (
corso restore groups --backup 1234abcd-12ab-cd34-56de-1234abcd --file 98765abcdef corso restore groups --backup 1234abcd-12ab-cd34-56de-1234abcd --file 98765abcdef
# Restore the file with ID 98765abcdef without its associated permissions # Restore the file with ID 98765abcdef without its associated permissions
corso restore groups --backup 1234abcd-12ab-cd34-56de-1234abcd --file 98765abcdef --skip-permissions corso restore groups --backup 1234abcd-12ab-cd34-56de-1234abcd --file 98765abcdef --no-permissions
# Restore all files named "FY2021 Planning.xlsx" # Restore all files named "FY2021 Planning.xlsx"
corso restore groups --backup 1234abcd-12ab-cd34-56de-1234abcd --file "FY2021 Planning.xlsx" corso restore groups --backup 1234abcd-12ab-cd34-56de-1234abcd --file "FY2021 Planning.xlsx"