Split backups by (resource owner, service) at CLI layer (#1609)

## Description

Sketch of how to split backups by (resource owner, service) for exchange. Needs more support from selectors package to be complete.

## Type of change

<!--- Please check the type of change your PR introduces: --->
- [x] 🌻 Feature
- [ ] 🐛 Bugfix
- [ ] 🗺️ Documentation
- [ ] 🤖 Test
- [ ] 💻 CI/Deployment
- [ ] 🐹 Trivial/Minor

## Issue(s)

* closes #1505

## Test Plan

<!-- How will this be tested prior to merging.-->
- [x] 💪 Manual
- [ ]  Unit test
- [ ] 💚 E2E
This commit is contained in:
ashmrtn 2022-11-30 13:50:49 -08:00 committed by GitHub
parent c4333ad531
commit 27d0980526
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 155 additions and 29 deletions

View File

@ -3,6 +3,7 @@ package backup
import ( import (
"context" "context"
"github.com/hashicorp/go-multierror"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/pflag" "github.com/spf13/pflag"
@ -19,6 +20,7 @@ import (
"github.com/alcionai/corso/src/pkg/path" "github.com/alcionai/corso/src/pkg/path"
"github.com/alcionai/corso/src/pkg/repository" "github.com/alcionai/corso/src/pkg/repository"
"github.com/alcionai/corso/src/pkg/selectors" "github.com/alcionai/corso/src/pkg/selectors"
"github.com/alcionai/corso/src/pkg/services/m365"
"github.com/alcionai/corso/src/pkg/store" "github.com/alcionai/corso/src/pkg/store"
) )
@ -270,27 +272,62 @@ func createExchangeCmd(cmd *cobra.Command, args []string) error {
sel := exchangeBackupCreateSelectors(user, exchangeData) sel := exchangeBackupCreateSelectors(user, exchangeData)
bo, err := r.NewBackup(ctx, sel) users, err := m365.UserIDs(ctx, acct)
if err != nil { if err != nil {
return Only(ctx, errors.Wrap(err, "Failed to initialize Exchange backup")) return Only(ctx, errors.Wrap(err, "Failed to retrieve M365 users"))
} }
err = bo.Run(ctx) var (
if err != nil { errs *multierror.Error
return Only(ctx, errors.Wrap(err, "Failed to run Exchange backup")) bIDs []model.StableID
)
for _, scope := range sel.DiscreteScopes(users) {
for _, selUser := range scope.Get(selectors.ExchangeUser) {
opSel := selectors.NewExchangeBackup()
opSel.Include([]selectors.ExchangeScope{scope.DiscreteCopy(selUser)})
bo, err := r.NewBackup(ctx, opSel.Selector)
if err != nil {
errs = multierror.Append(errs, errors.Wrapf(
err,
"Failed to initialize Exchange backup for user %s",
scope.Get(selectors.ExchangeUser),
))
continue
}
err = bo.Run(ctx)
if err != nil {
errs = multierror.Append(errs, errors.Wrapf(
err,
"Failed to run Exchange backup for user %s",
scope.Get(selectors.ExchangeUser),
))
continue
}
bIDs = append(bIDs, bo.Results.BackupID)
}
} }
bu, err := r.Backup(ctx, bo.Results.BackupID) bups, err := r.Backups(ctx, bIDs)
if err != nil { if err != nil {
return Only(ctx, errors.Wrap(err, "Unable to retrieve backup results from storage")) return Only(ctx, errors.Wrap(err, "Unable to retrieve backup results from storage"))
} }
bu.Print(ctx) backup.PrintAll(ctx, bups)
if e := errs.ErrorOrNil(); e != nil {
return Only(ctx, e)
}
return nil return nil
} }
func exchangeBackupCreateSelectors(userIDs, data []string) selectors.Selector { func exchangeBackupCreateSelectors(userIDs, data []string) *selectors.ExchangeBackup {
sel := selectors.NewExchangeBackup() sel := selectors.NewExchangeBackup()
if len(data) == 0 { if len(data) == 0 {
@ -310,7 +347,7 @@ func exchangeBackupCreateSelectors(userIDs, data []string) selectors.Selector {
} }
} }
return sel.Selector return sel
} }
func validateExchangeBackupCreateFlags(userIDs, data []string) error { func validateExchangeBackupCreateFlags(userIDs, data []string) error {

View File

@ -3,6 +3,7 @@ package backup
import ( import (
"context" "context"
"github.com/hashicorp/go-multierror"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/pflag" "github.com/spf13/pflag"
@ -18,6 +19,7 @@ import (
"github.com/alcionai/corso/src/pkg/path" "github.com/alcionai/corso/src/pkg/path"
"github.com/alcionai/corso/src/pkg/repository" "github.com/alcionai/corso/src/pkg/repository"
"github.com/alcionai/corso/src/pkg/selectors" "github.com/alcionai/corso/src/pkg/selectors"
"github.com/alcionai/corso/src/pkg/services/m365"
"github.com/alcionai/corso/src/pkg/store" "github.com/alcionai/corso/src/pkg/store"
) )
@ -192,22 +194,57 @@ func createOneDriveCmd(cmd *cobra.Command, args []string) error {
sel := oneDriveBackupCreateSelectors(user) sel := oneDriveBackupCreateSelectors(user)
bo, err := r.NewBackup(ctx, sel) users, err := m365.UserIDs(ctx, acct)
if err != nil { if err != nil {
return Only(ctx, errors.Wrap(err, "Failed to initialize OneDrive backup")) return Only(ctx, errors.Wrap(err, "Failed to retrieve M365 users"))
} }
err = bo.Run(ctx) var (
if err != nil { errs *multierror.Error
return Only(ctx, errors.Wrap(err, "Failed to run OneDrive backup")) bIDs []model.StableID
)
for _, scope := range sel.DiscreteScopes(users) {
for _, selUser := range scope.Get(selectors.OneDriveUser) {
opSel := selectors.NewOneDriveBackup()
opSel.Include([]selectors.OneDriveScope{scope.DiscreteCopy(selUser)})
bo, err := r.NewBackup(ctx, opSel.Selector)
if err != nil {
errs = multierror.Append(errs, errors.Wrapf(
err,
"Failed to initialize OneDrive backup for user %s",
scope.Get(selectors.OneDriveUser),
))
continue
}
err = bo.Run(ctx)
if err != nil {
errs = multierror.Append(errs, errors.Wrapf(
err,
"Failed to run OneDrive backup for user %s",
scope.Get(selectors.OneDriveUser),
))
continue
}
bIDs = append(bIDs, bo.Results.BackupID)
}
} }
bu, err := r.Backup(ctx, bo.Results.BackupID) bups, err := r.Backups(ctx, bIDs)
if err != nil { if err != nil {
return errors.Wrap(err, "Unable to retrieve backup results from storage") return Only(ctx, errors.Wrap(err, "Unable to retrieve backup results from storage"))
} }
bu.Print(ctx) backup.PrintAll(ctx, bups)
if e := errs.ErrorOrNil(); e != nil {
return Only(ctx, e)
}
return nil return nil
} }
@ -220,11 +257,11 @@ func validateOneDriveBackupCreateFlags(users []string) error {
return nil return nil
} }
func oneDriveBackupCreateSelectors(users []string) selectors.Selector { func oneDriveBackupCreateSelectors(users []string) *selectors.OneDriveBackup {
sel := selectors.NewOneDriveBackup() sel := selectors.NewOneDriveBackup()
sel.Include(sel.Users(users)) sel.Include(sel.Users(users))
return sel.Selector return sel
} }
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------

View File

@ -3,6 +3,7 @@ package backup
import ( import (
"context" "context"
"github.com/hashicorp/go-multierror"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/pflag" "github.com/spf13/pflag"
@ -18,6 +19,7 @@ import (
"github.com/alcionai/corso/src/pkg/path" "github.com/alcionai/corso/src/pkg/path"
"github.com/alcionai/corso/src/pkg/repository" "github.com/alcionai/corso/src/pkg/repository"
"github.com/alcionai/corso/src/pkg/selectors" "github.com/alcionai/corso/src/pkg/selectors"
"github.com/alcionai/corso/src/pkg/services/m365"
"github.com/alcionai/corso/src/pkg/store" "github.com/alcionai/corso/src/pkg/store"
) )
@ -181,22 +183,57 @@ func createSharePointCmd(cmd *cobra.Command, args []string) error {
sel := sharePointBackupCreateSelectors(site) sel := sharePointBackupCreateSelectors(site)
bo, err := r.NewBackup(ctx, sel) sites, err := m365.Sites(ctx, acct)
if err != nil { if err != nil {
return Only(ctx, errors.Wrap(err, "Failed to initialize SharePoint backup")) return Only(ctx, errors.Wrap(err, "Failed to retrieve SharePoint sites"))
} }
err = bo.Run(ctx) var (
if err != nil { errs *multierror.Error
return Only(ctx, errors.Wrap(err, "Failed to run SharePoint backup")) bIDs []model.StableID
)
for _, scope := range sel.DiscreteScopes(sites) {
for _, selSite := range scope.Get(selectors.SharePointSite) {
opSel := selectors.NewSharePointBackup()
opSel.Include([]selectors.SharePointScope{scope.DiscreteCopy(selSite)})
bo, err := r.NewBackup(ctx, opSel.Selector)
if err != nil {
errs = multierror.Append(errs, errors.Wrapf(
err,
"Failed to initialize SharePoint backup for site %s",
scope.Get(selectors.SharePointSite),
))
continue
}
err = bo.Run(ctx)
if err != nil {
errs = multierror.Append(errs, errors.Wrapf(
err,
"Failed to run SharePoint backup for site %s",
scope.Get(selectors.SharePointSite),
))
continue
}
bIDs = append(bIDs, bo.Results.BackupID)
}
} }
bu, err := r.Backup(ctx, bo.Results.BackupID) bups, err := r.Backups(ctx, bIDs)
if err != nil { if err != nil {
return errors.Wrap(err, "Unable to retrieve backup results from storage") return Only(ctx, errors.Wrap(err, "Unable to retrieve backup results from storage"))
} }
bu.Print(ctx) backup.PrintAll(ctx, bups)
if e := errs.ErrorOrNil(); e != nil {
return Only(ctx, e)
}
return nil return nil
} }
@ -209,11 +246,11 @@ func validateSharePointBackupCreateFlags(sites []string) error {
return nil return nil
} }
func sharePointBackupCreateSelectors(sites []string) selectors.Selector { func sharePointBackupCreateSelectors(sites []string) *selectors.SharePointBackup {
sel := selectors.NewSharePointBackup() sel := selectors.NewSharePointBackup()
sel.Include(sel.Sites(sites)) sel.Include(sel.Sites(sites))
return sel.Selector return sel
} }
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------

View File

@ -104,6 +104,10 @@ func NewGraphConnector(ctx context.Context, acct account.Account, r resource) (*
gc.graphService = *aService gc.graphService = *aService
// TODO(ashmrtn): When selectors only encapsulate a single resource owner that
// is not a wildcard don't populate users or sites when making the connector.
// For now this keeps things functioning if callers do pass in a selector like
// "*" instead of.
if r == AllResources || r == Users { if r == AllResources || r == Users {
if err = gc.setTenantUsers(ctx); err != nil { if err = gc.setTenantUsers(ctx); err != nil {
return nil, errors.Wrap(err, "retrieving tenant user list") return nil, errors.Wrap(err, "retrieving tenant user list")

View File

@ -39,3 +39,14 @@ func GetEmailAndUserID(ctx context.Context, m365Account account.Account) (map[st
return gc.Users, nil return gc.Users, nil
} }
// Sites returns a list of SharePoint sites in the specified M365 tenant
// TODO: Implement paging support
func Sites(ctx context.Context, m365Account account.Account) ([]string, error) {
gc, err := connector.NewGraphConnector(ctx, m365Account, connector.Sites)
if err != nil {
return nil, errors.Wrap(err, "could not initialize M365 graph connection")
}
return gc.GetSites(), nil
}