diff --git a/src/cli/backup/exchange.go b/src/cli/backup/exchange.go index aca5a3d4b..1271a55ce 100644 --- a/src/cli/backup/exchange.go +++ b/src/cli/backup/exchange.go @@ -3,6 +3,7 @@ package backup import ( "context" + "github.com/hashicorp/go-multierror" "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/spf13/pflag" @@ -19,6 +20,7 @@ import ( "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" "github.com/alcionai/corso/src/pkg/store" ) @@ -270,27 +272,62 @@ func createExchangeCmd(cmd *cobra.Command, args []string) error { sel := exchangeBackupCreateSelectors(user, exchangeData) - bo, err := r.NewBackup(ctx, sel) + users, err := m365.UserIDs(ctx, acct) 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) - if err != nil { - return Only(ctx, errors.Wrap(err, "Failed to run Exchange backup")) + var ( + errs *multierror.Error + 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 { 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 } -func exchangeBackupCreateSelectors(userIDs, data []string) selectors.Selector { +func exchangeBackupCreateSelectors(userIDs, data []string) *selectors.ExchangeBackup { sel := selectors.NewExchangeBackup() 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 { diff --git a/src/cli/backup/onedrive.go b/src/cli/backup/onedrive.go index f5af193da..7ad598c58 100644 --- a/src/cli/backup/onedrive.go +++ b/src/cli/backup/onedrive.go @@ -3,6 +3,7 @@ package backup import ( "context" + "github.com/hashicorp/go-multierror" "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/spf13/pflag" @@ -18,6 +19,7 @@ import ( "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" "github.com/alcionai/corso/src/pkg/store" ) @@ -192,22 +194,57 @@ func createOneDriveCmd(cmd *cobra.Command, args []string) error { sel := oneDriveBackupCreateSelectors(user) - bo, err := r.NewBackup(ctx, sel) + users, err := m365.UserIDs(ctx, acct) 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) - if err != nil { - return Only(ctx, errors.Wrap(err, "Failed to run OneDrive backup")) + var ( + errs *multierror.Error + 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 { - 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 } @@ -220,11 +257,11 @@ func validateOneDriveBackupCreateFlags(users []string) error { return nil } -func oneDriveBackupCreateSelectors(users []string) selectors.Selector { +func oneDriveBackupCreateSelectors(users []string) *selectors.OneDriveBackup { sel := selectors.NewOneDriveBackup() sel.Include(sel.Users(users)) - return sel.Selector + return sel } // ------------------------------------------------------------------------------------------------ diff --git a/src/cli/backup/sharepoint.go b/src/cli/backup/sharepoint.go index a379c21c5..d6ce2b292 100644 --- a/src/cli/backup/sharepoint.go +++ b/src/cli/backup/sharepoint.go @@ -3,6 +3,7 @@ package backup import ( "context" + "github.com/hashicorp/go-multierror" "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/spf13/pflag" @@ -18,6 +19,7 @@ import ( "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" "github.com/alcionai/corso/src/pkg/store" ) @@ -181,22 +183,57 @@ func createSharePointCmd(cmd *cobra.Command, args []string) error { sel := sharePointBackupCreateSelectors(site) - bo, err := r.NewBackup(ctx, sel) + sites, err := m365.Sites(ctx, acct) 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) - if err != nil { - return Only(ctx, errors.Wrap(err, "Failed to run SharePoint backup")) + var ( + errs *multierror.Error + 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 { - 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 } @@ -209,11 +246,11 @@ func validateSharePointBackupCreateFlags(sites []string) error { return nil } -func sharePointBackupCreateSelectors(sites []string) selectors.Selector { +func sharePointBackupCreateSelectors(sites []string) *selectors.SharePointBackup { sel := selectors.NewSharePointBackup() sel.Include(sel.Sites(sites)) - return sel.Selector + return sel } // ------------------------------------------------------------------------------------------------ diff --git a/src/internal/connector/graph_connector.go b/src/internal/connector/graph_connector.go index 4edbafb75..6230d3b5a 100644 --- a/src/internal/connector/graph_connector.go +++ b/src/internal/connector/graph_connector.go @@ -104,6 +104,10 @@ func NewGraphConnector(ctx context.Context, acct account.Account, r resource) (* 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 err = gc.setTenantUsers(ctx); err != nil { return nil, errors.Wrap(err, "retrieving tenant user list") diff --git a/src/pkg/services/m365/m365.go b/src/pkg/services/m365/m365.go index 53ea636be..6415b456b 100644 --- a/src/pkg/services/m365/m365.go +++ b/src/pkg/services/m365/m365.go @@ -39,3 +39,14 @@ func GetEmailAndUserID(ctx context.Context, m365Account account.Account) (map[st 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 +}