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"
)
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 [<subcommand>] [<flag>...]`
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 <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
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",
}
}

View File

@ -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)
}
}

View File

@ -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)
}