diff --git a/docs/sidebars.js b/docs/sidebars.js index 6b9ecddf8..5ae0852fc 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -34,7 +34,10 @@ const sidebars = { items: [ 'cli/corso_repo_init_s3', 'cli/corso_repo_connect_s3', 'cli/corso_backup_create_exchange', 'cli/corso_backup_list_exchange', 'cli/corso_backup_details_exchange', - 'cli/corso_restore_exchange', 'cli/corso_env' + 'cli/corso_restore_exchange', + 'cli/corso_backup_create_onedrive', 'cli/corso_backup_list_onedrive', 'cli/corso_backup_details_onedrive', + 'cli/corso_restore_onedrive', + 'cli/corso_env' ] }, { diff --git a/src/cli/backup/exchange.go b/src/cli/backup/exchange.go index 90c9d2847..86cd3c445 100644 --- a/src/cli/backup/exchange.go +++ b/src/cli/backup/exchange.go @@ -59,11 +59,40 @@ const ( const ( exchangeServiceCommand = "exchange" - exchangeServiceCommandCreateUseSuffix = " --all | --user " + exchangeServiceCommandCreateUseSuffix = " --user | '" + utils.Wildcard + "'" exchangeServiceCommandDeleteUseSuffix = " --backup " exchangeServiceCommandDetailsUseSuffix = " --backup " ) +const ( + exchangeServiceCommandCreateExamples = `# Backup all Exchange data for Alice +corso backup create exchange --user alice@example.com + +# Backup only Exchange contacts for Alice and Bob +corso backup create exchange --user alice@example.com,bob@example.com --data contacts + +# Backup all Exchange data for all M365 users +corso backup create exchange --user '*'` + + exchangeServiceCommandDeleteExamples = `# Delete Exchange backup with ID 1234abcd-12ab-cd34-56de-1234abcd +corso backup delete exchange --backup 1234abcd-12ab-cd34-56de-1234abcd` + + exchangeServiceCommandDetailsExamples = `# Explore Alice's items in backup 1234abcd-12ab-cd34-56de-1234abcd +corso backup details exchange --backup 1234abcd-12ab-cd34-56de-1234abcd --user alice@example.com + +# Explore Alice's emails with subject containing "Hello world" in folder "Inbox" from a specific backup +corso backup details exchange --backup 1234abcd-12ab-cd34-56de-1234abcd \ + --user alice@example.com --email-subject "Hello world" --email-folder Inbox + +# Explore Bobs's events occurring after start of 2022 from a specific backup +corso backup details exchange --backup 1234abcd-12ab-cd34-56de-1234abcd \ + --user bob@example.com --event-starts-after 2022-01-01T00:00:00 + +# Explore Alice's contacts with name containing Andy from a specific backup +corso backup details exchange --backup 1234abcd-12ab-cd34-56de-1234abcd \ + --user alice@example.com --contact-name Andy` +) + // called by backup.go to map parent subcommands to provider-specific handling. func addExchangeCommands(parent *cobra.Command) *cobra.Command { var ( @@ -76,6 +105,7 @@ func addExchangeCommands(parent *cobra.Command) *cobra.Command { c, fs = utils.AddCommand(parent, exchangeCreateCmd()) c.Use = c.Use + exchangeServiceCommandCreateUseSuffix + c.Example = utils.IndentExamples(exchangeServiceCommandCreateExamples) // Flags addition ordering should follow the order we want them to appear in help and docs: // More generic (ex: --all) and more frequently used flags take precedence. @@ -85,7 +115,7 @@ func addExchangeCommands(parent *cobra.Command) *cobra.Command { fs.StringSliceVar( &user, "user", nil, - "Backup Exchange data by user ID; accepts "+utils.Wildcard+" to select all users") + "Backup Exchange data by user ID; accepts '"+utils.Wildcard+"' to select all users") fs.StringSliceVar( &exchangeData, "data", nil, @@ -99,6 +129,7 @@ func addExchangeCommands(parent *cobra.Command) *cobra.Command { c, fs = utils.AddCommand(parent, exchangeDetailsCmd()) c.Use = c.Use + exchangeServiceCommandDetailsUseSuffix + c.Example = utils.IndentExamples(exchangeServiceCommandDetailsExamples) // Flags addition ordering should follow the order we want them to appear in help and docs: // More generic (ex: --all) and more frequently used flags take precedence. @@ -109,17 +140,17 @@ func addExchangeCommands(parent *cobra.Command) *cobra.Command { fs.StringSliceVar( &user, "user", nil, - "Select backup details by user ID; accepts "+utils.Wildcard+" to select all users.") + "Select backup details by user ID; accepts '"+utils.Wildcard+"' to select all users.") // email flags fs.StringSliceVar( &email, "email", nil, - "Select backup details for emails by email ID; accepts "+utils.Wildcard+" to select all emails.") + "Select backup details for emails by email ID; accepts '"+utils.Wildcard+"' to select all emails.") fs.StringSliceVar( &emailFolder, "email-folder", nil, - "Select backup details for emails within a folder; accepts "+utils.Wildcard+" to select all email folders.") + "Select backup details for emails within a folder; accepts '"+utils.Wildcard+"' to select all email folders.") fs.StringVar( &emailSubject, "email-subject", "", @@ -141,11 +172,11 @@ func addExchangeCommands(parent *cobra.Command) *cobra.Command { fs.StringSliceVar( &event, "event", nil, - "Select backup details for events by event ID; accepts "+utils.Wildcard+" to select all events.") + "Select backup details for events by event ID; accepts '"+utils.Wildcard+"' to select all events.") fs.StringSliceVar( &eventCalendar, "event-calendar", nil, - "Select backup details for events under a calendar; accepts "+utils.Wildcard+" to select all events.") + "Select backup details for events under a calendar; accepts '"+utils.Wildcard+"' to select all events.") fs.StringVar( &eventSubject, "event-subject", "", @@ -171,11 +202,11 @@ func addExchangeCommands(parent *cobra.Command) *cobra.Command { fs.StringSliceVar( &contact, "contact", nil, - "Select backup details for contacts by contact ID; accepts "+utils.Wildcard+" to select all contacts.") + "Select backup details for contacts by contact ID; accepts '"+utils.Wildcard+"' to select all contacts.") fs.StringSliceVar( &contactFolder, "contact-folder", nil, - "Select backup details for contacts within a folder; accepts "+utils.Wildcard+" to select all contact folders.") + "Select backup details for contacts within a folder; accepts '"+utils.Wildcard+"' to select all contact folders.") fs.StringVar( &contactName, @@ -186,6 +217,7 @@ func addExchangeCommands(parent *cobra.Command) *cobra.Command { c, fs = utils.AddCommand(parent, exchangeDeleteCmd()) c.Use = c.Use + exchangeServiceCommandDeleteUseSuffix + c.Example = utils.IndentExamples(exchangeServiceCommandDeleteExamples) fs.StringVar(&backupID, "backup", "", "ID of the backup to delete. (required)") cobra.CheckErr(c.MarkFlagRequired("backup")) diff --git a/src/cli/backup/onedrive.go b/src/cli/backup/onedrive.go index 6f3401962..46a87666c 100644 --- a/src/cli/backup/onedrive.go +++ b/src/cli/backup/onedrive.go @@ -25,11 +25,36 @@ import ( const ( oneDriveServiceCommand = "onedrive" - oneDriveServiceCommandCreateUseSuffix = " --user | " + utils.Wildcard + oneDriveServiceCommandCreateUseSuffix = " --user | '" + utils.Wildcard + "'" oneDriveServiceCommandDeleteUseSuffix = " --backup " oneDriveServiceCommandDetailsUseSuffix = " --backup " ) +const ( + oneDriveServiceCommandCreateExamples = `# Backup OneDrive data for Alice +corso backup create onedrive --user alice@example.com + +# Backup OneDrive for Alice and Bob +corso backup create onedrive --user alice@example.com,bob@example.com + +# Backup all OneDrive data for all M365 users +corso backup create onedrive --user '*'` + + oneDriveServiceCommandDeleteExamples = `# Delete OneDrive backup with ID 1234abcd-12ab-cd34-56de-1234abcd +corso backup delete onedrive --backup 1234abcd-12ab-cd34-56de-1234abcd` + + oneDriveServiceCommandDetailsExamples = `# Explore Alice's files from backup 1234abcd-12ab-cd34-56de-1234abcd +corso backup details onedrive --backup 1234abcd-12ab-cd34-56de-1234abcd --user alice@example.com + +# Explore Alice or Bob's files with name containing "Fiscal 22" in folder "Reports" +corso backup details onedrive --backup 1234abcd-12ab-cd34-56de-1234abcd \ + --user alice@example.com,bob@example.com --file-name "Fiscal 22" --folder "Reports" + +# Explore Alice's files created before end of 2015 from a specific backup +corso backup details onedrive --backup 1234abcd-12ab-cd34-56de-1234abcd \ + --user alice@example.com --file-created-before 2015-01-01T00:00:00` +) + var ( folderPaths []string fileNames []string @@ -52,9 +77,10 @@ func addOneDriveCommands(parent *cobra.Command) *cobra.Command { c, fs = utils.AddCommand(parent, oneDriveCreateCmd()) c.Use = c.Use + oneDriveServiceCommandCreateUseSuffix + c.Example = utils.IndentExamples(oneDriveServiceCommandCreateExamples) fs.StringArrayVar(&user, "user", nil, - "Backup OneDrive data by user ID; accepts "+utils.Wildcard+" to select all users. (required)") + "Backup OneDrive data by user ID; accepts '"+utils.Wildcard+"' to select all users. (required)") options.AddOperationFlags(c) case listCommand: @@ -64,6 +90,7 @@ func addOneDriveCommands(parent *cobra.Command) *cobra.Command { c, fs = utils.AddCommand(parent, oneDriveDetailsCmd()) c.Use = c.Use + oneDriveServiceCommandDetailsUseSuffix + c.Example = utils.IndentExamples(oneDriveServiceCommandDetailsExamples) fs.StringVar(&backupID, "backup", "", "ID of the backup to explore. (required)") cobra.CheckErr(c.MarkFlagRequired("backup")) @@ -73,37 +100,38 @@ func addOneDriveCommands(parent *cobra.Command) *cobra.Command { fs.StringSliceVar( &folderPaths, "folder", nil, - "Select backup details by OneDrive folder; defaults to root") + "Select backup details by OneDrive folder; defaults to root.") fs.StringSliceVar( &fileNames, - "file-name", nil, - "Select backup details by OneDrive file name") + "file", nil, + "Select backup details by file name or ID.") // onedrive info flags fs.StringVar( &fileCreatedAfter, "file-created-after", "", - "Select files created after this datetime") + "Select backup details for files created after this datetime.") fs.StringVar( &fileCreatedBefore, "file-created-before", "", - "Select files created before this datetime") + "Select backup details for files created before this datetime.") fs.StringVar( &fileModifiedAfter, "file-modified-after", "", - "Select files modified after this datetime") + "Select backup details for files modified after this datetime.") fs.StringVar( &fileModifiedBefore, "file-modified-before", "", - "Select files modified before this datetime") + "Select backup details for files modified before this datetime.") case deleteCommand: c, fs = utils.AddCommand(parent, oneDriveDeleteCmd()) c.Use = c.Use + oneDriveServiceCommandDeleteUseSuffix + c.Example = utils.IndentExamples(oneDriveServiceCommandDeleteExamples) fs.StringVar(&backupID, "backup", "", "ID of the backup to delete. (required)") cobra.CheckErr(c.MarkFlagRequired("backup")) @@ -119,10 +147,11 @@ func addOneDriveCommands(parent *cobra.Command) *cobra.Command { // `corso backup create onedrive [...]` func oneDriveCreateCmd() *cobra.Command { return &cobra.Command{ - Use: oneDriveServiceCommand, - Short: "Backup M365 OneDrive service data", - RunE: createOneDriveCmd, - Args: cobra.NoArgs, + Use: oneDriveServiceCommand, + Short: "Backup M365 OneDrive service data", + RunE: createOneDriveCmd, + Args: cobra.NoArgs, + Example: oneDriveServiceCommandCreateExamples, } } @@ -234,10 +263,11 @@ func listOneDriveCmd(cmd *cobra.Command, args []string) error { // `corso backup details onedrive [...]` func oneDriveDetailsCmd() *cobra.Command { return &cobra.Command{ - Use: oneDriveServiceCommand, - Short: "Shows the details of a M365 OneDrive service backup", - RunE: detailsOneDriveCmd, - Args: cobra.NoArgs, + Use: oneDriveServiceCommand, + Short: "Shows the details of a M365 OneDrive service backup", + RunE: detailsOneDriveCmd, + Args: cobra.NoArgs, + Example: oneDriveServiceCommandDetailsExamples, } } @@ -318,10 +348,11 @@ func runDetailsOneDriveCmd( // `corso backup delete onedrive [...]` func oneDriveDeleteCmd() *cobra.Command { return &cobra.Command{ - Use: oneDriveServiceCommand, - Short: "Delete backed-up M365 OneDrive service data", - RunE: deleteOneDriveCmd, - Args: cobra.NoArgs, + Use: oneDriveServiceCommand, + Short: "Delete backed-up M365 OneDrive service data", + RunE: deleteOneDriveCmd, + Args: cobra.NoArgs, + Example: oneDriveServiceCommandDeleteExamples, } } diff --git a/src/cli/repo/s3.go b/src/cli/repo/s3.go index 628da3fc9..6c06d1b16 100644 --- a/src/cli/repo/s3.go +++ b/src/cli/repo/s3.go @@ -58,6 +58,26 @@ const ( s3ProviderCommandUseSuffix = " --bucket " ) +const ( + s3ProviderCommandInitExamples = `# Create a new Corso repo in AWS S3 bucket named "my-bucket" +corso repo init s3 --bucket my-bucket + +# Create a new Corso repo in AWS S3 bucket named "my-bucket" using a prefix +corso repo init s3 --bucket my-bucket --prefix my-prefix + +# Create a new Corso repo in an S3 compliant storage provider +corso repo init s3 --bucket my-bucket --endpoint https://my-s3-server-endpoint` + + s3ProviderCommandConnectExamples = `# Connect to a Corso repo in AWS S3 bucket named "my-bucket" +corso repo connect s3 --bucket my-bucket + +# Connect to a Corso repo in AWS S3 bucket named "my-bucket" using a prefix +corso repo connect s3 --bucket my-bucket --prefix my-prefix + +# Connect to a Corso repo in an S3 compliant storage provider +corso repo connect s3 --bucket my-bucket --endpoint https://my-s3-server-endpoint` +) + // --------------------------------------------------------------------------------------------------------- // Init // --------------------------------------------------------------------------------------------------------- @@ -65,11 +85,12 @@ const ( // `corso repo init s3 [...]` func s3InitCmd() *cobra.Command { return &cobra.Command{ - Use: s3ProviderCommand, - Short: "Initialize a S3 repository", - Long: `Bootstraps a new S3 repository and connects it to your m356 account.`, - RunE: initS3Cmd, - Args: cobra.NoArgs, + Use: s3ProviderCommand, + Short: "Initialize a S3 repository", + Long: `Bootstraps a new S3 repository and connects it to your m356 account.`, + RunE: initS3Cmd, + Args: cobra.NoArgs, + Example: utils.IndentExamples(s3ProviderCommandInitExamples), } } @@ -123,11 +144,12 @@ func initS3Cmd(cmd *cobra.Command, args []string) error { // `corso repo connect s3 [...]` func s3ConnectCmd() *cobra.Command { return &cobra.Command{ - Use: s3ProviderCommand, - Short: "Connect to a S3 repository", - Long: `Ensures a connection to an existing S3 repository.`, - RunE: connectS3Cmd, - Args: cobra.NoArgs, + Use: s3ProviderCommand, + Short: "Connect to a S3 repository", + Long: `Ensures a connection to an existing S3 repository.`, + RunE: connectS3Cmd, + Args: cobra.NoArgs, + Example: s3ProviderCommandConnectExamples, } } diff --git a/src/cli/restore/exchange.go b/src/cli/restore/exchange.go index fad2f7e0d..292184142 100644 --- a/src/cli/restore/exchange.go +++ b/src/cli/restore/exchange.go @@ -61,16 +61,16 @@ func addExchangeCommands(parent *cobra.Command) *cobra.Command { fs.StringSliceVar(&user, "user", nil, - "Restore data by user ID; accepts "+utils.Wildcard+" to select all users.") + "Restore data by user ID; accepts '"+utils.Wildcard+"' to select all users.") // email flags fs.StringSliceVar(&email, "email", nil, - "Restore emails by ID; accepts "+utils.Wildcard+" to select all emails.") + "Restore emails by ID; accepts '"+utils.Wildcard+"' to select all emails.") fs.StringSliceVar( &emailFolder, "email-folder", nil, - "Restore emails within a folder; accepts "+utils.Wildcard+" to select all email folders.") + "Restore emails within a folder; accepts '"+utils.Wildcard+"' to select all email folders.") fs.StringVar( &emailSubject, "email-subject", "", @@ -91,11 +91,11 @@ func addExchangeCommands(parent *cobra.Command) *cobra.Command { // event flags fs.StringSliceVar(&event, "event", nil, - "Restore events by event ID; accepts "+utils.Wildcard+" to select all events.") + "Restore events by event ID; accepts '"+utils.Wildcard+"' to select all events.") fs.StringSliceVar( &eventCalendar, "event-calendar", nil, - "Restore events under a calendar; accepts "+utils.Wildcard+" to select all event calendars.") + "Restore events under a calendar; accepts '"+utils.Wildcard+"' to select all event calendars.") fs.StringVar( &eventSubject, "event-subject", "", @@ -121,11 +121,11 @@ func addExchangeCommands(parent *cobra.Command) *cobra.Command { fs.StringSliceVar( &contact, "contact", nil, - "Restore contacts by contact ID; accepts "+utils.Wildcard+" to select all contacts.") + "Restore contacts by contact ID; accepts '"+utils.Wildcard+"' to select all contacts.") fs.StringSliceVar( &contactFolder, "contact-folder", nil, - "Restore contacts within a folder; accepts "+utils.Wildcard+" to select all contact folders.") + "Restore contacts within a folder; accepts '"+utils.Wildcard+"' to select all contact folders.") fs.StringVar( &contactName, "contact-name", "", @@ -141,15 +141,30 @@ func addExchangeCommands(parent *cobra.Command) *cobra.Command { const ( exchangeServiceCommand = "exchange" exchangeServiceCommandUseSuffix = " --backup " + + exchangeServiceCommandRestoreExamples = `# Restore emails with ID 98765abcdef and 12345abcdef from a specific backup +corso restore exchange --backup 1234abcd-12ab-cd34-56de-1234abcd --email 98765abcdef,12345abcdef + +# Restore Alice's emails with subject containing "Hello world" in "Inbox" from a specific backup +corso restore exchange --backup 1234abcd-12ab-cd34-56de-1234abcd \ + --user alice@example.com --email-subject "Hello world" --email-folder Inbox + +# Restore Bobs's entire calendar from a specific backup +corso restore exchange --backup 1234abcd-12ab-cd34-56de-1234abcd \ + --user bob@example.com --event-calendar Calendar + +# Restore contact with ID abdef0101 from a specific backup +corso restore exchange --backup 1234abcd-12ab-cd34-56de-1234abcd --contact abdef0101` ) // `corso restore exchange [...]` func exchangeRestoreCmd() *cobra.Command { return &cobra.Command{ - Use: exchangeServiceCommand, - Short: "Restore M365 Exchange service data", - RunE: restoreExchangeCmd, - Args: cobra.NoArgs, + Use: exchangeServiceCommand, + Short: "Restore M365 Exchange service data", + RunE: restoreExchangeCmd, + Args: cobra.NoArgs, + Example: utils.IndentExamples(exchangeServiceCommandRestoreExamples), } } diff --git a/src/cli/restore/onedrive.go b/src/cli/restore/onedrive.go index 1680cc35f..bd6b69d0b 100644 --- a/src/cli/restore/onedrive.go +++ b/src/cli/restore/onedrive.go @@ -47,7 +47,7 @@ func addOneDriveCommands(parent *cobra.Command) *cobra.Command { fs.StringSliceVar(&user, "user", nil, - "Restore data by user ID; accepts "+utils.Wildcard+" to select all users.") + "Restore data by user ID; accepts '"+utils.Wildcard+"' to select all users.") // onedrive hierarchy (path/name) flags @@ -58,8 +58,8 @@ func addOneDriveCommands(parent *cobra.Command) *cobra.Command { fs.StringSliceVar( &fileNames, - "file-name", nil, - "Restore items by OneDrive file name") + "file", nil, + "Restore items by file name or ID") // onedrive info flags @@ -91,15 +91,27 @@ func addOneDriveCommands(parent *cobra.Command) *cobra.Command { const ( oneDriveServiceCommand = "onedrive" oneDriveServiceCommandUseSuffix = " --backup " + + oneDriveServiceCommandRestoreExamples = `# Restore file with ID 98765abcdef +corso restore onedrive --backup 1234abcd-12ab-cd34-56de-1234abcd --file 98765abcdef + +# Restore Alice's file named "FY2021 Planning.xlsx in "Documents/Finance Reports" from a specific backup +corso restore onedrive --backup 1234abcd-12ab-cd34-56de-1234abcd \ + --user alice@example.com --file "FY2021 Planning.xlsx" --folder "Documents/Finance Reports" + +# Restore all files from Bob's folder that were created before 2020 when captured in a specific backup +corso restore onedrive --backup 1234abcd-12ab-cd34-56de-1234abcd + --user bob@example.com --folder "Documents/Finance Reports" --file-created-before 2020-01-01T00:00:00` ) // `corso restore onedrive [...]` func oneDriveRestoreCmd() *cobra.Command { return &cobra.Command{ - Use: oneDriveServiceCommand, - Short: "Restore M365 OneDrive service data", - RunE: restoreOneDriveCmd, - Args: cobra.NoArgs, + Use: oneDriveServiceCommand, + Short: "Restore M365 OneDrive service data", + RunE: restoreOneDriveCmd, + Args: cobra.NoArgs, + Example: utils.IndentExamples(oneDriveServiceCommandRestoreExamples), } } diff --git a/src/cli/utils/utils.go b/src/cli/utils/utils.go index 74b43cc57..520c34ada 100644 --- a/src/cli/utils/utils.go +++ b/src/cli/utils/utils.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "regexp" "github.com/spf13/cobra" "github.com/spf13/pflag" @@ -57,3 +58,12 @@ func AddCommand(parent, c *cobra.Command) (*cobra.Command, *pflag.FlagSet) { return c, c.Flags() } + +// Takes in a multi-line string and returns it indented by 2 spaces. +// This is only to be used with Examples strings which the default usage +// template does not properly indent to match other sections +func IndentExamples(examples string) string { + e := regexp.MustCompile(`(?m)^`) + + return e.ReplaceAllString(examples, " ") +} diff --git a/src/cmd/mdgen/mdgen.go b/src/cmd/mdgen/mdgen.go index 0b04c3389..3cc0020db 100644 --- a/src/cmd/mdgen/mdgen.go +++ b/src/cmd/mdgen/mdgen.go @@ -138,7 +138,12 @@ func genMarkdownCustomCorso(cmd *cobra.Command, w io.Writer) error { } if cmd.Runnable() { - buf.WriteString(fmt.Sprintf("```bash\n%s\n```\n", cmd.UseLine())) + buf.WriteString(fmt.Sprintf("```bash\n%s\n```\n\n", cmd.UseLine())) + } + + if cmd.HasExample() { + buf.WriteString("### Examples\n\n") + buf.WriteString(fmt.Sprintf("```bash\n%s\n```\n", cmd.Example)) } flags := cmd.NonInheritedFlags() @@ -155,11 +160,6 @@ func genMarkdownCustomCorso(cmd *cobra.Command, w io.Writer) error { printFlags(buf, parentFlags) } - if len(cmd.Example) > 0 { - buf.WriteString("\n### Examples\n\n") - buf.WriteString(fmt.Sprintf("```\n%s\n```\n\n", cmd.Example)) - } - _, err := buf.WriteTo(w) return err