add create and exchange backup commands (#145)

Adds the create subcommand for backups.  Adds the exchange
subcommand for backup creation.  Centralizes some
common code on top.
This commit is contained in:
Keepers 2022-06-06 16:33:04 -06:00 committed by GitHub
parent 39a9e0945d
commit d5d92e707d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 161 additions and 51 deletions

View File

@ -4,17 +4,25 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
var backupApplications = []func(parent *cobra.Command) *cobra.Command{
addExchangeApp,
}
// AddCommands attaches all `corso backup * *` commands to the parent. // AddCommands attaches all `corso backup * *` commands to the parent.
func AddCommands(parent *cobra.Command) { func AddCommands(parent *cobra.Command) {
parent.AddCommand(backupCmd) parent.AddCommand(backupCmd)
backupCmd.AddCommand(createCmd)
for _, addBackupTo := range backupApplications {
addBackupTo(createCmd)
}
} }
// The backup category of commands. // The backup category of commands.
// `corso backup [<subcommand>] [<flag>...]` // `corso backup [<subcommand>] [<flag>...]`
var backupCmd = &cobra.Command{ var backupCmd = &cobra.Command{
Use: "backup", Use: "backup",
Short: "Backup your application data.", Short: "Backup a M365 service",
Long: `Backup the data stored in one of your M365 applications.`,
Run: handleBackupCmd, Run: handleBackupCmd,
Args: cobra.NoArgs, Args: cobra.NoArgs,
} }
@ -24,3 +32,19 @@ var backupCmd = &cobra.Command{
func handleBackupCmd(cmd *cobra.Command, args []string) { func handleBackupCmd(cmd *cobra.Command, args []string) {
cmd.Help() cmd.Help()
} }
// The backup create subcommand.
// `corso backup create <service> [<flag>...]`
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()
}

View File

@ -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 [<flag>...]`
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)
}

View File

@ -1,8 +1,6 @@
package repo package repo
import ( import (
"os"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -69,19 +67,3 @@ var connectCmd = &cobra.Command{
func handleConnectCmd(cmd *cobra.Command, args []string) { func handleConnectCmd(cmd *cobra.Command, args []string) {
cmd.Help() 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",
}
}

View File

@ -1,7 +1,6 @@
package repo package repo
import ( import (
"context"
"fmt" "fmt"
"os" "os"
@ -50,7 +49,7 @@ var s3InitCmd = &cobra.Command{
// initializes a s3 repo. // initializes a s3 repo.
func initS3Cmd(cmd *cobra.Command, args []string) { func initS3Cmd(cmd *cobra.Command, args []string) {
mv := getM365Vars() mv := utils.GetM365Vars()
s3Cfg, commonCfg, err := makeS3Config() s3Cfg, commonCfg, err := makeS3Config()
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
@ -62,27 +61,25 @@ func initS3Cmd(cmd *cobra.Command, args []string) {
cmd.CommandPath(), cmd.CommandPath(),
s3Cfg.Bucket, s3Cfg.Bucket,
s3Cfg.AccessKey, s3Cfg.AccessKey,
mv.clientID, mv.ClientID,
len(mv.clientSecret) > 0, len(mv.ClientSecret) > 0,
len(s3Cfg.SecretKey) > 0) len(s3Cfg.SecretKey) > 0)
a := repository.Account{ a := repository.Account{
TenantID: mv.tenantID, TenantID: mv.TenantID,
ClientID: mv.clientID, ClientID: mv.ClientID,
ClientSecret: mv.clientSecret, ClientSecret: mv.ClientSecret,
} }
s, err := storage.NewStorage(storage.ProviderS3, s3Cfg, commonCfg) s, err := storage.NewStorage(storage.ProviderS3, s3Cfg, commonCfg)
if err != nil { if err != nil {
fmt.Printf("Failed to configure storage provider: %v", err) utils.Fatalf("Failed to configure storage provider: %v", err)
os.Exit(1)
} }
r, err := repository.Connect(cmd.Context(), a, s) r, err := repository.Initialize(cmd.Context(), a, s)
if err != nil { if err != nil {
fmt.Printf("Failed to initialize a new S3 repository: %v", err) utils.Fatalf("Failed to initialize a new S3 repository: %v", err)
os.Exit(1)
} }
defer closeRepo(cmd.Context(), r) defer utils.CloseRepo(cmd.Context(), r)
fmt.Printf("Initialized a S3 repository within bucket %s.\n", s3Cfg.Bucket) 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. // connects to an existing s3 repo.
func connectS3Cmd(cmd *cobra.Command, args []string) { func connectS3Cmd(cmd *cobra.Command, args []string) {
mv := getM365Vars() mv := utils.GetM365Vars()
s3Cfg, commonCfg, err := makeS3Config() s3Cfg, commonCfg, err := makeS3Config()
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
@ -110,27 +107,25 @@ func connectS3Cmd(cmd *cobra.Command, args []string) {
cmd.CommandPath(), cmd.CommandPath(),
s3Cfg.Bucket, s3Cfg.Bucket,
s3Cfg.AccessKey, s3Cfg.AccessKey,
mv.clientID, mv.ClientID,
len(mv.clientSecret) > 0, len(mv.ClientSecret) > 0,
len(s3Cfg.SecretKey) > 0) len(s3Cfg.SecretKey) > 0)
a := repository.Account{ a := repository.Account{
TenantID: mv.tenantID, TenantID: mv.TenantID,
ClientID: mv.clientID, ClientID: mv.ClientID,
ClientSecret: mv.clientSecret, ClientSecret: mv.ClientSecret,
} }
s, err := storage.NewStorage(storage.ProviderS3, s3Cfg, commonCfg) s, err := storage.NewStorage(storage.ProviderS3, s3Cfg, commonCfg)
if err != nil { if err != nil {
fmt.Printf("Failed to configure storage provider: %v", err) utils.Fatalf("Failed to configure storage provider: %v", err)
os.Exit(1)
} }
r, err := repository.Connect(cmd.Context(), a, s) r, err := repository.Connect(cmd.Context(), a, s)
if err != nil { if err != nil {
fmt.Printf("Failed to connect to the S3 repository: %v", err) utils.Fatalf("Failed to connect to the S3 repository: %v", err)
os.Exit(1)
} }
defer closeRepo(cmd.Context(), r) defer utils.CloseRepo(cmd.Context(), r)
fmt.Printf("Connected to S3 bucket %s.\n", s3Cfg.Bucket) 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, 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)
}
}

View File

@ -1,6 +1,13 @@
package utils package utils
import "errors" import (
"context"
"errors"
"fmt"
"os"
"github.com/alcionai/corso/pkg/repository"
)
// RequireProps validates the existence of the properties // RequireProps validates the existence of the properties
// in the map. Expects the format map[propName]propVal. // in the map. Expects the format map[propName]propVal.
@ -12,3 +19,34 @@ func RequireProps(props map[string]string) error {
} }
return nil 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)
}