diff --git a/CHANGELOG.md b/CHANGELOG.md index a75f3e835a..0fb7569048 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] (beta) +### Added +- Groups and Teams service support available as a feature preview! Channel messages and Files are now available for backup and restore in the CLI: `corso backup create groups --group '*'` + * The cli commands for "groups" and "teams" can be used interchangably, and will operate on the same backup data. + * New permissions are required to backup Channel messages. See the [Corso Documentation](https://corsobackup.io/docs/setup/m365-access/#configure-required-permissions) for complete details. + Even though Channel message restoration is not available, message write permissions are included to cover future integration. + * This is a feature preview, and may be subject to breaking changes based on feedback and testing. + ### Changed - Switched to Go 1.21 - SharePoint exported libraries are now exported with a `Libraries` prefix. diff --git a/src/cli/backup/backup.go b/src/cli/backup/backup.go index 610c806463..b0497b844d 100644 --- a/src/cli/backup/backup.go +++ b/src/cli/backup/backup.go @@ -3,7 +3,6 @@ package backup import ( "context" "fmt" - "os" "strings" "github.com/alcionai/clues" @@ -39,9 +38,7 @@ var serviceCommands = []func(cmd *cobra.Command) *cobra.Command{ addExchangeCommands, addOneDriveCommands, addSharePointCommands, - // awaiting release - // addGroupsCommands, - // addTeamsCommands, + addGroupsCommands, } // AddCommands attaches all `corso backup * *` commands to the parent. @@ -56,11 +53,6 @@ func AddCommands(cmd *cobra.Command) { for _, addBackupTo := range serviceCommands { addBackupTo(subCommand) } - - // delete after release - if len(os.Getenv("CORSO_ENABLE_GROUPS")) > 0 { - addGroupsCommands(subCommand) - } } } diff --git a/src/cli/backup/groups.go b/src/cli/backup/groups.go index a2c73b06a5..faffe76e1f 100644 --- a/src/cli/backup/groups.go +++ b/src/cli/backup/groups.go @@ -31,7 +31,7 @@ import ( const ( groupsServiceCommand = "groups" teamsServiceCommand = "teams" - groupsServiceCommandCreateUseSuffix = "--group | '" + flags.Wildcard + "'" + groupsServiceCommandCreateUseSuffix = "--group | '" + flags.Wildcard + "'" groupsServiceCommandDeleteUseSuffix = "--backup " groupsServiceCommandDetailsUseSuffix = "--backup " ) @@ -66,7 +66,7 @@ func addGroupsCommands(cmd *cobra.Command) *cobra.Command { switch cmd.Use { case createCommand: - c, fs = utils.AddCommand(cmd, groupsCreateCmd(), utils.MarkPreReleaseCommand()) + c, fs = utils.AddCommand(cmd, groupsCreateCmd(), utils.MarkPreviewCommand()) fs.SortFlags = false c.Use = c.Use + " " + groupsServiceCommandCreateUseSuffix @@ -84,7 +84,7 @@ func addGroupsCommands(cmd *cobra.Command) *cobra.Command { flags.AddForceItemDataDownloadFlag(c) case listCommand: - c, fs = utils.AddCommand(cmd, groupsListCmd(), utils.MarkPreReleaseCommand()) + c, fs = utils.AddCommand(cmd, groupsListCmd(), utils.MarkPreviewCommand()) fs.SortFlags = false flags.AddBackupIDFlag(c, false) @@ -96,7 +96,7 @@ func addGroupsCommands(cmd *cobra.Command) *cobra.Command { addRecoveredErrorsFN(c) case detailsCommand: - c, fs = utils.AddCommand(cmd, groupsDetailsCmd(), utils.MarkPreReleaseCommand()) + c, fs = utils.AddCommand(cmd, groupsDetailsCmd(), utils.MarkPreviewCommand()) fs.SortFlags = false c.Use = c.Use + " " + groupsServiceCommandDetailsUseSuffix @@ -114,7 +114,7 @@ func addGroupsCommands(cmd *cobra.Command) *cobra.Command { flags.AddSharePointDetailsAndRestoreFlags(c) case deleteCommand: - c, fs = utils.AddCommand(cmd, groupsDeleteCmd(), utils.MarkPreReleaseCommand()) + c, fs = utils.AddCommand(cmd, groupsDeleteCmd(), utils.MarkPreviewCommand()) fs.SortFlags = false c.Use = c.Use + " " + groupsServiceCommandDeleteUseSuffix diff --git a/src/cli/export/export.go b/src/cli/export/export.go index 9689ff9f7a..2e205ec984 100644 --- a/src/cli/export/export.go +++ b/src/cli/export/export.go @@ -3,7 +3,6 @@ package export import ( "context" "errors" - "os" "github.com/alcionai/clues" "github.com/spf13/cobra" @@ -21,9 +20,7 @@ import ( var exportCommands = []func(cmd *cobra.Command) *cobra.Command{ addOneDriveCommands, addSharePointCommands, - // awaiting release - // addGroupsCommands, - // addTeamsCommands, + addGroupsCommands, } // AddCommands attaches all `corso export * *` commands to the parent. @@ -34,11 +31,6 @@ func AddCommands(cmd *cobra.Command) { for _, addExportTo := range exportCommands { addExportTo(exportC) } - - // delete after release - if len(os.Getenv("CORSO_ENABLE_GROUPS")) > 0 { - addGroupsCommands(exportC) - } } const exportCommand = "export" diff --git a/src/cli/export/groups.go b/src/cli/export/groups.go index d46e05d41d..30e85fdafb 100644 --- a/src/cli/export/groups.go +++ b/src/cli/export/groups.go @@ -18,7 +18,7 @@ func addGroupsCommands(cmd *cobra.Command) *cobra.Command { switch cmd.Use { case exportCommand: - c, fs = utils.AddCommand(cmd, groupsExportCmd(), utils.MarkPreReleaseCommand()) + c, fs = utils.AddCommand(cmd, groupsExportCmd(), utils.MarkPreviewCommand()) c.Use = c.Use + " " + groupsServiceCommandUseSuffix @@ -43,16 +43,20 @@ const ( groupsServiceCommandUseSuffix = " --backup " //nolint:lll - groupsServiceCommandExportExamples = `# Export a message in Marketing's last backup (1234abcd...) to my-exports directory + groupsServiceCommandExportExamples = `# Export a message in Marketing's last backup (1234abcd...) to /my-exports corso export groups my-exports --backup 1234abcd-12ab-cd34-56de-1234abcd --message 98765abcdef # Export all messages named in channel "Finance Reports" to the current directory corso export groups . --backup 1234abcd-12ab-cd34-56de-1234abcd \ --message '*' --channel "Finance Reports" -# Export all messages in channel "Finance Reports" that were created before 2020 to my-exports +# Export all messages in channel "Finance Reports" that were created before 2020 to /my-exports corso export groups my-exports --backup 1234abcd-12ab-cd34-56de-1234abcd - --channel "Finance Reports" --message-created-before 2020-01-01T00:00:00` + --channel "Finance Reports" --message-created-before 2020-01-01T00:00:00 + +# 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 [...] ` diff --git a/src/cli/export/onedrive.go b/src/cli/export/onedrive.go index ea6537dc2e..3ebf5bdbfc 100644 --- a/src/cli/export/onedrive.go +++ b/src/cli/export/onedrive.go @@ -42,15 +42,15 @@ const ( oneDriveServiceCommandUseSuffix = " --backup " //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 corso export onedrive my-exports --backup 1234abcd-12ab-cd34-56de-1234abcd --file 98765abcdef -# Export files named "FY2021 Planning.xlsx" in "Documents/Finance Reports" to current directory +# Export files named "FY2021 Planning.xlsx" in "Documents/Finance Reports" to he current directory corso export onedrive . --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 onedrive my-exports --backup 1234abcd-12ab-cd34-56de-1234abcd +# Export all files and folders in folder "Documents/Finance Reports" that were created before 2020 to /my-exports +corso export onedrive my-exports --backup 1234abcd-12ab-cd34-56de-1234abcd \ --folder "Documents/Finance Reports" --file-created-before 2020-01-01T00:00:00` ) diff --git a/src/cli/export/sharepoint.go b/src/cli/export/sharepoint.go index 7293a02f9b..9d9a363671 100644 --- a/src/cli/export/sharepoint.go +++ b/src/cli/export/sharepoint.go @@ -42,19 +42,19 @@ const ( sharePointServiceCommandUseSuffix = " --backup " //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 corso export sharepoint --backup 1234abcd-12ab-cd34-56de-1234abcd --file 98765abcdef my-exports -# Export files named "ServerRenderTemplate.xsl" in the folder "Display Templates/Style Sheets". as archive to current directory +# Export file "ServerRenderTemplate.xsl" in "Display Templates/Style Sheets" as archive to the current directory corso export sharepoint --backup 1234abcd-12ab-cd34-56de-1234abcd \ --file "ServerRenderTemplate.xsl" --folder "Display Templates/Style Sheets" --archive . -# Export all files in the folder "Display Templates/Style Sheets" that were created before 2020 to my-exports directory. -corso export sharepoint --backup 1234abcd-12ab-cd34-56de-1234abcd +# Export all files in the folder "Display Templates/Style Sheets" that were created before 2020 to /my-exports +corso export sharepoint --backup 1234abcd-12ab-cd34-56de-1234abcd \ --file-created-before 2020-01-01T00:00:00 --folder "Display Templates/Style Sheets" my-exports -# Export all files in the "Documents" library to current directory. -corso export sharepoint --backup 1234abcd-12ab-cd34-56de-1234abcd +# Export all files in the "Documents" library to the current directory. +corso export sharepoint --backup 1234abcd-12ab-cd34-56de-1234abcd \ --library Documents --folder "Display Templates/Style Sheets" .` ) diff --git a/src/cli/restore/groups.go b/src/cli/restore/groups.go index cecffeee45..0b814d3467 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.MarkPreReleaseCommand()) + c, fs = utils.AddCommand(cmd, groupsRestoreCmd(), utils.MarkPreviewCommand()) c.Use = c.Use + " " + groupsServiceCommandUseSuffix @@ -40,24 +40,22 @@ func addGroupsCommands(cmd *cobra.Command) *cobra.Command { return c } -// TODO: correct examples const ( groupsServiceCommand = "groups" teamsServiceCommand = "teams" groupsServiceCommandUseSuffix = "--backup " - groupsServiceCommandRestoreExamples = `# Restore file with ID 98765abcdef in Bob's last backup (1234abcd...) + groupsServiceCommandRestoreExamples = `# Restore file with ID 98765abcdef in Marketing'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 named "FY2021 Planning.xlsx" +corso restore groups --backup 1234abcd-12ab-cd34-56de-1234abcd --file "FY2021 Planning.xlsx" # Restore all files and folders in folder "Documents/Finance Reports" that were created before 2020 -corso restore groups --backup 1234abcd-12ab-cd34-56de-1234abcd +corso restore groups --backup 1234abcd-12ab-cd34-56de-1234abcd \ --folder "Documents/Finance Reports" --file-created-before 2020-01-01T00:00:00` ) diff --git a/src/cli/restore/onedrive.go b/src/cli/restore/onedrive.go index 1bf0ead22f..6490f86308 100644 --- a/src/cli/restore/onedrive.go +++ b/src/cli/restore/onedrive.go @@ -54,7 +54,7 @@ corso restore onedrive --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 onedrive --backup 1234abcd-12ab-cd34-56de-1234abcd +corso restore onedrive --backup 1234abcd-12ab-cd34-56de-1234abcd \ --folder "Documents/Finance Reports" --file-created-before 2020-01-01T00:00:00` ) diff --git a/src/cli/restore/restore.go b/src/cli/restore/restore.go index 940a1f0843..3bd20b4fab 100644 --- a/src/cli/restore/restore.go +++ b/src/cli/restore/restore.go @@ -2,7 +2,6 @@ package restore import ( "context" - "os" "github.com/alcionai/clues" "github.com/pkg/errors" @@ -20,9 +19,7 @@ var restoreCommands = []func(cmd *cobra.Command) *cobra.Command{ addExchangeCommands, addOneDriveCommands, addSharePointCommands, - // awaiting release - // addGroupsCommands, - // addTeamsCommands, + addGroupsCommands, } // AddCommands attaches all `corso restore * *` commands to the parent. @@ -33,11 +30,6 @@ func AddCommands(cmd *cobra.Command) { for _, addRestoreTo := range restoreCommands { addRestoreTo(restoreC) } - - // delete after release - if len(os.Getenv("CORSO_ENABLE_GROUPS")) > 0 { - addGroupsCommands(restoreC) - } } const restoreCommand = "restore" diff --git a/src/cli/restore/sharepoint.go b/src/cli/restore/sharepoint.go index 640654b1b1..53973e83b9 100644 --- a/src/cli/restore/sharepoint.go +++ b/src/cli/restore/sharepoint.go @@ -56,11 +56,11 @@ corso restore sharepoint --backup 1234abcd-12ab-cd34-56de-1234abcd \ --file "ServerRenderTemplate.xsl" --folder "Display Templates/Style Sheets" # Restore all files in the folder "Display Templates/Style Sheets" that were created before 2020. -corso restore sharepoint --backup 1234abcd-12ab-cd34-56de-1234abcd +corso restore sharepoint --backup 1234abcd-12ab-cd34-56de-1234abcd \ --file-created-before 2020-01-01T00:00:00 --folder "Display Templates/Style Sheets" # Restore all files in the "Documents" library. -corso restore sharepoint --backup 1234abcd-12ab-cd34-56de-1234abcd +corso restore sharepoint --backup 1234abcd-12ab-cd34-56de-1234abcd \ --library Documents --folder "Display Templates/Style Sheets" ` ) diff --git a/src/cli/utils/utils.go b/src/cli/utils/utils.go index 1df944763e..901cb8fb4e 100644 --- a/src/cli/utils/utils.go +++ b/src/cli/utils/utils.go @@ -138,6 +138,7 @@ func HasNoFlagsAndShownHelp(cmd *cobra.Command) bool { type cmdCfg struct { hidden bool preRelease bool + preview bool } type cmdOpt func(*cmdCfg) @@ -161,6 +162,12 @@ func MarkPreReleaseCommand() cmdOpt { } } +func MarkPreviewCommand() cmdOpt { + return func(cc *cmdCfg) { + cc.preview = true + } +} + // AddCommand adds a clone of the subCommand to the parent, // and returns both the clone and its pflags. func AddCommand(parent, c *cobra.Command, opts ...cmdOpt) (*cobra.Command, *pflag.FlagSet) { @@ -178,6 +185,14 @@ func AddCommand(parent, c *cobra.Command, opts ...cmdOpt) (*cobra.Command, *pfla "==================================================================================================\n" } + if cc.preview { + // There is a default deprecated message that always shows so we do some terminal magic to overwrite it + c.Deprecated = "\n\033[1F\033[K" + + "=============================================================================================================\n" + + "\tWARNING!!! THIS IS A FEATURE PREVIEW THAT MAY NOT FUNCTION PROPERLY AND MAY BREAK ACROSS RELEASES\n" + + "=============================================================================================================\n" + } + c.Flags().SortFlags = false return c, c.Flags() diff --git a/src/cmd/mdgen/mdgen.go b/src/cmd/mdgen/mdgen.go index 604ffc90b0..54c16abef5 100644 --- a/src/cmd/mdgen/mdgen.go +++ b/src/cmd/mdgen/mdgen.go @@ -89,7 +89,7 @@ func fatal(err error) { // Adapted from https://github.com/spf13/cobra/blob/main/doc/md_docs.go for Corso specific formatting func genMarkdownCorso(cmd *cobra.Command, dir string) error { for _, c := range cmd.Commands() { - if !c.IsAvailableCommand() || c.IsAdditionalHelpTopicCommand() { + if !isAvailableCommand(c) || c.IsAdditionalHelpTopicCommand() { continue } @@ -116,6 +116,17 @@ func genMarkdownCorso(cmd *cobra.Command, dir string) error { return genMarkdownCustomCorso(cmd, f) } +// adapted from cobra.Command.IsAvailablecommand() +func isAvailableCommand(cmd *cobra.Command) bool { + return cmd.IsAvailableCommand() || + // exception case to the cobra isAvailable: + // preview commands hijack the "deprecated" + // value, but are not hidden. In order for + // this to work, we'll need to hide any commands + // that we deprecate. + (len(cmd.Deprecated) > 0 && !cmd.Hidden) +} + func genMarkdownCustomCorso(cmd *cobra.Command, w io.Writer) error { cmd.InitDefaultHelpCmd() cmd.InitDefaultHelpFlag() diff --git a/website/docs/developers/linters.md b/website/docs/developers/linters.md index e7416b2350..5d21311ffa 100644 --- a/website/docs/developers/linters.md +++ b/website/docs/developers/linters.md @@ -199,7 +199,9 @@ cryptic messages how you can fix the problems the linters flag. Each subsection also includes the version of golangci-lint it applies to and the linter in question. -### `gci` `Expected 's', Found 'a' at file.go` +```sh +gci Expected 's', Found 'a' at file.go +``` This applies to golangci-lint v1.45.2 for the `gci` linter and is due to an import ordering issue. It occurs because imports in the file aren't grouped according diff --git a/website/docs/quickstart.md b/website/docs/quickstart.md index da2ecf9dc8..3020d6f6ad 100644 --- a/website/docs/quickstart.md +++ b/website/docs/quickstart.md @@ -339,5 +339,5 @@ See [here](../setup/restore-options) for more restoration options. The above tutorial only scratches the surface for Corso's capabilities. We encourage you to dig deeper by: * Learning about [Corso concepts and setup](../setup/concepts) -* Explore Corso backup and restore options for Exchange and Onedrive in the [Command Line Reference](../cli/corso) +* Explore Corso backup and restore options for M365 Applications in the [Command Line Reference](../cli/corso) * Leverage Corso's [Advanced Restoration Options](../setup/restore-options) diff --git a/website/docs/setup/m365-access.md b/website/docs/setup/m365-access.md index baf139321c..13935eec25 100644 --- a/website/docs/setup/m365-access.md +++ b/website/docs/setup/m365-access.md @@ -52,11 +52,18 @@ then click **Add permissions**. | API / Permissions Name | Type | Description |:--|:--|:--| | Calendars.ReadWrite | Application | Read and write calendars in all mailboxes | +| ChannelMessage.Read.All | Application | Read all messages in Teams' channels | +| ChannelSettings.Read.All | Application | Read all Teams' channel settings | +| Chat.Read.All | Application | Read all Teams' chats and chat messages | | Contacts.ReadWrite | Application | Read and write contacts in all mailboxes | +| Directory.Read.All | Application | Read all organization directory data | | Files.ReadWrite.All | Application | Read and write files in all site collections | | MailboxSettings.Read | Application | Read all user mailbox settings | | Mail.ReadWrite | Application | Read and write mail in all mailboxes | +| Member.Read.Hidden | Application | Read hidden group memberships | | Sites.FullControl.All | Application | Have full control of all site collections | +| TeamMember.Read.All | Application | Read all Teams' user memberships | +| TeamSettings.Read.All | Application | Read all Teams' settings | | User.Read.All | Application | Read all users' full profiles | diff --git a/website/docs/support/known-issues.md b/website/docs/support/known-issues.md index 9f045863fc..5655a82f3d 100644 --- a/website/docs/support/known-issues.md +++ b/website/docs/support/known-issues.md @@ -26,4 +26,10 @@ Below is a list of known Corso issues and limitations: * Link shares with password protection can't be restored. -* All replies in a Teams Conversation are removed from backup when the root message gets deleted. +* Teams conversation replies are only backed up if the parent message is available at the time of backup. + +* Groups SharePoint files don't support Export. This limitation will be addressed in a follow-up release + +* Teams messages don't support Restore due to limited Graph API support for message creation. + +* Groups and Teams support is available in an early-access status, and may be subject to breaking changes. diff --git a/website/sidebars.js b/website/sidebars.js index c46fdb7b3b..d7982cbea8 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -67,6 +67,20 @@ const sidebars = { 'cli/corso-backup-delete-exchange', 'cli/corso-restore-exchange'] }, + { + type: 'category', + label: 'Groups & Teams', + link: { + slug: 'cli/groups', + description: 'Documentation for commonly-used Corso Groups & Teams CLI commands', + }, + items: [ + 'cli/corso-backup-create-groups', + 'cli/corso-backup-list-groups', + 'cli/corso-backup-details-groups', + 'cli/corso-backup-delete-groups', + 'cli/corso-restore-groups'] + }, { type: 'category', label: 'OneDrive',