diff --git a/src/cli/backup/backup.go b/src/cli/backup/backup.go index 270277125..50773c610 100644 --- a/src/cli/backup/backup.go +++ b/src/cli/backup/backup.go @@ -4,17 +4,25 @@ import ( "github.com/spf13/cobra" ) +var backupApplications = []func(parent *cobra.Command) *cobra.Command{ + addExchangeApp, +} + // AddCommands attaches all `corso backup * *` commands to the parent. func AddCommands(parent *cobra.Command) { parent.AddCommand(backupCmd) + backupCmd.AddCommand(createCmd) + + for _, addBackupTo := range backupApplications { + addBackupTo(createCmd) + } } // The backup category of commands. // `corso backup [] [...]` var backupCmd = &cobra.Command{ Use: "backup", - Short: "Backup your application data.", - Long: `Backup the data stored in one of your M365 applications.`, + Short: "Backup a M365 service", Run: handleBackupCmd, Args: cobra.NoArgs, } @@ -24,3 +32,19 @@ var backupCmd = &cobra.Command{ func handleBackupCmd(cmd *cobra.Command, args []string) { cmd.Help() } + +// The backup create subcommand. +// `corso backup create [...]` +var createCommand = "create" +var createCmd = &cobra.Command{ + Use: createCommand, + Short: "Backup M365 Exchange", + Run: handleCreateCmd, + Args: cobra.NoArgs, +} + +// Handler for calls to `corso backup create`. +// Produces the same output as `corso backup create --help`. +func handleCreateCmd(cmd *cobra.Command, args []string) { + cmd.Help() +} diff --git a/src/cli/backup/exchange.go b/src/cli/backup/exchange.go new file mode 100644 index 000000000..964128469 --- /dev/null +++ b/src/cli/backup/exchange.go @@ -0,0 +1,77 @@ +package backup + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/alcionai/corso/cli/utils" + "github.com/alcionai/corso/pkg/repository" + "github.com/alcionai/corso/pkg/storage" +) + +// exchange bucket info from flags +var ( + user string +) + +// called by backup.go to map parent subcommands to provider-specific handling. +func addExchangeApp(parent *cobra.Command) *cobra.Command { + var c *cobra.Command + switch parent.Use { + case createCommand: + c = exchangeCreateCmd + } + parent.AddCommand(c) + fs := c.Flags() + fs.StringVar(&user, "user", "", "ID of the user whose Exchange data is to be backed up.") + return c +} + +// `corso backup create exchange [...]` +var exchangeCreateCmd = &cobra.Command{ + Use: "exchange", + Short: "Start backing up Exchange data", + Long: `Creates a new backup of your microsoft Exchange data.`, + Run: createExchangeCmd, + Args: cobra.NoArgs, +} + +// initializes a s3 repo. +func createExchangeCmd(cmd *cobra.Command, args []string) { + mv := utils.GetM365Vars() + fmt.Printf( + "Called - %s\n\t365TenantID:\t%s\n\t356Client:\t%s\n\tfound 356Secret:\t%v\n", + cmd.CommandPath(), + mv.TenantID, + mv.ClientID, + len(mv.ClientSecret) > 0) + + a := repository.Account{ + TenantID: mv.TenantID, + ClientID: mv.ClientID, + ClientSecret: mv.ClientSecret, + } + // todo (rkeepers) - retrieve storage details from corso config + s, err := storage.NewStorage(storage.ProviderUnknown) + if err != nil { + utils.Fatalf("Failed to configure storage provider: %v", err) + } + + r, err := repository.Connect(cmd.Context(), a, s) + if err != nil { + utils.Fatalf("Failed to connect to the %s repository: %v", s.Provider, err) + } + defer utils.CloseRepo(cmd.Context(), r) + + bo, err := r.NewBackup(cmd.Context(), []string{user}) + if err != nil { + utils.Fatalf("Failed to initialize Exchange backup: %v", err) + } + + if err := bo.Run(cmd.Context()); err != nil { + utils.Fatalf("Failed to run Exchange backup: %v", err) + } + + fmt.Printf("Backed up Exchange in %s for user %s.\n", s.Provider, user) +} diff --git a/src/cli/repo/repo.go b/src/cli/repo/repo.go index 42a0ae3d1..20a588b95 100644 --- a/src/cli/repo/repo.go +++ b/src/cli/repo/repo.go @@ -1,8 +1,6 @@ package repo import ( - "os" - "github.com/spf13/cobra" ) @@ -69,19 +67,3 @@ var connectCmd = &cobra.Command{ func handleConnectCmd(cmd *cobra.Command, args []string) { cmd.Help() } - -// aggregates m365 details from flag and env_var values. -type m365Vars struct { - clientID string - clientSecret string - tenantID string -} - -// helper for aggregating m365 connection details. -func getM365Vars() m365Vars { - return m365Vars{ - clientID: os.Getenv("O365_CLIENT_ID"), - clientSecret: os.Getenv("O356_SECRET"), - tenantID: "todo:tenantID", - } -} diff --git a/src/cli/repo/s3.go b/src/cli/repo/s3.go index 584336774..8f953c276 100644 --- a/src/cli/repo/s3.go +++ b/src/cli/repo/s3.go @@ -1,7 +1,6 @@ package repo import ( - "context" "fmt" "os" @@ -50,7 +49,7 @@ var s3InitCmd = &cobra.Command{ // initializes a s3 repo. func initS3Cmd(cmd *cobra.Command, args []string) { - mv := getM365Vars() + mv := utils.GetM365Vars() s3Cfg, commonCfg, err := makeS3Config() if err != nil { fmt.Println(err) @@ -62,27 +61,25 @@ func initS3Cmd(cmd *cobra.Command, args []string) { cmd.CommandPath(), s3Cfg.Bucket, s3Cfg.AccessKey, - mv.clientID, - len(mv.clientSecret) > 0, + mv.ClientID, + len(mv.ClientSecret) > 0, len(s3Cfg.SecretKey) > 0) a := repository.Account{ - TenantID: mv.tenantID, - ClientID: mv.clientID, - ClientSecret: mv.clientSecret, + TenantID: mv.TenantID, + ClientID: mv.ClientID, + ClientSecret: mv.ClientSecret, } s, err := storage.NewStorage(storage.ProviderS3, s3Cfg, commonCfg) if err != nil { - fmt.Printf("Failed to configure storage provider: %v", err) - os.Exit(1) + utils.Fatalf("Failed to configure storage provider: %v", err) } - r, err := repository.Connect(cmd.Context(), a, s) + r, err := repository.Initialize(cmd.Context(), a, s) if err != nil { - fmt.Printf("Failed to initialize a new S3 repository: %v", err) - os.Exit(1) + utils.Fatalf("Failed to initialize a new S3 repository: %v", err) } - defer closeRepo(cmd.Context(), r) + defer utils.CloseRepo(cmd.Context(), r) fmt.Printf("Initialized a S3 repository within bucket %s.\n", s3Cfg.Bucket) } @@ -98,7 +95,7 @@ var s3ConnectCmd = &cobra.Command{ // connects to an existing s3 repo. func connectS3Cmd(cmd *cobra.Command, args []string) { - mv := getM365Vars() + mv := utils.GetM365Vars() s3Cfg, commonCfg, err := makeS3Config() if err != nil { fmt.Println(err) @@ -110,27 +107,25 @@ func connectS3Cmd(cmd *cobra.Command, args []string) { cmd.CommandPath(), s3Cfg.Bucket, s3Cfg.AccessKey, - mv.clientID, - len(mv.clientSecret) > 0, + mv.ClientID, + len(mv.ClientSecret) > 0, len(s3Cfg.SecretKey) > 0) a := repository.Account{ - TenantID: mv.tenantID, - ClientID: mv.clientID, - ClientSecret: mv.clientSecret, + TenantID: mv.TenantID, + ClientID: mv.ClientID, + ClientSecret: mv.ClientSecret, } s, err := storage.NewStorage(storage.ProviderS3, s3Cfg, commonCfg) if err != nil { - fmt.Printf("Failed to configure storage provider: %v", err) - os.Exit(1) + utils.Fatalf("Failed to configure storage provider: %v", err) } r, err := repository.Connect(cmd.Context(), a, s) if err != nil { - fmt.Printf("Failed to connect to the S3 repository: %v", err) - os.Exit(1) + utils.Fatalf("Failed to connect to the S3 repository: %v", err) } - defer closeRepo(cmd.Context(), r) + defer utils.CloseRepo(cmd.Context(), r) fmt.Printf("Connected to S3 bucket %s.\n", s3Cfg.Bucket) } @@ -164,9 +159,3 @@ func makeS3Config() (storage.S3Config, storage.CommonConfig, error) { storage.CORSO_PASSWORD: corsoPasswd, }) } - -func closeRepo(ctx context.Context, r *repository.Repository) { - if err := r.Close(ctx); err != nil { - fmt.Printf("Error closing repository: %v\n", err) - } -} diff --git a/src/cli/utils/utils.go b/src/cli/utils/utils.go index 911d434aa..5e88261cf 100644 --- a/src/cli/utils/utils.go +++ b/src/cli/utils/utils.go @@ -1,6 +1,13 @@ package utils -import "errors" +import ( + "context" + "errors" + "fmt" + "os" + + "github.com/alcionai/corso/pkg/repository" +) // RequireProps validates the existence of the properties // in the map. Expects the format map[propName]propVal. @@ -12,3 +19,34 @@ func RequireProps(props map[string]string) error { } return nil } + +// aggregates m365 details from flag and env_var values. +type m365Vars struct { + ClientID string + ClientSecret string + TenantID string +} + +// GetM365Vars is a helper for aggregating m365 connection details. +func GetM365Vars() m365Vars { + // todo (rkeeprs): read from either corso config file or env vars. + // https://github.com/alcionai/corso/issues/120 + return m365Vars{ + ClientID: os.Getenv("CLIENT_ID"), + ClientSecret: os.Getenv("CLIENT_SECRET"), + TenantID: os.Getenv("TENANT_ID"), + } +} + +// CloseRepo handles closing a repo. +func CloseRepo(ctx context.Context, r *repository.Repository) { + if err := r.Close(ctx); err != nil { + fmt.Print("Error closing repository:", err) + } +} + +// Fatalf prints the message and then exits with os code 1. +func Fatalf(format string, a ...any) { + fmt.Printf(format, a...) + os.Exit(1) +}