Updated teams cli addition (#4054)

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

- [x]  No

#### Type of change

<!--- Please check the type of change your PR introduces: --->
- [x] 🌻 Feature

#### Issue(s)

* #3989

#### Test Plan

- [x] 💪 Manual
- [x]  Unit test
This commit is contained in:
Keepers 2023-08-18 15:35:22 -06:00 committed by GitHub
parent 20675dbcf7
commit 2c00ca40ac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 1155 additions and 0 deletions

View File

@ -39,6 +39,7 @@ var serviceCommands = []func(cmd *cobra.Command) *cobra.Command{
addExchangeCommands,
addOneDriveCommands,
addSharePointCommands,
addTeamsCommands,
}
// AddCommands attaches all `corso backup * *` commands to the parent.

230
src/cli/backup/groups.go Normal file
View File

@ -0,0 +1,230 @@
package backup
import (
"github.com/alcionai/clues"
"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"
"github.com/alcionai/corso/src/pkg/path"
)
// ------------------------------------------------------------------------------------------------
// setup and globals
// ------------------------------------------------------------------------------------------------
const (
groupsServiceCommand = "groups"
groupsServiceCommandCreateUseSuffix = "--group <groupsName> | '" + flags.Wildcard + "'"
groupsServiceCommandDeleteUseSuffix = "--backup <backupId>"
groupsServiceCommandDetailsUseSuffix = "--backup <backupId>"
)
// TODO: correct examples
const (
groupsServiceCommandCreateExamples = `# Backup all Groups data for Alice
corso backup create groups --group alice@example.com
# Backup only Groups contacts for Alice and Bob
corso backup create groups --group engineering,sales --data contacts
# Backup all Groups data for all M365 users
corso backup create groups --group '*'`
groupsServiceCommandDeleteExamples = `# Delete Groups backup with ID 1234abcd-12ab-cd34-56de-1234abcd
corso backup delete groups --backup 1234abcd-12ab-cd34-56de-1234abcd`
groupsServiceCommandDetailsExamples = `# Explore items in Alice's latest backup (1234abcd...)
corso backup details groups --backup 1234abcd-12ab-cd34-56de-1234abcd
# Explore calendar events occurring after start of 2022
corso backup details groups --backup 1234abcd-12ab-cd34-56de-1234abcd \
--event-starts-after 2022-01-01T00:00:00`
)
// called by backup.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 createCommand:
c, fs = utils.AddCommand(cmd, groupsCreateCmd(), utils.HideCommand())
fs.SortFlags = false
c.Use = c.Use + " " + groupsServiceCommandCreateUseSuffix
c.Example = groupsServiceCommandCreateExamples
// Flags addition ordering should follow the order we want them to appear in help and docs:
flags.AddGroupFlag(c)
flags.AddDataFlag(c, []string{dataLibraries}, false)
flags.AddCorsoPassphaseFlags(c)
flags.AddAWSCredsFlags(c)
flags.AddAzureCredsFlags(c)
flags.AddFetchParallelismFlag(c)
flags.AddFailFastFlag(c)
case listCommand:
c, fs = utils.AddCommand(cmd, groupsListCmd(), utils.HideCommand())
fs.SortFlags = false
flags.AddBackupIDFlag(c, false)
flags.AddCorsoPassphaseFlags(c)
flags.AddAWSCredsFlags(c)
flags.AddAzureCredsFlags(c)
addFailedItemsFN(c)
addSkippedItemsFN(c)
addRecoveredErrorsFN(c)
case detailsCommand:
c, fs = utils.AddCommand(cmd, groupsDetailsCmd(), utils.HideCommand())
fs.SortFlags = false
c.Use = c.Use + " " + groupsServiceCommandDetailsUseSuffix
c.Example = groupsServiceCommandDetailsExamples
flags.AddSkipReduceFlag(c)
// 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.
flags.AddBackupIDFlag(c, true)
flags.AddCorsoPassphaseFlags(c)
flags.AddAWSCredsFlags(c)
flags.AddAzureCredsFlags(c)
case deleteCommand:
c, fs = utils.AddCommand(cmd, groupsDeleteCmd(), utils.HideCommand())
fs.SortFlags = false
c.Use = c.Use + " " + groupsServiceCommandDeleteUseSuffix
c.Example = groupsServiceCommandDeleteExamples
flags.AddBackupIDFlag(c, true)
flags.AddCorsoPassphaseFlags(c)
flags.AddAWSCredsFlags(c)
flags.AddAzureCredsFlags(c)
}
return c
}
// ------------------------------------------------------------------------------------------------
// backup create
// ------------------------------------------------------------------------------------------------
// `corso backup create groups [<flag>...]`
func groupsCreateCmd() *cobra.Command {
return &cobra.Command{
Use: groupsServiceCommand,
Short: "Backup M365 Group service data",
RunE: createGroupsCmd,
Args: cobra.NoArgs,
}
}
// processes a groups service backup.
func createGroupsCmd(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
if utils.HasNoFlagsAndShownHelp(cmd) {
return nil
}
return Only(ctx, utils.ErrNotYetImplemented)
}
// ------------------------------------------------------------------------------------------------
// backup list
// ------------------------------------------------------------------------------------------------
// `corso backup list groups [<flag>...]`
func groupsListCmd() *cobra.Command {
return &cobra.Command{
Use: groupsServiceCommand,
Short: "List the history of M365 Groups service backups",
RunE: listGroupsCmd,
Args: cobra.NoArgs,
}
}
// lists the history of backup operations
func listGroupsCmd(cmd *cobra.Command, args []string) error {
return genericListCommand(cmd, flags.BackupIDFV, path.GroupsService, args)
}
// ------------------------------------------------------------------------------------------------
// backup details
// ------------------------------------------------------------------------------------------------
// `corso backup details groups [<flag>...]`
func groupsDetailsCmd() *cobra.Command {
return &cobra.Command{
Use: groupsServiceCommand,
Short: "Shows the details of a M365 Groups service backup",
RunE: detailsGroupsCmd,
Args: cobra.NoArgs,
}
}
// processes a groups service backup.
func detailsGroupsCmd(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
if utils.HasNoFlagsAndShownHelp(cmd) {
return nil
}
if err := validateGroupBackupCreateFlags(flags.GroupFV); err != nil {
return Only(ctx, err)
}
return Only(ctx, utils.ErrNotYetImplemented)
}
// ------------------------------------------------------------------------------------------------
// backup delete
// ------------------------------------------------------------------------------------------------
// `corso backup delete groups [<flag>...]`
func groupsDeleteCmd() *cobra.Command {
return &cobra.Command{
Use: groupsServiceCommand,
Short: "Delete backed-up M365 Groups service data",
RunE: deleteGroupsCmd,
Args: cobra.NoArgs,
}
}
// deletes an groups service backup.
func deleteGroupsCmd(cmd *cobra.Command, args []string) error {
return genericDeleteCommand(cmd, path.GroupsService, flags.BackupIDFV, "Groups", args)
}
// ---------------------------------------------------------------------------
// helpers
// ---------------------------------------------------------------------------
func validateGroupBackupCreateFlags(groups []string) error {
if len(groups) == 0 {
return clues.New(
"requires one or more --" +
flags.GroupFN + " ids, or the wildcard --" +
flags.GroupFN + " *",
)
}
// TODO(meain)
// for _, d := range cats {
// if d != dataLibraries {
// return clues.New(
// d + " is an unrecognized data type; only " + dataLibraries + " is supported"
// )
// }
// }
return nil
}

View File

@ -0,0 +1,98 @@
package backup
import (
"testing"
"github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"github.com/alcionai/corso/src/cli/flags"
"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
table := []struct {
name string
use string
expectUse string
expectShort string
flags []string
expectRunE func(*cobra.Command, []string) error
}{
{
"create groups",
createCommand,
expectUse + " " + groupsServiceCommandCreateUseSuffix,
groupsCreateCmd().Short,
[]string{
flags.CategoryDataFN,
flags.FailFastFN,
flags.FetchParallelismFN,
flags.SkipReduceFN,
flags.NoStatsFN,
},
createGroupsCmd,
},
{
"list groups",
listCommand,
expectUse,
groupsListCmd().Short,
[]string{
flags.BackupFN,
flags.FailedItemsFN,
flags.SkippedItemsFN,
flags.RecoveredErrorsFN,
},
listGroupsCmd,
},
{
"details groups",
detailsCommand,
expectUse + " " + groupsServiceCommandDetailsUseSuffix,
groupsDetailsCmd().Short,
[]string{
flags.BackupFN,
},
detailsGroupsCmd,
},
{
"delete groups",
deleteCommand,
expectUse + " " + groupsServiceCommandDeleteUseSuffix,
groupsDeleteCmd().Short,
[]string{flags.BackupFN},
deleteGroupsCmd,
},
}
for _, test := range table {
suite.Run(test.name, func() {
t := suite.T()
cmd := &cobra.Command{Use: test.use}
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)
})
}
}

230
src/cli/backup/teams.go Normal file
View File

@ -0,0 +1,230 @@
package backup
import (
"github.com/alcionai/clues"
"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"
"github.com/alcionai/corso/src/pkg/path"
)
// ------------------------------------------------------------------------------------------------
// setup and globals
// ------------------------------------------------------------------------------------------------
const (
teamsServiceCommand = "teams"
teamsServiceCommandCreateUseSuffix = "--team <teamsName> | '" + flags.Wildcard + "'"
teamsServiceCommandDeleteUseSuffix = "--backup <backupId>"
teamsServiceCommandDetailsUseSuffix = "--backup <backupId>"
)
// TODO: correct examples
const (
teamsServiceCommandCreateExamples = `# Backup all Teams data for Alice
corso backup create teams --team alice@example.com
# Backup only Teams contacts for Alice and Bob
corso backup create teams --team engineering,sales --data contacts
# Backup all Teams data for all M365 users
corso backup create teams --team '*'`
teamsServiceCommandDeleteExamples = `# Delete Teams backup with ID 1234abcd-12ab-cd34-56de-1234abcd
corso backup delete teams --backup 1234abcd-12ab-cd34-56de-1234abcd`
teamsServiceCommandDetailsExamples = `# Explore items in Alice's latest backup (1234abcd...)
corso backup details teams --backup 1234abcd-12ab-cd34-56de-1234abcd
# Explore calendar events occurring after start of 2022
corso backup details teams --backup 1234abcd-12ab-cd34-56de-1234abcd \
--event-starts-after 2022-01-01T00:00:00`
)
// called by backup.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 createCommand:
c, fs = utils.AddCommand(cmd, teamsCreateCmd(), utils.HideCommand())
fs.SortFlags = false
c.Use = c.Use + " " + teamsServiceCommandCreateUseSuffix
c.Example = teamsServiceCommandCreateExamples
// Flags addition ordering should follow the order we want them to appear in help and docs:
flags.AddTeamFlag(c)
flags.AddDataFlag(c, []string{dataEmail, dataContacts, dataEvents}, false)
flags.AddCorsoPassphaseFlags(c)
flags.AddAWSCredsFlags(c)
flags.AddAzureCredsFlags(c)
flags.AddFetchParallelismFlag(c)
flags.AddFailFastFlag(c)
case listCommand:
c, fs = utils.AddCommand(cmd, teamsListCmd(), utils.HideCommand())
fs.SortFlags = false
flags.AddBackupIDFlag(c, false)
flags.AddCorsoPassphaseFlags(c)
flags.AddAWSCredsFlags(c)
flags.AddAzureCredsFlags(c)
addFailedItemsFN(c)
addSkippedItemsFN(c)
addRecoveredErrorsFN(c)
case detailsCommand:
c, fs = utils.AddCommand(cmd, teamsDetailsCmd(), utils.HideCommand())
fs.SortFlags = false
c.Use = c.Use + " " + teamsServiceCommandDetailsUseSuffix
c.Example = teamsServiceCommandDetailsExamples
flags.AddSkipReduceFlag(c)
// 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.
flags.AddBackupIDFlag(c, true)
flags.AddCorsoPassphaseFlags(c)
flags.AddAWSCredsFlags(c)
flags.AddAzureCredsFlags(c)
case deleteCommand:
c, fs = utils.AddCommand(cmd, teamsDeleteCmd(), utils.HideCommand())
fs.SortFlags = false
c.Use = c.Use + " " + teamsServiceCommandDeleteUseSuffix
c.Example = teamsServiceCommandDeleteExamples
flags.AddBackupIDFlag(c, true)
flags.AddCorsoPassphaseFlags(c)
flags.AddAWSCredsFlags(c)
flags.AddAzureCredsFlags(c)
}
return c
}
// ------------------------------------------------------------------------------------------------
// backup create
// ------------------------------------------------------------------------------------------------
// `corso backup create teams [<flag>...]`
func teamsCreateCmd() *cobra.Command {
return &cobra.Command{
Use: teamsServiceCommand,
Short: "Backup M365 Team service data",
RunE: createTeamsCmd,
Args: cobra.NoArgs,
}
}
// processes a teams service backup.
func createTeamsCmd(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
if utils.HasNoFlagsAndShownHelp(cmd) {
return nil
}
if err := validateTeamBackupCreateFlags(flags.TeamFV); err != nil {
return Only(ctx, err)
}
return Only(ctx, utils.ErrNotYetImplemented)
}
// ------------------------------------------------------------------------------------------------
// backup list
// ------------------------------------------------------------------------------------------------
// `corso backup list teams [<flag>...]`
func teamsListCmd() *cobra.Command {
return &cobra.Command{
Use: teamsServiceCommand,
Short: "List the history of M365 Teams service backups",
RunE: listTeamsCmd,
Args: cobra.NoArgs,
}
}
// lists the history of backup operations
func listTeamsCmd(cmd *cobra.Command, args []string) error {
return genericListCommand(cmd, flags.BackupIDFV, path.TeamsService, args)
}
// ------------------------------------------------------------------------------------------------
// backup details
// ------------------------------------------------------------------------------------------------
// `corso backup details teams [<flag>...]`
func teamsDetailsCmd() *cobra.Command {
return &cobra.Command{
Use: teamsServiceCommand,
Short: "Shows the details of a M365 Teams service backup",
RunE: detailsTeamsCmd,
Args: cobra.NoArgs,
}
}
// processes a teams service backup.
func detailsTeamsCmd(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
if utils.HasNoFlagsAndShownHelp(cmd) {
return nil
}
return Only(ctx, utils.ErrNotYetImplemented)
}
// ------------------------------------------------------------------------------------------------
// backup delete
// ------------------------------------------------------------------------------------------------
// `corso backup delete teams [<flag>...]`
func teamsDeleteCmd() *cobra.Command {
return &cobra.Command{
Use: teamsServiceCommand,
Short: "Delete backed-up M365 Teams service data",
RunE: deleteTeamsCmd,
Args: cobra.NoArgs,
}
}
// deletes an teams service backup.
func deleteTeamsCmd(cmd *cobra.Command, args []string) error {
return genericDeleteCommand(cmd, path.TeamsService, flags.BackupIDFV, "Teams", args)
}
// ---------------------------------------------------------------------------
// helpers
// ---------------------------------------------------------------------------
func validateTeamBackupCreateFlags(teams []string) error {
if len(teams) == 0 {
return clues.New(
"requires one or more --" +
flags.TeamFN + " ids, or the wildcard --" +
flags.TeamFN + " *",
)
}
// TODO(meain)
// for _, d := range cats {
// if d != dataLibraries {
// return clues.New(
// d + " is an unrecognized data type; only " + dataLibraries + " is supported"
// )
// }
// }
return nil
}

View File

@ -0,0 +1,98 @@
package backup
import (
"testing"
"github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"github.com/alcionai/corso/src/cli/flags"
"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
table := []struct {
name string
use string
expectUse string
expectShort string
flags []string
expectRunE func(*cobra.Command, []string) error
}{
{
"create teams",
createCommand,
expectUse + " " + teamsServiceCommandCreateUseSuffix,
teamsCreateCmd().Short,
[]string{
flags.CategoryDataFN,
flags.FailFastFN,
flags.FetchParallelismFN,
flags.SkipReduceFN,
flags.NoStatsFN,
},
createTeamsCmd,
},
{
"list teams",
listCommand,
expectUse,
teamsListCmd().Short,
[]string{
flags.BackupFN,
flags.FailedItemsFN,
flags.SkippedItemsFN,
flags.RecoveredErrorsFN,
},
listTeamsCmd,
},
{
"details teams",
detailsCommand,
expectUse + " " + teamsServiceCommandDetailsUseSuffix,
teamsDetailsCmd().Short,
[]string{
flags.BackupFN,
},
detailsTeamsCmd,
},
{
"delete teams",
deleteCommand,
expectUse + " " + teamsServiceCommandDeleteUseSuffix,
teamsDeleteCmd().Short,
[]string{flags.BackupFN},
deleteTeamsCmd,
},
}
for _, test := range table {
suite.Run(test.name, func() {
t := suite.T()
cmd := &cobra.Command{Use: test.use}
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)
})
}
}

28
src/cli/flags/groups.go Normal file
View File

@ -0,0 +1,28 @@
package flags
import (
"github.com/spf13/cobra"
)
const (
GroupFN = "group"
)
var GroupFV []string
func AddGroupDetailsAndRestoreFlags(cmd *cobra.Command) {
// TODO: implement flags
}
// AddGroupFlag adds the --group flag, which accepts id or name values.
// TODO: need to decide what the appropriate "name" to accept here is.
// keepers thinks its either DisplayName or MailNickname or Mail
// Mail is most accurate, MailNickame is accurate and shorter, but the end user
// may not see either one visibly.
// https://learn.microsoft.com/en-us/graph/api/group-list?view=graph-rest-1.0&tabs=http
func AddGroupFlag(cmd *cobra.Command) {
cmd.Flags().StringSliceVar(
&GroupFV,
GroupFN, nil,
"Backup data by group; accepts '"+Wildcard+"' to select all groups.")
}

28
src/cli/flags/teams.go Normal file
View File

@ -0,0 +1,28 @@
package flags
import (
"github.com/spf13/cobra"
)
const (
TeamFN = "team"
)
var TeamFV []string
func AddTeamDetailsAndRestoreFlags(cmd *cobra.Command) {
// TODO: implement flags
}
// AddTeamFlag adds the --team flag, which accepts id or name values.
// TODO: need to decide what the appropriate "name" to accept here is.
// keepers thinks its either DisplayName or MailNickname or Mail
// Mail is most accurate, MailNickame is accurate and shorter, but the end user
// may not see either one visibly.
// https://learn.microsoft.com/en-us/graph/api/team-list?view=graph-rest-1.0&tabs=http
func AddTeamFlag(cmd *cobra.Command) {
cmd.Flags().StringSliceVar(
&TeamFV,
TeamFN, nil,
"Backup data by team; accepts '"+Wildcard+"' to select all teams.")
}

81
src/cli/restore/groups.go Normal file
View File

@ -0,0 +1,81 @@
package restore
import (
"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 restore.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 restoreCommand:
c, fs = utils.AddCommand(cmd, groupsRestoreCmd(), utils.HideCommand())
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.AddRestorePermissionsFlag(c)
flags.AddRestoreConfigFlags(c)
flags.AddFailFastFlag(c)
flags.AddCorsoPassphaseFlags(c)
flags.AddAWSCredsFlags(c)
flags.AddAzureCredsFlags(c)
}
return c
}
// TODO: correct examples
const (
groupsServiceCommand = "groups"
groupsServiceCommandUseSuffix = "--backup <backupId>"
groupsServiceCommandRestoreExamples = `# Restore file with ID 98765abcdef in Bob's last backup (1234abcd...)
corso restore groups --backup 1234abcd-12ab-cd34-56de-1234abcd --file 98765abcdef
# Restore the file with ID 98765abcdef along with its associated permissions
corso restore groups --backup 1234abcd-12ab-cd34-56de-1234abcd --file 98765abcdef --restore-permissions
# Restore files named "FY2021 Planning.xlsx" in "Documents/Finance Reports"
corso restore groups --backup 1234abcd-12ab-cd34-56de-1234abcd \
--file "FY2021 Planning.xlsx" --folder "Documents/Finance Reports"
# Restore all files and folders in folder "Documents/Finance Reports" that were created before 2020
corso restore groups --backup 1234abcd-12ab-cd34-56de-1234abcd
--folder "Documents/Finance Reports" --file-created-before 2020-01-01T00:00:00`
)
// `corso restore groups [<flag>...]`
func groupsRestoreCmd() *cobra.Command {
return &cobra.Command{
Use: groupsServiceCommand,
Short: "Restore M365 Groups service data",
RunE: restoreGroupsCmd,
Args: cobra.NoArgs,
Example: groupsServiceCommandRestoreExamples,
}
}
// processes an groups service restore.
func restoreGroupsCmd(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,108 @@
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/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
}{
{"restore groups", restoreCommand, expectUse, groupsRestoreCmd().Short, restoreGroupsCmd},
}
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",
"--" + flags.RunModeFN, flags.RunModeFlagTest,
"--" + flags.BackupFN, testdata.BackupInput,
"--" + flags.CollisionsFN, testdata.Collisions,
"--" + flags.DestinationFN, testdata.Destination,
"--" + flags.ToResourceFN, testdata.ToResource,
"--" + flags.AWSAccessKeyFN, testdata.AWSAccessKeyID,
"--" + flags.AWSSecretAccessKeyFN, testdata.AWSSecretAccessKey,
"--" + flags.AWSSessionTokenFN, testdata.AWSSessionToken,
"--" + flags.AzureClientIDFN, testdata.AzureClientID,
"--" + flags.AzureClientTenantFN, testdata.AzureTenantID,
"--" + flags.AzureClientSecretFN, testdata.AzureClientSecret,
"--" + flags.CorsoPassphraseFN, testdata.CorsoPassphrase,
// bool flags
"--" + flags.RestorePermissionsFN,
})
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.Collisions, opts.RestoreCfg.Collisions)
assert.Equal(t, testdata.Destination, opts.RestoreCfg.Destination)
assert.Equal(t, testdata.ToResource, opts.RestoreCfg.ProtectedResource)
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.AzureClientID, flags.AzureClientIDFV)
assert.Equal(t, testdata.AzureTenantID, flags.AzureClientTenantFV)
assert.Equal(t, testdata.AzureClientSecret, flags.AzureClientSecretFV)
assert.Equal(t, testdata.CorsoPassphrase, flags.CorsoPassphraseFV)
assert.True(t, flags.RestorePermissionsFV)
})
}
}

81
src/cli/restore/teams.go Normal file
View File

@ -0,0 +1,81 @@
package restore
import (
"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 restore.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 restoreCommand:
c, fs = utils.AddCommand(cmd, teamsRestoreCmd(), utils.HideCommand())
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.AddRestorePermissionsFlag(c)
flags.AddRestoreConfigFlags(c)
flags.AddFailFastFlag(c)
flags.AddCorsoPassphaseFlags(c)
flags.AddAWSCredsFlags(c)
flags.AddAzureCredsFlags(c)
}
return c
}
// TODO: correct examples
const (
teamsServiceCommand = "teams"
teamsServiceCommandUseSuffix = "--backup <backupId>"
teamsServiceCommandRestoreExamples = `# Restore file with ID 98765abcdef in Bob's last backup (1234abcd...)
corso restore teams --backup 1234abcd-12ab-cd34-56de-1234abcd --file 98765abcdef
# Restore the file with ID 98765abcdef along with its associated permissions
corso restore teams --backup 1234abcd-12ab-cd34-56de-1234abcd --file 98765abcdef --restore-permissions
# Restore files named "FY2021 Planning.xlsx" in "Documents/Finance Reports"
corso restore teams --backup 1234abcd-12ab-cd34-56de-1234abcd \
--file "FY2021 Planning.xlsx" --folder "Documents/Finance Reports"
# Restore all files and folders in folder "Documents/Finance Reports" that were created before 2020
corso restore teams --backup 1234abcd-12ab-cd34-56de-1234abcd
--folder "Documents/Finance Reports" --file-created-before 2020-01-01T00:00:00`
)
// `corso restore teams [<flag>...]`
func teamsRestoreCmd() *cobra.Command {
return &cobra.Command{
Use: teamsServiceCommand,
Short: "Restore M365 Teams service data",
RunE: restoreTeamsCmd,
Args: cobra.NoArgs,
Example: teamsServiceCommandRestoreExamples,
}
}
// processes an teams service restore.
func restoreTeamsCmd(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,108 @@
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/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
}{
{"restore teams", restoreCommand, expectUse, teamsRestoreCmd().Short, restoreTeamsCmd},
}
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",
"--" + flags.RunModeFN, flags.RunModeFlagTest,
"--" + flags.BackupFN, testdata.BackupInput,
"--" + flags.CollisionsFN, testdata.Collisions,
"--" + flags.DestinationFN, testdata.Destination,
"--" + flags.ToResourceFN, testdata.ToResource,
"--" + flags.AWSAccessKeyFN, testdata.AWSAccessKeyID,
"--" + flags.AWSSecretAccessKeyFN, testdata.AWSSecretAccessKey,
"--" + flags.AWSSessionTokenFN, testdata.AWSSessionToken,
"--" + flags.AzureClientIDFN, testdata.AzureClientID,
"--" + flags.AzureClientTenantFN, testdata.AzureTenantID,
"--" + flags.AzureClientSecretFN, testdata.AzureClientSecret,
"--" + flags.CorsoPassphraseFN, testdata.CorsoPassphrase,
// bool flags
"--" + flags.RestorePermissionsFN,
})
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.Collisions, opts.RestoreCfg.Collisions)
assert.Equal(t, testdata.Destination, opts.RestoreCfg.Destination)
assert.Equal(t, testdata.ToResource, opts.RestoreCfg.ProtectedResource)
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.AzureClientID, flags.AzureClientIDFV)
assert.Equal(t, testdata.AzureTenantID, flags.AzureClientTenantFV)
assert.Equal(t, testdata.AzureClientSecret, flags.AzureClientSecretFV)
assert.Equal(t, testdata.CorsoPassphrase, flags.CorsoPassphraseFV)
assert.True(t, flags.RestorePermissionsFV)
})
}
}

30
src/cli/utils/groups.go Normal file
View File

@ -0,0 +1,30 @@
package utils
import (
"github.com/spf13/cobra"
"github.com/alcionai/corso/src/cli/flags"
)
type GroupsOpts struct {
Groups []string
RestoreCfg RestoreCfgOpts
ExportCfg ExportCfgOpts
Populated flags.PopulatedFlags
}
func MakeGroupsOpts(cmd *cobra.Command) GroupsOpts {
return GroupsOpts{
Groups: flags.UserFV,
RestoreCfg: makeRestoreCfgOpts(cmd),
ExportCfg: makeExportCfgOpts(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),
}
}

30
src/cli/utils/teams.go Normal file
View File

@ -0,0 +1,30 @@
package utils
import (
"github.com/spf13/cobra"
"github.com/alcionai/corso/src/cli/flags"
)
type TeamsOpts struct {
Teams []string
RestoreCfg RestoreCfgOpts
ExportCfg ExportCfgOpts
Populated flags.PopulatedFlags
}
func MakeTeamsOpts(cmd *cobra.Command) TeamsOpts {
return TeamsOpts{
Teams: flags.UserFV,
RestoreCfg: makeRestoreCfgOpts(cmd),
ExportCfg: makeExportCfgOpts(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),
}
}

View File

@ -19,6 +19,8 @@ import (
"github.com/alcionai/corso/src/pkg/storage"
)
var ErrNotYetImplemented = clues.New("not yet implemented")
func GetAccountAndConnect(
ctx context.Context,
pst path.ServiceType,

View File

@ -31,6 +31,8 @@ const (
SharePointMetadataService // sharepointMetadata
GroupsService // groups
GroupsMetadataService // groupsMetadata
TeamsService // teams
TeamsMetadataService // teamsMetadata
)
func toServiceType(service string) ServiceType {