diff --git a/src/cli/backup/backup.go b/src/cli/backup/backup.go index 5ce2a8f64..1c43058a3 100644 --- a/src/cli/backup/backup.go +++ b/src/cli/backup/backup.go @@ -18,6 +18,7 @@ var serviceCommands = []func(parent *cobra.Command) *cobra.Command{ // AddCommands attaches all `corso backup * *` commands to the parent. func AddCommands(parent *cobra.Command) { parent.AddCommand(backupCmd) + for _, sc := range subCommands { backupCmd.AddCommand(sc) } diff --git a/src/cli/backup/exchange.go b/src/cli/backup/exchange.go index 905790439..0c1d45ffe 100644 --- a/src/cli/backup/exchange.go +++ b/src/cli/backup/exchange.go @@ -53,7 +53,6 @@ func addExchangeCommands(parent *cobra.Command) *cobra.Command { ) switch parent.Use { - case createCommand: c, fs = utils.AddCommand(parent, exchangeCreateCmd()) fs.StringSliceVar( @@ -191,6 +190,7 @@ func createExchangeCmd(cmd *cobra.Command, args []string) error { if err != nil { return Only(ctx, errors.Wrapf(err, "Failed to connect to the %s repository", s.Provider)) } + defer utils.CloseRepo(ctx, r) sel := exchangeBackupCreateSelectors(exchangeAll, user, exchangeData) @@ -211,6 +211,7 @@ func createExchangeCmd(cmd *cobra.Command, args []string) error { } bu.Print(ctx) + return nil } @@ -220,11 +221,13 @@ func exchangeBackupCreateSelectors(all bool, users, data []string) selectors.Sel sel.Include(sel.Users(selectors.Any())) return sel.Selector } + if len(data) == 0 { sel.Include(sel.ContactFolders(user, selectors.Any())) sel.Include(sel.MailFolders(user, selectors.Any())) sel.Include(sel.Events(user, selectors.Any())) } + for _, d := range data { switch d { case dataContacts: @@ -235,6 +238,7 @@ func exchangeBackupCreateSelectors(all bool, users, data []string) selectors.Sel sel.Include(sel.Events(users, selectors.Any())) } } + return sel.Selector } @@ -242,15 +246,18 @@ func validateExchangeBackupCreateFlags(all bool, users, data []string) error { if len(users) == 0 && !all { return errors.New("requires one or more --user ids, the wildcard --user *, or the --all flag") } + if len(data) > 0 && all { return errors.New("--all does a backup on all data, and cannot be reduced with --data") } + for _, d := range data { if d != dataContacts && d != dataEmail && d != dataEvents { return errors.New( d + " is an unrecognized data type; must be one of " + dataContacts + ", " + dataEmail + ", or " + dataEvents) } } + return nil } @@ -290,6 +297,7 @@ func listExchangeCmd(cmd *cobra.Command, args []string) error { if err != nil { return Only(ctx, errors.Wrapf(err, "Failed to connect to the %s repository", s.Provider)) } + defer utils.CloseRepo(ctx, r) bs, err := r.Backups(ctx) @@ -298,6 +306,7 @@ func listExchangeCmd(cmd *cobra.Command, args []string) error { } backup.PrintAll(ctx, bs) + return nil } @@ -353,6 +362,7 @@ func detailsExchangeCmd(cmd *cobra.Command, args []string) error { if err != nil { return Only(ctx, errors.Wrapf(err, "Failed to connect to the %s repository", s.Provider)) } + defer utils.CloseRepo(ctx, r) d, _, err := r.BackupDetails(ctx, backupID) @@ -387,6 +397,7 @@ func detailsExchangeCmd(cmd *cobra.Command, args []string) error { } ds.PrintEntries(ctx) + return nil } @@ -420,6 +431,7 @@ func includeExchangeContacts(sel *selectors.ExchangeRestore, users, contactFolde if len(contactFolders) == 0 { return } + if len(contacts) > 0 { sel.Include(sel.Contacts(users, contactFolders, contacts)) } else { @@ -431,6 +443,7 @@ func includeExchangeEmails(sel *selectors.ExchangeRestore, users, emailFolders, if len(emailFolders) == 0 { return } + if len(emails) > 0 { sel.Include(sel.Mails(users, emailFolders, emails)) } else { @@ -442,6 +455,7 @@ func includeExchangeEvents(sel *selectors.ExchangeRestore, users, events []strin if len(events) == 0 { return } + sel.Include(sel.Events(users, events)) } @@ -460,6 +474,7 @@ func filterExchangeInfoMailReceivedAfter(sel *selectors.ExchangeRestore, receive if len(receivedAfter) == 0 { return } + sel.Filter(sel.MailReceivedAfter(receivedAfter)) } @@ -467,6 +482,7 @@ func filterExchangeInfoMailReceivedBefore(sel *selectors.ExchangeRestore, receiv if len(receivedBefore) == 0 { return } + sel.Filter(sel.MailReceivedBefore(receivedBefore)) } @@ -474,6 +490,7 @@ func filterExchangeInfoMailSender(sel *selectors.ExchangeRestore, sender string) if len(sender) == 0 { return } + sel.Filter(sel.MailSender([]string{sender})) } @@ -481,6 +498,7 @@ func filterExchangeInfoMailSubject(sel *selectors.ExchangeRestore, subject strin if len(subject) == 0 { return } + sel.Filter(sel.MailSubject([]string{subject})) } @@ -492,24 +510,30 @@ func validateExchangeBackupDetailFlags( if len(backupID) == 0 { return errors.New("a backup ID is required") } + lu := len(users) lc, lcf := len(contacts), len(contactFolders) le, lef := len(emails), len(emailFolders) lev := len(events) + if lu+lc+lcf+le+lef+lev == 0 { return nil } + if lu == 0 { return errors.New("requires one or more --user ids, the wildcard --user *, or the --all flag") } + if lc > 0 && lcf == 0 { return errors.New( "one or more --contact-folder ids or the wildcard --contact-folder * must be included to specify a --contact") } + if le > 0 && lef == 0 { return errors.New( "one or more --email-folder ids or the wildcard --email-folder * must be included to specify a --email") } + return nil } @@ -555,6 +579,7 @@ func deleteExchangeCmd(cmd *cobra.Command, args []string) error { if err != nil { return Only(ctx, errors.Wrapf(err, "Failed to connect to the %s repository", s.Provider)) } + defer utils.CloseRepo(ctx, r) if err := r.DeleteBackup(ctx, model.StableID(backupID)); err != nil { diff --git a/src/cli/backup/exchange_integration_test.go b/src/cli/backup/exchange_integration_test.go index d4e60958d..ccda3c3cc 100644 --- a/src/cli/backup/exchange_integration_test.go +++ b/src/cli/backup/exchange_integration_test.go @@ -46,6 +46,7 @@ func TestBackupExchangeIntegrationSuite(t *testing.T) { ); err != nil { t.Skip(err) } + suite.Run(t, new(BackupExchangeIntegrationSuite)) } @@ -70,8 +71,8 @@ func (suite *BackupExchangeIntegrationSuite) SetupSuite() { } suite.vpr, suite.cfgFP, err = tester.MakeTempTestConfigClone(t, force) require.NoError(t, err) - ctx := config.SetViper(tester.NewContext(), suite.vpr) + ctx := config.SetViper(tester.NewContext(), suite.vpr) suite.m365UserID = tester.M365UserID(t) // init the repo first @@ -89,8 +90,10 @@ func (suite *BackupExchangeIntegrationSuite) TestExchangeBackupCmd() { "--user", suite.m365UserID, "--data", "email") cli.BuildCommandTree(cmd) - var recorder strings.Builder + + recorder := strings.Builder{} cmd.SetOut(&recorder) + ctx = print.SetRootCmd(ctx, cmd) // run the command @@ -126,6 +129,7 @@ func TestPreparedBackupExchangeIntegrationSuite(t *testing.T) { ); err != nil { t.Skip(err) } + suite.Run(t, new(PreparedBackupExchangeIntegrationSuite)) } @@ -150,8 +154,8 @@ func (suite *PreparedBackupExchangeIntegrationSuite) SetupSuite() { } suite.vpr, suite.cfgFP, err = tester.MakeTempTestConfigClone(t, force) require.NoError(t, err) - ctx := config.SetViper(tester.NewContext(), suite.vpr) + ctx := config.SetViper(tester.NewContext(), suite.vpr) suite.m365UserID = tester.M365UserID(t) // init the repo first @@ -178,8 +182,10 @@ func (suite *PreparedBackupExchangeIntegrationSuite) TestExchangeListCmd() { "backup", "list", "exchange", "--config-file", suite.cfgFP) cli.BuildCommandTree(cmd) - var recorder strings.Builder + + recorder := strings.Builder{} cmd.SetOut(&recorder) + ctx = print.SetRootCmd(ctx, cmd) // run the command @@ -203,8 +209,10 @@ func (suite *PreparedBackupExchangeIntegrationSuite) TestExchangeDetailsCmd() { "--config-file", suite.cfgFP, "--backup", string(suite.backupOp.Results.BackupID)) cli.BuildCommandTree(cmd) - var recorder strings.Builder + + recorder := strings.Builder{} cmd.SetOut(&recorder) + ctx = print.SetRootCmd(ctx, cmd) // run the command @@ -212,6 +220,7 @@ func (suite *PreparedBackupExchangeIntegrationSuite) TestExchangeDetailsCmd() { // compare the output result := recorder.String() + for i, ent := range deets.Entries { t.Run(fmt.Sprintf("detail %d", i), func(t *testing.T) { assert.Contains(t, result, ent.RepoRef) @@ -241,6 +250,7 @@ func TestBackupDeleteExchangeIntegrationSuite(t *testing.T) { ); err != nil { t.Skip(err) } + suite.Run(t, new(BackupDeleteExchangeIntegrationSuite)) } @@ -265,6 +275,7 @@ func (suite *BackupDeleteExchangeIntegrationSuite) SetupSuite() { } suite.vpr, suite.cfgFP, err = tester.MakeTempTestConfigClone(t, force) require.NoError(t, err) + ctx := config.SetViper(tester.NewContext(), suite.vpr) // init the repo first diff --git a/src/cli/backup/exchange_test.go b/src/cli/backup/exchange_test.go index e5bea46d3..6e1000da4 100644 --- a/src/cli/backup/exchange_test.go +++ b/src/cli/backup/exchange_test.go @@ -23,6 +23,7 @@ func TestExchangeSuite(t *testing.T) { func (suite *ExchangeSuite) TestAddExchangeCommands() { expectUse := exchangeServiceCommand + table := []struct { name string use string @@ -217,6 +218,7 @@ func (suite *ExchangeSuite) TestExchangeBackupCreateSelectors() { func (suite *ExchangeSuite) TestValidateBackupDetailFlags() { stub := []string{"id-stub"} + table := []struct { name string contacts, contactFolders, emails, emailFolders, events, users []string @@ -302,6 +304,7 @@ func (suite *ExchangeSuite) TestValidateBackupDetailFlags() { func (suite *ExchangeSuite) TestIncludeExchangeBackupDetailDataSelectors() { stub := []string{"id-stub"} a := []string{utils.Wildcard} + table := []struct { name string contacts, contactFolders, emails, emailFolders, events, users []string @@ -484,6 +487,7 @@ func (suite *ExchangeSuite) TestIncludeExchangeBackupDetailDataSelectors() { func (suite *ExchangeSuite) TestFilterExchangeBackupDetailInfoSelectors() { stub := "id-stub" a := utils.Wildcard + table := []struct { name string after, before, sender, subject string diff --git a/src/cli/cli.go b/src/cli/cli.go index 2839b9deb..45537a7ae 100644 --- a/src/cli/cli.go +++ b/src/cli/cli.go @@ -41,6 +41,7 @@ func handleCorsoCmd(cmd *cobra.Command, args []string) error { print.Infof(cmd.Context(), "Corso\nversion:\tpre-alpha\n") return nil } + return cmd.Help() } @@ -50,6 +51,7 @@ func CorsoCommand() *cobra.Command { c := &cobra.Command{} *c = *corsoCmd BuildCommandTree(c) + return c } diff --git a/src/cli/config/account.go b/src/cli/config/account.go index e604a869b..8e746842c 100644 --- a/src/cli/config/account.go +++ b/src/cli/config/account.go @@ -22,6 +22,7 @@ func m365ConfigsFromViper(vpr *viper.Viper) (account.M365Config, error) { } m365.TenantID = vpr.GetString(TenantIDKey) + return m365, nil } diff --git a/src/cli/config/config.go b/src/cli/config/config.go index 19cd3ef28..424699855 100644 --- a/src/cli/config/config.go +++ b/src/cli/config/config.go @@ -34,10 +34,12 @@ var configFilePath string // adds the persistent flag --config-file to the provided command. func AddConfigFlags(cmd *cobra.Command) { fs := cmd.PersistentFlags() + homeDir, err := os.UserHomeDir() if err != nil { Err(cmd.Context(), "finding $HOME directory (default) for config file") } + fs.StringVar( &configFilePath, "config-file", @@ -57,6 +59,7 @@ func InitFunc() func(*cobra.Command, []string) error { if err != nil { return err } + return Read(cmd.Context()) } } @@ -76,6 +79,7 @@ func initWithViper(vpr *viper.Viper, configFP string) error { vpr.AddConfigPath(home) vpr.SetConfigType("toml") vpr.SetConfigName(".corso") + return nil } @@ -85,11 +89,12 @@ func initWithViper(vpr *viper.Viper, configFP string) error { // work correctly (it does not use the configured file) vpr.AddConfigPath(path.Dir(configFP)) - fileName := path.Base(configFP) ext := path.Ext(configFP) if len(ext) == 0 { return errors.New("config file requires an extension e.g. `toml`") } + + fileName := path.Base(configFP) fileName = strings.TrimSuffix(fileName, ext) vpr.SetConfigType(strings.TrimPrefix(ext, ".")) vpr.SetConfigName(fileName) @@ -110,6 +115,7 @@ func SetViper(ctx context.Context, vpr *viper.Viper) context.Context { if vpr == nil { vpr = viper.GetViper() } + return context.WithValue(ctx, viperCtx{}, vpr) } @@ -118,10 +124,12 @@ func SetViper(ctx context.Context, vpr *viper.Viper) context.Context { // (global) viper instance. func GetViper(ctx context.Context) *viper.Viper { vprIface := ctx.Value(viperCtx{}) + vpr, ok := vprIface.(*viper.Viper) if vpr == nil || !ok { return viper.GetViper() } + return vpr } @@ -137,6 +145,7 @@ func Read(ctx context.Context) error { logger.Ctx(ctx).Debugw("found config file", "configFile", viper.ConfigFileUsed()) return err } + return nil } @@ -163,8 +172,10 @@ func writeRepoConfigWithViper(vpr *viper.Viper, s3Config storage.S3Config, m365C if _, ok := err.(viper.ConfigFileAlreadyExistsError); ok { return vpr.WriteConfig() } + return err } + return nil } @@ -200,6 +211,7 @@ func getStorageAndAccountWithViper( if _, ok := err.(viper.ConfigFileNotFoundError); !ok { return store, acct, errors.Wrap(err, "reading corso config file: "+vpr.ConfigFileUsed()) } + readConfigFromViper = false } } @@ -238,14 +250,17 @@ func mustMatchConfig(vpr *viper.Viper, m map[string]string) error { if len(v) == 0 { continue // empty variables will get caught by configuration validators, if necessary } + tomlK, ok := constToTomlKeyMap[k] if !ok { continue // m may declare values which aren't stored in the config file } + vv := vpr.GetString(tomlK) if v != vv { return errors.New("value of " + k + " (" + v + ") does not match corso configuration value (" + vv + ")") } } + return nil } diff --git a/src/cli/config/config_test.go b/src/cli/config/config_test.go index 7103ea426..4e7eb637f 100644 --- a/src/cli/config/config_test.go +++ b/src/cli/config/config_test.go @@ -190,6 +190,7 @@ func TestConfigIntegrationSuite(t *testing.T) { ); err != nil { t.Skip(err) } + suite.Run(t, new(ConfigIntegrationSuite)) } diff --git a/src/cli/config/storage.go b/src/cli/config/storage.go index 6025236c8..3504dd647 100644 --- a/src/cli/config/storage.go +++ b/src/cli/config/storage.go @@ -25,6 +25,7 @@ func s3ConfigsFromViper(vpr *viper.Viper) (storage.S3Config, error) { s3Config.Bucket = vpr.GetString(BucketNameKey) s3Config.Endpoint = vpr.GetString(EndpointKey) s3Config.Prefix = vpr.GetString(PrefixKey) + return s3Config, nil } @@ -78,6 +79,7 @@ func configureStorage( if err := corso.Validate(); err != nil { return store, errors.Wrap(err, "validating corso credentials") } + cCfg := storage.CommonConfig{ Corso: corso, } diff --git a/src/cli/help/env.go b/src/cli/help/env.go index fe23305ca..6a8783cf2 100644 --- a/src/cli/help/env.go +++ b/src/cli/help/env.go @@ -22,6 +22,7 @@ func envCmd() *cobra.Command { Args: cobra.NoArgs, } envCmd.SetHelpFunc(envGuide) + return envCmd } @@ -81,6 +82,7 @@ func toPrintable(evs []envVar) []Printable { for _, ev := range evs { ps = append(ps, ev) } + return ps } diff --git a/src/cli/help/env_test.go b/src/cli/help/env_test.go index 8b54c58fe..8b1c04fb4 100644 --- a/src/cli/help/env_test.go +++ b/src/cli/help/env_test.go @@ -24,6 +24,7 @@ func (suite *EnvSuite) TestAddEnvCommands() { cmd := &cobra.Command{Use: "root"} AddCommands(cmd) + c := envCmd() require.NotNil(t, c) diff --git a/src/cli/print/print.go b/src/cli/print/print.go index a23fb8d35..d50315170 100644 --- a/src/cli/print/print.go +++ b/src/cli/print/print.go @@ -30,9 +30,11 @@ func SetRootCmd(ctx context.Context, root *cobra.Command) context.Context { func getRootCmd(ctx context.Context) *cobra.Command { cmdIface := ctx.Value(rootCmdCtx{}) cmd, ok := cmdIface.(*cobra.Command) + if cmd == nil || !ok { return &cobra.Command{} } + return cmd } @@ -67,6 +69,7 @@ func err(w io.Writer, s ...any) { if len(s) == 0 { return } + msg := append([]any{"Error: "}, s...) fmt.Fprint(w, msg...) } @@ -82,6 +85,7 @@ func info(w io.Writer, s ...any) { if len(s) == 0 { return } + fmt.Fprint(w, s...) fmt.Fprintf(w, "\n") } @@ -97,6 +101,7 @@ func infof(w io.Writer, t string, s ...any) { if len(t) == 0 { return } + fmt.Fprintf(w, t, s...) fmt.Fprintf(w, "\n") } @@ -129,6 +134,7 @@ func print(w io.Writer, p Printable) { outputJSON(w, p, outputAsJSONDebug) return } + outputTable(w, []Printable{p}) } @@ -144,10 +150,12 @@ func printAll(w io.Writer, ps []Printable) { if len(ps) == 0 { return } + if outputAsJSON || outputAsJSONDebug { outputJSONArr(w, ps, outputAsJSONDebug) return } + outputTable(w, ps) } @@ -167,9 +175,11 @@ func outputTable(w io.Writer, ps []Printable) { Headers: ps[0].Headers(), Rows: [][]string{}, } + for _, p := range ps { t.Rows = append(t.Rows, p.Values()) } + _ = t.WriteTable( w, &table.Config{ @@ -188,11 +198,13 @@ func outputJSON(w io.Writer, p Printable, debug bool) { printJSON(w, p) return } + printJSON(w, p.MinimumPrintable()) } func outputJSONArr(w io.Writer, ps []Printable, debug bool) { sl := make([]any, 0, len(ps)) + for _, p := range ps { if debug { sl = append(sl, p) @@ -200,6 +212,7 @@ func outputJSONArr(w io.Writer, ps []Printable, debug bool) { sl = append(sl, p.MinimumPrintable()) } } + printJSON(w, sl) } @@ -210,5 +223,6 @@ func printJSON(w io.Writer, a any) { fmt.Fprintf(w, "error formatting results to json: %v\n", err) return } + fmt.Fprintln(w, string(pretty.Pretty(bs))) } diff --git a/src/cli/print/print_test.go b/src/cli/print/print_test.go index 1416eeedd..259bf8e3c 100644 --- a/src/cli/print/print_test.go +++ b/src/cli/print/print_test.go @@ -30,8 +30,9 @@ func (suite *PrintUnitSuite) TestOnly() { func (suite *PrintUnitSuite) TestErr() { t := suite.T() - var b bytes.Buffer + b := bytes.Buffer{} msg := "I have seen the fnords!" + err(&b, msg) assert.Contains(t, b.String(), "Error: ") assert.Contains(t, b.String(), msg) @@ -39,17 +40,19 @@ func (suite *PrintUnitSuite) TestErr() { func (suite *PrintUnitSuite) TestInfo() { t := suite.T() - var b bytes.Buffer + b := bytes.Buffer{} msg := "I have seen the fnords!" + info(&b, msg) assert.Contains(t, b.String(), msg) } func (suite *PrintUnitSuite) TestInfof() { t := suite.T() - var b bytes.Buffer + b := bytes.Buffer{} msg := "I have seen the fnords!" msg2 := "smarf" + infof(&b, msg, msg2) bs := b.String() assert.Contains(t, bs, msg) diff --git a/src/cli/repo/s3.go b/src/cli/repo/s3.go index 709f5309b..25bb3208f 100644 --- a/src/cli/repo/s3.go +++ b/src/cli/repo/s3.go @@ -31,6 +31,7 @@ func addS3Commands(parent *cobra.Command) *cobra.Command { c *cobra.Command fs *pflag.FlagSet ) + switch parent.Use { case initCommand: c, fs = utils.AddCommand(parent, s3InitCmd()) @@ -47,6 +48,7 @@ func addS3Commands(parent *cobra.Command) *cobra.Command { // In general, we don't want to expose this flag to users and have them mistake it // for a broad-scale idempotency solution. We can un-hide it later the need arises. cobra.CheckErr(fs.MarkHidden("succeed-if-exists")) + return c } @@ -80,6 +82,7 @@ func initS3Cmd(cmd *cobra.Command, args []string) error { if err != nil { return Only(ctx, err) } + s3Cfg, err := s.S3Config() if err != nil { return Only(ctx, errors.Wrap(err, "Retrieving s3 configuration")) @@ -103,8 +106,10 @@ func initS3Cmd(cmd *cobra.Command, args []string) error { if succeedIfExists && kopia.IsRepoAlreadyExistsError(err) { return nil } + return Only(ctx, errors.Wrap(err, "Failed to initialize a new S3 repository")) } + defer utils.CloseRepo(ctx, r) Infof(ctx, "Initialized a S3 repository within bucket %s.", s3Cfg.Bucket) @@ -112,6 +117,7 @@ func initS3Cmd(cmd *cobra.Command, args []string) error { if err = config.WriteRepoConfig(ctx, s3Cfg, m365); err != nil { return Only(ctx, errors.Wrap(err, "Failed to write repository configuration")) } + return nil } @@ -143,10 +149,12 @@ func connectS3Cmd(cmd *cobra.Command, args []string) error { if err != nil { return Only(ctx, err) } + s3Cfg, err := s.S3Config() if err != nil { return Only(ctx, errors.Wrap(err, "Retrieving s3 configuration")) } + m365, err := a.M365Config() if err != nil { return Only(ctx, errors.Wrap(err, "Failed to parse m365 account config")) @@ -164,6 +172,7 @@ func connectS3Cmd(cmd *cobra.Command, args []string) error { if err != nil { return Only(ctx, errors.Wrap(err, "Failed to connect to the S3 repository")) } + defer utils.CloseRepo(ctx, r) Infof(ctx, "Connected to S3 bucket %s.", s3Cfg.Bucket) @@ -171,6 +180,7 @@ func connectS3Cmd(cmd *cobra.Command, args []string) error { if err = config.WriteRepoConfig(ctx, s3Cfg, m365); err != nil { return Only(ctx, errors.Wrap(err, "Failed to write repository configuration")) } + return nil } diff --git a/src/cli/repo/s3_integration_test.go b/src/cli/repo/s3_integration_test.go index 9e9dffcf2..6a5b8bb77 100644 --- a/src/cli/repo/s3_integration_test.go +++ b/src/cli/repo/s3_integration_test.go @@ -25,6 +25,7 @@ func TestS3IntegrationSuite(t *testing.T) { ); err != nil { t.Skip(err) } + suite.Run(t, new(S3IntegrationSuite)) } @@ -45,6 +46,7 @@ func (suite *S3IntegrationSuite) TestInitS3Cmd() { vpr, configFP, err := tester.MakeTempTestConfigClone(t, nil) require.NoError(t, err) + ctx = config.SetViper(ctx, vpr) cmd := tester.StubRootCmd( @@ -68,6 +70,7 @@ func (suite *S3IntegrationSuite) TestInitS3Cmd_missingBucket() { vpr, configFP, err := tester.MakeTempTestConfigClone(t, nil) require.NoError(t, err) + ctx = config.SetViper(ctx, vpr) cmd := tester.StubRootCmd( @@ -95,6 +98,7 @@ func (suite *S3IntegrationSuite) TestConnectS3Cmd() { } vpr, configFP, err := tester.MakeTempTestConfigClone(t, force) require.NoError(t, err) + ctx = config.SetViper(ctx, vpr) // init the repo first @@ -123,6 +127,7 @@ func (suite *S3IntegrationSuite) TestConnectS3Cmd_BadBucket() { vpr, configFP, err := tester.MakeTempTestConfigClone(t, nil) require.NoError(t, err) + ctx = config.SetViper(ctx, vpr) cmd := tester.StubRootCmd( @@ -146,6 +151,7 @@ func (suite *S3IntegrationSuite) TestConnectS3Cmd_BadPrefix() { vpr, configFP, err := tester.MakeTempTestConfigClone(t, nil) require.NoError(t, err) + ctx = config.SetViper(ctx, vpr) cmd := tester.StubRootCmd( diff --git a/src/cli/repo/s3_test.go b/src/cli/repo/s3_test.go index 7974b0f57..0cef2094e 100644 --- a/src/cli/repo/s3_test.go +++ b/src/cli/repo/s3_test.go @@ -21,6 +21,7 @@ func TestS3Suite(t *testing.T) { func (suite *S3Suite) TestAddS3Commands() { expectUse := s3ProviderCommand + table := []struct { name string use string diff --git a/src/cli/restore/exchange.go b/src/cli/restore/exchange.go index 3343d27f4..3e8dc2edf 100644 --- a/src/cli/restore/exchange.go +++ b/src/cli/restore/exchange.go @@ -88,6 +88,7 @@ func addExchangeCommands(parent *cobra.Command) *cobra.Command { // others options.AddOperationFlags(c) } + return c } @@ -144,6 +145,7 @@ func restoreExchangeCmd(cmd *cobra.Command, args []string) error { if err != nil { return Only(ctx, errors.Wrapf(err, "Failed to connect to the %s repository", s.Provider)) } + defer utils.CloseRepo(ctx, r) sel := selectors.NewExchangeRestore() @@ -177,6 +179,7 @@ func restoreExchangeCmd(cmd *cobra.Command, args []string) error { } Infof(ctx, "Restored Exchange in %s for user %s.\n", s.Provider, user) + return nil } @@ -210,6 +213,7 @@ func includeExchangeContacts(sel *selectors.ExchangeRestore, users, contactFolde if len(contactFolders) == 0 { return } + if len(contacts) > 0 { sel.Include(sel.Contacts(users, contactFolders, contacts)) } else { @@ -221,6 +225,7 @@ func includeExchangeEmails(sel *selectors.ExchangeRestore, users, emailFolders, if len(emailFolders) == 0 { return } + if len(emails) > 0 { sel.Include(sel.Mails(users, emailFolders, emails)) } else { @@ -232,6 +237,7 @@ func includeExchangeEvents(sel *selectors.ExchangeRestore, users, events []strin if len(events) == 0 { return } + sel.Include(sel.Events(users, events)) } @@ -250,6 +256,7 @@ func filterExchangeInfoMailReceivedAfter(sel *selectors.ExchangeRestore, receive if len(receivedAfter) == 0 { return } + sel.Filter(sel.MailReceivedAfter(receivedAfter)) } @@ -257,6 +264,7 @@ func filterExchangeInfoMailReceivedBefore(sel *selectors.ExchangeRestore, receiv if len(receivedBefore) == 0 { return } + sel.Filter(sel.MailReceivedBefore(receivedBefore)) } @@ -264,6 +272,7 @@ func filterExchangeInfoMailSender(sel *selectors.ExchangeRestore, sender string) if len(sender) == 0 { return } + sel.Filter(sel.MailSender([]string{sender})) } @@ -271,6 +280,7 @@ func filterExchangeInfoMailSubject(sel *selectors.ExchangeRestore, subject strin if len(subject) == 0 { return } + sel.Filter(sel.MailSubject([]string{subject})) } @@ -282,24 +292,30 @@ func validateExchangeRestoreFlags( if len(backupID) == 0 { return errors.New("a backup ID is required") } + lu := len(users) lc, lcf := len(contacts), len(contactFolders) le, lef := len(emails), len(emailFolders) lev := len(events) + // if only the backupID is populated, that's the same as --all if lu+lc+lcf+le+lef+lev == 0 { return nil } + if lu == 0 { return errors.New("requires one or more --user ids, the wildcard --user *, or the --all flag") } + if lc > 0 && lcf == 0 { return errors.New( "one or more --contact-folder ids or the wildcard --contact-folder * must be included to specify a --contact") } + if le > 0 && lef == 0 { return errors.New( "one or more --email-folder ids or the wildcard --email-folder * must be included to specify a --email") } + return nil } diff --git a/src/cli/restore/exchange_integration_test.go b/src/cli/restore/exchange_integration_test.go index eefc776d4..6d868f6e6 100644 --- a/src/cli/restore/exchange_integration_test.go +++ b/src/cli/restore/exchange_integration_test.go @@ -37,6 +37,7 @@ func TestRestoreExchangeIntegrationSuite(t *testing.T) { ); err != nil { t.Skip(err) } + suite.Run(t, new(RestoreExchangeIntegrationSuite)) } @@ -62,8 +63,8 @@ func (suite *RestoreExchangeIntegrationSuite) SetupSuite() { } suite.vpr, suite.cfgFP, err = tester.MakeTempTestConfigClone(t, force) require.NoError(t, err) - ctx := config.SetViper(tester.NewContext(), suite.vpr) + ctx := config.SetViper(tester.NewContext(), suite.vpr) suite.m365UserID = tester.M365UserID(t) // init the repo first diff --git a/src/cli/restore/exchange_test.go b/src/cli/restore/exchange_test.go index cf82e6b73..229005113 100644 --- a/src/cli/restore/exchange_test.go +++ b/src/cli/restore/exchange_test.go @@ -23,6 +23,7 @@ func TestExchangeSuite(t *testing.T) { func (suite *ExchangeSuite) TestAddExchangeCommands() { expectUse := exchangeServiceCommand + table := []struct { name string use string @@ -52,6 +53,7 @@ func (suite *ExchangeSuite) TestAddExchangeCommands() { func (suite *ExchangeSuite) TestValidateExchangeRestoreFlags() { stub := []string{"id-stub"} + table := []struct { name string contacts, contactFolders, emails, emailFolders, events, users []string @@ -137,6 +139,7 @@ func (suite *ExchangeSuite) TestValidateExchangeRestoreFlags() { func (suite *ExchangeSuite) TestIncludeExchangeRestoreDataSelectors() { stub := []string{"id-stub"} a := []string{utils.Wildcard} + table := []struct { name string contacts, contactFolders, emails, emailFolders, events, users []string @@ -319,6 +322,7 @@ func (suite *ExchangeSuite) TestIncludeExchangeRestoreDataSelectors() { func (suite *ExchangeSuite) TestFilterExchangeRestoreInfoSelectors() { stub := "id-stub" a := utils.Wildcard + table := []struct { name string after, before, sender, subject string diff --git a/src/cli/utils/utils.go b/src/cli/utils/utils.go index f575492a0..bcd68c094 100644 --- a/src/cli/utils/utils.go +++ b/src/cli/utils/utils.go @@ -23,6 +23,7 @@ func RequireProps(props map[string]string) error { return errors.New(name + " is required to perform this command") } } + return nil } @@ -43,6 +44,7 @@ func HasNoFlagsAndShownHelp(cmd *cobra.Command) bool { cobra.CheckErr(cmd.Help()) return true } + return false } diff --git a/src/cmd/mdgen/mdgen.go b/src/cmd/mdgen/mdgen.go index 7159d3369..ce1e48f80 100644 --- a/src/cmd/mdgen/mdgen.go +++ b/src/cmd/mdgen/mdgen.go @@ -34,6 +34,7 @@ func main() { "cli-folder", "./cmd/mdgen/cli_markdown", "relative path to the folder where cli docs will be generated (default: ./cmd/mdgen/cli_markdown)") + if err := cmd.Execute(); err != nil { os.Exit(1) } @@ -43,6 +44,7 @@ func genDocs(cmd *cobra.Command, args []string) { if err := makeDir(cliMarkdownDir); err != nil { fatal(errors.Wrap(err, "preparing directory for markdown generation")) } + err := doc.GenMarkdownTree(cli.CorsoCommand(), cliMarkdownDir) if err != nil { fatal(errors.Wrap(err, "generating the Corso CLI markdown")) diff --git a/src/cmd/purge/purge.go b/src/cmd/purge/purge.go index 0096aaf5d..d2c8c8721 100644 --- a/src/cmd/purge/purge.go +++ b/src/cmd/purge/purge.go @@ -42,6 +42,7 @@ func doFolderPurge(cmd *cobra.Command, args []string) error { M365: credentials.GetM365(), TenantID: common.First(tenant, os.Getenv(account.TenantID)), } + acct, err := account.NewAccount(account.ProviderM365, m365Cfg) if err != nil { return Only(ctx, errors.Wrap(err, "finding m365 account details")) @@ -67,21 +68,26 @@ func doFolderPurge(cmd *cobra.Command, args []string) error { return Only(ctx, errors.Wrap(err, "parsing before flag to time")) } } + stLen := len(common.SimpleDateTimeFormat) // delete files for _, mf := range mfs { - // compare the folder time to the deletion boundary time first - var del bool - dnLen := len(mf.DisplayName) + var ( + del bool + dnLen = len(mf.DisplayName) + ) + if dnLen > stLen { dnSuff := mf.DisplayName[dnLen-stLen:] + dnTime, err := common.ParseTime(dnSuff) if err != nil { Info(ctx, errors.Wrapf(err, "Error: deleting folder [%s]", mf.DisplayName)) continue } + del = dnTime.Before(beforeTime) } @@ -90,6 +96,7 @@ func doFolderPurge(cmd *cobra.Command, args []string) error { } Info(ctx, "Deleting folder: ", mf.DisplayName) + err = exchange.DeleteMailFolder(gc.Service(), user, mf.ID) if err != nil { Info(ctx, errors.Wrapf(err, "Error: deleting folder [%s]", mf.DisplayName))