From acb468d0f6ef1bb6295957590c586b26cb5bdf0e Mon Sep 17 00:00:00 2001 From: Vaibhav Kamra Date: Fri, 8 Jul 2022 16:16:34 -0700 Subject: [PATCH] Implement `backup details` CLI (#306) Returns RestorePointDetails Closes #297 --- src/cli/backup/backup.go | 18 +++++++++++ src/cli/backup/exchange.go | 53 +++++++++++++++++++++++++++++++ src/cli/backup/exchange_test.go | 1 + src/internal/operations/backup.go | 1 - src/pkg/repository/repository.go | 11 +++++++ 5 files changed, 83 insertions(+), 1 deletion(-) diff --git a/src/cli/backup/backup.go b/src/cli/backup/backup.go index efc8ea6c6..71558d5ed 100644 --- a/src/cli/backup/backup.go +++ b/src/cli/backup/backup.go @@ -13,10 +13,12 @@ func AddCommands(parent *cobra.Command) { parent.AddCommand(backupCmd) backupCmd.AddCommand(createCmd) backupCmd.AddCommand(listCmd) + backupCmd.AddCommand(detailsCmd) for _, addBackupTo := range backupCommands { addBackupTo(createCmd) addBackupTo(listCmd) + addBackupTo(detailsCmd) } } @@ -67,3 +69,19 @@ var listCmd = &cobra.Command{ func handleListCmd(cmd *cobra.Command, args []string) { cmd.Help() } + +// The backup details subcommand. +// `corso backup list [...]` +var detailsCommand = "details" +var detailsCmd = &cobra.Command{ + Use: detailsCommand, + Short: "Shows the details of a restore point for a service", + Run: handleDetailsCmd, + Args: cobra.NoArgs, +} + +// Handler for calls to `corso backup details`. +// Produces the same output as `corso backup details --help`. +func handleDetailsCmd(cmd *cobra.Command, args []string) { + cmd.Help() +} diff --git a/src/cli/backup/exchange.go b/src/cli/backup/exchange.go index 1c5eb5f6b..610ba3dd9 100644 --- a/src/cli/backup/exchange.go +++ b/src/cli/backup/exchange.go @@ -18,6 +18,7 @@ import ( // exchange bucket info from flags var ( user string + rpID string ) // called by backup.go to map parent subcommands to provider-specific handling. @@ -32,6 +33,10 @@ func addExchangeCommands(parent *cobra.Command) *cobra.Command { fs.StringVar(&user, "user", "", "ID of the user whose Exchange data is to be backed up.") case listCommand: c, _ = utils.AddCommand(parent, exchangeListCmd) + case detailsCommand: + c, fs = utils.AddCommand(parent, exchangeDetailsCmd) + fs.StringVar(&rpID, "restore-point-details", "", "ID of the restore point details to be shown.") + c.MarkFlagRequired("restore-point-details") } return c } @@ -142,3 +147,51 @@ func listExchangeCmd(cmd *cobra.Command, args []string) error { } return nil } + +// `corso backup details exchange [...]` +var exchangeDetailsCmd = &cobra.Command{ + Use: exchangeServiceCommand, + Short: "Shows the details of a M365 Exchange service backup", + RunE: detailsExchangeCmd, + Args: cobra.NoArgs, +} + +// lists the history of backup operations +func detailsExchangeCmd(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + + s, acct, err := config.GetStorageAndAccount(true, nil) + if err != nil { + return err + } + + m365, err := acct.M365Config() + if err != nil { + return errors.Wrap(err, "Failed to parse m365 account config") + } + + logger.Ctx(ctx).Debugw( + "Called - "+cmd.CommandPath(), + "tenantID", m365.TenantID) + + r, err := repository.Connect(ctx, acct, s) + if err != nil { + return errors.Wrapf(err, "Failed to connect to the %s repository", s.Provider) + } + defer utils.CloseRepo(ctx, r) + + rpd, err := r.RestorePointDetails(ctx, rpID) + if err != nil { + return errors.Wrap(err, "Failed to get restorepoint details in the repository") + } + + // TODO: Can be used to print in alternative forms + p, err := cli.Format("json", os.Stdout) + if err != nil { + return err + } + defer p.Flush() + p.Print(*rpd) + + return nil +} diff --git a/src/cli/backup/exchange_test.go b/src/cli/backup/exchange_test.go index 90fb37ad8..8cacad8ee 100644 --- a/src/cli/backup/exchange_test.go +++ b/src/cli/backup/exchange_test.go @@ -30,6 +30,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}, } for _, test := range table { suite.T().Run(test.name, func(t *testing.T) { diff --git a/src/internal/operations/backup.go b/src/internal/operations/backup.go index ac8764ad4..d5238bc00 100644 --- a/src/internal/operations/backup.go +++ b/src/internal/operations/backup.go @@ -104,7 +104,6 @@ func (op *BackupOperation) Run(ctx context.Context) error { stats.writeErr = err return err } - return nil } diff --git a/src/pkg/repository/repository.go b/src/pkg/repository/repository.go index 2f52c6bf1..fbf377857 100644 --- a/src/pkg/repository/repository.go +++ b/src/pkg/repository/repository.go @@ -5,6 +5,7 @@ import ( "time" "github.com/google/uuid" + "github.com/kopia/kopia/repo/manifest" "github.com/pkg/errors" "github.com/alcionai/corso/internal/kopia" @@ -165,3 +166,13 @@ func (r Repository) RestorePoints(ctx context.Context) ([]*restorepoint.RestoreP } return rps, nil } + +// RestorePoints lists restorepoints in a respository +func (r Repository) RestorePointDetails(ctx context.Context, rpDetailsID string) (*restorepoint.Details, error) { + rpd := restorepoint.Details{} + err := r.modelStore.GetWithModelStoreID(ctx, kopia.RestorePointDetailsModel, manifest.ID(rpDetailsID), &rpd) + if err != nil { + return nil, err + } + return &rpd, nil +}