adds cli flags for restore config (#3704)
implements cli integration for configuring restore operation collision policy and restore destination. flags are hidden and will be made visible at a later time. --- #### Does this PR need a docs update or release note? - [x] 🕐 Yes, but in a later PR #### Type of change - [x] 🌻 Feature #### Issue(s) * #3562 #### Test Plan - [x] ⚡ Unit test - [x] 💚 E2E
This commit is contained in:
parent
a48b5f415c
commit
24c590040b
30
src/cli/flags/restore_config.go
Normal file
30
src/cli/flags/restore_config.go
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
package flags
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
CollisionsFN = "collisions"
|
||||||
|
DestinationFN = "destination"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
CollisionsFV string
|
||||||
|
DestinationFV string
|
||||||
|
)
|
||||||
|
|
||||||
|
// AddRestoreConfigFlags adds the restore config flag set.
|
||||||
|
func AddRestoreConfigFlags(cmd *cobra.Command) {
|
||||||
|
fs := cmd.Flags()
|
||||||
|
fs.StringVar(
|
||||||
|
&CollisionsFV, CollisionsFN, string(control.Skip),
|
||||||
|
"How to handle item collisions: "+string(control.Skip)+", "+string(control.Copy)+", or "+string(control.Replace))
|
||||||
|
cobra.CheckErr(fs.MarkHidden(CollisionsFN))
|
||||||
|
fs.StringVar(
|
||||||
|
&DestinationFV, DestinationFN, "",
|
||||||
|
"Overrides the destination where items get restored. '/' places items back in their original location.")
|
||||||
|
cobra.CheckErr(fs.MarkHidden(DestinationFN))
|
||||||
|
}
|
||||||
@ -12,7 +12,6 @@ import (
|
|||||||
"github.com/alcionai/corso/src/cli/utils"
|
"github.com/alcionai/corso/src/cli/utils"
|
||||||
"github.com/alcionai/corso/src/internal/common/dttm"
|
"github.com/alcionai/corso/src/internal/common/dttm"
|
||||||
"github.com/alcionai/corso/src/internal/data"
|
"github.com/alcionai/corso/src/internal/data"
|
||||||
"github.com/alcionai/corso/src/pkg/control"
|
|
||||||
"github.com/alcionai/corso/src/pkg/path"
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -36,6 +35,7 @@ func addExchangeCommands(cmd *cobra.Command) *cobra.Command {
|
|||||||
|
|
||||||
flags.AddBackupIDFlag(c, true)
|
flags.AddBackupIDFlag(c, true)
|
||||||
flags.AddExchangeDetailsAndRestoreFlags(c)
|
flags.AddExchangeDetailsAndRestoreFlags(c)
|
||||||
|
flags.AddRestoreConfigFlags(c)
|
||||||
flags.AddFailFastFlag(c)
|
flags.AddFailFastFlag(c)
|
||||||
flags.AddCorsoPassphaseFlags(c)
|
flags.AddCorsoPassphaseFlags(c)
|
||||||
flags.AddAWSCredsFlags(c)
|
flags.AddAWSCredsFlags(c)
|
||||||
@ -101,8 +101,7 @@ func restoreExchangeCmd(cmd *cobra.Command, args []string) error {
|
|||||||
|
|
||||||
defer utils.CloseRepo(ctx, r)
|
defer utils.CloseRepo(ctx, r)
|
||||||
|
|
||||||
restoreCfg := control.DefaultRestoreConfig(dttm.HumanReadable)
|
restoreCfg := utils.MakeRestoreConfig(ctx, opts.RestoreCfg, dttm.HumanReadable)
|
||||||
Infof(ctx, "Restoring to folder %s", restoreCfg.Location)
|
|
||||||
|
|
||||||
sel := utils.IncludeExchangeRestoreDataSelectors(opts)
|
sel := utils.IncludeExchangeRestoreDataSelectors(opts)
|
||||||
utils.FilterExchangeRestoreInfoSelectors(sel, opts)
|
utils.FilterExchangeRestoreInfoSelectors(sel, opts)
|
||||||
|
|||||||
@ -62,15 +62,18 @@ func (suite *ExchangeUnitSuite) TestAddExchangeCommands() {
|
|||||||
"exchange",
|
"exchange",
|
||||||
"--" + flags.RunModeFN, flags.RunModeFlagTest,
|
"--" + flags.RunModeFN, flags.RunModeFlagTest,
|
||||||
"--" + flags.BackupFN, testdata.BackupInput,
|
"--" + flags.BackupFN, testdata.BackupInput,
|
||||||
|
|
||||||
"--" + flags.ContactFN, testdata.FlgInputs(testdata.ContactInput),
|
"--" + flags.ContactFN, testdata.FlgInputs(testdata.ContactInput),
|
||||||
"--" + flags.ContactFolderFN, testdata.FlgInputs(testdata.ContactFldInput),
|
"--" + flags.ContactFolderFN, testdata.FlgInputs(testdata.ContactFldInput),
|
||||||
"--" + flags.ContactNameFN, testdata.ContactNameInput,
|
"--" + flags.ContactNameFN, testdata.ContactNameInput,
|
||||||
|
|
||||||
"--" + flags.EmailFN, testdata.FlgInputs(testdata.EmailInput),
|
"--" + flags.EmailFN, testdata.FlgInputs(testdata.EmailInput),
|
||||||
"--" + flags.EmailFolderFN, testdata.FlgInputs(testdata.EmailFldInput),
|
"--" + flags.EmailFolderFN, testdata.FlgInputs(testdata.EmailFldInput),
|
||||||
"--" + flags.EmailReceivedAfterFN, testdata.EmailReceivedAfterInput,
|
"--" + flags.EmailReceivedAfterFN, testdata.EmailReceivedAfterInput,
|
||||||
"--" + flags.EmailReceivedBeforeFN, testdata.EmailReceivedBeforeInput,
|
"--" + flags.EmailReceivedBeforeFN, testdata.EmailReceivedBeforeInput,
|
||||||
"--" + flags.EmailSenderFN, testdata.EmailSenderInput,
|
"--" + flags.EmailSenderFN, testdata.EmailSenderInput,
|
||||||
"--" + flags.EmailSubjectFN, testdata.EmailSubjectInput,
|
"--" + flags.EmailSubjectFN, testdata.EmailSubjectInput,
|
||||||
|
|
||||||
"--" + flags.EventFN, testdata.FlgInputs(testdata.EventInput),
|
"--" + flags.EventFN, testdata.FlgInputs(testdata.EventInput),
|
||||||
"--" + flags.EventCalendarFN, testdata.FlgInputs(testdata.EventCalInput),
|
"--" + flags.EventCalendarFN, testdata.FlgInputs(testdata.EventCalInput),
|
||||||
"--" + flags.EventOrganizerFN, testdata.EventOrganizerInput,
|
"--" + flags.EventOrganizerFN, testdata.EventOrganizerInput,
|
||||||
@ -78,6 +81,10 @@ func (suite *ExchangeUnitSuite) TestAddExchangeCommands() {
|
|||||||
"--" + flags.EventStartsAfterFN, testdata.EventStartsAfterInput,
|
"--" + flags.EventStartsAfterFN, testdata.EventStartsAfterInput,
|
||||||
"--" + flags.EventStartsBeforeFN, testdata.EventStartsBeforeInput,
|
"--" + flags.EventStartsBeforeFN, testdata.EventStartsBeforeInput,
|
||||||
"--" + flags.EventSubjectFN, testdata.EventSubjectInput,
|
"--" + flags.EventSubjectFN, testdata.EventSubjectInput,
|
||||||
|
|
||||||
|
"--" + flags.CollisionsFN, testdata.Collisions,
|
||||||
|
"--" + flags.DestinationFN, testdata.Destination,
|
||||||
|
|
||||||
"--" + flags.AWSAccessKeyFN, testdata.AWSAccessKeyID,
|
"--" + flags.AWSAccessKeyFN, testdata.AWSAccessKeyID,
|
||||||
"--" + flags.AWSSecretAccessKeyFN, testdata.AWSSecretAccessKey,
|
"--" + flags.AWSSecretAccessKeyFN, testdata.AWSSecretAccessKey,
|
||||||
"--" + flags.AWSSessionTokenFN, testdata.AWSSessionToken,
|
"--" + flags.AWSSessionTokenFN, testdata.AWSSessionToken,
|
||||||
@ -116,6 +123,9 @@ func (suite *ExchangeUnitSuite) TestAddExchangeCommands() {
|
|||||||
assert.Equal(t, testdata.EventStartsBeforeInput, opts.EventStartsBefore)
|
assert.Equal(t, testdata.EventStartsBeforeInput, opts.EventStartsBefore)
|
||||||
assert.Equal(t, testdata.EventSubjectInput, opts.EventSubject)
|
assert.Equal(t, testdata.EventSubjectInput, opts.EventSubject)
|
||||||
|
|
||||||
|
assert.Equal(t, testdata.Collisions, opts.RestoreCfg.Collisions)
|
||||||
|
assert.Equal(t, testdata.Destination, opts.RestoreCfg.Destination)
|
||||||
|
|
||||||
assert.Equal(t, testdata.AWSAccessKeyID, flags.AWSAccessKeyFV)
|
assert.Equal(t, testdata.AWSAccessKeyID, flags.AWSAccessKeyFV)
|
||||||
assert.Equal(t, testdata.AWSSecretAccessKey, flags.AWSSecretAccessKeyFV)
|
assert.Equal(t, testdata.AWSSecretAccessKey, flags.AWSSecretAccessKeyFV)
|
||||||
assert.Equal(t, testdata.AWSSessionToken, flags.AWSSessionTokenFV)
|
assert.Equal(t, testdata.AWSSessionToken, flags.AWSSessionTokenFV)
|
||||||
|
|||||||
@ -12,7 +12,6 @@ import (
|
|||||||
"github.com/alcionai/corso/src/cli/utils"
|
"github.com/alcionai/corso/src/cli/utils"
|
||||||
"github.com/alcionai/corso/src/internal/common/dttm"
|
"github.com/alcionai/corso/src/internal/common/dttm"
|
||||||
"github.com/alcionai/corso/src/internal/data"
|
"github.com/alcionai/corso/src/internal/data"
|
||||||
"github.com/alcionai/corso/src/pkg/control"
|
|
||||||
"github.com/alcionai/corso/src/pkg/path"
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -36,6 +35,7 @@ func addOneDriveCommands(cmd *cobra.Command) *cobra.Command {
|
|||||||
flags.AddBackupIDFlag(c, true)
|
flags.AddBackupIDFlag(c, true)
|
||||||
flags.AddOneDriveDetailsAndRestoreFlags(c)
|
flags.AddOneDriveDetailsAndRestoreFlags(c)
|
||||||
flags.AddRestorePermissionsFlag(c)
|
flags.AddRestorePermissionsFlag(c)
|
||||||
|
flags.AddRestoreConfigFlags(c)
|
||||||
flags.AddFailFastFlag(c)
|
flags.AddFailFastFlag(c)
|
||||||
flags.AddCorsoPassphaseFlags(c)
|
flags.AddCorsoPassphaseFlags(c)
|
||||||
flags.AddAWSCredsFlags(c)
|
flags.AddAWSCredsFlags(c)
|
||||||
@ -100,12 +100,11 @@ func restoreOneDriveCmd(cmd *cobra.Command, args []string) error {
|
|||||||
|
|
||||||
defer utils.CloseRepo(ctx, r)
|
defer utils.CloseRepo(ctx, r)
|
||||||
|
|
||||||
restoreCfg := control.DefaultRestoreConfig(dttm.HumanReadableDriveItem)
|
|
||||||
Infof(ctx, "Restoring to folder %s", restoreCfg.Location)
|
|
||||||
|
|
||||||
sel := utils.IncludeOneDriveRestoreDataSelectors(opts)
|
sel := utils.IncludeOneDriveRestoreDataSelectors(opts)
|
||||||
utils.FilterOneDriveRestoreInfoSelectors(sel, opts)
|
utils.FilterOneDriveRestoreInfoSelectors(sel, opts)
|
||||||
|
|
||||||
|
restoreCfg := utils.MakeRestoreConfig(ctx, opts.RestoreCfg, dttm.HumanReadableDriveItem)
|
||||||
|
|
||||||
ro, err := r.NewRestore(ctx, flags.BackupIDFV, sel.Selector, restoreCfg)
|
ro, err := r.NewRestore(ctx, flags.BackupIDFV, sel.Selector, restoreCfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Only(ctx, clues.Wrap(err, "Failed to initialize OneDrive restore"))
|
return Only(ctx, clues.Wrap(err, "Failed to initialize OneDrive restore"))
|
||||||
|
|||||||
@ -67,6 +67,10 @@ func (suite *OneDriveUnitSuite) TestAddOneDriveCommands() {
|
|||||||
"--" + flags.FileCreatedBeforeFN, testdata.FileCreatedBeforeInput,
|
"--" + flags.FileCreatedBeforeFN, testdata.FileCreatedBeforeInput,
|
||||||
"--" + flags.FileModifiedAfterFN, testdata.FileModifiedAfterInput,
|
"--" + flags.FileModifiedAfterFN, testdata.FileModifiedAfterInput,
|
||||||
"--" + flags.FileModifiedBeforeFN, testdata.FileModifiedBeforeInput,
|
"--" + flags.FileModifiedBeforeFN, testdata.FileModifiedBeforeInput,
|
||||||
|
|
||||||
|
"--" + flags.CollisionsFN, testdata.Collisions,
|
||||||
|
"--" + flags.DestinationFN, testdata.Destination,
|
||||||
|
|
||||||
"--" + flags.AWSAccessKeyFN, testdata.AWSAccessKeyID,
|
"--" + flags.AWSAccessKeyFN, testdata.AWSAccessKeyID,
|
||||||
"--" + flags.AWSSecretAccessKeyFN, testdata.AWSSecretAccessKey,
|
"--" + flags.AWSSecretAccessKeyFN, testdata.AWSSecretAccessKey,
|
||||||
"--" + flags.AWSSessionTokenFN, testdata.AWSSessionToken,
|
"--" + flags.AWSSessionTokenFN, testdata.AWSSessionToken,
|
||||||
@ -93,6 +97,9 @@ func (suite *OneDriveUnitSuite) TestAddOneDriveCommands() {
|
|||||||
assert.Equal(t, testdata.FileModifiedAfterInput, opts.FileModifiedAfter)
|
assert.Equal(t, testdata.FileModifiedAfterInput, opts.FileModifiedAfter)
|
||||||
assert.Equal(t, testdata.FileModifiedBeforeInput, opts.FileModifiedBefore)
|
assert.Equal(t, testdata.FileModifiedBeforeInput, opts.FileModifiedBefore)
|
||||||
|
|
||||||
|
assert.Equal(t, testdata.Collisions, opts.RestoreCfg.Collisions)
|
||||||
|
assert.Equal(t, testdata.Destination, opts.RestoreCfg.Destination)
|
||||||
|
|
||||||
assert.Equal(t, testdata.AWSAccessKeyID, flags.AWSAccessKeyFV)
|
assert.Equal(t, testdata.AWSAccessKeyID, flags.AWSAccessKeyFV)
|
||||||
assert.Equal(t, testdata.AWSSecretAccessKey, flags.AWSSecretAccessKeyFV)
|
assert.Equal(t, testdata.AWSSecretAccessKey, flags.AWSSecretAccessKeyFV)
|
||||||
assert.Equal(t, testdata.AWSSessionToken, flags.AWSSessionTokenFV)
|
assert.Equal(t, testdata.AWSSessionToken, flags.AWSSessionTokenFV)
|
||||||
|
|||||||
@ -12,7 +12,6 @@ import (
|
|||||||
"github.com/alcionai/corso/src/cli/utils"
|
"github.com/alcionai/corso/src/cli/utils"
|
||||||
"github.com/alcionai/corso/src/internal/common/dttm"
|
"github.com/alcionai/corso/src/internal/common/dttm"
|
||||||
"github.com/alcionai/corso/src/internal/data"
|
"github.com/alcionai/corso/src/internal/data"
|
||||||
"github.com/alcionai/corso/src/pkg/control"
|
|
||||||
"github.com/alcionai/corso/src/pkg/path"
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -36,8 +35,8 @@ func addSharePointCommands(cmd *cobra.Command) *cobra.Command {
|
|||||||
flags.AddBackupIDFlag(c, true)
|
flags.AddBackupIDFlag(c, true)
|
||||||
flags.AddSharePointDetailsAndRestoreFlags(c)
|
flags.AddSharePointDetailsAndRestoreFlags(c)
|
||||||
flags.AddRestorePermissionsFlag(c)
|
flags.AddRestorePermissionsFlag(c)
|
||||||
|
flags.AddRestoreConfigFlags(c)
|
||||||
flags.AddFailFastFlag(c)
|
flags.AddFailFastFlag(c)
|
||||||
|
|
||||||
flags.AddCorsoPassphaseFlags(c)
|
flags.AddCorsoPassphaseFlags(c)
|
||||||
flags.AddAWSCredsFlags(c)
|
flags.AddAWSCredsFlags(c)
|
||||||
flags.AddAzureCredsFlags(c)
|
flags.AddAzureCredsFlags(c)
|
||||||
@ -107,12 +106,11 @@ func restoreSharePointCmd(cmd *cobra.Command, args []string) error {
|
|||||||
|
|
||||||
defer utils.CloseRepo(ctx, r)
|
defer utils.CloseRepo(ctx, r)
|
||||||
|
|
||||||
restoreCfg := control.DefaultRestoreConfig(dttm.HumanReadableDriveItem)
|
|
||||||
Infof(ctx, "Restoring to folder %s", restoreCfg.Location)
|
|
||||||
|
|
||||||
sel := utils.IncludeSharePointRestoreDataSelectors(ctx, opts)
|
sel := utils.IncludeSharePointRestoreDataSelectors(ctx, opts)
|
||||||
utils.FilterSharePointRestoreInfoSelectors(sel, opts)
|
utils.FilterSharePointRestoreInfoSelectors(sel, opts)
|
||||||
|
|
||||||
|
restoreCfg := utils.MakeRestoreConfig(ctx, opts.RestoreCfg, dttm.HumanReadableDriveItem)
|
||||||
|
|
||||||
ro, err := r.NewRestore(ctx, flags.BackupIDFV, sel.Selector, restoreCfg)
|
ro, err := r.NewRestore(ctx, flags.BackupIDFV, sel.Selector, restoreCfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Only(ctx, clues.Wrap(err, "Failed to initialize SharePoint restore"))
|
return Only(ctx, clues.Wrap(err, "Failed to initialize SharePoint restore"))
|
||||||
|
|||||||
@ -72,6 +72,10 @@ func (suite *SharePointUnitSuite) TestAddSharePointCommands() {
|
|||||||
"--" + flags.ListFolderFN, testdata.FlgInputs(testdata.ListFolderInput),
|
"--" + flags.ListFolderFN, testdata.FlgInputs(testdata.ListFolderInput),
|
||||||
"--" + flags.PageFN, testdata.FlgInputs(testdata.PageInput),
|
"--" + flags.PageFN, testdata.FlgInputs(testdata.PageInput),
|
||||||
"--" + flags.PageFolderFN, testdata.FlgInputs(testdata.PageFolderInput),
|
"--" + flags.PageFolderFN, testdata.FlgInputs(testdata.PageFolderInput),
|
||||||
|
|
||||||
|
"--" + flags.CollisionsFN, testdata.Collisions,
|
||||||
|
"--" + flags.DestinationFN, testdata.Destination,
|
||||||
|
|
||||||
"--" + flags.AWSAccessKeyFN, testdata.AWSAccessKeyID,
|
"--" + flags.AWSAccessKeyFN, testdata.AWSAccessKeyID,
|
||||||
"--" + flags.AWSSecretAccessKeyFN, testdata.AWSSecretAccessKey,
|
"--" + flags.AWSSecretAccessKeyFN, testdata.AWSSecretAccessKey,
|
||||||
"--" + flags.AWSSessionTokenFN, testdata.AWSSessionToken,
|
"--" + flags.AWSSessionTokenFN, testdata.AWSSessionToken,
|
||||||
@ -105,6 +109,9 @@ func (suite *SharePointUnitSuite) TestAddSharePointCommands() {
|
|||||||
assert.ElementsMatch(t, testdata.PageInput, opts.Page)
|
assert.ElementsMatch(t, testdata.PageInput, opts.Page)
|
||||||
assert.ElementsMatch(t, testdata.PageFolderInput, opts.PageFolder)
|
assert.ElementsMatch(t, testdata.PageFolderInput, opts.PageFolder)
|
||||||
|
|
||||||
|
assert.Equal(t, testdata.Collisions, opts.RestoreCfg.Collisions)
|
||||||
|
assert.Equal(t, testdata.Destination, opts.RestoreCfg.Destination)
|
||||||
|
|
||||||
assert.Equal(t, testdata.AWSAccessKeyID, flags.AWSAccessKeyFV)
|
assert.Equal(t, testdata.AWSAccessKeyID, flags.AWSAccessKeyFV)
|
||||||
assert.Equal(t, testdata.AWSSecretAccessKey, flags.AWSSecretAccessKeyFV)
|
assert.Equal(t, testdata.AWSSecretAccessKey, flags.AWSSecretAccessKeyFV)
|
||||||
assert.Equal(t, testdata.AWSSessionToken, flags.AWSSessionTokenFV)
|
assert.Equal(t, testdata.AWSSessionToken, flags.AWSSessionTokenFV)
|
||||||
|
|||||||
4
src/cli/testdata/cli.go
vendored
4
src/cli/testdata/cli.go
vendored
@ -6,15 +6,13 @@ import (
|
|||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/alcionai/corso/src/internal/common/dttm"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// StubRootCmd builds a stub cobra command to be used as
|
// StubRootCmd builds a stub cobra command to be used as
|
||||||
// the root command for integration testing on the CLI
|
// the root command for integration testing on the CLI
|
||||||
func StubRootCmd(args ...string) *cobra.Command {
|
func StubRootCmd(args ...string) *cobra.Command {
|
||||||
id := uuid.NewString()
|
id := uuid.NewString()
|
||||||
now := dttm.Format(time.Now())
|
now := time.Now().UTC().Format(time.RFC3339Nano)
|
||||||
cmdArg := "testing-corso"
|
cmdArg := "testing-corso"
|
||||||
c := &cobra.Command{
|
c := &cobra.Command{
|
||||||
Use: cmdArg,
|
Use: cmdArg,
|
||||||
|
|||||||
@ -30,6 +30,8 @@ type ExchangeOpts struct {
|
|||||||
EventStartsBefore string
|
EventStartsBefore string
|
||||||
EventSubject string
|
EventSubject string
|
||||||
|
|
||||||
|
RestoreCfg RestoreCfgOpts
|
||||||
|
|
||||||
Populated flags.PopulatedFlags
|
Populated flags.PopulatedFlags
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,6 +59,11 @@ func MakeExchangeOpts(cmd *cobra.Command) ExchangeOpts {
|
|||||||
EventStartsBefore: flags.EventStartsBeforeFV,
|
EventStartsBefore: flags.EventStartsBeforeFV,
|
||||||
EventSubject: flags.EventSubjectFV,
|
EventSubject: flags.EventSubjectFV,
|
||||||
|
|
||||||
|
RestoreCfg: makeRestoreCfgOpts(cmd),
|
||||||
|
|
||||||
|
// populated contains the list of flags that appear in the
|
||||||
|
// command, according to pflags. Use this to differentiate
|
||||||
|
// between an "empty" and a "missing" value.
|
||||||
Populated: flags.GetPopulatedFlags(cmd),
|
Populated: flags.GetPopulatedFlags(cmd),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -132,7 +139,7 @@ func ValidateExchangeRestoreFlags(backupID string, opts ExchangeOpts) error {
|
|||||||
return clues.New("invalid format for event-recurs")
|
return clues.New("invalid format for event-recurs")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return validateRestoreConfigFlags(flags.CollisionsFV, opts.RestoreCfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IncludeExchangeRestoreDataSelectors builds the common data-selector
|
// IncludeExchangeRestoreDataSelectors builds the common data-selector
|
||||||
|
|||||||
@ -18,6 +18,8 @@ type OneDriveOpts struct {
|
|||||||
FileModifiedAfter string
|
FileModifiedAfter string
|
||||||
FileModifiedBefore string
|
FileModifiedBefore string
|
||||||
|
|
||||||
|
RestoreCfg RestoreCfgOpts
|
||||||
|
|
||||||
Populated flags.PopulatedFlags
|
Populated flags.PopulatedFlags
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,6 +34,11 @@ func MakeOneDriveOpts(cmd *cobra.Command) OneDriveOpts {
|
|||||||
FileModifiedAfter: flags.FileModifiedAfterFV,
|
FileModifiedAfter: flags.FileModifiedAfterFV,
|
||||||
FileModifiedBefore: flags.FileModifiedBeforeFV,
|
FileModifiedBefore: flags.FileModifiedBeforeFV,
|
||||||
|
|
||||||
|
RestoreCfg: makeRestoreCfgOpts(cmd),
|
||||||
|
|
||||||
|
// populated contains the list of flags that appear in the
|
||||||
|
// command, according to pflags. Use this to differentiate
|
||||||
|
// between an "empty" and a "missing" value.
|
||||||
Populated: flags.GetPopulatedFlags(cmd),
|
Populated: flags.GetPopulatedFlags(cmd),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -58,7 +65,7 @@ func ValidateOneDriveRestoreFlags(backupID string, opts OneDriveOpts) error {
|
|||||||
return clues.New("invalid time format for modified-before")
|
return clues.New("invalid time format for modified-before")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return validateRestoreConfigFlags(flags.CollisionsFV, opts.RestoreCfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddOneDriveFilter adds the scope of the provided values to the selector's
|
// AddOneDriveFilter adds the scope of the provided values to the selector's
|
||||||
|
|||||||
65
src/cli/utils/restore_config.go
Normal file
65
src/cli/utils/restore_config.go
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/alcionai/clues"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/alcionai/corso/src/cli/flags"
|
||||||
|
. "github.com/alcionai/corso/src/cli/print"
|
||||||
|
"github.com/alcionai/corso/src/internal/common/dttm"
|
||||||
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RestoreCfgOpts struct {
|
||||||
|
Collisions string
|
||||||
|
Destination string
|
||||||
|
|
||||||
|
Populated flags.PopulatedFlags
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeRestoreCfgOpts(cmd *cobra.Command) RestoreCfgOpts {
|
||||||
|
return RestoreCfgOpts{
|
||||||
|
Collisions: flags.CollisionsFV,
|
||||||
|
Destination: flags.DestinationFV,
|
||||||
|
|
||||||
|
// populated contains the list of flags that appear in the
|
||||||
|
// command, according to pflags. Use this to differentiate
|
||||||
|
// between an "empty" and a "missing" value.
|
||||||
|
Populated: flags.GetPopulatedFlags(cmd),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateRestoreConfigFlags checks common restore flags for
|
||||||
|
// correctness and interdependencies.
|
||||||
|
func validateRestoreConfigFlags(fv string, opts RestoreCfgOpts) error {
|
||||||
|
_, populated := opts.Populated[flags.CollisionsFN]
|
||||||
|
_, foundInValidSet := control.ValidCollisionPolicies()[control.CollisionPolicy(fv)]
|
||||||
|
|
||||||
|
if populated && !foundInValidSet {
|
||||||
|
return clues.New("invalid entry for " + flags.CollisionsFN)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func MakeRestoreConfig(
|
||||||
|
ctx context.Context,
|
||||||
|
opts RestoreCfgOpts,
|
||||||
|
locationTimeFormat dttm.TimeFormat,
|
||||||
|
) control.RestoreConfig {
|
||||||
|
restoreCfg := control.DefaultRestoreConfig(locationTimeFormat)
|
||||||
|
|
||||||
|
if _, ok := opts.Populated[flags.CollisionsFN]; ok {
|
||||||
|
restoreCfg.OnCollision = control.CollisionPolicy(opts.Collisions)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := opts.Populated[flags.DestinationFN]; ok {
|
||||||
|
restoreCfg.Location = opts.Destination
|
||||||
|
}
|
||||||
|
|
||||||
|
Infof(ctx, "Restoring to folder %s", restoreCfg.Location)
|
||||||
|
|
||||||
|
return restoreCfg
|
||||||
|
}
|
||||||
137
src/cli/utils/restore_config_test.go
Normal file
137
src/cli/utils/restore_config_test.go
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/alcionai/clues"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
|
"github.com/alcionai/corso/src/cli/flags"
|
||||||
|
"github.com/alcionai/corso/src/internal/common/dttm"
|
||||||
|
"github.com/alcionai/corso/src/internal/tester"
|
||||||
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RestoreCfgUnitSuite struct {
|
||||||
|
tester.Suite
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRestoreCfgUnitSuite(t *testing.T) {
|
||||||
|
suite.Run(t, &RestoreCfgUnitSuite{Suite: tester.NewUnitSuite(t)})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *RestoreCfgUnitSuite) TestValidateRestoreConfigFlags() {
|
||||||
|
table := []struct {
|
||||||
|
name string
|
||||||
|
fv string
|
||||||
|
opts RestoreCfgOpts
|
||||||
|
expect assert.ErrorAssertionFunc
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no error",
|
||||||
|
fv: string(control.Skip),
|
||||||
|
opts: RestoreCfgOpts{
|
||||||
|
Collisions: string(control.Skip),
|
||||||
|
Populated: flags.PopulatedFlags{
|
||||||
|
flags.CollisionsFN: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expect: assert.NoError,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bad but not populated",
|
||||||
|
fv: "foo",
|
||||||
|
opts: RestoreCfgOpts{
|
||||||
|
Collisions: "foo",
|
||||||
|
Populated: flags.PopulatedFlags{},
|
||||||
|
},
|
||||||
|
expect: assert.NoError,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "error",
|
||||||
|
fv: "foo",
|
||||||
|
opts: RestoreCfgOpts{
|
||||||
|
Collisions: "foo",
|
||||||
|
Populated: flags.PopulatedFlags{
|
||||||
|
flags.CollisionsFN: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expect: assert.Error,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range table {
|
||||||
|
suite.Run(test.name, func() {
|
||||||
|
err := validateRestoreConfigFlags(test.fv, test.opts)
|
||||||
|
test.expect(suite.T(), err, clues.ToCore(err))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *RestoreCfgUnitSuite) TestMakeRestoreConfig() {
|
||||||
|
rco := &RestoreCfgOpts{
|
||||||
|
Collisions: "collisions",
|
||||||
|
Destination: "destination",
|
||||||
|
}
|
||||||
|
|
||||||
|
table := []struct {
|
||||||
|
name string
|
||||||
|
populated flags.PopulatedFlags
|
||||||
|
expect control.RestoreConfig
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "not populated",
|
||||||
|
populated: flags.PopulatedFlags{},
|
||||||
|
expect: control.RestoreConfig{
|
||||||
|
OnCollision: control.Skip,
|
||||||
|
Location: "Corso_Restore_",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "collision populated",
|
||||||
|
populated: flags.PopulatedFlags{
|
||||||
|
flags.CollisionsFN: {},
|
||||||
|
},
|
||||||
|
expect: control.RestoreConfig{
|
||||||
|
OnCollision: control.CollisionPolicy("collisions"),
|
||||||
|
Location: "Corso_Restore_",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "destination populated",
|
||||||
|
populated: flags.PopulatedFlags{
|
||||||
|
flags.DestinationFN: {},
|
||||||
|
},
|
||||||
|
expect: control.RestoreConfig{
|
||||||
|
OnCollision: control.Skip,
|
||||||
|
Location: "destination",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "both populated",
|
||||||
|
populated: flags.PopulatedFlags{
|
||||||
|
flags.CollisionsFN: {},
|
||||||
|
flags.DestinationFN: {},
|
||||||
|
},
|
||||||
|
expect: control.RestoreConfig{
|
||||||
|
OnCollision: control.CollisionPolicy("collisions"),
|
||||||
|
Location: "destination",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range table {
|
||||||
|
suite.Run(test.name, func() {
|
||||||
|
t := suite.T()
|
||||||
|
|
||||||
|
ctx, flush := tester.NewContext(t)
|
||||||
|
defer flush()
|
||||||
|
|
||||||
|
opts := *rco
|
||||||
|
opts.Populated = test.populated
|
||||||
|
|
||||||
|
result := MakeRestoreConfig(ctx, opts, dttm.HumanReadable)
|
||||||
|
assert.Equal(t, test.expect.OnCollision, result.OnCollision)
|
||||||
|
assert.Contains(t, result.Location, test.expect.Location)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -31,6 +31,8 @@ type SharePointOpts struct {
|
|||||||
PageFolder []string
|
PageFolder []string
|
||||||
Page []string
|
Page []string
|
||||||
|
|
||||||
|
RestoreCfg RestoreCfgOpts
|
||||||
|
|
||||||
Populated flags.PopulatedFlags
|
Populated flags.PopulatedFlags
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,6 +55,11 @@ func MakeSharePointOpts(cmd *cobra.Command) SharePointOpts {
|
|||||||
Page: flags.PageFV,
|
Page: flags.PageFV,
|
||||||
PageFolder: flags.PageFolderFV,
|
PageFolder: flags.PageFolderFV,
|
||||||
|
|
||||||
|
RestoreCfg: makeRestoreCfgOpts(cmd),
|
||||||
|
|
||||||
|
// populated contains the list of flags that appear in the
|
||||||
|
// command, according to pflags. Use this to differentiate
|
||||||
|
// between an "empty" and a "missing" value.
|
||||||
Populated: flags.GetPopulatedFlags(cmd),
|
Populated: flags.GetPopulatedFlags(cmd),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -88,7 +95,7 @@ func ValidateSharePointRestoreFlags(backupID string, opts SharePointOpts) error
|
|||||||
return clues.New("invalid time format for " + flags.FileModifiedBeforeFN)
|
return clues.New("invalid time format for " + flags.FileModifiedBeforeFN)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return validateRestoreConfigFlags(flags.CollisionsFV, opts.RestoreCfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddSharePointInfo adds the scope of the provided values to the selector's
|
// AddSharePointInfo adds the scope of the provided values to the selector's
|
||||||
|
|||||||
2
src/cli/utils/testdata/flags.go
vendored
2
src/cli/utils/testdata/flags.go
vendored
@ -44,6 +44,8 @@ var (
|
|||||||
PageFolderInput = []string{"pageFolder1", "pageFolder2"}
|
PageFolderInput = []string{"pageFolder1", "pageFolder2"}
|
||||||
PageInput = []string{"page1", "page2"}
|
PageInput = []string{"page1", "page2"}
|
||||||
|
|
||||||
|
Collisions = "collisions"
|
||||||
|
Destination = "destination"
|
||||||
RestorePermissions = true
|
RestorePermissions = true
|
||||||
|
|
||||||
AzureClientID = "testAzureClientId"
|
AzureClientID = "testAzureClientId"
|
||||||
|
|||||||
@ -72,6 +72,14 @@ const (
|
|||||||
Replace CollisionPolicy = "replace"
|
Replace CollisionPolicy = "replace"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func ValidCollisionPolicies() map[CollisionPolicy]struct{} {
|
||||||
|
return map[CollisionPolicy]struct{}{
|
||||||
|
Skip: {},
|
||||||
|
Copy: {},
|
||||||
|
Replace: {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const RootLocation = "/"
|
const RootLocation = "/"
|
||||||
|
|
||||||
// RestoreConfig contains
|
// RestoreConfig contains
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
package control
|
package test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
@ -7,6 +7,7 @@ import (
|
|||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
"github.com/alcionai/corso/src/internal/tester"
|
"github.com/alcionai/corso/src/internal/tester"
|
||||||
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
)
|
)
|
||||||
|
|
||||||
type OptionsUnitSuite struct {
|
type OptionsUnitSuite struct {
|
||||||
@ -20,19 +21,19 @@ func TestOptionsUnitSuite(t *testing.T) {
|
|||||||
func (suite *OptionsUnitSuite) TestEnsureRestoreConfigDefaults() {
|
func (suite *OptionsUnitSuite) TestEnsureRestoreConfigDefaults() {
|
||||||
table := []struct {
|
table := []struct {
|
||||||
name string
|
name string
|
||||||
input RestoreConfig
|
input control.RestoreConfig
|
||||||
expect RestoreConfig
|
expect control.RestoreConfig
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "populated",
|
name: "populated",
|
||||||
input: RestoreConfig{
|
input: control.RestoreConfig{
|
||||||
OnCollision: Copy,
|
OnCollision: control.Copy,
|
||||||
ProtectedResource: "batman",
|
ProtectedResource: "batman",
|
||||||
Location: "badman",
|
Location: "badman",
|
||||||
Drive: "hatman",
|
Drive: "hatman",
|
||||||
},
|
},
|
||||||
expect: RestoreConfig{
|
expect: control.RestoreConfig{
|
||||||
OnCollision: Copy,
|
OnCollision: control.Copy,
|
||||||
ProtectedResource: "batman",
|
ProtectedResource: "batman",
|
||||||
Location: "badman",
|
Location: "badman",
|
||||||
Drive: "hatman",
|
Drive: "hatman",
|
||||||
@ -40,14 +41,14 @@ func (suite *OptionsUnitSuite) TestEnsureRestoreConfigDefaults() {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "unpopulated",
|
name: "unpopulated",
|
||||||
input: RestoreConfig{
|
input: control.RestoreConfig{
|
||||||
OnCollision: Unknown,
|
OnCollision: control.Unknown,
|
||||||
ProtectedResource: "",
|
ProtectedResource: "",
|
||||||
Location: "",
|
Location: "",
|
||||||
Drive: "",
|
Drive: "",
|
||||||
},
|
},
|
||||||
expect: RestoreConfig{
|
expect: control.RestoreConfig{
|
||||||
OnCollision: Skip,
|
OnCollision: control.Skip,
|
||||||
ProtectedResource: "",
|
ProtectedResource: "",
|
||||||
Location: "",
|
Location: "",
|
||||||
Drive: "",
|
Drive: "",
|
||||||
@ -55,14 +56,14 @@ func (suite *OptionsUnitSuite) TestEnsureRestoreConfigDefaults() {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "populated, but modified",
|
name: "populated, but modified",
|
||||||
input: RestoreConfig{
|
input: control.RestoreConfig{
|
||||||
OnCollision: CollisionPolicy("batman"),
|
OnCollision: control.CollisionPolicy("batman"),
|
||||||
ProtectedResource: "",
|
ProtectedResource: "",
|
||||||
Location: "/",
|
Location: "/",
|
||||||
Drive: "",
|
Drive: "",
|
||||||
},
|
},
|
||||||
expect: RestoreConfig{
|
expect: control.RestoreConfig{
|
||||||
OnCollision: Skip,
|
OnCollision: control.Skip,
|
||||||
ProtectedResource: "",
|
ProtectedResource: "",
|
||||||
Location: "",
|
Location: "",
|
||||||
Drive: "",
|
Drive: "",
|
||||||
@ -76,7 +77,7 @@ func (suite *OptionsUnitSuite) TestEnsureRestoreConfigDefaults() {
|
|||||||
ctx, flush := tester.NewContext(t)
|
ctx, flush := tester.NewContext(t)
|
||||||
defer flush()
|
defer flush()
|
||||||
|
|
||||||
result := EnsureRestoreConfigDefaults(ctx, test.input)
|
result := control.EnsureRestoreConfigDefaults(ctx, test.input)
|
||||||
assert.Equal(t, test.expect, result)
|
assert.Equal(t, test.expect, result)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user