add cli commands to delete backups (#641)

This commit is contained in:
Keepers 2022-08-24 12:26:14 -06:00 committed by GitHub
parent db2c1ec8e2
commit f1370b36e4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 196 additions and 9 deletions

View File

@ -4,21 +4,28 @@ import (
"github.com/spf13/cobra"
)
var backupCommands = []func(parent *cobra.Command) *cobra.Command{
var subCommands = []*cobra.Command{
createCmd,
listCmd,
detailsCmd,
deleteCmd,
}
var serviceCommands = []func(parent *cobra.Command) *cobra.Command{
addExchangeCommands,
}
// AddCommands attaches all `corso backup * *` commands to the parent.
func AddCommands(parent *cobra.Command) {
parent.AddCommand(backupCmd)
backupCmd.AddCommand(createCmd)
backupCmd.AddCommand(listCmd)
backupCmd.AddCommand(detailsCmd)
for _, sc := range subCommands {
backupCmd.AddCommand(sc)
}
for _, addBackupTo := range backupCommands {
addBackupTo(createCmd)
addBackupTo(listCmd)
addBackupTo(detailsCmd)
for _, addBackupTo := range serviceCommands {
for _, sc := range subCommands {
addBackupTo(sc)
}
}
}
@ -75,7 +82,7 @@ func handleListCmd(cmd *cobra.Command, args []string) error {
}
// The backup details subcommand.
// `corso backup list <service> [<flag>...]`
// `corso backup details <service> [<flag>...]`
var (
detailsCommand = "details"
detailsCmd = &cobra.Command{
@ -91,3 +98,21 @@ var (
func handleDetailsCmd(cmd *cobra.Command, args []string) error {
return cmd.Help()
}
// The backup delete subcommand.
// `corso backup delete <service> [<flag>...]`
var (
deleteCommand = "delete"
deleteCmd = &cobra.Command{
Use: deleteCommand,
Short: "Deletes a backup for a service",
RunE: handleDeleteCmd,
Args: cobra.NoArgs,
}
)
// Handler for calls to `corso backup delete`.
// Produces the same output as `corso backup delete --help`.
func handleDeleteCmd(cmd *cobra.Command, args []string) error {
return cmd.Help()
}

View File

@ -9,6 +9,7 @@ import (
"github.com/alcionai/corso/cli/options"
. "github.com/alcionai/corso/cli/print"
"github.com/alcionai/corso/cli/utils"
"github.com/alcionai/corso/internal/model"
"github.com/alcionai/corso/pkg/backup"
"github.com/alcionai/corso/pkg/logger"
"github.com/alcionai/corso/pkg/repository"
@ -134,6 +135,11 @@ func addExchangeCommands(parent *cobra.Command) *cobra.Command {
"",
"Select backup details where the email subject lines contain this value",
)
case deleteCommand:
c, fs = utils.AddCommand(parent, exchangeDeleteCmd())
fs.StringVar(&backupID, "backup", "", "ID of the backup containing the details to be shown")
cobra.CheckErr(c.MarkFlagRequired("backup"))
}
return c
@ -506,3 +512,54 @@ func validateExchangeBackupDetailFlags(
}
return nil
}
// ------------------------------------------------------------------------------------------------
// backup delete
// ------------------------------------------------------------------------------------------------
// `corso backup delete exchange [<flag>...]`
func exchangeDeleteCmd() *cobra.Command {
return &cobra.Command{
Use: exchangeServiceCommand,
Short: "Delete backed-up M365 Exchange service data",
RunE: deleteExchangeCmd,
Args: cobra.NoArgs,
}
}
// deletes an exchange service backup.
func deleteExchangeCmd(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
if utils.HasNoFlagsAndShownHelp(cmd) {
return nil
}
s, acct, err := config.GetStorageAndAccount(ctx, true, nil)
if err != nil {
return Only(ctx, err)
}
m365, err := acct.M365Config()
if err != nil {
return Only(ctx, errors.Wrap(err, "Failed to parse m365 account config"))
}
logger.Ctx(ctx).Debugw(
"Called - "+cmd.CommandPath(),
"tenantID", m365.TenantID,
"clientID", m365.ClientID,
"hasClientSecret", len(m365.ClientSecret) > 0)
r, err := repository.Connect(ctx, acct, s)
if err != nil {
return Only(ctx, errors.Wrapf(err, "Failed to connect to the %s repository", s.Provider))
}
defer utils.CloseRepo(ctx, r)
if err := r.DeleteBackup(ctx, model.StableID(backupID)); err != nil {
return Only(ctx, errors.Wrapf(err, "Deleting backup %s", backupID))
}
return nil
}

View File

@ -6,6 +6,7 @@ import (
"testing"
"time"
"github.com/google/uuid"
"github.com/spf13/viper"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@ -217,3 +218,106 @@ func (suite *PreparedBackupExchangeIntegrationSuite) TestExchangeDetailsCmd() {
})
}
}
// ---------------------------------------------------------------------------
// tests for deleting backups
// ---------------------------------------------------------------------------
type BackupDeleteExchangeIntegrationSuite struct {
suite.Suite
acct account.Account
st storage.Storage
vpr *viper.Viper
cfgFP string
repo *repository.Repository
backupOp operations.BackupOperation
}
func TestBackupDeleteExchangeIntegrationSuite(t *testing.T) {
if err := tester.RunOnAny(
tester.CorsoCITests,
tester.CorsoCLITests,
tester.CorsoCLIBackupTests,
); err != nil {
t.Skip(err)
}
suite.Run(t, new(BackupDeleteExchangeIntegrationSuite))
}
func (suite *BackupDeleteExchangeIntegrationSuite) SetupSuite() {
t := suite.T()
_, err := tester.GetRequiredEnvSls(
tester.AWSStorageCredEnvs,
tester.M365AcctCredEnvs)
require.NoError(t, err)
// prepare common details
suite.acct = tester.NewM365Account(t)
suite.st = tester.NewPrefixedS3Storage(t)
cfg, err := suite.st.S3Config()
require.NoError(t, err)
force := map[string]string{
tester.TestCfgAccountProvider: "M365",
tester.TestCfgStorageProvider: "S3",
tester.TestCfgPrefix: cfg.Prefix,
}
suite.vpr, suite.cfgFP, err = tester.MakeTempTestConfigClone(t, force)
require.NoError(t, err)
ctx := config.SetViper(tester.NewContext(), suite.vpr)
// init the repo first
suite.repo, err = repository.Initialize(ctx, suite.acct, suite.st)
require.NoError(t, err)
m365UserID := tester.M365UserID(t)
// some tests require an existing backup
sel := selectors.NewExchangeBackup()
sel.Include(sel.MailFolders([]string{m365UserID}, []string{"Inbox"}))
suite.backupOp, err = suite.repo.NewBackup(
ctx,
sel.Selector,
control.NewOptions(false))
require.NoError(t, suite.backupOp.Run(ctx))
require.NoError(t, err)
}
func (suite *BackupDeleteExchangeIntegrationSuite) TestExchangeBackupDeleteCmd() {
ctx := config.SetViper(tester.NewContext(), suite.vpr)
t := suite.T()
cmd := tester.StubRootCmd(
"backup", "delete", "exchange",
"--config-file", suite.cfgFP,
"--backup", string(suite.backupOp.Results.BackupID))
cli.BuildCommandTree(cmd)
// run the command
require.NoError(t, cmd.ExecuteContext(ctx))
// a follow-up details call should fail, due to the backup ID being deleted
cmd = tester.StubRootCmd(
"backup", "details", "exchange",
"--config-file", suite.cfgFP,
"--backup", string(suite.backupOp.Results.BackupID))
cli.BuildCommandTree(cmd)
require.Error(t, cmd.ExecuteContext(ctx))
}
func (suite *BackupDeleteExchangeIntegrationSuite) TestExchangeBackupDeleteCmd_UnknownID() {
ctx := config.SetViper(tester.NewContext(), suite.vpr)
t := suite.T()
cmd := tester.StubRootCmd(
"backup", "delete", "exchange",
"--config-file", suite.cfgFP,
"--backup", uuid.NewString())
cli.BuildCommandTree(cmd)
// unknown backupIDs should error since the modelStore can't find the backup
require.Error(t, cmd.ExecuteContext(ctx))
}

View File

@ -33,6 +33,7 @@ func (suite *ExchangeSuite) TestAddExchangeCommands() {
{"create exchange", createCommand, expectUse, exchangeCreateCmd().Short, createExchangeCmd},
{"list exchange", listCommand, expectUse, exchangeListCmd().Short, listExchangeCmd},
{"details exchange", detailsCommand, expectUse, exchangeDetailsCmd().Short, detailsExchangeCmd},
{"delete exchange", deleteCommand, expectUse, exchangeDeleteCmd().Short, deleteExchangeCmd},
}
for _, test := range table {
suite.T().Run(test.name, func(t *testing.T) {