Fix wsl errors in cmd and cli packages (#651)

This commit is contained in:
ashmrtn 2022-08-26 09:13:39 -07:00 committed by GitHub
parent f24ad6ccbd
commit 88a318a7c0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 145 additions and 14 deletions

View File

@ -18,6 +18,7 @@ var serviceCommands = []func(parent *cobra.Command) *cobra.Command{
// 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)
for _, sc := range subCommands { for _, sc := range subCommands {
backupCmd.AddCommand(sc) backupCmd.AddCommand(sc)
} }

View File

@ -53,7 +53,6 @@ func addExchangeCommands(parent *cobra.Command) *cobra.Command {
) )
switch parent.Use { switch parent.Use {
case createCommand: case createCommand:
c, fs = utils.AddCommand(parent, exchangeCreateCmd()) c, fs = utils.AddCommand(parent, exchangeCreateCmd())
fs.StringSliceVar( fs.StringSliceVar(
@ -191,6 +190,7 @@ func createExchangeCmd(cmd *cobra.Command, args []string) error {
if err != nil { if err != nil {
return Only(ctx, errors.Wrapf(err, "Failed to connect to the %s repository", s.Provider)) return Only(ctx, errors.Wrapf(err, "Failed to connect to the %s repository", s.Provider))
} }
defer utils.CloseRepo(ctx, r) defer utils.CloseRepo(ctx, r)
sel := exchangeBackupCreateSelectors(exchangeAll, user, exchangeData) sel := exchangeBackupCreateSelectors(exchangeAll, user, exchangeData)
@ -211,6 +211,7 @@ func createExchangeCmd(cmd *cobra.Command, args []string) error {
} }
bu.Print(ctx) bu.Print(ctx)
return nil return nil
} }
@ -220,11 +221,13 @@ func exchangeBackupCreateSelectors(all bool, users, data []string) selectors.Sel
sel.Include(sel.Users(selectors.Any())) sel.Include(sel.Users(selectors.Any()))
return sel.Selector return sel.Selector
} }
if len(data) == 0 { if len(data) == 0 {
sel.Include(sel.ContactFolders(user, selectors.Any())) sel.Include(sel.ContactFolders(user, selectors.Any()))
sel.Include(sel.MailFolders(user, selectors.Any())) sel.Include(sel.MailFolders(user, selectors.Any()))
sel.Include(sel.Events(user, selectors.Any())) sel.Include(sel.Events(user, selectors.Any()))
} }
for _, d := range data { for _, d := range data {
switch d { switch d {
case dataContacts: case dataContacts:
@ -235,6 +238,7 @@ func exchangeBackupCreateSelectors(all bool, users, data []string) selectors.Sel
sel.Include(sel.Events(users, selectors.Any())) sel.Include(sel.Events(users, selectors.Any()))
} }
} }
return sel.Selector return sel.Selector
} }
@ -242,15 +246,18 @@ func validateExchangeBackupCreateFlags(all bool, users, data []string) error {
if len(users) == 0 && !all { if len(users) == 0 && !all {
return errors.New("requires one or more --user ids, the wildcard --user *, or the --all flag") return errors.New("requires one or more --user ids, the wildcard --user *, or the --all flag")
} }
if len(data) > 0 && all { if len(data) > 0 && all {
return errors.New("--all does a backup on all data, and cannot be reduced with --data") return errors.New("--all does a backup on all data, and cannot be reduced with --data")
} }
for _, d := range data { for _, d := range data {
if d != dataContacts && d != dataEmail && d != dataEvents { if d != dataContacts && d != dataEmail && d != dataEvents {
return errors.New( return errors.New(
d + " is an unrecognized data type; must be one of " + dataContacts + ", " + dataEmail + ", or " + dataEvents) d + " is an unrecognized data type; must be one of " + dataContacts + ", " + dataEmail + ", or " + dataEvents)
} }
} }
return nil return nil
} }
@ -290,6 +297,7 @@ func listExchangeCmd(cmd *cobra.Command, args []string) error {
if err != nil { if err != nil {
return Only(ctx, errors.Wrapf(err, "Failed to connect to the %s repository", s.Provider)) return Only(ctx, errors.Wrapf(err, "Failed to connect to the %s repository", s.Provider))
} }
defer utils.CloseRepo(ctx, r) defer utils.CloseRepo(ctx, r)
bs, err := r.Backups(ctx) bs, err := r.Backups(ctx)
@ -298,6 +306,7 @@ func listExchangeCmd(cmd *cobra.Command, args []string) error {
} }
backup.PrintAll(ctx, bs) backup.PrintAll(ctx, bs)
return nil return nil
} }
@ -353,6 +362,7 @@ func detailsExchangeCmd(cmd *cobra.Command, args []string) error {
if err != nil { if err != nil {
return Only(ctx, errors.Wrapf(err, "Failed to connect to the %s repository", s.Provider)) return Only(ctx, errors.Wrapf(err, "Failed to connect to the %s repository", s.Provider))
} }
defer utils.CloseRepo(ctx, r) defer utils.CloseRepo(ctx, r)
d, _, err := r.BackupDetails(ctx, backupID) d, _, err := r.BackupDetails(ctx, backupID)
@ -387,6 +397,7 @@ func detailsExchangeCmd(cmd *cobra.Command, args []string) error {
} }
ds.PrintEntries(ctx) ds.PrintEntries(ctx)
return nil return nil
} }
@ -420,6 +431,7 @@ func includeExchangeContacts(sel *selectors.ExchangeRestore, users, contactFolde
if len(contactFolders) == 0 { if len(contactFolders) == 0 {
return return
} }
if len(contacts) > 0 { if len(contacts) > 0 {
sel.Include(sel.Contacts(users, contactFolders, contacts)) sel.Include(sel.Contacts(users, contactFolders, contacts))
} else { } else {
@ -431,6 +443,7 @@ func includeExchangeEmails(sel *selectors.ExchangeRestore, users, emailFolders,
if len(emailFolders) == 0 { if len(emailFolders) == 0 {
return return
} }
if len(emails) > 0 { if len(emails) > 0 {
sel.Include(sel.Mails(users, emailFolders, emails)) sel.Include(sel.Mails(users, emailFolders, emails))
} else { } else {
@ -442,6 +455,7 @@ func includeExchangeEvents(sel *selectors.ExchangeRestore, users, events []strin
if len(events) == 0 { if len(events) == 0 {
return return
} }
sel.Include(sel.Events(users, events)) sel.Include(sel.Events(users, events))
} }
@ -460,6 +474,7 @@ func filterExchangeInfoMailReceivedAfter(sel *selectors.ExchangeRestore, receive
if len(receivedAfter) == 0 { if len(receivedAfter) == 0 {
return return
} }
sel.Filter(sel.MailReceivedAfter(receivedAfter)) sel.Filter(sel.MailReceivedAfter(receivedAfter))
} }
@ -467,6 +482,7 @@ func filterExchangeInfoMailReceivedBefore(sel *selectors.ExchangeRestore, receiv
if len(receivedBefore) == 0 { if len(receivedBefore) == 0 {
return return
} }
sel.Filter(sel.MailReceivedBefore(receivedBefore)) sel.Filter(sel.MailReceivedBefore(receivedBefore))
} }
@ -474,6 +490,7 @@ func filterExchangeInfoMailSender(sel *selectors.ExchangeRestore, sender string)
if len(sender) == 0 { if len(sender) == 0 {
return return
} }
sel.Filter(sel.MailSender([]string{sender})) sel.Filter(sel.MailSender([]string{sender}))
} }
@ -481,6 +498,7 @@ func filterExchangeInfoMailSubject(sel *selectors.ExchangeRestore, subject strin
if len(subject) == 0 { if len(subject) == 0 {
return return
} }
sel.Filter(sel.MailSubject([]string{subject})) sel.Filter(sel.MailSubject([]string{subject}))
} }
@ -492,24 +510,30 @@ func validateExchangeBackupDetailFlags(
if len(backupID) == 0 { if len(backupID) == 0 {
return errors.New("a backup ID is required") return errors.New("a backup ID is required")
} }
lu := len(users) lu := len(users)
lc, lcf := len(contacts), len(contactFolders) lc, lcf := len(contacts), len(contactFolders)
le, lef := len(emails), len(emailFolders) le, lef := len(emails), len(emailFolders)
lev := len(events) lev := len(events)
if lu+lc+lcf+le+lef+lev == 0 { if lu+lc+lcf+le+lef+lev == 0 {
return nil return nil
} }
if lu == 0 { if lu == 0 {
return errors.New("requires one or more --user ids, the wildcard --user *, or the --all flag") return errors.New("requires one or more --user ids, the wildcard --user *, or the --all flag")
} }
if lc > 0 && lcf == 0 { if lc > 0 && lcf == 0 {
return errors.New( return errors.New(
"one or more --contact-folder ids or the wildcard --contact-folder * must be included to specify a --contact") "one or more --contact-folder ids or the wildcard --contact-folder * must be included to specify a --contact")
} }
if le > 0 && lef == 0 { if le > 0 && lef == 0 {
return errors.New( return errors.New(
"one or more --email-folder ids or the wildcard --email-folder * must be included to specify a --email") "one or more --email-folder ids or the wildcard --email-folder * must be included to specify a --email")
} }
return nil return nil
} }
@ -555,6 +579,7 @@ func deleteExchangeCmd(cmd *cobra.Command, args []string) error {
if err != nil { if err != nil {
return Only(ctx, errors.Wrapf(err, "Failed to connect to the %s repository", s.Provider)) return Only(ctx, errors.Wrapf(err, "Failed to connect to the %s repository", s.Provider))
} }
defer utils.CloseRepo(ctx, r) defer utils.CloseRepo(ctx, r)
if err := r.DeleteBackup(ctx, model.StableID(backupID)); err != nil { if err := r.DeleteBackup(ctx, model.StableID(backupID)); err != nil {

View File

@ -46,6 +46,7 @@ func TestBackupExchangeIntegrationSuite(t *testing.T) {
); err != nil { ); err != nil {
t.Skip(err) t.Skip(err)
} }
suite.Run(t, new(BackupExchangeIntegrationSuite)) suite.Run(t, new(BackupExchangeIntegrationSuite))
} }
@ -70,8 +71,8 @@ func (suite *BackupExchangeIntegrationSuite) SetupSuite() {
} }
suite.vpr, suite.cfgFP, err = tester.MakeTempTestConfigClone(t, force) suite.vpr, suite.cfgFP, err = tester.MakeTempTestConfigClone(t, force)
require.NoError(t, err) require.NoError(t, err)
ctx := config.SetViper(tester.NewContext(), suite.vpr)
ctx := config.SetViper(tester.NewContext(), suite.vpr)
suite.m365UserID = tester.M365UserID(t) suite.m365UserID = tester.M365UserID(t)
// init the repo first // init the repo first
@ -89,8 +90,10 @@ func (suite *BackupExchangeIntegrationSuite) TestExchangeBackupCmd() {
"--user", suite.m365UserID, "--user", suite.m365UserID,
"--data", "email") "--data", "email")
cli.BuildCommandTree(cmd) cli.BuildCommandTree(cmd)
var recorder strings.Builder
recorder := strings.Builder{}
cmd.SetOut(&recorder) cmd.SetOut(&recorder)
ctx = print.SetRootCmd(ctx, cmd) ctx = print.SetRootCmd(ctx, cmd)
// run the command // run the command
@ -126,6 +129,7 @@ func TestPreparedBackupExchangeIntegrationSuite(t *testing.T) {
); err != nil { ); err != nil {
t.Skip(err) t.Skip(err)
} }
suite.Run(t, new(PreparedBackupExchangeIntegrationSuite)) suite.Run(t, new(PreparedBackupExchangeIntegrationSuite))
} }
@ -150,8 +154,8 @@ func (suite *PreparedBackupExchangeIntegrationSuite) SetupSuite() {
} }
suite.vpr, suite.cfgFP, err = tester.MakeTempTestConfigClone(t, force) suite.vpr, suite.cfgFP, err = tester.MakeTempTestConfigClone(t, force)
require.NoError(t, err) require.NoError(t, err)
ctx := config.SetViper(tester.NewContext(), suite.vpr)
ctx := config.SetViper(tester.NewContext(), suite.vpr)
suite.m365UserID = tester.M365UserID(t) suite.m365UserID = tester.M365UserID(t)
// init the repo first // init the repo first
@ -178,8 +182,10 @@ func (suite *PreparedBackupExchangeIntegrationSuite) TestExchangeListCmd() {
"backup", "list", "exchange", "backup", "list", "exchange",
"--config-file", suite.cfgFP) "--config-file", suite.cfgFP)
cli.BuildCommandTree(cmd) cli.BuildCommandTree(cmd)
var recorder strings.Builder
recorder := strings.Builder{}
cmd.SetOut(&recorder) cmd.SetOut(&recorder)
ctx = print.SetRootCmd(ctx, cmd) ctx = print.SetRootCmd(ctx, cmd)
// run the command // run the command
@ -203,8 +209,10 @@ func (suite *PreparedBackupExchangeIntegrationSuite) TestExchangeDetailsCmd() {
"--config-file", suite.cfgFP, "--config-file", suite.cfgFP,
"--backup", string(suite.backupOp.Results.BackupID)) "--backup", string(suite.backupOp.Results.BackupID))
cli.BuildCommandTree(cmd) cli.BuildCommandTree(cmd)
var recorder strings.Builder
recorder := strings.Builder{}
cmd.SetOut(&recorder) cmd.SetOut(&recorder)
ctx = print.SetRootCmd(ctx, cmd) ctx = print.SetRootCmd(ctx, cmd)
// run the command // run the command
@ -212,6 +220,7 @@ func (suite *PreparedBackupExchangeIntegrationSuite) TestExchangeDetailsCmd() {
// compare the output // compare the output
result := recorder.String() result := recorder.String()
for i, ent := range deets.Entries { for i, ent := range deets.Entries {
t.Run(fmt.Sprintf("detail %d", i), func(t *testing.T) { t.Run(fmt.Sprintf("detail %d", i), func(t *testing.T) {
assert.Contains(t, result, ent.RepoRef) assert.Contains(t, result, ent.RepoRef)
@ -241,6 +250,7 @@ func TestBackupDeleteExchangeIntegrationSuite(t *testing.T) {
); err != nil { ); err != nil {
t.Skip(err) t.Skip(err)
} }
suite.Run(t, new(BackupDeleteExchangeIntegrationSuite)) suite.Run(t, new(BackupDeleteExchangeIntegrationSuite))
} }
@ -265,6 +275,7 @@ func (suite *BackupDeleteExchangeIntegrationSuite) SetupSuite() {
} }
suite.vpr, suite.cfgFP, err = tester.MakeTempTestConfigClone(t, force) suite.vpr, suite.cfgFP, err = tester.MakeTempTestConfigClone(t, force)
require.NoError(t, err) require.NoError(t, err)
ctx := config.SetViper(tester.NewContext(), suite.vpr) ctx := config.SetViper(tester.NewContext(), suite.vpr)
// init the repo first // init the repo first

View File

@ -23,6 +23,7 @@ func TestExchangeSuite(t *testing.T) {
func (suite *ExchangeSuite) TestAddExchangeCommands() { func (suite *ExchangeSuite) TestAddExchangeCommands() {
expectUse := exchangeServiceCommand expectUse := exchangeServiceCommand
table := []struct { table := []struct {
name string name string
use string use string
@ -217,6 +218,7 @@ func (suite *ExchangeSuite) TestExchangeBackupCreateSelectors() {
func (suite *ExchangeSuite) TestValidateBackupDetailFlags() { func (suite *ExchangeSuite) TestValidateBackupDetailFlags() {
stub := []string{"id-stub"} stub := []string{"id-stub"}
table := []struct { table := []struct {
name string name string
contacts, contactFolders, emails, emailFolders, events, users []string contacts, contactFolders, emails, emailFolders, events, users []string
@ -302,6 +304,7 @@ func (suite *ExchangeSuite) TestValidateBackupDetailFlags() {
func (suite *ExchangeSuite) TestIncludeExchangeBackupDetailDataSelectors() { func (suite *ExchangeSuite) TestIncludeExchangeBackupDetailDataSelectors() {
stub := []string{"id-stub"} stub := []string{"id-stub"}
a := []string{utils.Wildcard} a := []string{utils.Wildcard}
table := []struct { table := []struct {
name string name string
contacts, contactFolders, emails, emailFolders, events, users []string contacts, contactFolders, emails, emailFolders, events, users []string
@ -484,6 +487,7 @@ func (suite *ExchangeSuite) TestIncludeExchangeBackupDetailDataSelectors() {
func (suite *ExchangeSuite) TestFilterExchangeBackupDetailInfoSelectors() { func (suite *ExchangeSuite) TestFilterExchangeBackupDetailInfoSelectors() {
stub := "id-stub" stub := "id-stub"
a := utils.Wildcard a := utils.Wildcard
table := []struct { table := []struct {
name string name string
after, before, sender, subject string after, before, sender, subject string

View File

@ -41,6 +41,7 @@ func handleCorsoCmd(cmd *cobra.Command, args []string) error {
print.Infof(cmd.Context(), "Corso\nversion:\tpre-alpha\n") print.Infof(cmd.Context(), "Corso\nversion:\tpre-alpha\n")
return nil return nil
} }
return cmd.Help() return cmd.Help()
} }
@ -50,6 +51,7 @@ func CorsoCommand() *cobra.Command {
c := &cobra.Command{} c := &cobra.Command{}
*c = *corsoCmd *c = *corsoCmd
BuildCommandTree(c) BuildCommandTree(c)
return c return c
} }

View File

@ -22,6 +22,7 @@ func m365ConfigsFromViper(vpr *viper.Viper) (account.M365Config, error) {
} }
m365.TenantID = vpr.GetString(TenantIDKey) m365.TenantID = vpr.GetString(TenantIDKey)
return m365, nil return m365, nil
} }

View File

@ -34,10 +34,12 @@ var configFilePath string
// adds the persistent flag --config-file to the provided command. // adds the persistent flag --config-file to the provided command.
func AddConfigFlags(cmd *cobra.Command) { func AddConfigFlags(cmd *cobra.Command) {
fs := cmd.PersistentFlags() fs := cmd.PersistentFlags()
homeDir, err := os.UserHomeDir() homeDir, err := os.UserHomeDir()
if err != nil { if err != nil {
Err(cmd.Context(), "finding $HOME directory (default) for config file") Err(cmd.Context(), "finding $HOME directory (default) for config file")
} }
fs.StringVar( fs.StringVar(
&configFilePath, &configFilePath,
"config-file", "config-file",
@ -57,6 +59,7 @@ func InitFunc() func(*cobra.Command, []string) error {
if err != nil { if err != nil {
return err return err
} }
return Read(cmd.Context()) return Read(cmd.Context())
} }
} }
@ -76,6 +79,7 @@ func initWithViper(vpr *viper.Viper, configFP string) error {
vpr.AddConfigPath(home) vpr.AddConfigPath(home)
vpr.SetConfigType("toml") vpr.SetConfigType("toml")
vpr.SetConfigName(".corso") vpr.SetConfigName(".corso")
return nil return nil
} }
@ -85,11 +89,12 @@ func initWithViper(vpr *viper.Viper, configFP string) error {
// work correctly (it does not use the configured file) // work correctly (it does not use the configured file)
vpr.AddConfigPath(path.Dir(configFP)) vpr.AddConfigPath(path.Dir(configFP))
fileName := path.Base(configFP)
ext := path.Ext(configFP) ext := path.Ext(configFP)
if len(ext) == 0 { if len(ext) == 0 {
return errors.New("config file requires an extension e.g. `toml`") return errors.New("config file requires an extension e.g. `toml`")
} }
fileName := path.Base(configFP)
fileName = strings.TrimSuffix(fileName, ext) fileName = strings.TrimSuffix(fileName, ext)
vpr.SetConfigType(strings.TrimPrefix(ext, ".")) vpr.SetConfigType(strings.TrimPrefix(ext, "."))
vpr.SetConfigName(fileName) vpr.SetConfigName(fileName)
@ -110,6 +115,7 @@ func SetViper(ctx context.Context, vpr *viper.Viper) context.Context {
if vpr == nil { if vpr == nil {
vpr = viper.GetViper() vpr = viper.GetViper()
} }
return context.WithValue(ctx, viperCtx{}, vpr) return context.WithValue(ctx, viperCtx{}, vpr)
} }
@ -118,10 +124,12 @@ func SetViper(ctx context.Context, vpr *viper.Viper) context.Context {
// (global) viper instance. // (global) viper instance.
func GetViper(ctx context.Context) *viper.Viper { func GetViper(ctx context.Context) *viper.Viper {
vprIface := ctx.Value(viperCtx{}) vprIface := ctx.Value(viperCtx{})
vpr, ok := vprIface.(*viper.Viper) vpr, ok := vprIface.(*viper.Viper)
if vpr == nil || !ok { if vpr == nil || !ok {
return viper.GetViper() return viper.GetViper()
} }
return vpr return vpr
} }
@ -137,6 +145,7 @@ func Read(ctx context.Context) error {
logger.Ctx(ctx).Debugw("found config file", "configFile", viper.ConfigFileUsed()) logger.Ctx(ctx).Debugw("found config file", "configFile", viper.ConfigFileUsed())
return err return err
} }
return nil return nil
} }
@ -163,8 +172,10 @@ func writeRepoConfigWithViper(vpr *viper.Viper, s3Config storage.S3Config, m365C
if _, ok := err.(viper.ConfigFileAlreadyExistsError); ok { if _, ok := err.(viper.ConfigFileAlreadyExistsError); ok {
return vpr.WriteConfig() return vpr.WriteConfig()
} }
return err return err
} }
return nil return nil
} }
@ -200,6 +211,7 @@ func getStorageAndAccountWithViper(
if _, ok := err.(viper.ConfigFileNotFoundError); !ok { if _, ok := err.(viper.ConfigFileNotFoundError); !ok {
return store, acct, errors.Wrap(err, "reading corso config file: "+vpr.ConfigFileUsed()) return store, acct, errors.Wrap(err, "reading corso config file: "+vpr.ConfigFileUsed())
} }
readConfigFromViper = false readConfigFromViper = false
} }
} }
@ -238,14 +250,17 @@ func mustMatchConfig(vpr *viper.Viper, m map[string]string) error {
if len(v) == 0 { if len(v) == 0 {
continue // empty variables will get caught by configuration validators, if necessary continue // empty variables will get caught by configuration validators, if necessary
} }
tomlK, ok := constToTomlKeyMap[k] tomlK, ok := constToTomlKeyMap[k]
if !ok { if !ok {
continue // m may declare values which aren't stored in the config file continue // m may declare values which aren't stored in the config file
} }
vv := vpr.GetString(tomlK) vv := vpr.GetString(tomlK)
if v != vv { if v != vv {
return errors.New("value of " + k + " (" + v + ") does not match corso configuration value (" + vv + ")") return errors.New("value of " + k + " (" + v + ") does not match corso configuration value (" + vv + ")")
} }
} }
return nil return nil
} }

View File

@ -190,6 +190,7 @@ func TestConfigIntegrationSuite(t *testing.T) {
); err != nil { ); err != nil {
t.Skip(err) t.Skip(err)
} }
suite.Run(t, new(ConfigIntegrationSuite)) suite.Run(t, new(ConfigIntegrationSuite))
} }

View File

@ -25,6 +25,7 @@ func s3ConfigsFromViper(vpr *viper.Viper) (storage.S3Config, error) {
s3Config.Bucket = vpr.GetString(BucketNameKey) s3Config.Bucket = vpr.GetString(BucketNameKey)
s3Config.Endpoint = vpr.GetString(EndpointKey) s3Config.Endpoint = vpr.GetString(EndpointKey)
s3Config.Prefix = vpr.GetString(PrefixKey) s3Config.Prefix = vpr.GetString(PrefixKey)
return s3Config, nil return s3Config, nil
} }
@ -78,6 +79,7 @@ func configureStorage(
if err := corso.Validate(); err != nil { if err := corso.Validate(); err != nil {
return store, errors.Wrap(err, "validating corso credentials") return store, errors.Wrap(err, "validating corso credentials")
} }
cCfg := storage.CommonConfig{ cCfg := storage.CommonConfig{
Corso: corso, Corso: corso,
} }

View File

@ -22,6 +22,7 @@ func envCmd() *cobra.Command {
Args: cobra.NoArgs, Args: cobra.NoArgs,
} }
envCmd.SetHelpFunc(envGuide) envCmd.SetHelpFunc(envGuide)
return envCmd return envCmd
} }
@ -81,6 +82,7 @@ func toPrintable(evs []envVar) []Printable {
for _, ev := range evs { for _, ev := range evs {
ps = append(ps, ev) ps = append(ps, ev)
} }
return ps return ps
} }

View File

@ -24,6 +24,7 @@ func (suite *EnvSuite) TestAddEnvCommands() {
cmd := &cobra.Command{Use: "root"} cmd := &cobra.Command{Use: "root"}
AddCommands(cmd) AddCommands(cmd)
c := envCmd() c := envCmd()
require.NotNil(t, c) require.NotNil(t, c)

View File

@ -30,9 +30,11 @@ func SetRootCmd(ctx context.Context, root *cobra.Command) context.Context {
func getRootCmd(ctx context.Context) *cobra.Command { func getRootCmd(ctx context.Context) *cobra.Command {
cmdIface := ctx.Value(rootCmdCtx{}) cmdIface := ctx.Value(rootCmdCtx{})
cmd, ok := cmdIface.(*cobra.Command) cmd, ok := cmdIface.(*cobra.Command)
if cmd == nil || !ok { if cmd == nil || !ok {
return &cobra.Command{} return &cobra.Command{}
} }
return cmd return cmd
} }
@ -67,6 +69,7 @@ func err(w io.Writer, s ...any) {
if len(s) == 0 { if len(s) == 0 {
return return
} }
msg := append([]any{"Error: "}, s...) msg := append([]any{"Error: "}, s...)
fmt.Fprint(w, msg...) fmt.Fprint(w, msg...)
} }
@ -82,6 +85,7 @@ func info(w io.Writer, s ...any) {
if len(s) == 0 { if len(s) == 0 {
return return
} }
fmt.Fprint(w, s...) fmt.Fprint(w, s...)
fmt.Fprintf(w, "\n") fmt.Fprintf(w, "\n")
} }
@ -97,6 +101,7 @@ func infof(w io.Writer, t string, s ...any) {
if len(t) == 0 { if len(t) == 0 {
return return
} }
fmt.Fprintf(w, t, s...) fmt.Fprintf(w, t, s...)
fmt.Fprintf(w, "\n") fmt.Fprintf(w, "\n")
} }
@ -129,6 +134,7 @@ func print(w io.Writer, p Printable) {
outputJSON(w, p, outputAsJSONDebug) outputJSON(w, p, outputAsJSONDebug)
return return
} }
outputTable(w, []Printable{p}) outputTable(w, []Printable{p})
} }
@ -144,10 +150,12 @@ func printAll(w io.Writer, ps []Printable) {
if len(ps) == 0 { if len(ps) == 0 {
return return
} }
if outputAsJSON || outputAsJSONDebug { if outputAsJSON || outputAsJSONDebug {
outputJSONArr(w, ps, outputAsJSONDebug) outputJSONArr(w, ps, outputAsJSONDebug)
return return
} }
outputTable(w, ps) outputTable(w, ps)
} }
@ -167,9 +175,11 @@ func outputTable(w io.Writer, ps []Printable) {
Headers: ps[0].Headers(), Headers: ps[0].Headers(),
Rows: [][]string{}, Rows: [][]string{},
} }
for _, p := range ps { for _, p := range ps {
t.Rows = append(t.Rows, p.Values()) t.Rows = append(t.Rows, p.Values())
} }
_ = t.WriteTable( _ = t.WriteTable(
w, w,
&table.Config{ &table.Config{
@ -188,11 +198,13 @@ func outputJSON(w io.Writer, p Printable, debug bool) {
printJSON(w, p) printJSON(w, p)
return return
} }
printJSON(w, p.MinimumPrintable()) printJSON(w, p.MinimumPrintable())
} }
func outputJSONArr(w io.Writer, ps []Printable, debug bool) { func outputJSONArr(w io.Writer, ps []Printable, debug bool) {
sl := make([]any, 0, len(ps)) sl := make([]any, 0, len(ps))
for _, p := range ps { for _, p := range ps {
if debug { if debug {
sl = append(sl, p) sl = append(sl, p)
@ -200,6 +212,7 @@ func outputJSONArr(w io.Writer, ps []Printable, debug bool) {
sl = append(sl, p.MinimumPrintable()) sl = append(sl, p.MinimumPrintable())
} }
} }
printJSON(w, sl) 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) fmt.Fprintf(w, "error formatting results to json: %v\n", err)
return return
} }
fmt.Fprintln(w, string(pretty.Pretty(bs))) fmt.Fprintln(w, string(pretty.Pretty(bs)))
} }

View File

@ -30,8 +30,9 @@ func (suite *PrintUnitSuite) TestOnly() {
func (suite *PrintUnitSuite) TestErr() { func (suite *PrintUnitSuite) TestErr() {
t := suite.T() t := suite.T()
var b bytes.Buffer b := bytes.Buffer{}
msg := "I have seen the fnords!" msg := "I have seen the fnords!"
err(&b, msg) err(&b, msg)
assert.Contains(t, b.String(), "Error: ") assert.Contains(t, b.String(), "Error: ")
assert.Contains(t, b.String(), msg) assert.Contains(t, b.String(), msg)
@ -39,17 +40,19 @@ func (suite *PrintUnitSuite) TestErr() {
func (suite *PrintUnitSuite) TestInfo() { func (suite *PrintUnitSuite) TestInfo() {
t := suite.T() t := suite.T()
var b bytes.Buffer b := bytes.Buffer{}
msg := "I have seen the fnords!" msg := "I have seen the fnords!"
info(&b, msg) info(&b, msg)
assert.Contains(t, b.String(), msg) assert.Contains(t, b.String(), msg)
} }
func (suite *PrintUnitSuite) TestInfof() { func (suite *PrintUnitSuite) TestInfof() {
t := suite.T() t := suite.T()
var b bytes.Buffer b := bytes.Buffer{}
msg := "I have seen the fnords!" msg := "I have seen the fnords!"
msg2 := "smarf" msg2 := "smarf"
infof(&b, msg, msg2) infof(&b, msg, msg2)
bs := b.String() bs := b.String()
assert.Contains(t, bs, msg) assert.Contains(t, bs, msg)

View File

@ -31,6 +31,7 @@ func addS3Commands(parent *cobra.Command) *cobra.Command {
c *cobra.Command c *cobra.Command
fs *pflag.FlagSet fs *pflag.FlagSet
) )
switch parent.Use { switch parent.Use {
case initCommand: case initCommand:
c, fs = utils.AddCommand(parent, s3InitCmd()) 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 // 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. // for a broad-scale idempotency solution. We can un-hide it later the need arises.
cobra.CheckErr(fs.MarkHidden("succeed-if-exists")) cobra.CheckErr(fs.MarkHidden("succeed-if-exists"))
return c return c
} }
@ -80,6 +82,7 @@ func initS3Cmd(cmd *cobra.Command, args []string) error {
if err != nil { if err != nil {
return Only(ctx, err) return Only(ctx, err)
} }
s3Cfg, err := s.S3Config() s3Cfg, err := s.S3Config()
if err != nil { if err != nil {
return Only(ctx, errors.Wrap(err, "Retrieving s3 configuration")) 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) { if succeedIfExists && kopia.IsRepoAlreadyExistsError(err) {
return nil return nil
} }
return Only(ctx, errors.Wrap(err, "Failed to initialize a new S3 repository")) return Only(ctx, errors.Wrap(err, "Failed to initialize a new S3 repository"))
} }
defer utils.CloseRepo(ctx, r) defer utils.CloseRepo(ctx, r)
Infof(ctx, "Initialized a S3 repository within bucket %s.", s3Cfg.Bucket) 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 { if err = config.WriteRepoConfig(ctx, s3Cfg, m365); err != nil {
return Only(ctx, errors.Wrap(err, "Failed to write repository configuration")) return Only(ctx, errors.Wrap(err, "Failed to write repository configuration"))
} }
return nil return nil
} }
@ -143,10 +149,12 @@ func connectS3Cmd(cmd *cobra.Command, args []string) error {
if err != nil { if err != nil {
return Only(ctx, err) return Only(ctx, err)
} }
s3Cfg, err := s.S3Config() s3Cfg, err := s.S3Config()
if err != nil { if err != nil {
return Only(ctx, errors.Wrap(err, "Retrieving s3 configuration")) return Only(ctx, errors.Wrap(err, "Retrieving s3 configuration"))
} }
m365, err := a.M365Config() m365, err := a.M365Config()
if err != nil { if err != nil {
return Only(ctx, errors.Wrap(err, "Failed to parse m365 account config")) 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 { if err != nil {
return Only(ctx, errors.Wrap(err, "Failed to connect to the S3 repository")) return Only(ctx, errors.Wrap(err, "Failed to connect to the S3 repository"))
} }
defer utils.CloseRepo(ctx, r) defer utils.CloseRepo(ctx, r)
Infof(ctx, "Connected to S3 bucket %s.", s3Cfg.Bucket) 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 { if err = config.WriteRepoConfig(ctx, s3Cfg, m365); err != nil {
return Only(ctx, errors.Wrap(err, "Failed to write repository configuration")) return Only(ctx, errors.Wrap(err, "Failed to write repository configuration"))
} }
return nil return nil
} }

View File

@ -25,6 +25,7 @@ func TestS3IntegrationSuite(t *testing.T) {
); err != nil { ); err != nil {
t.Skip(err) t.Skip(err)
} }
suite.Run(t, new(S3IntegrationSuite)) suite.Run(t, new(S3IntegrationSuite))
} }
@ -45,6 +46,7 @@ func (suite *S3IntegrationSuite) TestInitS3Cmd() {
vpr, configFP, err := tester.MakeTempTestConfigClone(t, nil) vpr, configFP, err := tester.MakeTempTestConfigClone(t, nil)
require.NoError(t, err) require.NoError(t, err)
ctx = config.SetViper(ctx, vpr) ctx = config.SetViper(ctx, vpr)
cmd := tester.StubRootCmd( cmd := tester.StubRootCmd(
@ -68,6 +70,7 @@ func (suite *S3IntegrationSuite) TestInitS3Cmd_missingBucket() {
vpr, configFP, err := tester.MakeTempTestConfigClone(t, nil) vpr, configFP, err := tester.MakeTempTestConfigClone(t, nil)
require.NoError(t, err) require.NoError(t, err)
ctx = config.SetViper(ctx, vpr) ctx = config.SetViper(ctx, vpr)
cmd := tester.StubRootCmd( cmd := tester.StubRootCmd(
@ -95,6 +98,7 @@ func (suite *S3IntegrationSuite) TestConnectS3Cmd() {
} }
vpr, configFP, err := tester.MakeTempTestConfigClone(t, force) vpr, configFP, err := tester.MakeTempTestConfigClone(t, force)
require.NoError(t, err) require.NoError(t, err)
ctx = config.SetViper(ctx, vpr) ctx = config.SetViper(ctx, vpr)
// init the repo first // init the repo first
@ -123,6 +127,7 @@ func (suite *S3IntegrationSuite) TestConnectS3Cmd_BadBucket() {
vpr, configFP, err := tester.MakeTempTestConfigClone(t, nil) vpr, configFP, err := tester.MakeTempTestConfigClone(t, nil)
require.NoError(t, err) require.NoError(t, err)
ctx = config.SetViper(ctx, vpr) ctx = config.SetViper(ctx, vpr)
cmd := tester.StubRootCmd( cmd := tester.StubRootCmd(
@ -146,6 +151,7 @@ func (suite *S3IntegrationSuite) TestConnectS3Cmd_BadPrefix() {
vpr, configFP, err := tester.MakeTempTestConfigClone(t, nil) vpr, configFP, err := tester.MakeTempTestConfigClone(t, nil)
require.NoError(t, err) require.NoError(t, err)
ctx = config.SetViper(ctx, vpr) ctx = config.SetViper(ctx, vpr)
cmd := tester.StubRootCmd( cmd := tester.StubRootCmd(

View File

@ -21,6 +21,7 @@ func TestS3Suite(t *testing.T) {
func (suite *S3Suite) TestAddS3Commands() { func (suite *S3Suite) TestAddS3Commands() {
expectUse := s3ProviderCommand expectUse := s3ProviderCommand
table := []struct { table := []struct {
name string name string
use string use string

View File

@ -88,6 +88,7 @@ func addExchangeCommands(parent *cobra.Command) *cobra.Command {
// others // others
options.AddOperationFlags(c) options.AddOperationFlags(c)
} }
return c return c
} }
@ -144,6 +145,7 @@ func restoreExchangeCmd(cmd *cobra.Command, args []string) error {
if err != nil { if err != nil {
return Only(ctx, errors.Wrapf(err, "Failed to connect to the %s repository", s.Provider)) return Only(ctx, errors.Wrapf(err, "Failed to connect to the %s repository", s.Provider))
} }
defer utils.CloseRepo(ctx, r) defer utils.CloseRepo(ctx, r)
sel := selectors.NewExchangeRestore() 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) Infof(ctx, "Restored Exchange in %s for user %s.\n", s.Provider, user)
return nil return nil
} }
@ -210,6 +213,7 @@ func includeExchangeContacts(sel *selectors.ExchangeRestore, users, contactFolde
if len(contactFolders) == 0 { if len(contactFolders) == 0 {
return return
} }
if len(contacts) > 0 { if len(contacts) > 0 {
sel.Include(sel.Contacts(users, contactFolders, contacts)) sel.Include(sel.Contacts(users, contactFolders, contacts))
} else { } else {
@ -221,6 +225,7 @@ func includeExchangeEmails(sel *selectors.ExchangeRestore, users, emailFolders,
if len(emailFolders) == 0 { if len(emailFolders) == 0 {
return return
} }
if len(emails) > 0 { if len(emails) > 0 {
sel.Include(sel.Mails(users, emailFolders, emails)) sel.Include(sel.Mails(users, emailFolders, emails))
} else { } else {
@ -232,6 +237,7 @@ func includeExchangeEvents(sel *selectors.ExchangeRestore, users, events []strin
if len(events) == 0 { if len(events) == 0 {
return return
} }
sel.Include(sel.Events(users, events)) sel.Include(sel.Events(users, events))
} }
@ -250,6 +256,7 @@ func filterExchangeInfoMailReceivedAfter(sel *selectors.ExchangeRestore, receive
if len(receivedAfter) == 0 { if len(receivedAfter) == 0 {
return return
} }
sel.Filter(sel.MailReceivedAfter(receivedAfter)) sel.Filter(sel.MailReceivedAfter(receivedAfter))
} }
@ -257,6 +264,7 @@ func filterExchangeInfoMailReceivedBefore(sel *selectors.ExchangeRestore, receiv
if len(receivedBefore) == 0 { if len(receivedBefore) == 0 {
return return
} }
sel.Filter(sel.MailReceivedBefore(receivedBefore)) sel.Filter(sel.MailReceivedBefore(receivedBefore))
} }
@ -264,6 +272,7 @@ func filterExchangeInfoMailSender(sel *selectors.ExchangeRestore, sender string)
if len(sender) == 0 { if len(sender) == 0 {
return return
} }
sel.Filter(sel.MailSender([]string{sender})) sel.Filter(sel.MailSender([]string{sender}))
} }
@ -271,6 +280,7 @@ func filterExchangeInfoMailSubject(sel *selectors.ExchangeRestore, subject strin
if len(subject) == 0 { if len(subject) == 0 {
return return
} }
sel.Filter(sel.MailSubject([]string{subject})) sel.Filter(sel.MailSubject([]string{subject}))
} }
@ -282,24 +292,30 @@ func validateExchangeRestoreFlags(
if len(backupID) == 0 { if len(backupID) == 0 {
return errors.New("a backup ID is required") return errors.New("a backup ID is required")
} }
lu := len(users) lu := len(users)
lc, lcf := len(contacts), len(contactFolders) lc, lcf := len(contacts), len(contactFolders)
le, lef := len(emails), len(emailFolders) le, lef := len(emails), len(emailFolders)
lev := len(events) lev := len(events)
// if only the backupID is populated, that's the same as --all // if only the backupID is populated, that's the same as --all
if lu+lc+lcf+le+lef+lev == 0 { if lu+lc+lcf+le+lef+lev == 0 {
return nil return nil
} }
if lu == 0 { if lu == 0 {
return errors.New("requires one or more --user ids, the wildcard --user *, or the --all flag") return errors.New("requires one or more --user ids, the wildcard --user *, or the --all flag")
} }
if lc > 0 && lcf == 0 { if lc > 0 && lcf == 0 {
return errors.New( return errors.New(
"one or more --contact-folder ids or the wildcard --contact-folder * must be included to specify a --contact") "one or more --contact-folder ids or the wildcard --contact-folder * must be included to specify a --contact")
} }
if le > 0 && lef == 0 { if le > 0 && lef == 0 {
return errors.New( return errors.New(
"one or more --email-folder ids or the wildcard --email-folder * must be included to specify a --email") "one or more --email-folder ids or the wildcard --email-folder * must be included to specify a --email")
} }
return nil return nil
} }

View File

@ -37,6 +37,7 @@ func TestRestoreExchangeIntegrationSuite(t *testing.T) {
); err != nil { ); err != nil {
t.Skip(err) t.Skip(err)
} }
suite.Run(t, new(RestoreExchangeIntegrationSuite)) suite.Run(t, new(RestoreExchangeIntegrationSuite))
} }
@ -62,8 +63,8 @@ func (suite *RestoreExchangeIntegrationSuite) SetupSuite() {
} }
suite.vpr, suite.cfgFP, err = tester.MakeTempTestConfigClone(t, force) suite.vpr, suite.cfgFP, err = tester.MakeTempTestConfigClone(t, force)
require.NoError(t, err) require.NoError(t, err)
ctx := config.SetViper(tester.NewContext(), suite.vpr)
ctx := config.SetViper(tester.NewContext(), suite.vpr)
suite.m365UserID = tester.M365UserID(t) suite.m365UserID = tester.M365UserID(t)
// init the repo first // init the repo first

View File

@ -23,6 +23,7 @@ func TestExchangeSuite(t *testing.T) {
func (suite *ExchangeSuite) TestAddExchangeCommands() { func (suite *ExchangeSuite) TestAddExchangeCommands() {
expectUse := exchangeServiceCommand expectUse := exchangeServiceCommand
table := []struct { table := []struct {
name string name string
use string use string
@ -52,6 +53,7 @@ func (suite *ExchangeSuite) TestAddExchangeCommands() {
func (suite *ExchangeSuite) TestValidateExchangeRestoreFlags() { func (suite *ExchangeSuite) TestValidateExchangeRestoreFlags() {
stub := []string{"id-stub"} stub := []string{"id-stub"}
table := []struct { table := []struct {
name string name string
contacts, contactFolders, emails, emailFolders, events, users []string contacts, contactFolders, emails, emailFolders, events, users []string
@ -137,6 +139,7 @@ func (suite *ExchangeSuite) TestValidateExchangeRestoreFlags() {
func (suite *ExchangeSuite) TestIncludeExchangeRestoreDataSelectors() { func (suite *ExchangeSuite) TestIncludeExchangeRestoreDataSelectors() {
stub := []string{"id-stub"} stub := []string{"id-stub"}
a := []string{utils.Wildcard} a := []string{utils.Wildcard}
table := []struct { table := []struct {
name string name string
contacts, contactFolders, emails, emailFolders, events, users []string contacts, contactFolders, emails, emailFolders, events, users []string
@ -319,6 +322,7 @@ func (suite *ExchangeSuite) TestIncludeExchangeRestoreDataSelectors() {
func (suite *ExchangeSuite) TestFilterExchangeRestoreInfoSelectors() { func (suite *ExchangeSuite) TestFilterExchangeRestoreInfoSelectors() {
stub := "id-stub" stub := "id-stub"
a := utils.Wildcard a := utils.Wildcard
table := []struct { table := []struct {
name string name string
after, before, sender, subject string after, before, sender, subject string

View File

@ -23,6 +23,7 @@ func RequireProps(props map[string]string) error {
return errors.New(name + " is required to perform this command") return errors.New(name + " is required to perform this command")
} }
} }
return nil return nil
} }
@ -43,6 +44,7 @@ func HasNoFlagsAndShownHelp(cmd *cobra.Command) bool {
cobra.CheckErr(cmd.Help()) cobra.CheckErr(cmd.Help())
return true return true
} }
return false return false
} }

View File

@ -34,6 +34,7 @@ func main() {
"cli-folder", "cli-folder",
"./cmd/mdgen/cli_markdown", "./cmd/mdgen/cli_markdown",
"relative path to the folder where cli docs will be generated (default: ./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 { if err := cmd.Execute(); err != nil {
os.Exit(1) os.Exit(1)
} }
@ -43,6 +44,7 @@ func genDocs(cmd *cobra.Command, args []string) {
if err := makeDir(cliMarkdownDir); err != nil { if err := makeDir(cliMarkdownDir); err != nil {
fatal(errors.Wrap(err, "preparing directory for markdown generation")) fatal(errors.Wrap(err, "preparing directory for markdown generation"))
} }
err := doc.GenMarkdownTree(cli.CorsoCommand(), cliMarkdownDir) err := doc.GenMarkdownTree(cli.CorsoCommand(), cliMarkdownDir)
if err != nil { if err != nil {
fatal(errors.Wrap(err, "generating the Corso CLI markdown")) fatal(errors.Wrap(err, "generating the Corso CLI markdown"))

View File

@ -42,6 +42,7 @@ func doFolderPurge(cmd *cobra.Command, args []string) error {
M365: credentials.GetM365(), M365: credentials.GetM365(),
TenantID: common.First(tenant, os.Getenv(account.TenantID)), TenantID: common.First(tenant, os.Getenv(account.TenantID)),
} }
acct, err := account.NewAccount(account.ProviderM365, m365Cfg) acct, err := account.NewAccount(account.ProviderM365, m365Cfg)
if err != nil { if err != nil {
return Only(ctx, errors.Wrap(err, "finding m365 account details")) 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")) return Only(ctx, errors.Wrap(err, "parsing before flag to time"))
} }
} }
stLen := len(common.SimpleDateTimeFormat) stLen := len(common.SimpleDateTimeFormat)
// delete files // delete files
for _, mf := range mfs { for _, mf := range mfs {
// compare the folder time to the deletion boundary time first // compare the folder time to the deletion boundary time first
var del bool var (
dnLen := len(mf.DisplayName) del bool
dnLen = len(mf.DisplayName)
)
if dnLen > stLen { if dnLen > stLen {
dnSuff := mf.DisplayName[dnLen-stLen:] dnSuff := mf.DisplayName[dnLen-stLen:]
dnTime, err := common.ParseTime(dnSuff) dnTime, err := common.ParseTime(dnSuff)
if err != nil { if err != nil {
Info(ctx, errors.Wrapf(err, "Error: deleting folder [%s]", mf.DisplayName)) Info(ctx, errors.Wrapf(err, "Error: deleting folder [%s]", mf.DisplayName))
continue continue
} }
del = dnTime.Before(beforeTime) del = dnTime.Before(beforeTime)
} }
@ -90,6 +96,7 @@ func doFolderPurge(cmd *cobra.Command, args []string) error {
} }
Info(ctx, "Deleting folder: ", mf.DisplayName) Info(ctx, "Deleting folder: ", mf.DisplayName)
err = exchange.DeleteMailFolder(gc.Service(), user, mf.ID) err = exchange.DeleteMailFolder(gc.Service(), user, mf.ID)
if err != nil { if err != nil {
Info(ctx, errors.Wrapf(err, "Error: deleting folder [%s]", mf.DisplayName)) Info(ctx, errors.Wrapf(err, "Error: deleting folder [%s]", mf.DisplayName))