corso/src/cli/backup/onedrive.go
Keepers 2341d61842
introduce id-name lookup maps (#2955)
Adds two maps to resource-owner handling:
id-to-name and name-to-id.  Expectation is that
these maps will either get populated by a caller
as a pre-process before initializing the gc client, or
gc will (later pr) be able to look up the owner and
populate those maps itself.  The maps are
used to set the selector id and name for iface
compliance.  Only supported by exchange in this PR.

---

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

- [x]  No

#### Type of change

- [x] 🌻 Feature

#### Issue(s)

* #2825

#### Test Plan

- [x]  Unit test
- [x] 💚 E2E
2023-04-04 20:21:58 +00:00

304 lines
8.5 KiB
Go

package backup
import (
"context"
"github.com/alcionai/clues"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/alcionai/corso/src/cli/options"
. "github.com/alcionai/corso/src/cli/print"
"github.com/alcionai/corso/src/cli/utils"
"github.com/alcionai/corso/src/internal/data"
"github.com/alcionai/corso/src/pkg/backup/details"
"github.com/alcionai/corso/src/pkg/fault"
"github.com/alcionai/corso/src/pkg/path"
"github.com/alcionai/corso/src/pkg/repository"
"github.com/alcionai/corso/src/pkg/selectors"
"github.com/alcionai/corso/src/pkg/services/m365"
)
// ------------------------------------------------------------------------------------------------
// setup and globals
// ------------------------------------------------------------------------------------------------
const (
oneDriveServiceCommand = "onedrive"
oneDriveServiceCommandCreateUseSuffix = "--user <email> | '" + utils.Wildcard + "'"
oneDriveServiceCommandDeleteUseSuffix = "--backup <backupId>"
oneDriveServiceCommandDetailsUseSuffix = "--backup <backupId>"
)
const (
oneDriveServiceCommandCreateExamples = `# Backup OneDrive data for Alice
corso backup create onedrive --user alice@example.com
# Backup OneDrive for Alice and Bob
corso backup create onedrive --user alice@example.com,bob@example.com
# Backup all OneDrive data for all M365 users
corso backup create onedrive --user '*'`
oneDriveServiceCommandDeleteExamples = `# Delete OneDrive backup with ID 1234abcd-12ab-cd34-56de-1234abcd
corso backup delete onedrive --backup 1234abcd-12ab-cd34-56de-1234abcd`
oneDriveServiceCommandDetailsExamples = `# Explore Alice's files from backup 1234abcd-12ab-cd34-56de-1234abcd
corso backup details onedrive --backup 1234abcd-12ab-cd34-56de-1234abcd --user alice@example.com
# Explore Alice or Bob's files with name containing "Fiscal 22" in folder "Reports"
corso backup details onedrive --backup 1234abcd-12ab-cd34-56de-1234abcd \
--user alice@example.com,bob@example.com --file-name "Fiscal 22" --folder "Reports"
# Explore Alice's files created before end of 2015 from a specific backup
corso backup details onedrive --backup 1234abcd-12ab-cd34-56de-1234abcd \
--user alice@example.com --file-created-before 2015-01-01T00:00:00`
)
// called by backup.go to map subcommands to provider-specific handling.
func addOneDriveCommands(cmd *cobra.Command) *cobra.Command {
var (
c *cobra.Command
fs *pflag.FlagSet
)
switch cmd.Use {
case createCommand:
c, fs = utils.AddCommand(cmd, oneDriveCreateCmd())
fs.SortFlags = false
options.AddFeatureToggle(cmd)
c.Use = c.Use + " " + oneDriveServiceCommandCreateUseSuffix
c.Example = oneDriveServiceCommandCreateExamples
utils.AddUserFlag(c)
options.AddOperationFlags(c)
case listCommand:
c, fs = utils.AddCommand(cmd, oneDriveListCmd())
fs.SortFlags = false
utils.AddBackupIDFlag(c, false)
addFailedItemsFN(c)
addSkippedItemsFN(c)
addRecoveredErrorsFN(c)
case detailsCommand:
c, fs = utils.AddCommand(cmd, oneDriveDetailsCmd())
fs.SortFlags = false
c.Use = c.Use + " " + oneDriveServiceCommandDetailsUseSuffix
c.Example = oneDriveServiceCommandDetailsExamples
options.AddSkipReduceFlag(c)
utils.AddBackupIDFlag(c, true)
utils.AddOneDriveDetailsAndRestoreFlags(c)
case deleteCommand:
c, fs = utils.AddCommand(cmd, oneDriveDeleteCmd())
fs.SortFlags = false
c.Use = c.Use + " " + oneDriveServiceCommandDeleteUseSuffix
c.Example = oneDriveServiceCommandDeleteExamples
utils.AddBackupIDFlag(c, true)
}
return c
}
// ------------------------------------------------------------------------------------------------
// backup create
// ------------------------------------------------------------------------------------------------
// `corso backup create onedrive [<flag>...]`
func oneDriveCreateCmd() *cobra.Command {
return &cobra.Command{
Use: oneDriveServiceCommand,
Short: "Backup M365 OneDrive service data",
RunE: createOneDriveCmd,
Args: cobra.NoArgs,
Example: oneDriveServiceCommandCreateExamples,
}
}
// processes an onedrive service backup.
func createOneDriveCmd(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
if utils.HasNoFlagsAndShownHelp(cmd) {
return nil
}
if err := validateOneDriveBackupCreateFlags(utils.UserFV); err != nil {
return err
}
r, acct, err := getAccountAndConnect(ctx)
if err != nil {
return Only(ctx, err)
}
defer utils.CloseRepo(ctx, r)
sel := oneDriveBackupCreateSelectors(utils.UserFV)
// TODO: log/print recoverable errors
errs := fault.New(false)
ins, err := m365.UsersMap(ctx, *acct, errs)
if err != nil {
return Only(ctx, clues.Wrap(err, "Failed to retrieve M365 users"))
}
selectorSet := []selectors.Selector{}
for _, discSel := range sel.SplitByResourceOwner(ins.IDs()) {
selectorSet = append(selectorSet, discSel.Selector)
}
return runBackups(
ctx,
r,
"OneDrive", "user",
selectorSet,
ins)
}
func validateOneDriveBackupCreateFlags(users []string) error {
if len(users) == 0 {
return clues.New("requires one or more --user ids or the wildcard --user *")
}
return nil
}
func oneDriveBackupCreateSelectors(users []string) *selectors.OneDriveBackup {
sel := selectors.NewOneDriveBackup(users)
sel.Include(sel.AllData())
return sel
}
// ------------------------------------------------------------------------------------------------
// backup list
// ------------------------------------------------------------------------------------------------
// `corso backup list onedrive [<flag>...]`
func oneDriveListCmd() *cobra.Command {
return &cobra.Command{
Use: oneDriveServiceCommand,
Short: "List the history of M365 OneDrive service backups",
RunE: listOneDriveCmd,
Args: cobra.NoArgs,
}
}
// lists the history of backup operations
func listOneDriveCmd(cmd *cobra.Command, args []string) error {
return genericListCommand(cmd, utils.BackupIDFV, path.OneDriveService, args)
}
// ------------------------------------------------------------------------------------------------
// backup details
// ------------------------------------------------------------------------------------------------
// `corso backup details onedrive [<flag>...]`
func oneDriveDetailsCmd() *cobra.Command {
return &cobra.Command{
Use: oneDriveServiceCommand,
Short: "Shows the details of a M365 OneDrive service backup",
RunE: detailsOneDriveCmd,
Args: cobra.NoArgs,
Example: oneDriveServiceCommandDetailsExamples,
}
}
// prints the item details for a given backup
func detailsOneDriveCmd(cmd *cobra.Command, args []string) error {
if utils.HasNoFlagsAndShownHelp(cmd) {
return nil
}
ctx := cmd.Context()
opts := utils.MakeOneDriveOpts(cmd)
r, _, err := getAccountAndConnect(ctx)
if err != nil {
return Only(ctx, err)
}
defer utils.CloseRepo(ctx, r)
ctrlOpts := options.Control()
ds, err := runDetailsOneDriveCmd(ctx, r, utils.BackupIDFV, opts, ctrlOpts.SkipReduce)
if err != nil {
return Only(ctx, err)
}
if len(ds.Entries) == 0 {
Info(ctx, selectors.ErrorNoMatchingItems)
return nil
}
ds.PrintEntries(ctx)
return nil
}
// runDetailsOneDriveCmd actually performs the lookup in backup details.
// the fault.Errors return is always non-nil. Callers should check if
// errs.Failure() == nil.
func runDetailsOneDriveCmd(
ctx context.Context,
r repository.BackupGetter,
backupID string,
opts utils.OneDriveOpts,
skipReduce bool,
) (*details.Details, error) {
if err := utils.ValidateOneDriveRestoreFlags(backupID, opts); err != nil {
return nil, err
}
ctx = clues.Add(ctx, "backup_id", backupID)
d, _, errs := r.GetBackupDetails(ctx, backupID)
// TODO: log/track recoverable errors
if errs.Failure() != nil {
if errors.Is(errs.Failure(), data.ErrNotFound) {
return nil, clues.New("no backup exists with the id " + backupID)
}
return nil, clues.Wrap(errs.Failure(), "Failed to get backup details in the repository")
}
ctx = clues.Add(ctx, "details_entries", len(d.Entries))
if !skipReduce {
sel := utils.IncludeOneDriveRestoreDataSelectors(opts)
utils.FilterOneDriveRestoreInfoSelectors(sel, opts)
d = sel.Reduce(ctx, d, errs)
}
return d, nil
}
// `corso backup delete onedrive [<flag>...]`
func oneDriveDeleteCmd() *cobra.Command {
return &cobra.Command{
Use: oneDriveServiceCommand,
Short: "Delete backed-up M365 OneDrive service data",
RunE: deleteOneDriveCmd,
Args: cobra.NoArgs,
Example: oneDriveServiceCommandDeleteExamples,
}
}
// deletes a oneDrive service backup.
func deleteOneDriveCmd(cmd *cobra.Command, args []string) error {
return genericDeleteCommand(cmd, utils.BackupIDFV, "OneDrive", args)
}