Fix flags for Exchange and OneDrive (#2978)

While we were reading flags from the cli, they were not being passed onto the restore path.

---

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

- [x]  Yes, it's included
- [ ] 🕐 Yes, but in a later PR
- [ ]  No

#### Type of change

<!--- Please check the type of change your PR introduces: --->
- [ ] 🌻 Feature
- [x] 🐛 Bugfix
- [ ] 🗺️ Documentation
- [ ] 🤖 Supportability/Tests
- [ ] 💻 CI/Deployment
- [ ] 🧹 Tech Debt/Cleanup

#### Issue(s)

<!-- Can reference multiple issues. Use one of the following "magic words" - "closes, fixes" to auto-close the Github issue. -->
* #<issue>

#### Test Plan

<!-- How will this be tested prior to merging.-->
- [ ] 💪 Manual
- [x]  Unit test
- [ ] 💚 E2E
This commit is contained in:
Abin Simon 2023-03-30 04:06:48 +05:30 committed by GitHub
parent 4a395d44a8
commit 43e16b6ab2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 430 additions and 223 deletions

View File

@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Items not being deleted if they were created and deleted during item enumeration of a OneDrive backup.
- Enable compression for all data uploaded by kopia.
- SharePoint --folder selectors correctly return items.
- Fix Exchange cli args for filtering items
## [v0.6.1] (beta) - 2023-03-21

View File

@ -60,13 +60,6 @@ func AddCommands(cmd *cobra.Command) {
// common flags and flag attachers for commands
// ---------------------------------------------------------------------------
var (
fileCreatedAfter string
fileCreatedBefore string
fileModifiedAfter string
fileModifiedBefore string
)
// list output filter flags
var (
failedItemsFN = "failed-items"

View File

@ -24,11 +24,6 @@ import (
// setup and globals
// ------------------------------------------------------------------------------------------------
// exchange bucket info from flags
var (
exchangeData []string
)
const (
dataContacts = "contacts"
dataEmail = "email"
@ -81,6 +76,8 @@ func addExchangeCommands(cmd *cobra.Command) *cobra.Command {
switch cmd.Use {
case createCommand:
c, fs = utils.AddCommand(cmd, exchangeCreateCmd())
fs.SortFlags = false
options.AddFeatureToggle(cmd, options.DisableIncrementals())
c.Use = c.Use + " " + exchangeServiceCommandCreateUseSuffix
@ -89,11 +86,7 @@ func addExchangeCommands(cmd *cobra.Command) *cobra.Command {
// Flags addition ordering should follow the order we want them to appear in help and docs:
// More generic (ex: --user) and more frequently used flags take precedence.
utils.AddUserFlag(c)
fs.StringSliceVar(
&exchangeData,
utils.DataFN, nil,
"Select one or more types of data to backup: "+dataEmail+", "+dataContacts+", or "+dataEvents)
utils.AddDataFlag(c, []string{dataEmail, dataContacts, dataEvents}, false)
options.AddFetchParallelismFlag(c)
options.AddOperationFlags(c)
@ -107,7 +100,8 @@ func addExchangeCommands(cmd *cobra.Command) *cobra.Command {
addRecoveredErrorsFN(c)
case detailsCommand:
c, _ = utils.AddCommand(cmd, exchangeDetailsCmd())
c, fs = utils.AddCommand(cmd, exchangeDetailsCmd())
fs.SortFlags = false
c.Use = c.Use + " " + exchangeServiceCommandDetailsUseSuffix
c.Example = exchangeServiceCommandDetailsExamples
@ -120,7 +114,8 @@ func addExchangeCommands(cmd *cobra.Command) *cobra.Command {
utils.AddExchangeDetailsAndRestoreFlags(c)
case deleteCommand:
c, _ = utils.AddCommand(cmd, exchangeDeleteCmd())
c, fs = utils.AddCommand(cmd, exchangeDeleteCmd())
fs.SortFlags = false
c.Use = c.Use + " " + exchangeServiceCommandDeleteUseSuffix
c.Example = exchangeServiceCommandDeleteExamples
@ -153,7 +148,7 @@ func createExchangeCmd(cmd *cobra.Command, args []string) error {
return nil
}
if err := validateExchangeBackupCreateFlags(utils.User, exchangeData); err != nil {
if err := validateExchangeBackupCreateFlags(utils.User, utils.CategoryData); err != nil {
return err
}
@ -164,7 +159,7 @@ func createExchangeCmd(cmd *cobra.Command, args []string) error {
defer utils.CloseRepo(ctx, r)
sel := exchangeBackupCreateSelectors(utils.User, exchangeData)
sel := exchangeBackupCreateSelectors(utils.User, utils.CategoryData)
// TODO: log/print recoverable errors
errs := fault.New(false)
@ -267,30 +262,7 @@ func detailsExchangeCmd(cmd *cobra.Command, args []string) error {
}
ctx := cmd.Context()
opts := utils.ExchangeOpts{
Users: utils.User,
Contact: utils.Contact,
ContactFolder: utils.ContactFolder,
ContactName: utils.ContactName,
Email: utils.Email,
EmailFolder: utils.EmailFolder,
EmailReceivedAfter: utils.EmailReceivedAfter,
EmailReceivedBefore: utils.EmailReceivedBefore,
EmailSender: utils.EmailSender,
EmailSubject: utils.EmailSubject,
EventOrganizer: utils.EventOrganizer,
Event: utils.Event,
EventCalendar: utils.EventCalendar,
EventRecurs: utils.EventRecurs,
EventStartsAfter: utils.EventStartsAfter,
EventStartsBefore: utils.EventStartsBefore,
EventSubject: utils.EventSubject,
Populated: utils.GetPopulatedFlags(cmd),
}
opts := utils.MakeExchangeOpts(cmd)
r, _, err := getAccountAndConnect(ctx)
if err != nil {

View File

@ -184,7 +184,7 @@ func (suite *BackupExchangeE2ESuite) TestExchangeBackupCmd() {
"backup", "create", "exchange",
"--config-file", suite.cfgFP,
"--"+utils.UserFN, suite.m365UserID,
"--"+utils.DataFN, set.String())
"--"+utils.CategoryDataFN, set.String())
cli.BuildCommandTree(cmd)
cmd.SetOut(&recorder)
@ -221,7 +221,7 @@ func (suite *BackupExchangeE2ESuite) TestExchangeBackupCmd_UserNotInTenant() {
"backup", "create", "exchange",
"--config-file", suite.cfgFP,
"--"+utils.UserFN, "foo@nothere.com",
"--"+utils.DataFN, set.String())
"--"+utils.CategoryDataFN, set.String())
cli.BuildCommandTree(cmd)
cmd.SetOut(&recorder)

View File

@ -65,7 +65,9 @@ func addOneDriveCommands(cmd *cobra.Command) *cobra.Command {
switch cmd.Use {
case createCommand:
c, _ = utils.AddCommand(cmd, oneDriveCreateCmd())
c, fs = utils.AddCommand(cmd, oneDriveCreateCmd())
fs.SortFlags = false
options.AddFeatureToggle(cmd, options.EnablePermissionsBackup())
c.Use = c.Use + " " + oneDriveServiceCommandCreateUseSuffix
@ -84,7 +86,8 @@ func addOneDriveCommands(cmd *cobra.Command) *cobra.Command {
addRecoveredErrorsFN(c)
case detailsCommand:
c, _ = utils.AddCommand(cmd, oneDriveDetailsCmd())
c, fs = utils.AddCommand(cmd, oneDriveDetailsCmd())
fs.SortFlags = false
c.Use = c.Use + " " + oneDriveServiceCommandDetailsUseSuffix
c.Example = oneDriveServiceCommandDetailsExamples
@ -94,7 +97,8 @@ func addOneDriveCommands(cmd *cobra.Command) *cobra.Command {
utils.AddOneDriveDetailsAndRestoreFlags(c)
case deleteCommand:
c, _ = utils.AddCommand(cmd, oneDriveDeleteCmd())
c, fs = utils.AddCommand(cmd, oneDriveDeleteCmd())
fs.SortFlags = false
c.Use = c.Use + " " + oneDriveServiceCommandDeleteUseSuffix
c.Example = oneDriveServiceCommandDeleteExamples
@ -219,17 +223,7 @@ func detailsOneDriveCmd(cmd *cobra.Command, args []string) error {
}
ctx := cmd.Context()
opts := utils.OneDriveOpts{
Users: utils.User,
FileNames: utils.FileName,
FolderPaths: utils.FolderPath,
FileCreatedAfter: utils.FileCreatedAfter,
FileCreatedBefore: utils.FileCreatedBefore,
FileModifiedAfter: utils.FileModifiedAfter,
FileModifiedBefore: utils.FileModifiedBefore,
Populated: utils.GetPopulatedFlags(cmd),
}
opts := utils.MakeOneDriveOpts(cmd)
r, _, err := getAccountAndConnect(ctx)
if err != nil {

View File

@ -25,9 +25,6 @@ import (
// setup and globals
// ------------------------------------------------------------------------------------------------
// sharePoint bucket info from flags
var sharepointData []string
const (
dataLibraries = "libraries"
dataPages = "pages"
@ -76,18 +73,14 @@ func addSharePointCommands(cmd *cobra.Command) *cobra.Command {
switch cmd.Use {
case createCommand:
c, fs = utils.AddCommand(cmd, sharePointCreateCmd())
fs.SortFlags = false
c.Use = c.Use + " " + sharePointServiceCommandCreateUseSuffix
c.Example = sharePointServiceCommandCreateExamples
utils.AddSiteFlag(c)
utils.AddSiteIDFlag(c)
fs.StringSliceVar(
&sharepointData,
utils.DataFN, nil,
"Select one or more types of data to backup: "+dataLibraries+" or "+dataPages+".")
cobra.CheckErr(fs.MarkHidden(utils.DataFN))
utils.AddDataFlag(c, []string{dataLibraries}, true)
options.AddOperationFlags(c)
@ -101,7 +94,8 @@ func addSharePointCommands(cmd *cobra.Command) *cobra.Command {
addRecoveredErrorsFN(c)
case detailsCommand:
c, _ = utils.AddCommand(cmd, sharePointDetailsCmd())
c, fs = utils.AddCommand(cmd, sharePointDetailsCmd())
fs.SortFlags = false
c.Use = c.Use + " " + sharePointServiceCommandDetailsUseSuffix
c.Example = sharePointServiceCommandDetailsExamples
@ -111,7 +105,8 @@ func addSharePointCommands(cmd *cobra.Command) *cobra.Command {
utils.AddSharePointDetailsAndRestoreFlags(c)
case deleteCommand:
c, _ = utils.AddCommand(cmd, sharePointDeleteCmd())
c, fs = utils.AddCommand(cmd, sharePointDeleteCmd())
fs.SortFlags = false
c.Use = c.Use + " " + sharePointServiceCommandDeleteUseSuffix
c.Example = sharePointServiceCommandDeleteExamples
@ -145,7 +140,7 @@ func createSharePointCmd(cmd *cobra.Command, args []string) error {
return nil
}
if err := validateSharePointBackupCreateFlags(utils.SiteID, utils.WebURL, sharepointData); err != nil {
if err := validateSharePointBackupCreateFlags(utils.SiteID, utils.WebURL, utils.CategoryData); err != nil {
return err
}
@ -164,7 +159,7 @@ func createSharePointCmd(cmd *cobra.Command, args []string) error {
return Only(ctx, clues.Wrap(err, "Failed to connect to Microsoft APIs"))
}
sel, err := sharePointBackupCreateSelectors(ctx, utils.SiteID, utils.WebURL, sharepointData, gc)
sel, err := sharePointBackupCreateSelectors(ctx, utils.SiteID, utils.WebURL, utils.CategoryData, gc)
if err != nil {
return Only(ctx, clues.Wrap(err, "Retrieving up sharepoint sites by ID and URL"))
}
@ -325,19 +320,7 @@ func detailsSharePointCmd(cmd *cobra.Command, args []string) error {
}
ctx := cmd.Context()
opts := utils.SharePointOpts{
FolderPath: utils.FolderPath,
FileName: utils.FileName,
Library: utils.Library,
SiteID: utils.SiteID,
WebURL: utils.WebURL,
FileCreatedAfter: fileCreatedAfter,
FileCreatedBefore: fileCreatedBefore,
FileModifiedAfter: fileModifiedAfter,
FileModifiedBefore: fileModifiedBefore,
Populated: utils.GetPopulatedFlags(cmd),
}
opts := utils.MakeSharePointOpts(cmd)
r, _, err := getAccountAndConnect(ctx)
if err != nil {

View File

@ -121,6 +121,7 @@ func CorsoCommand() *cobra.Command {
func BuildCommandTree(cmd *cobra.Command) {
// want to order flags explicitly
cmd.PersistentFlags().SortFlags = false
utils.AddRunModeFlag(cmd, true)
cmd.Flags().BoolP("version", "v", false, "current version info")
cmd.PersistentPreRunE = preRun
@ -129,7 +130,6 @@ func BuildCommandTree(cmd *cobra.Command) {
observe.AddProgressBarFlags(cmd)
print.AddOutputFlag(cmd)
options.AddGlobalOperationFlags(cmd)
cmd.SetUsageTemplate(indentExamplesTemplate(corsoCmd.UsageTemplate()))
cmd.CompletionOptions.DisableDefaultCmd = true

View File

@ -75,9 +75,7 @@ func AddConfigFlags(cmd *cobra.Command) {
fs := cmd.PersistentFlags()
fs.StringVar(
&configFilePathFlag,
"config-file",
displayDefaultFP,
"config file location")
"config-file", displayDefaultFP, "config file location")
}
// ---------------------------------------------------------------------------------------------------------

View File

@ -16,30 +16,6 @@ import (
"github.com/alcionai/corso/src/pkg/repository"
)
// exchange bucket info from flags
var (
user []string
contact []string
contactFolder []string
contactName string
email []string
emailFolder []string
emailReceivedAfter string
emailReceivedBefore string
emailSender string
emailSubject string
event []string
eventCalendar []string
eventOrganizer string
eventRecurs string
eventStartsAfter string
eventStartsBefore string
eventSubject string
)
// called by restore.go to map subcommands to provider-specific handling.
func addExchangeCommands(cmd *cobra.Command) *cobra.Command {
var (
@ -106,26 +82,10 @@ func restoreExchangeCmd(cmd *cobra.Command, args []string) error {
return nil
}
opts := utils.ExchangeOpts{
Contact: contact,
ContactFolder: contactFolder,
Email: email,
EmailFolder: emailFolder,
Event: event,
EventCalendar: eventCalendar,
Users: user,
ContactName: contactName,
EmailReceivedAfter: emailReceivedAfter,
EmailReceivedBefore: emailReceivedBefore,
EmailSender: emailSender,
EmailSubject: emailSubject,
EventOrganizer: eventOrganizer,
EventRecurs: eventRecurs,
EventStartsAfter: eventStartsAfter,
EventStartsBefore: eventStartsBefore,
EventSubject: eventSubject,
opts := utils.MakeExchangeOpts(cmd)
Populated: utils.GetPopulatedFlags(cmd),
if utils.RunMode == utils.RunModeFlagTest {
return nil
}
if err := utils.ValidateExchangeRestoreFlags(utils.BackupID, opts); err != nil {

View File

@ -1,13 +1,17 @@
package restore
import (
"bytes"
"testing"
"github.com/alcionai/clues"
"github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"github.com/alcionai/corso/src/cli/utils"
"github.com/alcionai/corso/src/cli/utils/testdata"
"github.com/alcionai/corso/src/internal/tester"
)
@ -37,6 +41,10 @@ func (suite *ExchangeUnitSuite) TestAddExchangeCommands() {
cmd := &cobra.Command{Use: test.use}
// normally a persisten flag from the root.
// required to ensure a dry run.
utils.AddRunModeFlag(cmd, true)
c := addExchangeCommands(cmd)
require.NotNil(t, c)
@ -47,6 +55,59 @@ func (suite *ExchangeUnitSuite) TestAddExchangeCommands() {
assert.Equal(t, test.expectUse, child.Use)
assert.Equal(t, test.expectShort, child.Short)
tester.AreSameFunc(t, test.expectRunE, child.RunE)
// Test arg parsing for few args
cmd.SetArgs([]string{
"exchange",
"--" + utils.RunModeFN, utils.RunModeFlagTest,
"--" + utils.BackupFN, testdata.BackupInpt,
"--" + utils.ContactFN, testdata.FlgInpts(testdata.ContactInpt),
"--" + utils.ContactFolderFN, testdata.FlgInpts(testdata.ContactFldInpt),
"--" + utils.ContactNameFN, testdata.ContactNameInpt,
"--" + utils.EmailFN, testdata.FlgInpts(testdata.EmailInpt),
"--" + utils.EmailFolderFN, testdata.FlgInpts(testdata.EmailFldInpt),
"--" + utils.EmailReceivedAfterFN, testdata.EmailReceivedAfterInpt,
"--" + utils.EmailReceivedBeforeFN, testdata.EmailReceivedBeforeInpt,
"--" + utils.EmailSenderFN, testdata.EmailSenderInpt,
"--" + utils.EmailSubjectFN, testdata.EmailSubjectInpt,
"--" + utils.EventFN, testdata.FlgInpts(testdata.EventInpt),
"--" + utils.EventCalendarFN, testdata.FlgInpts(testdata.EventCalInpt),
"--" + utils.EventOrganizerFN, testdata.EventOrganizerInpt,
"--" + utils.EventRecursFN, testdata.EventRecursInpt,
"--" + utils.EventStartsAfterFN, testdata.EventStartsAfterInpt,
"--" + utils.EventStartsBeforeFN, testdata.EventStartsBeforeInpt,
"--" + utils.EventSubjectFN, testdata.EventSubjectInpt,
})
cmd.SetOut(new(bytes.Buffer)) // drop output
cmd.SetErr(new(bytes.Buffer)) // drop output
err := cmd.Execute()
assert.NoError(t, err, clues.ToCore(err))
opts := utils.MakeExchangeOpts(cmd)
assert.Equal(t, testdata.BackupInpt, utils.BackupID)
assert.ElementsMatch(t, testdata.ContactInpt, opts.Contact)
assert.ElementsMatch(t, testdata.ContactFldInpt, opts.ContactFolder)
assert.Equal(t, testdata.ContactNameInpt, opts.ContactName)
assert.ElementsMatch(t, testdata.EmailInpt, opts.Email)
assert.ElementsMatch(t, testdata.EmailFldInpt, opts.EmailFolder)
assert.Equal(t, testdata.EmailReceivedAfterInpt, opts.EmailReceivedAfter)
assert.Equal(t, testdata.EmailReceivedBeforeInpt, opts.EmailReceivedBefore)
assert.Equal(t, testdata.EmailSenderInpt, opts.EmailSender)
assert.Equal(t, testdata.EmailSubjectInpt, opts.EmailSubject)
assert.ElementsMatch(t, testdata.EventInpt, opts.Event)
assert.ElementsMatch(t, testdata.EventCalInpt, opts.EventCalendar)
assert.Equal(t, testdata.EventOrganizerInpt, opts.EventOrganizer)
assert.Equal(t, testdata.EventRecursInpt, opts.EventRecurs)
assert.Equal(t, testdata.EventStartsAfterInpt, opts.EventStartsAfter)
assert.Equal(t, testdata.EventStartsBeforeInpt, opts.EventStartsBefore)
assert.Equal(t, testdata.EventSubjectInpt, opts.EventSubject)
})
}
}

View File

@ -84,16 +84,10 @@ func restoreOneDriveCmd(cmd *cobra.Command, args []string) error {
return nil
}
opts := utils.OneDriveOpts{
Users: user,
FileNames: utils.FileName,
FolderPaths: utils.FolderPath,
FileCreatedAfter: utils.FileCreatedAfter,
FileCreatedBefore: utils.FileCreatedBefore,
FileModifiedAfter: utils.FileModifiedAfter,
FileModifiedBefore: utils.FileModifiedBefore,
opts := utils.MakeOneDriveOpts(cmd)
Populated: utils.GetPopulatedFlags(cmd),
if utils.RunMode == utils.RunModeFlagTest {
return nil
}
if err := utils.ValidateOneDriveRestoreFlags(utils.BackupID, opts); err != nil {

View File

@ -1,14 +1,17 @@
package restore
import (
"bytes"
"testing"
"github.com/alcionai/clues"
"github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"github.com/alcionai/corso/src/cli/utils"
"github.com/alcionai/corso/src/cli/utils/testdata"
"github.com/alcionai/corso/src/internal/tester"
)
@ -38,6 +41,10 @@ func (suite *OneDriveUnitSuite) TestAddOneDriveCommands() {
cmd := &cobra.Command{Use: test.use}
// normally a persisten flag from the root.
// required to ensure a dry run.
utils.AddRunModeFlag(cmd, true)
c := addOneDriveCommands(cmd)
require.NotNil(t, c)
@ -49,8 +56,33 @@ func (suite *OneDriveUnitSuite) TestAddOneDriveCommands() {
assert.Equal(t, test.expectShort, child.Short)
tester.AreSameFunc(t, test.expectRunE, child.RunE)
assert.NotNil(t, c.Flag(utils.BackupFN))
assert.NotNil(t, c.Flag("restore-permissions"))
cmd.SetArgs([]string{
"onedrive",
"--" + utils.RunModeFN, utils.RunModeFlagTest,
"--" + utils.BackupFN, testdata.BackupInpt,
"--" + utils.FileFN, testdata.FlgInpts(testdata.FileNamesInpt),
"--" + utils.FolderFN, testdata.FlgInpts(testdata.FolderPathsInpt),
"--" + utils.FileCreatedAfterFN, testdata.FileCreatedAfterInpt,
"--" + utils.FileCreatedBeforeFN, testdata.FileCreatedBeforeInpt,
"--" + utils.FileModifiedAfterFN, testdata.FileModifiedAfterInpt,
"--" + utils.FileModifiedBeforeFN, testdata.FileModifiedBeforeInpt,
})
cmd.SetOut(new(bytes.Buffer)) // drop output
cmd.SetErr(new(bytes.Buffer)) // drop output
err := cmd.Execute()
assert.NoError(t, err, clues.ToCore(err))
opts := utils.MakeOneDriveOpts(cmd)
assert.Equal(t, testdata.BackupInpt, utils.BackupID)
assert.ElementsMatch(t, testdata.FileNamesInpt, opts.FileNames)
assert.ElementsMatch(t, testdata.FolderPathsInpt, opts.FolderPaths)
assert.Equal(t, testdata.FileCreatedAfterInpt, opts.FileCreatedAfter)
assert.Equal(t, testdata.FileCreatedBeforeInpt, opts.FileCreatedBefore)
assert.Equal(t, testdata.FileModifiedAfterInpt, opts.FileModifiedAfter)
assert.Equal(t, testdata.FileModifiedBeforeInpt, opts.FileModifiedBefore)
})
}
}

View File

@ -16,13 +16,6 @@ import (
"github.com/alcionai/corso/src/pkg/repository"
)
var (
listItems []string
listPaths []string
pageFolders []string
pages []string
)
// called by restore.go to map subcommands to provider-specific handling.
func addSharePointCommands(cmd *cobra.Command) *cobra.Command {
var (
@ -90,21 +83,10 @@ func restoreSharePointCmd(cmd *cobra.Command, args []string) error {
return nil
}
opts := utils.SharePointOpts{
FileName: utils.FileName,
FolderPath: utils.FolderPath,
Library: utils.Library,
ListItem: listItems,
ListPath: listPaths,
PageFolder: pageFolders,
Page: pages,
SiteID: utils.SiteID,
WebURL: utils.WebURL,
FileCreatedAfter: utils.FileCreatedAfter,
FileCreatedBefore: utils.FileCreatedBefore,
FileModifiedAfter: utils.FileModifiedAfter,
FileModifiedBefore: utils.FileModifiedBefore,
Populated: utils.GetPopulatedFlags(cmd),
opts := utils.MakeSharePointOpts(cmd)
if utils.RunMode == utils.RunModeFlagTest {
return nil
}
if err := utils.ValidateSharePointRestoreFlags(utils.BackupID, opts); err != nil {

View File

@ -1,13 +1,17 @@
package restore
import (
"bytes"
"testing"
"github.com/alcionai/clues"
"github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"github.com/alcionai/corso/src/cli/utils"
"github.com/alcionai/corso/src/cli/utils/testdata"
"github.com/alcionai/corso/src/internal/tester"
)
@ -37,6 +41,10 @@ func (suite *SharePointUnitSuite) TestAddSharePointCommands() {
cmd := &cobra.Command{Use: test.use}
// normally a persisten flag from the root.
// required to ensure a dry run.
utils.AddRunModeFlag(cmd, true)
c := addSharePointCommands(cmd)
require.NotNil(t, c)
@ -47,6 +55,48 @@ func (suite *SharePointUnitSuite) TestAddSharePointCommands() {
assert.Equal(t, test.expectUse, child.Use)
assert.Equal(t, test.expectShort, child.Short)
tester.AreSameFunc(t, test.expectRunE, child.RunE)
cmd.SetArgs([]string{
"sharepoint",
"--" + utils.RunModeFN, utils.RunModeFlagTest,
"--" + utils.BackupFN, testdata.BackupInpt,
"--" + utils.LibraryFN, testdata.LibraryInpt,
"--" + utils.FileFN, testdata.FlgInpts(testdata.FileNamesInpt),
"--" + utils.FolderFN, testdata.FlgInpts(testdata.FolderPathsInpt),
"--" + utils.FileCreatedAfterFN, testdata.FileCreatedAfterInpt,
"--" + utils.FileCreatedBeforeFN, testdata.FileCreatedBeforeInpt,
"--" + utils.FileModifiedAfterFN, testdata.FileModifiedAfterInpt,
"--" + utils.FileModifiedBeforeFN, testdata.FileModifiedBeforeInpt,
"--" + utils.ListItemFN, testdata.FlgInpts(testdata.ListItemInpt),
"--" + utils.ListFolderFN, testdata.FlgInpts(testdata.ListFolderInpt),
"--" + utils.PageFN, testdata.FlgInpts(testdata.PageInpt),
"--" + utils.PageFolderFN, testdata.FlgInpts(testdata.PageFolderInpt),
})
cmd.SetOut(new(bytes.Buffer)) // drop output
cmd.SetErr(new(bytes.Buffer)) // drop output
err := cmd.Execute()
assert.NoError(t, err, clues.ToCore(err))
opts := utils.MakeSharePointOpts(cmd)
assert.Equal(t, testdata.BackupInpt, utils.BackupID)
assert.Equal(t, testdata.LibraryInpt, opts.Library)
assert.ElementsMatch(t, testdata.FileNamesInpt, opts.FileName)
assert.ElementsMatch(t, testdata.FolderPathsInpt, opts.FolderPath)
assert.Equal(t, testdata.FileCreatedAfterInpt, opts.FileCreatedAfter)
assert.Equal(t, testdata.FileCreatedBeforeInpt, opts.FileCreatedBefore)
assert.Equal(t, testdata.FileModifiedAfterInpt, opts.FileModifiedAfter)
assert.Equal(t, testdata.FileModifiedBeforeInpt, opts.FileModifiedBefore)
assert.ElementsMatch(t, testdata.ListItemInpt, opts.ListItem)
assert.ElementsMatch(t, testdata.ListFolderInpt, opts.ListFolder)
assert.ElementsMatch(t, testdata.PageInpt, opts.Page)
assert.ElementsMatch(t, testdata.PageFolderInpt, opts.PageFolder)
})
}
}

View File

@ -52,27 +52,58 @@ var (
)
type ExchangeOpts struct {
Contact []string
ContactFolder []string
Users []string
Contact []string
ContactFolder []string
ContactName string
Email []string
EmailFolder []string
Event []string
EventCalendar []string
Users []string
ContactName string
EmailReceivedAfter string
EmailReceivedBefore string
EmailSender string
EmailSubject string
EventOrganizer string
EventRecurs string
EventStartsAfter string
EventStartsBefore string
EventSubject string
Event []string
EventCalendar []string
EventOrganizer string
EventRecurs string
EventStartsAfter string
EventStartsBefore string
EventSubject string
Populated PopulatedFlags
}
// populates an ExchangeOpts struct with the command's current flags.
func MakeExchangeOpts(cmd *cobra.Command) ExchangeOpts {
return ExchangeOpts{
Users: User,
Contact: Contact,
ContactFolder: ContactFolder,
ContactName: ContactName,
Email: Email,
EmailFolder: EmailFolder,
EmailReceivedAfter: EmailReceivedAfter,
EmailReceivedBefore: EmailReceivedBefore,
EmailSender: EmailSender,
EmailSubject: EmailSubject,
Event: Event,
EventCalendar: EventCalendar,
EventOrganizer: EventOrganizer,
EventRecurs: EventRecurs,
EventStartsAfter: EventStartsAfter,
EventStartsBefore: EventStartsBefore,
EventSubject: EventSubject,
Populated: GetPopulatedFlags(cmd),
}
}
// AddExchangeDetailsAndRestoreFlags adds flags that are common to both the
// details and restore commands.
func AddExchangeDetailsAndRestoreFlags(cmd *cobra.Command) {

View File

@ -1,7 +1,9 @@
package utils
import (
"fmt"
"strconv"
"strings"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
@ -12,6 +14,10 @@ import (
// common flag vars
var (
// RunMode describes the type of run, such as:
// flagtest, dry, run. Should default to 'run'.
RunMode string
BackupID string
FolderPath []string
@ -27,19 +33,25 @@ var (
WebURL []string
User []string
// for selection of data by category. eg: `--data email,contacts`
CategoryData []string
)
// common flag names
const (
BackupFN = "backup"
DataFN = "data"
LibraryFN = "library"
SiteFN = "site" // site only accepts WebURL values
SiteIDFN = "site-id" // site-id accepts actual site ids
UserFN = "user"
RunModeFN = "run-mode"
FileFN = "file"
FolderFN = "folder"
BackupFN = "backup"
CategoryDataFN = "data"
SiteFN = "site" // site only accepts WebURL values
SiteIDFN = "site-id" // site-id accepts actual site ids
UserFN = "user"
LibraryFN = "library"
FileFN = "file"
FolderFN = "folder"
FileCreatedAfterFN = "file-created-after"
FileCreatedBeforeFN = "file-created-before"
@ -47,6 +59,12 @@ const (
FileModifiedBeforeFN = "file-modified-before"
)
// well-knwon flag values
const (
RunModeFlagTest = "flag-test"
RunModeRun = "run"
)
// AddBackupIDFlag adds the --backup flag.
func AddBackupIDFlag(cmd *cobra.Command, require bool) {
cmd.Flags().StringVar(&BackupID, BackupFN, "", "ID of the backup to retrieve.")
@ -56,6 +74,47 @@ func AddBackupIDFlag(cmd *cobra.Command, require bool) {
}
}
func AddDataFlag(cmd *cobra.Command, allowed []string, hide bool) {
var (
allowedMsg string
fs = cmd.Flags()
)
switch len(allowed) {
case 0:
return
case 1:
allowedMsg = allowed[0]
case 2:
allowedMsg = fmt.Sprintf("%s or %s", allowed[0], allowed[1])
default:
allowedMsg = fmt.Sprintf(
"%s or %s",
strings.Join(allowed[:len(allowed)-1], ", "),
allowed[len(allowed)-1])
}
fs.StringSliceVar(
&CategoryData,
CategoryDataFN, nil,
"Select one or more types of data to backup: "+allowedMsg+".")
if hide {
cobra.CheckErr(fs.MarkHidden(CategoryDataFN))
}
}
// AddRunModeFlag adds the hidden --run-mode flag.
func AddRunModeFlag(cmd *cobra.Command, persistent bool) {
fs := cmd.Flags()
if persistent {
fs = cmd.PersistentFlags()
}
fs.StringVar(&RunMode, RunModeFN, "run", "What mode to run: dry, test, run. Defaults to run.")
cobra.CheckErr(fs.MarkHidden(RunModeFN))
}
// AddUserFlag adds the --user flag.
func AddUserFlag(cmd *cobra.Command) {
cmd.Flags().StringSliceVar(

View File

@ -8,7 +8,8 @@ import (
)
type OneDriveOpts struct {
Users []string
Users []string
FileNames []string
FolderPaths []string
FileCreatedAfter string
@ -19,6 +20,21 @@ type OneDriveOpts struct {
Populated PopulatedFlags
}
func MakeOneDriveOpts(cmd *cobra.Command) OneDriveOpts {
return OneDriveOpts{
Users: User,
FileNames: FileName,
FolderPaths: FolderPath,
FileCreatedAfter: FileCreatedAfter,
FileCreatedBefore: FileCreatedBefore,
FileModifiedAfter: FileModifiedAfter,
FileModifiedBefore: FileModifiedBefore,
Populated: GetPopulatedFlags(cmd),
}
}
// AddOneDriveDetailsAndRestoreFlags adds flags that are common to both the
// details and restore commands.
func AddOneDriveDetailsAndRestoreFlags(cmd *cobra.Command) {

View File

@ -8,82 +8,91 @@ import (
)
const (
ListFolderFN = "list"
ListItemFN = "list-item"
ListFN = "list"
PageFolderFN = "page-folder"
PagesFN = "page"
PageFN = "page"
)
// flag population variables
var (
ListFolder []string
ListItem []string
PageFolder []string
Page []string
)
type SharePointOpts struct {
Library string
FileName []string // for libraries, to duplicate onedrive interface
FolderPath []string // for libraries, to duplicate onedrive interface
ListItem []string
ListPath []string
PageFolder []string
Page []string
SiteID []string
WebURL []string
Library string
FileName []string // for libraries, to duplicate onedrive interface
FolderPath []string // for libraries, to duplicate onedrive interface
FileCreatedAfter string
FileCreatedBefore string
FileModifiedAfter string
FileModifiedBefore string
ListFolder []string
ListItem []string
PageFolder []string
Page []string
Populated PopulatedFlags
}
func MakeSharePointOpts(cmd *cobra.Command) SharePointOpts {
return SharePointOpts{
SiteID: SiteID,
WebURL: WebURL,
Library: Library,
FileName: FileName,
FolderPath: FolderPath,
FileCreatedAfter: FileCreatedAfter,
FileCreatedBefore: FileCreatedBefore,
FileModifiedAfter: FileModifiedAfter,
FileModifiedBefore: FileModifiedBefore,
ListFolder: ListFolder,
ListItem: ListItem,
Page: Page,
PageFolder: PageFolder,
Populated: GetPopulatedFlags(cmd),
}
}
// AddSharePointDetailsAndRestoreFlags adds flags that are common to both the
// details and restore commands.
func AddSharePointDetailsAndRestoreFlags(cmd *cobra.Command) {
fs := cmd.Flags()
// libraries
fs.StringVar(
&Library,
LibraryFN, "",
"Select only this library; defaults to all libraries.")
fs.StringSliceVar(
&FolderPath,
FolderFN, nil,
"Select by folder; defaults to root.")
fs.StringSliceVar(
&FileName,
FileFN, nil,
"Select by file name.")
fs.StringSliceVar(
&PageFolder,
PageFolderFN, nil,
"Select pages by folder name; accepts '"+Wildcard+"' to select all folders.")
cobra.CheckErr(fs.MarkHidden(PageFolderFN))
fs.StringSliceVar(
&Page,
PagesFN, nil,
"Select pages by item name; accepts '"+Wildcard+"' to select all pages within the site.")
cobra.CheckErr(fs.MarkHidden(PagesFN))
fs.StringVar(
&FileCreatedAfter,
FileCreatedAfterFN, "",
"Select files created after this datetime.")
fs.StringVar(
&FileCreatedBefore,
FileCreatedBeforeFN, "",
"Select files created before this datetime.")
fs.StringVar(
&FileModifiedAfter,
FileModifiedAfterFN, "",
@ -92,6 +101,32 @@ func AddSharePointDetailsAndRestoreFlags(cmd *cobra.Command) {
&FileModifiedBefore,
FileModifiedBeforeFN, "",
"Select files modified before this datetime.")
// lists
fs.StringSliceVar(
&ListFolder,
ListFolderFN, nil,
"Select lists by name; accepts '"+Wildcard+"' to select all lists.")
cobra.CheckErr(fs.MarkHidden(ListFolderFN))
fs.StringSliceVar(
&ListItem,
ListItemFN, nil,
"Select lists by item name; accepts '"+Wildcard+"' to select all lists.")
cobra.CheckErr(fs.MarkHidden(ListItemFN))
// pages
fs.StringSliceVar(
&PageFolder,
PageFolderFN, nil,
"Select pages by folder name; accepts '"+Wildcard+"' to select all pages.")
cobra.CheckErr(fs.MarkHidden(PageFolderFN))
fs.StringSliceVar(
&Page,
PageFN, nil,
"Select pages by item name; accepts '"+Wildcard+"' to select all pages.")
cobra.CheckErr(fs.MarkHidden(PageFN))
}
// ValidateSharePointRestoreFlags checks common flags for correctness and interdependencies
@ -140,7 +175,7 @@ func IncludeSharePointRestoreDataSelectors(opts SharePointOpts) *selectors.Share
lfp, lfn := len(opts.FolderPath), len(opts.FileName)
ls, lwu := len(opts.SiteID), len(opts.WebURL)
slp, sli := len(opts.ListPath), len(opts.ListItem)
slp, sli := len(opts.ListFolder), len(opts.ListItem)
pf, pi := len(opts.PageFolder), len(opts.Page)
if ls == 0 {
@ -176,8 +211,8 @@ func IncludeSharePointRestoreDataSelectors(opts SharePointOpts) *selectors.Share
opts.ListItem = selectors.Any()
}
opts.ListPath = trimFolderSlash(opts.ListPath)
containsFolders, prefixFolders := splitFoldersIntoContainsAndPrefix(opts.ListPath)
opts.ListFolder = trimFolderSlash(opts.ListFolder)
containsFolders, prefixFolders := splitFoldersIntoContainsAndPrefix(opts.ListFolder)
if len(containsFolders) > 0 {
sel.Include(sel.ListItems(containsFolders, opts.ListItem))

View File

@ -56,7 +56,7 @@ func (suite *SharePointUtilsSuite) TestIncludeSharePointRestoreDataSelectors() {
FileName: single,
FolderPath: single,
ListItem: single,
ListPath: single,
ListFolder: single,
SiteID: single,
WebURL: single,
},
@ -108,7 +108,7 @@ func (suite *SharePointUtilsSuite) TestIncludeSharePointRestoreDataSelectors() {
FileName: empty,
FolderPath: empty,
ListItem: empty,
ListPath: containsOnly,
ListFolder: containsOnly,
SiteID: empty,
WebURL: empty,
},
@ -117,14 +117,14 @@ func (suite *SharePointUtilsSuite) TestIncludeSharePointRestoreDataSelectors() {
{
name: "list prefixes",
opts: utils.SharePointOpts{
ListPath: prefixOnly,
ListFolder: prefixOnly,
},
expectIncludeLen: 1,
},
{
name: "list prefixes and contains",
opts: utils.SharePointOpts{
ListPath: containsAndPrefix,
ListFolder: containsAndPrefix,
},
expectIncludeLen: 2,
},

46
src/cli/utils/testdata/flags.go vendored Normal file
View File

@ -0,0 +1,46 @@
package testdata
import "strings"
func FlgInpts(in []string) string { return strings.Join(in, ",") }
var (
BackupInpt = "backup-id"
UsersInpt = []string{"users1", "users2"}
SiteIDInpt = []string{"siteID1", "siteID2"}
WebURLInpt = []string{"webURL1", "webURL2"}
ContactInpt = []string{"contact1", "contact2"}
ContactFldInpt = []string{"contactFld1", "contactFld2"}
ContactNameInpt = "contactName"
EmailInpt = []string{"mail1", "mail2"}
EmailFldInpt = []string{"mailFld1", "mailFld2"}
EmailReceivedAfterInpt = "mailReceivedAfter"
EmailReceivedBeforeInpt = "mailReceivedBefore"
EmailSenderInpt = "mailSender"
EmailSubjectInpt = "mailSubjet"
EventInpt = []string{"event1", "event2"}
EventCalInpt = []string{"eventCal1", "eventCal2"}
EventOrganizerInpt = "eventOrganizer"
EventRecursInpt = "eventRecurs"
EventStartsAfterInpt = "eventStartsAfter"
EventStartsBeforeInpt = "eventStartsBefore"
EventSubjectInpt = "eventSubject"
LibraryInpt = "library"
FileNamesInpt = []string{"fileName1", "fileName2"}
FolderPathsInpt = []string{"folderPath1", "folderPath2"}
FileCreatedAfterInpt = "fileCreatedAfter"
FileCreatedBeforeInpt = "fileCreatedBefore"
FileModifiedAfterInpt = "fileModifiedAfter"
FileModifiedBeforeInpt = "fileModifiedBefore"
ListFolderInpt = []string{"listFolder1", "listFolder2"}
ListItemInpt = []string{"listItem1", "listItem2"}
PageFolderInpt = []string{"pageFolder1", "pageFolder2"}
PageInpt = []string{"page1", "page2"}
)