Teams groups export cli (#4069)

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

- [x]  No

#### Type of change

- [x] 🌻 Feature

#### Issue(s)

* #3989

#### Test Plan

- [x] 💪 Manual
- [x]  Unit test
This commit is contained in:
Keepers 2023-08-21 12:49:04 -06:00 committed by GitHub
parent 1468c0881a
commit 0a87590769
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 372 additions and 14 deletions

View File

@ -53,7 +53,7 @@ func addGroupsCommands(cmd *cobra.Command) *cobra.Command {
switch cmd.Use { switch cmd.Use {
case createCommand: case createCommand:
c, fs = utils.AddCommand(cmd, groupsCreateCmd(), utils.HideCommand()) c, fs = utils.AddCommand(cmd, groupsCreateCmd(), utils.MarkPreReleaseCommand())
fs.SortFlags = false fs.SortFlags = false
c.Use = c.Use + " " + groupsServiceCommandCreateUseSuffix c.Use = c.Use + " " + groupsServiceCommandCreateUseSuffix
@ -69,7 +69,7 @@ func addGroupsCommands(cmd *cobra.Command) *cobra.Command {
flags.AddFailFastFlag(c) flags.AddFailFastFlag(c)
case listCommand: case listCommand:
c, fs = utils.AddCommand(cmd, groupsListCmd(), utils.HideCommand()) c, fs = utils.AddCommand(cmd, groupsListCmd(), utils.MarkPreReleaseCommand())
fs.SortFlags = false fs.SortFlags = false
flags.AddBackupIDFlag(c, false) flags.AddBackupIDFlag(c, false)
@ -81,7 +81,7 @@ func addGroupsCommands(cmd *cobra.Command) *cobra.Command {
addRecoveredErrorsFN(c) addRecoveredErrorsFN(c)
case detailsCommand: case detailsCommand:
c, fs = utils.AddCommand(cmd, groupsDetailsCmd(), utils.HideCommand()) c, fs = utils.AddCommand(cmd, groupsDetailsCmd(), utils.MarkPreReleaseCommand())
fs.SortFlags = false fs.SortFlags = false
c.Use = c.Use + " " + groupsServiceCommandDetailsUseSuffix c.Use = c.Use + " " + groupsServiceCommandDetailsUseSuffix
@ -97,7 +97,7 @@ func addGroupsCommands(cmd *cobra.Command) *cobra.Command {
flags.AddAzureCredsFlags(c) flags.AddAzureCredsFlags(c)
case deleteCommand: case deleteCommand:
c, fs = utils.AddCommand(cmd, groupsDeleteCmd(), utils.HideCommand()) c, fs = utils.AddCommand(cmd, groupsDeleteCmd(), utils.MarkPreReleaseCommand())
fs.SortFlags = false fs.SortFlags = false
c.Use = c.Use + " " + groupsServiceCommandDeleteUseSuffix c.Use = c.Use + " " + groupsServiceCommandDeleteUseSuffix

View File

@ -53,7 +53,7 @@ func addTeamsCommands(cmd *cobra.Command) *cobra.Command {
switch cmd.Use { switch cmd.Use {
case createCommand: case createCommand:
c, fs = utils.AddCommand(cmd, teamsCreateCmd(), utils.HideCommand()) c, fs = utils.AddCommand(cmd, teamsCreateCmd(), utils.MarkPreReleaseCommand())
fs.SortFlags = false fs.SortFlags = false
c.Use = c.Use + " " + teamsServiceCommandCreateUseSuffix c.Use = c.Use + " " + teamsServiceCommandCreateUseSuffix
@ -69,7 +69,7 @@ func addTeamsCommands(cmd *cobra.Command) *cobra.Command {
flags.AddFailFastFlag(c) flags.AddFailFastFlag(c)
case listCommand: case listCommand:
c, fs = utils.AddCommand(cmd, teamsListCmd(), utils.HideCommand()) c, fs = utils.AddCommand(cmd, teamsListCmd(), utils.MarkPreReleaseCommand())
fs.SortFlags = false fs.SortFlags = false
flags.AddBackupIDFlag(c, false) flags.AddBackupIDFlag(c, false)
@ -81,7 +81,7 @@ func addTeamsCommands(cmd *cobra.Command) *cobra.Command {
addRecoveredErrorsFN(c) addRecoveredErrorsFN(c)
case detailsCommand: case detailsCommand:
c, fs = utils.AddCommand(cmd, teamsDetailsCmd(), utils.HideCommand()) c, fs = utils.AddCommand(cmd, teamsDetailsCmd(), utils.MarkPreReleaseCommand())
fs.SortFlags = false fs.SortFlags = false
c.Use = c.Use + " " + teamsServiceCommandDetailsUseSuffix c.Use = c.Use + " " + teamsServiceCommandDetailsUseSuffix
@ -97,7 +97,7 @@ func addTeamsCommands(cmd *cobra.Command) *cobra.Command {
flags.AddAzureCredsFlags(c) flags.AddAzureCredsFlags(c)
case deleteCommand: case deleteCommand:
c, fs = utils.AddCommand(cmd, teamsDeleteCmd(), utils.HideCommand()) c, fs = utils.AddCommand(cmd, teamsDeleteCmd(), utils.MarkPreReleaseCommand())
fs.SortFlags = false fs.SortFlags = false
c.Use = c.Use + " " + teamsServiceCommandDeleteUseSuffix c.Use = c.Use + " " + teamsServiceCommandDeleteUseSuffix

View File

@ -21,6 +21,8 @@ import (
var exportCommands = []func(cmd *cobra.Command) *cobra.Command{ var exportCommands = []func(cmd *cobra.Command) *cobra.Command{
addOneDriveCommands, addOneDriveCommands,
addSharePointCommands, addSharePointCommands,
addGroupsCommands,
addTeamsCommands,
} }
// AddCommands attaches all `corso export * *` commands to the parent. // AddCommands attaches all `corso export * *` commands to the parent.

84
src/cli/export/groups.go Normal file
View File

@ -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 = "<destination> --backup <backupId>"
//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 [<flag>...] <destination>`
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)
}

View File

@ -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)
})
}
}

View File

@ -39,7 +39,7 @@ func addOneDriveCommands(cmd *cobra.Command) *cobra.Command {
const ( const (
oneDriveServiceCommand = "onedrive" oneDriveServiceCommand = "onedrive"
oneDriveServiceCommandUseSuffix = "--backup <backupId> <destination>" oneDriveServiceCommandUseSuffix = "<destination> --backup <backupId>"
//nolint:lll //nolint:lll
oneDriveServiceCommandExportExamples = `# Export file with ID 98765abcdef in Bob's last backup (1234abcd...) to my-exports directory 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, RunE: exportOneDriveCmd,
Args: func(cmd *cobra.Command, args []string) error { Args: func(cmd *cobra.Command, args []string) error {
if len(args) != 1 { if len(args) != 1 {
return errors.New("missing restore destination") return errors.New("missing export destination")
} }
return nil return nil

View File

@ -39,7 +39,7 @@ func addSharePointCommands(cmd *cobra.Command) *cobra.Command {
const ( const (
sharePointServiceCommand = "sharepoint" sharePointServiceCommand = "sharepoint"
sharePointServiceCommandUseSuffix = "--backup <backupId> <destination>" sharePointServiceCommandUseSuffix = "<destination> --backup <backupId>"
//nolint:lll //nolint:lll
sharePointServiceCommandExportExamples = `# Export file with ID 98765abcdef in Bob's latest backup (1234abcd...) to my-exports directory 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, RunE: exportSharePointCmd,
Args: func(cmd *cobra.Command, args []string) error { Args: func(cmd *cobra.Command, args []string) error {
if len(args) != 1 { if len(args) != 1 {
return errors.New("missing restore destination") return errors.New("missing export destination")
} }
return nil return nil

84
src/cli/export/teams.go Normal file
View File

@ -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 = "<destination> --backup <backupId>"
//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 [<flag>...] <destination>`
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)
}

View File

@ -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)
})
}
}

View File

@ -18,7 +18,7 @@ func addGroupsCommands(cmd *cobra.Command) *cobra.Command {
switch cmd.Use { switch cmd.Use {
case restoreCommand: case restoreCommand:
c, fs = utils.AddCommand(cmd, groupsRestoreCmd(), utils.HideCommand()) c, fs = utils.AddCommand(cmd, groupsRestoreCmd(), utils.MarkPreReleaseCommand())
c.Use = c.Use + " " + groupsServiceCommandUseSuffix c.Use = c.Use + " " + groupsServiceCommandUseSuffix

View File

@ -18,7 +18,7 @@ func addTeamsCommands(cmd *cobra.Command) *cobra.Command {
switch cmd.Use { switch cmd.Use {
case restoreCommand: case restoreCommand:
c, fs = utils.AddCommand(cmd, teamsRestoreCmd(), utils.HideCommand()) c, fs = utils.AddCommand(cmd, teamsRestoreCmd(), utils.MarkPreReleaseCommand())
c.Use = c.Use + " " + teamsServiceCommandUseSuffix c.Use = c.Use + " " + teamsServiceCommandUseSuffix