adds sharepoint restore cli commands (#1637)

## Description

Adds restore commands to the cli for sharepoint.
The restore process is only partially functional at this time.
Library files that pass auth are able to be restored as expected.
However, auth issues (not directly related to these changes) prevent
restoration of all library items at this time.

## Type of change

- [x] 🌻 Feature

## Issue(s)

* #1615

## Test Plan

- [x] 💪 Manual
- [x] 💚 E2E
This commit is contained in:
Keepers 2022-12-06 10:00:37 -07:00 committed by GitHub
parent a9d7f0fd27
commit 7e2fbbea4f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 274 additions and 62 deletions

View File

@ -11,16 +11,16 @@ var subCommandFuncs = []func() *cobra.Command{
deleteCmd,
}
var serviceCommands = []func(parent *cobra.Command) *cobra.Command{
var serviceCommands = []func(cmd *cobra.Command) *cobra.Command{
addExchangeCommands,
addOneDriveCommands,
addSharePointCommands,
}
// AddCommands attaches all `corso backup * *` commands to the parent.
func AddCommands(parent *cobra.Command) {
func AddCommands(cmd *cobra.Command) {
backupC := backupCmd()
parent.AddCommand(backupC)
cmd.AddCommand(backupC)
for _, sc := range subCommandFuncs {
subCommand := sc()

View File

@ -96,16 +96,16 @@ corso backup details exchange --backup 1234abcd-12ab-cd34-56de-1234abcd \
--user alice@example.com --contact-name Andy`
)
// called by backup.go to map parent subcommands to provider-specific handling.
func addExchangeCommands(parent *cobra.Command) *cobra.Command {
// called by backup.go to map subcommands to provider-specific handling.
func addExchangeCommands(cmd *cobra.Command) *cobra.Command {
var (
c *cobra.Command
fs *pflag.FlagSet
)
switch parent.Use {
switch cmd.Use {
case createCommand:
c, fs = utils.AddCommand(parent, exchangeCreateCmd())
c, fs = utils.AddCommand(cmd, exchangeCreateCmd())
c.Use = c.Use + " " + exchangeServiceCommandCreateUseSuffix
c.Example = exchangeServiceCommandCreateExamples
@ -123,14 +123,14 @@ func addExchangeCommands(parent *cobra.Command) *cobra.Command {
options.AddOperationFlags(c)
case listCommand:
c, fs = utils.AddCommand(parent, exchangeListCmd())
c, fs = utils.AddCommand(cmd, exchangeListCmd())
fs.StringVar(&backupID,
"backup", "",
"ID of the backup to retrieve.")
case detailsCommand:
c, fs = utils.AddCommand(parent, exchangeDetailsCmd())
c, fs = utils.AddCommand(cmd, exchangeDetailsCmd())
c.Use = c.Use + " " + exchangeServiceCommandDetailsUseSuffix
c.Example = exchangeServiceCommandDetailsExamples
@ -218,7 +218,7 @@ func addExchangeCommands(parent *cobra.Command) *cobra.Command {
"Select backup details for contacts whose contact name contains this value.")
case deleteCommand:
c, fs = utils.AddCommand(parent, exchangeDeleteCmd())
c, fs = utils.AddCommand(cmd, exchangeDeleteCmd())
c.Use = c.Use + " " + exchangeServiceCommandDeleteUseSuffix
c.Example = exchangeServiceCommandDeleteExamples

View File

@ -69,16 +69,16 @@ var (
fileModifiedBefore string
)
// called by backup.go to map parent subcommands to provider-specific handling.
func addOneDriveCommands(parent *cobra.Command) *cobra.Command {
// 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 parent.Use {
switch cmd.Use {
case createCommand:
c, fs = utils.AddCommand(parent, oneDriveCreateCmd())
c, fs = utils.AddCommand(cmd, oneDriveCreateCmd())
c.Use = c.Use + " " + oneDriveServiceCommandCreateUseSuffix
c.Example = oneDriveServiceCommandCreateExamples
@ -89,14 +89,14 @@ func addOneDriveCommands(parent *cobra.Command) *cobra.Command {
options.AddOperationFlags(c)
case listCommand:
c, fs = utils.AddCommand(parent, oneDriveListCmd())
c, fs = utils.AddCommand(cmd, oneDriveListCmd())
fs.StringVar(&backupID,
utils.BackupFN, "",
"ID of the backup to retrieve.")
case detailsCommand:
c, fs = utils.AddCommand(parent, oneDriveDetailsCmd())
c, fs = utils.AddCommand(cmd, oneDriveDetailsCmd())
c.Use = c.Use + " " + oneDriveServiceCommandDetailsUseSuffix
c.Example = oneDriveServiceCommandDetailsExamples
@ -139,7 +139,7 @@ func addOneDriveCommands(parent *cobra.Command) *cobra.Command {
"Select backup details for files modified before this datetime.")
case deleteCommand:
c, fs = utils.AddCommand(parent, oneDriveDeleteCmd())
c, fs = utils.AddCommand(cmd, oneDriveDeleteCmd())
c.Use = c.Use + " " + oneDriveServiceCommandDeleteUseSuffix
c.Example = oneDriveServiceCommandDeleteExamples

View File

@ -66,16 +66,16 @@ corso backup delete sharepoint --backup 1234abcd-12ab-cd34-56de-1234abcd`
corso backup details sharepoint --backup 1234abcd-12ab-cd34-56de-1234abcd --site <site_id>`
)
// called by backup.go to map parent subcommands to provider-specific handling.
func addSharePointCommands(parent *cobra.Command) *cobra.Command {
// called by backup.go to map subcommands to provider-specific handling.
func addSharePointCommands(cmd *cobra.Command) *cobra.Command {
var (
c *cobra.Command
fs *pflag.FlagSet
)
switch parent.Use {
switch cmd.Use {
case createCommand:
c, fs = utils.AddCommand(parent, sharePointCreateCmd(), utils.HideCommand())
c, fs = utils.AddCommand(cmd, sharePointCreateCmd(), utils.HideCommand())
c.Use = c.Use + " " + sharePointServiceCommandCreateUseSuffix
c.Example = sharePointServiceCommandCreateExamples
@ -91,14 +91,14 @@ func addSharePointCommands(parent *cobra.Command) *cobra.Command {
options.AddOperationFlags(c)
case listCommand:
c, fs = utils.AddCommand(parent, sharePointListCmd(), utils.HideCommand())
c, fs = utils.AddCommand(cmd, sharePointListCmd(), utils.HideCommand())
fs.StringVar(&backupID,
utils.BackupFN, "",
"ID of the backup to retrieve.")
case detailsCommand:
c, fs = utils.AddCommand(parent, sharePointDetailsCmd())
c, fs = utils.AddCommand(cmd, sharePointDetailsCmd())
c.Use = c.Use + " " + sharePointServiceCommandDetailsUseSuffix
c.Example = sharePointServiceCommandDetailsExamples
@ -128,7 +128,7 @@ func addSharePointCommands(parent *cobra.Command) *cobra.Command {
// "Select backup details for items created after this datetime.")
case deleteCommand:
c, fs = utils.AddCommand(parent, sharePointDeleteCmd(), utils.HideCommand())
c, fs = utils.AddCommand(cmd, sharePointDeleteCmd(), utils.HideCommand())
c.Use = c.Use + " " + sharePointServiceCommandDeleteUseSuffix
c.Example = sharePointServiceCommandDeleteExamples

View File

@ -7,8 +7,8 @@ import (
)
// AddCommands attaches all `corso env * *` commands to the parent.
func AddCommands(parent *cobra.Command) {
parent.AddCommand(envCmd())
func AddCommands(cmd *cobra.Command) {
cmd.AddCommand(envCmd())
}
// The env command: purely a help display.

View File

@ -12,16 +12,16 @@ var (
)
// AddOperationFlags adds command-local operation flags
func AddOperationFlags(parent *cobra.Command) {
fs := parent.Flags()
func AddOperationFlags(cmd *cobra.Command) {
fs := cmd.Flags()
fs.BoolVar(&fastFail, "fast-fail", false, "stop processing immediately if any error occurs")
// TODO: reveal this flag when fail-fast support is implemented
cobra.CheckErr(fs.MarkHidden("fast-fail"))
}
// AddGlobalOperationFlags adds the global operations flag set.
func AddGlobalOperationFlags(parent *cobra.Command) {
fs := parent.PersistentFlags()
func AddGlobalOperationFlags(cmd *cobra.Command) {
fs := cmd.PersistentFlags()
fs.BoolVar(&noStats, "no-stats", false, "disable anonymous usage statistics gathering")
}

View File

@ -43,8 +43,8 @@ func getRootCmd(ctx context.Context) *cobra.Command {
}
// adds the persistent flag --output to the provided command.
func AddOutputFlag(parent *cobra.Command) {
fs := parent.PersistentFlags()
func AddOutputFlag(cmd *cobra.Command) {
fs := cmd.PersistentFlags()
fs.BoolVar(&outputAsJSON, "json", false, "output data in JSON format")
fs.BoolVar(&outputAsJSONDebug, "json-debug", false, "output all internal and debugging data in JSON format")
cobra.CheckErr(fs.MarkHidden("json-debug"))

View File

@ -9,12 +9,12 @@ const (
connectCommand = "connect"
)
var repoCommands = []func(parent *cobra.Command) *cobra.Command{
var repoCommands = []func(cmd *cobra.Command) *cobra.Command{
addS3Commands,
}
// AddCommands attaches all `corso repo * *` commands to the parent.
func AddCommands(parent *cobra.Command) {
func AddCommands(cmd *cobra.Command) {
var (
// Get new instances so that setting the context during tests works
// properly.
@ -23,7 +23,7 @@ func AddCommands(parent *cobra.Command) {
connectCmd = connectCmd()
)
parent.AddCommand(repoCmd)
cmd.AddCommand(repoCmd)
repoCmd.AddCommand(initCmd)
repoCmd.AddCommand(connectCmd)

View File

@ -26,22 +26,22 @@ var (
succeedIfExists bool
)
// called by repo.go to map parent subcommands to provider-specific handling.
func addS3Commands(parent *cobra.Command) *cobra.Command {
// called by repo.go to map subcommands to provider-specific handling.
func addS3Commands(cmd *cobra.Command) *cobra.Command {
var (
c *cobra.Command
fs *pflag.FlagSet
)
switch parent.Use {
switch cmd.Use {
case initCommand:
c, fs = utils.AddCommand(parent, s3InitCmd())
c, fs = utils.AddCommand(cmd, s3InitCmd())
case connectCommand:
c, fs = utils.AddCommand(parent, s3ConnectCmd())
c, fs = utils.AddCommand(cmd, s3ConnectCmd())
}
c.Use = c.Use + " " + s3ProviderCommandUseSuffix
c.SetUsageTemplate(parent.UsageTemplate())
c.SetUsageTemplate(cmd.UsageTemplate())
// Flags addition ordering should follow the order we want them to appear in help and docs:
// More generic and more frequently used flags take precedence.

View File

@ -40,16 +40,16 @@ var (
eventSubject string
)
// called by restore.go to map parent subcommands to provider-specific handling.
func addExchangeCommands(parent *cobra.Command) *cobra.Command {
// called by restore.go to map subcommands to provider-specific handling.
func addExchangeCommands(cmd *cobra.Command) *cobra.Command {
var (
c *cobra.Command
fs *pflag.FlagSet
)
switch parent.Use {
switch cmd.Use {
case restoreCommand:
c, fs = utils.AddCommand(parent, exchangeRestoreCmd())
c, fs = utils.AddCommand(cmd, exchangeRestoreCmd())
c.Use = c.Use + " " + exchangeServiceCommandUseSuffix

View File

@ -25,16 +25,16 @@ var (
fileModifiedBefore string
)
// called by restore.go to map parent subcommands to provider-specific handling.
func addOneDriveCommands(parent *cobra.Command) *cobra.Command {
// called by restore.go to map subcommands to provider-specific handling.
func addOneDriveCommands(cmd *cobra.Command) *cobra.Command {
var (
c *cobra.Command
fs *pflag.FlagSet
)
switch parent.Use {
switch cmd.Use {
case restoreCommand:
c, fs = utils.AddCommand(parent, oneDriveRestoreCmd())
c, fs = utils.AddCommand(cmd, oneDriveRestoreCmd())
c.Use = c.Use + " " + oneDriveServiceCommandUseSuffix

View File

@ -4,15 +4,16 @@ import (
"github.com/spf13/cobra"
)
var restoreCommands = []func(parent *cobra.Command) *cobra.Command{
var restoreCommands = []func(cmd *cobra.Command) *cobra.Command{
addExchangeCommands,
addOneDriveCommands,
addSharePointCommands,
}
// AddCommands attaches all `corso restore * *` commands to the parent.
func AddCommands(parent *cobra.Command) {
func AddCommands(cmd *cobra.Command) {
restoreC := restoreCmd()
parent.AddCommand(restoreC)
cmd.AddCommand(restoreC)
for _, addRestoreTo := range restoreCommands {
addRestoreTo(restoreC)

View File

@ -0,0 +1,161 @@
package restore
import (
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/alcionai/corso/src/cli/config"
"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/common"
"github.com/alcionai/corso/src/pkg/control"
"github.com/alcionai/corso/src/pkg/repository"
"github.com/alcionai/corso/src/pkg/selectors"
)
var (
site []string
libraryPaths []string
libraryItems []string
)
// called by restore.go to map subcommands to provider-specific handling.
func addSharePointCommands(cmd *cobra.Command) *cobra.Command {
var (
c *cobra.Command
fs *pflag.FlagSet
)
switch cmd.Use {
case restoreCommand:
c, fs = utils.AddCommand(cmd, sharePointRestoreCmd(), utils.HideCommand())
c.Use = c.Use + " " + sharePointServiceCommandUseSuffix
// Flags addition ordering should follow the order we want them to appear in help and docs:
// More generic (ex: --site) and more frequently used flags take precedence.
fs.SortFlags = false
fs.StringVar(&backupID,
utils.BackupFN, "",
"ID of the backup to restore. (required)")
cobra.CheckErr(c.MarkFlagRequired(utils.BackupFN))
fs.StringSliceVar(&site,
utils.SiteFN, nil,
"Restore data by site ID; accepts '"+utils.Wildcard+"' to select all sites.")
// sharepoint hierarchy (path/name) flags
fs.StringSliceVar(
&folderPaths,
utils.LibraryFN, nil,
"Restore library items by SharePoint library")
fs.StringSliceVar(
&libraryItems,
utils.LibraryItemFN, nil,
"Restore library items by file name or ID")
// sharepoint info flags
// fs.StringVar(
// &fileCreatedAfter,
// utils.FileCreatedAfterFN, "",
// "Restore files created after this datetime")
// others
options.AddOperationFlags(c)
}
return c
}
const (
sharePointServiceCommand = "sharepoint"
sharePointServiceCommandUseSuffix = "--backup <backupId>"
//nolint:lll
sharePointServiceCommandRestoreExamples = `# Restore file with ID 98765abcdef
corso restore sharepoint --backup 1234abcd-12ab-cd34-56de-1234abcd --file 98765abcdef
# Restore <site>'s file named "ServerRenderTemplate.xsl in "Display Templates/Style Sheets" from a specific backup
corso restore sharepoint --backup 1234abcd-12ab-cd34-56de-1234abcd \
--site <siteID> --file "ServerRenderTemplate.xsl" --folder "Display Templates/Style Sheets"
# Restore all files from <site> that were created before 2020 when captured in a specific backup
corso restore sharepoint --backup 1234abcd-12ab-cd34-56de-1234abcd
--site <siteID> --folder "Display Templates/Style Sheets" --file-created-before 2020-01-01T00:00:00`
)
// `corso restore sharepoint [<flag>...]`
func sharePointRestoreCmd() *cobra.Command {
return &cobra.Command{
Use: sharePointServiceCommand,
Short: "Restore M365 SharePoint service data",
RunE: restoreSharePointCmd,
Args: cobra.NoArgs,
Example: sharePointServiceCommandRestoreExamples,
}
}
// processes an sharepoint service restore.
func restoreSharePointCmd(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
if utils.HasNoFlagsAndShownHelp(cmd) {
return nil
}
opts := utils.SharePointOpts{
Sites: site,
LibraryPaths: libraryPaths,
LibraryItems: libraryItems,
// FileCreatedAfter: fileCreatedAfter,
Populated: utils.GetPopulatedFlags(cmd),
}
if err := utils.ValidateSharePointRestoreFlags(backupID, opts); err != nil {
return err
}
s, a, err := config.GetStorageAndAccount(ctx, true, nil)
if err != nil {
return Only(ctx, err)
}
r, err := repository.Connect(ctx, a, s, options.Control())
if err != nil {
return Only(ctx, errors.Wrapf(err, "Failed to connect to the %s repository", s.Provider))
}
defer utils.CloseRepo(ctx, r)
sel := selectors.NewSharePointRestore()
utils.IncludeSharePointRestoreDataSelectors(sel, opts)
utils.FilterSharePointRestoreInfoSelectors(sel, opts)
// if no selector flags were specified, get all data in the service.
if len(sel.Scopes()) == 0 {
sel.Include(sel.Sites(selectors.Any()))
}
restoreDest := control.DefaultRestoreDestination(common.SimpleDateTimeOneDrive)
ro, err := r.NewRestore(ctx, backupID, sel.Selector, restoreDest)
if err != nil {
return Only(ctx, errors.Wrap(err, "Failed to initialize SharePoint restore"))
}
ds, err := ro.Run(ctx)
if err != nil {
return Only(ctx, errors.Wrap(err, "Failed to run SharePoint restore"))
}
ds.PrintEntries(ctx)
return nil
}

View File

@ -0,0 +1,50 @@
package restore
import (
"testing"
"github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"github.com/alcionai/corso/src/internal/tester"
)
type SharePointSuite struct {
suite.Suite
}
func TestSharePointSuite(t *testing.T) {
suite.Run(t, new(SharePointSuite))
}
func (suite *SharePointSuite) TestAddSharePointCommands() {
expectUse := sharePointServiceCommand + " " + sharePointServiceCommandUseSuffix
table := []struct {
name string
use string
expectUse string
expectShort string
expectRunE func(*cobra.Command, []string) error
}{
{"restore onedrive", restoreCommand, expectUse, sharePointRestoreCmd().Short, restoreSharePointCmd},
}
for _, test := range table {
suite.T().Run(test.name, func(t *testing.T) {
cmd := &cobra.Command{Use: test.use}
c := addSharePointCommands(cmd)
require.NotNil(t, c)
cmds := cmd.Commands()
require.Len(t, cmds, 1)
child := cmds[0]
assert.Equal(t, test.expectUse, child.Use)
assert.Equal(t, test.expectShort, child.Short)
tester.AreSameFunc(t, test.expectRunE, child.RunE)
})
}
}

View File

@ -30,10 +30,10 @@ var (
}
)
func addExchangeCommands(parent *cobra.Command) {
parent.AddCommand(emailsCmd)
parent.AddCommand(eventsCmd)
parent.AddCommand(contactsCmd)
func addExchangeCommands(cmd *cobra.Command) {
cmd.AddCommand(emailsCmd)
cmd.AddCommand(eventsCmd)
cmd.AddCommand(contactsCmd)
}
func handleExchangeEmailFactory(cmd *cobra.Command, args []string) error {

View File

@ -13,8 +13,8 @@ var filesCmd = &cobra.Command{
RunE: handleOneDriveFileFactory,
}
func addOneDriveCommands(parent *cobra.Command) {
parent.AddCommand(filesCmd)
func addOneDriveCommands(cmd *cobra.Command) {
cmd.AddCommand(filesCmd)
}
func handleOneDriveFileFactory(cmd *cobra.Command, args []string) error {

View File

@ -38,8 +38,8 @@ func init() {
// adds the persistent boolean flag --hide-progress to the provided command.
// This is a hack for help displays. Due to seeding the context, we also
// need to parse the configuration before we execute the command.
func AddProgressBarFlags(parent *cobra.Command) {
fs := parent.PersistentFlags()
func AddProgressBarFlags(cmd *cobra.Command) {
fs := cmd.PersistentFlags()
fs.Bool(hideProgressBarsFN, false, "turn off the progress bar displays")
fs.Bool(retainProgressBarsFN, false, "retain the progress bar displays after completion")
}

View File

@ -39,8 +39,8 @@ const (
// defaults to "info".
// This is a hack for help displays. Due to seeding the context, we also
// need to parse the log level before we execute the command.
func AddLogLevelFlag(parent *cobra.Command) {
fs := parent.PersistentFlags()
func AddLogLevelFlag(cmd *cobra.Command) {
fs := cmd.PersistentFlags()
fs.StringVar(&llFlag, logLevelFN, "info", "set the log level to debug|info|warn|error")
fs.Bool(