diff --git a/src/cli/backup/groups.go b/src/cli/backup/groups.go index 3f1f83eb7..1dc490ae7 100644 --- a/src/cli/backup/groups.go +++ b/src/cli/backup/groups.go @@ -53,7 +53,7 @@ func addGroupsCommands(cmd *cobra.Command) *cobra.Command { switch cmd.Use { case createCommand: - c, fs = utils.AddCommand(cmd, groupsCreateCmd(), utils.HideCommand()) + c, fs = utils.AddCommand(cmd, groupsCreateCmd(), utils.MarkPreReleaseCommand()) fs.SortFlags = false c.Use = c.Use + " " + groupsServiceCommandCreateUseSuffix @@ -69,7 +69,7 @@ func addGroupsCommands(cmd *cobra.Command) *cobra.Command { flags.AddFailFastFlag(c) case listCommand: - c, fs = utils.AddCommand(cmd, groupsListCmd(), utils.HideCommand()) + c, fs = utils.AddCommand(cmd, groupsListCmd(), utils.MarkPreReleaseCommand()) fs.SortFlags = false flags.AddBackupIDFlag(c, false) @@ -81,7 +81,7 @@ func addGroupsCommands(cmd *cobra.Command) *cobra.Command { addRecoveredErrorsFN(c) case detailsCommand: - c, fs = utils.AddCommand(cmd, groupsDetailsCmd(), utils.HideCommand()) + c, fs = utils.AddCommand(cmd, groupsDetailsCmd(), utils.MarkPreReleaseCommand()) fs.SortFlags = false c.Use = c.Use + " " + groupsServiceCommandDetailsUseSuffix @@ -97,7 +97,7 @@ func addGroupsCommands(cmd *cobra.Command) *cobra.Command { flags.AddAzureCredsFlags(c) case deleteCommand: - c, fs = utils.AddCommand(cmd, groupsDeleteCmd(), utils.HideCommand()) + c, fs = utils.AddCommand(cmd, groupsDeleteCmd(), utils.MarkPreReleaseCommand()) fs.SortFlags = false c.Use = c.Use + " " + groupsServiceCommandDeleteUseSuffix diff --git a/src/cli/backup/teams.go b/src/cli/backup/teams.go index fcac3394d..97e314cfd 100644 --- a/src/cli/backup/teams.go +++ b/src/cli/backup/teams.go @@ -53,7 +53,7 @@ func addTeamsCommands(cmd *cobra.Command) *cobra.Command { switch cmd.Use { case createCommand: - c, fs = utils.AddCommand(cmd, teamsCreateCmd(), utils.HideCommand()) + c, fs = utils.AddCommand(cmd, teamsCreateCmd(), utils.MarkPreReleaseCommand()) fs.SortFlags = false c.Use = c.Use + " " + teamsServiceCommandCreateUseSuffix @@ -69,7 +69,7 @@ func addTeamsCommands(cmd *cobra.Command) *cobra.Command { flags.AddFailFastFlag(c) case listCommand: - c, fs = utils.AddCommand(cmd, teamsListCmd(), utils.HideCommand()) + c, fs = utils.AddCommand(cmd, teamsListCmd(), utils.MarkPreReleaseCommand()) fs.SortFlags = false flags.AddBackupIDFlag(c, false) @@ -81,7 +81,7 @@ func addTeamsCommands(cmd *cobra.Command) *cobra.Command { addRecoveredErrorsFN(c) case detailsCommand: - c, fs = utils.AddCommand(cmd, teamsDetailsCmd(), utils.HideCommand()) + c, fs = utils.AddCommand(cmd, teamsDetailsCmd(), utils.MarkPreReleaseCommand()) fs.SortFlags = false c.Use = c.Use + " " + teamsServiceCommandDetailsUseSuffix @@ -97,7 +97,7 @@ func addTeamsCommands(cmd *cobra.Command) *cobra.Command { flags.AddAzureCredsFlags(c) case deleteCommand: - c, fs = utils.AddCommand(cmd, teamsDeleteCmd(), utils.HideCommand()) + c, fs = utils.AddCommand(cmd, teamsDeleteCmd(), utils.MarkPreReleaseCommand()) fs.SortFlags = false c.Use = c.Use + " " + teamsServiceCommandDeleteUseSuffix diff --git a/src/cli/export/export.go b/src/cli/export/export.go index e0deed014..5f63895c0 100644 --- a/src/cli/export/export.go +++ b/src/cli/export/export.go @@ -21,6 +21,8 @@ import ( var exportCommands = []func(cmd *cobra.Command) *cobra.Command{ addOneDriveCommands, addSharePointCommands, + addGroupsCommands, + addTeamsCommands, } // AddCommands attaches all `corso export * *` commands to the parent. diff --git a/src/cli/export/groups.go b/src/cli/export/groups.go new file mode 100644 index 000000000..36b56e60f --- /dev/null +++ b/src/cli/export/groups.go @@ -0,0 +1,84 @@ +package export + +import ( + "github.com/pkg/errors" + "github.com/spf13/cobra" + "github.com/spf13/pflag" + + "github.com/alcionai/corso/src/cli/flags" + . "github.com/alcionai/corso/src/cli/print" + "github.com/alcionai/corso/src/cli/utils" +) + +// called by export.go to map subcommands to provider-specific handling. +func addGroupsCommands(cmd *cobra.Command) *cobra.Command { + var ( + c *cobra.Command + fs *pflag.FlagSet + ) + + switch cmd.Use { + case exportCommand: + c, fs = utils.AddCommand(cmd, groupsExportCmd(), utils.MarkPreReleaseCommand()) + + c.Use = c.Use + " " + groupsServiceCommandUseSuffix + + // 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. + fs.SortFlags = false + + flags.AddBackupIDFlag(c, true) + flags.AddExportConfigFlags(c) + flags.AddFailFastFlag(c) + flags.AddCorsoPassphaseFlags(c) + flags.AddAWSCredsFlags(c) + } + + return c +} + +// TODO: correct examples +const ( + groupsServiceCommand = "groups" + groupsServiceCommandUseSuffix = " --backup " + + //nolint:lll + groupsServiceCommandExportExamples = `# Export file with ID 98765abcdef in Bob's last backup (1234abcd...) to my-exports directory +corso export groups my-exports --backup 1234abcd-12ab-cd34-56de-1234abcd --file 98765abcdef + +# Export files named "FY2021 Planning.xlsx" in "Documents/Finance Reports" to current directory +corso export groups . --backup 1234abcd-12ab-cd34-56de-1234abcd \ + --file "FY2021 Planning.xlsx" --folder "Documents/Finance Reports" + +# Export all files and folders in folder "Documents/Finance Reports" that were created before 2020 to my-exports +corso export groups my-exports --backup 1234abcd-12ab-cd34-56de-1234abcd + --folder "Documents/Finance Reports" --file-created-before 2020-01-01T00:00:00` +) + +// `corso export groups [...] ` +func groupsExportCmd() *cobra.Command { + return &cobra.Command{ + Use: groupsServiceCommand, + Short: "Export M365 Groups service data", + RunE: exportGroupsCmd, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) != 1 { + return errors.New("missing export destination") + } + + return nil + }, + Example: groupsServiceCommandExportExamples, + } +} + +// processes an groups service export. +func exportGroupsCmd(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + + if utils.HasNoFlagsAndShownHelp(cmd) { + return nil + } + + return Only(ctx, utils.ErrNotYetImplemented) +} diff --git a/src/cli/export/groups_test.go b/src/cli/export/groups_test.go new file mode 100644 index 000000000..d2a091e79 --- /dev/null +++ b/src/cli/export/groups_test.go @@ -0,0 +1,94 @@ +package export + +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/flags" + "github.com/alcionai/corso/src/cli/utils" + "github.com/alcionai/corso/src/cli/utils/testdata" + "github.com/alcionai/corso/src/internal/tester" +) + +type GroupsUnitSuite struct { + tester.Suite +} + +func TestGroupsUnitSuite(t *testing.T) { + suite.Run(t, &GroupsUnitSuite{Suite: tester.NewUnitSuite(t)}) +} + +func (suite *GroupsUnitSuite) TestAddGroupsCommands() { + expectUse := groupsServiceCommand + " " + groupsServiceCommandUseSuffix + + table := []struct { + name string + use string + expectUse string + expectShort string + expectRunE func(*cobra.Command, []string) error + }{ + {"export groups", exportCommand, expectUse, groupsExportCmd().Short, exportGroupsCmd}, + } + for _, test := range table { + suite.Run(test.name, func() { + t := suite.T() + + cmd := &cobra.Command{Use: test.use} + + // normally a persistent flag from the root. + // required to ensure a dry run. + flags.AddRunModeFlag(cmd, true) + + c := addGroupsCommands(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) + + cmd.SetArgs([]string{ + "groups", + testdata.RestoreDestination, + "--" + flags.RunModeFN, flags.RunModeFlagTest, + "--" + flags.BackupFN, testdata.BackupInput, + + "--" + flags.AWSAccessKeyFN, testdata.AWSAccessKeyID, + "--" + flags.AWSSecretAccessKeyFN, testdata.AWSSecretAccessKey, + "--" + flags.AWSSessionTokenFN, testdata.AWSSessionToken, + + "--" + flags.CorsoPassphraseFN, testdata.CorsoPassphrase, + + // bool flags + "--" + flags.ArchiveFN, + }) + + cmd.SetOut(new(bytes.Buffer)) // drop output + cmd.SetErr(new(bytes.Buffer)) // drop output + err := cmd.Execute() + // assert.NoError(t, err, clues.ToCore(err)) + assert.ErrorIs(t, err, utils.ErrNotYetImplemented, clues.ToCore(err)) + + opts := utils.MakeGroupsOpts(cmd) + assert.Equal(t, testdata.BackupInput, flags.BackupIDFV) + + assert.Equal(t, testdata.Archive, opts.ExportCfg.Archive) + + assert.Equal(t, testdata.AWSAccessKeyID, flags.AWSAccessKeyFV) + assert.Equal(t, testdata.AWSSecretAccessKey, flags.AWSSecretAccessKeyFV) + assert.Equal(t, testdata.AWSSessionToken, flags.AWSSessionTokenFV) + + assert.Equal(t, testdata.CorsoPassphrase, flags.CorsoPassphraseFV) + }) + } +} diff --git a/src/cli/export/onedrive.go b/src/cli/export/onedrive.go index 593149bd9..ea6537dc2 100644 --- a/src/cli/export/onedrive.go +++ b/src/cli/export/onedrive.go @@ -39,7 +39,7 @@ func addOneDriveCommands(cmd *cobra.Command) *cobra.Command { const ( oneDriveServiceCommand = "onedrive" - oneDriveServiceCommandUseSuffix = "--backup " + oneDriveServiceCommandUseSuffix = " --backup " //nolint:lll oneDriveServiceCommandExportExamples = `# Export file with ID 98765abcdef in Bob's last backup (1234abcd...) to my-exports directory @@ -62,7 +62,7 @@ func oneDriveExportCmd() *cobra.Command { RunE: exportOneDriveCmd, Args: func(cmd *cobra.Command, args []string) error { if len(args) != 1 { - return errors.New("missing restore destination") + return errors.New("missing export destination") } return nil diff --git a/src/cli/export/sharepoint.go b/src/cli/export/sharepoint.go index ec71a5f2b..7293a02f9 100644 --- a/src/cli/export/sharepoint.go +++ b/src/cli/export/sharepoint.go @@ -39,7 +39,7 @@ func addSharePointCommands(cmd *cobra.Command) *cobra.Command { const ( sharePointServiceCommand = "sharepoint" - sharePointServiceCommandUseSuffix = "--backup " + sharePointServiceCommandUseSuffix = " --backup " //nolint:lll sharePointServiceCommandExportExamples = `# Export file with ID 98765abcdef in Bob's latest backup (1234abcd...) to my-exports directory @@ -66,7 +66,7 @@ func sharePointExportCmd() *cobra.Command { RunE: exportSharePointCmd, Args: func(cmd *cobra.Command, args []string) error { if len(args) != 1 { - return errors.New("missing restore destination") + return errors.New("missing export destination") } return nil diff --git a/src/cli/export/teams.go b/src/cli/export/teams.go new file mode 100644 index 000000000..7e680c28d --- /dev/null +++ b/src/cli/export/teams.go @@ -0,0 +1,84 @@ +package export + +import ( + "github.com/pkg/errors" + "github.com/spf13/cobra" + "github.com/spf13/pflag" + + "github.com/alcionai/corso/src/cli/flags" + . "github.com/alcionai/corso/src/cli/print" + "github.com/alcionai/corso/src/cli/utils" +) + +// called by export.go to map subcommands to provider-specific handling. +func addTeamsCommands(cmd *cobra.Command) *cobra.Command { + var ( + c *cobra.Command + fs *pflag.FlagSet + ) + + switch cmd.Use { + case exportCommand: + c, fs = utils.AddCommand(cmd, teamsExportCmd(), utils.MarkPreReleaseCommand()) + + c.Use = c.Use + " " + teamsServiceCommandUseSuffix + + // 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. + fs.SortFlags = false + + flags.AddBackupIDFlag(c, true) + flags.AddExportConfigFlags(c) + flags.AddFailFastFlag(c) + flags.AddCorsoPassphaseFlags(c) + flags.AddAWSCredsFlags(c) + } + + return c +} + +// TODO: correct examples +const ( + teamsServiceCommand = "teams" + teamsServiceCommandUseSuffix = " --backup " + + //nolint:lll + teamsServiceCommandExportExamples = `# Export file with ID 98765abcdef in Bob's last backup (1234abcd...) to my-exports directory +corso export teams my-exports --backup 1234abcd-12ab-cd34-56de-1234abcd --file 98765abcdef + +# Export files named "FY2021 Planning.xlsx" in "Documents/Finance Reports" to current directory +corso export teams . --backup 1234abcd-12ab-cd34-56de-1234abcd \ + --file "FY2021 Planning.xlsx" --folder "Documents/Finance Reports" + +# Export all files and folders in folder "Documents/Finance Reports" that were created before 2020 to my-exports +corso export teams my-exports --backup 1234abcd-12ab-cd34-56de-1234abcd + --folder "Documents/Finance Reports" --file-created-before 2020-01-01T00:00:00` +) + +// `corso export teams [...] ` +func teamsExportCmd() *cobra.Command { + return &cobra.Command{ + Use: teamsServiceCommand, + Short: "Export M365 Teams service data", + RunE: exportTeamsCmd, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) != 1 { + return errors.New("missing export destination") + } + + return nil + }, + Example: teamsServiceCommandExportExamples, + } +} + +// processes an teams service export. +func exportTeamsCmd(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + + if utils.HasNoFlagsAndShownHelp(cmd) { + return nil + } + + return Only(ctx, utils.ErrNotYetImplemented) +} diff --git a/src/cli/export/teams_test.go b/src/cli/export/teams_test.go new file mode 100644 index 000000000..d431359d6 --- /dev/null +++ b/src/cli/export/teams_test.go @@ -0,0 +1,94 @@ +package export + +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/flags" + "github.com/alcionai/corso/src/cli/utils" + "github.com/alcionai/corso/src/cli/utils/testdata" + "github.com/alcionai/corso/src/internal/tester" +) + +type TeamsUnitSuite struct { + tester.Suite +} + +func TestTeamsUnitSuite(t *testing.T) { + suite.Run(t, &TeamsUnitSuite{Suite: tester.NewUnitSuite(t)}) +} + +func (suite *TeamsUnitSuite) TestAddTeamsCommands() { + expectUse := teamsServiceCommand + " " + teamsServiceCommandUseSuffix + + table := []struct { + name string + use string + expectUse string + expectShort string + expectRunE func(*cobra.Command, []string) error + }{ + {"export teams", exportCommand, expectUse, teamsExportCmd().Short, exportTeamsCmd}, + } + for _, test := range table { + suite.Run(test.name, func() { + t := suite.T() + + cmd := &cobra.Command{Use: test.use} + + // normally a persistent flag from the root. + // required to ensure a dry run. + flags.AddRunModeFlag(cmd, true) + + c := addTeamsCommands(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) + + cmd.SetArgs([]string{ + "teams", + testdata.RestoreDestination, + "--" + flags.RunModeFN, flags.RunModeFlagTest, + "--" + flags.BackupFN, testdata.BackupInput, + + "--" + flags.AWSAccessKeyFN, testdata.AWSAccessKeyID, + "--" + flags.AWSSecretAccessKeyFN, testdata.AWSSecretAccessKey, + "--" + flags.AWSSessionTokenFN, testdata.AWSSessionToken, + + "--" + flags.CorsoPassphraseFN, testdata.CorsoPassphrase, + + // bool flags + "--" + flags.ArchiveFN, + }) + + cmd.SetOut(new(bytes.Buffer)) // drop output + cmd.SetErr(new(bytes.Buffer)) // drop output + err := cmd.Execute() + // assert.NoError(t, err, clues.ToCore(err)) + assert.ErrorIs(t, err, utils.ErrNotYetImplemented, clues.ToCore(err)) + + opts := utils.MakeTeamsOpts(cmd) + assert.Equal(t, testdata.BackupInput, flags.BackupIDFV) + + assert.Equal(t, testdata.Archive, opts.ExportCfg.Archive) + + assert.Equal(t, testdata.AWSAccessKeyID, flags.AWSAccessKeyFV) + assert.Equal(t, testdata.AWSSecretAccessKey, flags.AWSSecretAccessKeyFV) + assert.Equal(t, testdata.AWSSessionToken, flags.AWSSessionTokenFV) + + assert.Equal(t, testdata.CorsoPassphrase, flags.CorsoPassphraseFV) + }) + } +} diff --git a/src/cli/restore/groups.go b/src/cli/restore/groups.go index a98c9d088..3907b17d0 100644 --- a/src/cli/restore/groups.go +++ b/src/cli/restore/groups.go @@ -18,7 +18,7 @@ func addGroupsCommands(cmd *cobra.Command) *cobra.Command { switch cmd.Use { case restoreCommand: - c, fs = utils.AddCommand(cmd, groupsRestoreCmd(), utils.HideCommand()) + c, fs = utils.AddCommand(cmd, groupsRestoreCmd(), utils.MarkPreReleaseCommand()) c.Use = c.Use + " " + groupsServiceCommandUseSuffix diff --git a/src/cli/restore/teams.go b/src/cli/restore/teams.go index 59623024a..059c2182a 100644 --- a/src/cli/restore/teams.go +++ b/src/cli/restore/teams.go @@ -18,7 +18,7 @@ func addTeamsCommands(cmd *cobra.Command) *cobra.Command { switch cmd.Use { case restoreCommand: - c, fs = utils.AddCommand(cmd, teamsRestoreCmd(), utils.HideCommand()) + c, fs = utils.AddCommand(cmd, teamsRestoreCmd(), utils.MarkPreReleaseCommand()) c.Use = c.Use + " " + teamsServiceCommandUseSuffix