diff --git a/src/cli/backup/backup.go b/src/cli/backup/backup.go index 801d0b97d..65e8a69a9 100644 --- a/src/cli/backup/backup.go +++ b/src/cli/backup/backup.go @@ -2,10 +2,10 @@ package backup import ( "context" + "fmt" "strings" - "github.com/hashicorp/go-multierror" - + "github.com/alcionai/clues" "github.com/alcionai/corso/src/cli/config" "github.com/alcionai/corso/src/cli/options" . "github.com/alcionai/corso/src/cli/print" @@ -14,6 +14,7 @@ import ( "github.com/alcionai/corso/src/internal/model" "github.com/alcionai/corso/src/pkg/account" "github.com/alcionai/corso/src/pkg/backup" + "github.com/alcionai/corso/src/pkg/logger" "github.com/alcionai/corso/src/pkg/path" "github.com/alcionai/corso/src/pkg/repository" "github.com/alcionai/corso/src/pkg/selectors" @@ -202,48 +203,53 @@ func runBackups( selectorSet []selectors.Selector, ) error { var ( - merrs *multierror.Error - bIDs []model.StableID + bIDs []model.StableID + errs = []error{} ) for _, discSel := range selectorSet { - bo, err := r.NewBackup(ctx, discSel) + var ( + owner = discSel.DiscreteOwner + bctx = clues.Add(ctx, "resource_owner", owner) + ) + + bo, err := r.NewBackup(bctx, discSel) if err != nil { - merrs = multierror.Append(merrs, errors.Wrapf( - err, - "Failed to initialize %s backup for %s %s", - serviceName, - resourceOwnerType, - discSel.DiscreteOwner)) + errs = append(errs, clues.Wrap(err, owner).WithClues(bctx)) + Errf(bctx, "%v\n", err) continue } - err = bo.Run(ctx) + err = bo.Run(bctx) if err != nil { - merrs = multierror.Append(merrs, errors.Wrapf( - err, - "Failed to run %s backup for %s %s", - serviceName, - resourceOwnerType, - discSel.DiscreteOwner)) + errs = append(errs, clues.Wrap(err, owner).WithClues(bctx)) + Errf(bctx, "%v\n", err) continue } bIDs = append(bIDs, bo.Results.BackupID) + Infof(ctx, "Done - ID: %v\n", bo.Results.BackupID) } - bups, ferrs := r.Backups(ctx, bIDs) - // TODO: print/log recoverable errors - if ferrs.Failure() != nil { - return Only(ctx, errors.Wrap(ferrs.Failure(), "Unable to retrieve backup results from storage")) + bups, berrs := r.Backups(ctx, bIDs) + if berrs.Failure() != nil { + return Only(ctx, errors.Wrap(berrs.Failure(), "Unable to retrieve backup results from storage")) } + Info(ctx, "Completed Backups:") backup.PrintAll(ctx, bups) - if e := merrs.ErrorOrNil(); e != nil { - return Only(ctx, e) + if len(errs) > 0 { + sb := fmt.Sprintf("%d of %d backups failed:\n", len(errs), len(selectorSet)) + + for i, e := range errs { + logger.CtxErr(ctx, e).Errorf("Backup %d of %d failed", i+1, len(selectorSet)) + sb += "∙ " + e.Error() + "\n" + } + + return Only(ctx, clues.New(sb)) } return nil diff --git a/src/cli/print/print.go b/src/cli/print/print.go index f8d95c570..98c774015 100644 --- a/src/cli/print/print.go +++ b/src/cli/print/print.go @@ -76,17 +76,14 @@ func Only(ctx context.Context, e error) error { // if s is nil, prints nothing. // Prepends the message with "Error: " func Err(ctx context.Context, s ...any) { - err(getRootCmd(ctx).ErrOrStderr(), s...) + out(getRootCmd(ctx).ErrOrStderr()) } -// err is the testable core of Err() -func err(w io.Writer, s ...any) { - if len(s) == 0 { - return - } - - msg := append([]any{"Error: "}, s...) - fmt.Fprint(w, msg...) +// Errf prints the params to cobra's error writer (stdErr by default) +// if s is nil, prints nothing. +// Prepends the message with "Error: " +func Errf(ctx context.Context, tmpl string, s ...any) { + outf(getRootCmd(ctx).ErrOrStderr(), "Error: "+tmpl, s...) } // Out prints the params to cobra's output writer (stdOut by default) @@ -95,7 +92,25 @@ func Out(ctx context.Context, s ...any) { out(getRootCmd(ctx).OutOrStdout(), s...) } -// out is the testable core of Out() +// Out prints the formatted strings to cobra's output writer (stdOut by default) +// if t is empty, prints nothing. +func Outf(ctx context.Context, t string, s ...any) { + outf(getRootCmd(ctx).OutOrStdout(), t, s...) +} + +// Info prints the params to cobra's error writer (stdErr by default) +// if s is nil, prints nothing. +func Info(ctx context.Context, s ...any) { + out(getRootCmd(ctx).ErrOrStderr(), s...) +} + +// Info prints the formatted strings to cobra's error writer (stdErr by default) +// if t is empty, prints nothing. +func Infof(ctx context.Context, t string, s ...any) { + outf(getRootCmd(ctx).ErrOrStderr(), t, s...) +} + +// out is the testable core of exported print funcs func out(w io.Writer, s ...any) { if len(s) == 0 { return @@ -105,13 +120,7 @@ func out(w io.Writer, s ...any) { fmt.Fprintf(w, "\n") } -// Out prints the formatted strings to cobra's output writer (stdOut by default) -// if t is empty, prints nothing. -func Outf(ctx context.Context, t string, s ...any) { - outf(getRootCmd(ctx).OutOrStdout(), t, s...) -} - -// outf is the testable core of Outf() +// outf is the testable core of exported print funcs func outf(w io.Writer, t string, s ...any) { if len(t) == 0 { return @@ -121,38 +130,6 @@ func outf(w io.Writer, t string, s ...any) { fmt.Fprintf(w, "\n") } -// Info prints the params to cobra's error writer (stdErr by default) -// if s is nil, prints nothing. -func Info(ctx context.Context, s ...any) { - info(getRootCmd(ctx).ErrOrStderr(), s...) -} - -// info is the testable core of Info() -func info(w io.Writer, s ...any) { - if len(s) == 0 { - return - } - - fmt.Fprint(w, s...) - fmt.Fprintf(w, "\n") -} - -// Info prints the formatted strings to cobra's error writer (stdErr by default) -// if t is empty, prints nothing. -func Infof(ctx context.Context, t string, s ...any) { - infof(getRootCmd(ctx).ErrOrStderr(), t, s...) -} - -// infof is the testable core of Infof() -func infof(w io.Writer, t string, s ...any) { - if len(t) == 0 { - return - } - - fmt.Fprintf(w, t, s...) - fmt.Fprintf(w, "\n") -} - // --------------------------------------------------------------------------------------------------------- // Output control for backup list/details // --------------------------------------------------------------------------------------------------------- diff --git a/src/cli/print/print_test.go b/src/cli/print/print_test.go index aa41394bd..c495c933b 100644 --- a/src/cli/print/print_test.go +++ b/src/cli/print/print_test.go @@ -33,32 +33,22 @@ func (suite *PrintUnitSuite) TestOnly() { assert.True(t, c.SilenceUsage) } -func (suite *PrintUnitSuite) TestErr() { +func (suite *PrintUnitSuite) TestOut() { t := suite.T() b := bytes.Buffer{} msg := "I have seen the fnords!" - err(&b, msg) - assert.Contains(t, b.String(), "Error: ") + out(&b, msg) assert.Contains(t, b.String(), msg) } -func (suite *PrintUnitSuite) TestInfo() { - t := suite.T() - b := bytes.Buffer{} - msg := "I have seen the fnords!" - - info(&b, msg) - assert.Contains(t, b.String(), msg) -} - -func (suite *PrintUnitSuite) TestInfof() { +func (suite *PrintUnitSuite) TestOutf() { t := suite.T() b := bytes.Buffer{} msg := "I have seen the fnords!" msg2 := "smarf" - infof(&b, msg, msg2) + outf(&b, msg, msg2) bs := b.String() assert.Contains(t, bs, msg) assert.Contains(t, bs, msg2) diff --git a/src/cmd/purge/purge.go b/src/cmd/purge/purge.go index 74be0e805..3f5747cf6 100644 --- a/src/cmd/purge/purge.go +++ b/src/cmd/purge/purge.go @@ -6,7 +6,6 @@ import ( "time" "github.com/alcionai/clues" - "github.com/hashicorp/go-multierror" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -134,32 +133,22 @@ func runPurgeForEachUser( boundary time.Time, ps ...purger, ) error { - var ( - errs error - ferrs = fault.New(false) - ) - - users, err := m365.Users(ctx, acct, ferrs) + users, err := m365.Users(ctx, acct, fault.New(true)) if err != nil { return clues.Wrap(err, "getting users") } - if len(ferrs.Errors().Recovered) > 0 { - // TODO(keepers): remove multierr - errs = multierror.Append(errs, ferrs.Recovered()...) - } - for _, u := range userOrUsers(user, users) { Infof(ctx, "\nUser: %s - %s", u.PrincipalName, u.ID) for _, p := range ps { if err := p(ctx, gc, boundary, u.PrincipalName); err != nil { - errs = multierror.Append(errs, err) + return err } } } - return errs + return nil } // ----- OneDrive @@ -240,10 +229,9 @@ func purgeFolders( dnTime, err := common.ExtractTime(displayName) if err != nil && !errors.Is(err, common.ErrNoTimeString) { err = errors.Wrapf(err, "!! Error: parsing container named [%s]", displayName) - errs = multierror.Append(errs, err) Info(ctx, err) - continue + return err } if !dnTime.Before(boundary) || dnTime == (time.Time{}) { @@ -255,7 +243,6 @@ func purgeFolders( err = deleter(gc.Service, uid, fld) if err != nil { err = errors.Wrapf(err, "!! Error") - errs = multierror.Append(errs, err) Info(ctx, err) } } diff --git a/src/go.mod b/src/go.mod index 2bf902150..a2fd5fa96 100644 --- a/src/go.mod +++ b/src/go.mod @@ -9,7 +9,6 @@ require ( github.com/aws/aws-xray-sdk-go v1.8.1 github.com/cenkalti/backoff/v4 v4.2.0 github.com/google/uuid v1.3.0 - github.com/hashicorp/go-multierror v1.1.1 github.com/kopia/kopia v0.12.2-0.20230123092305-e5387cec0acb github.com/microsoft/kiota-abstractions-go v0.18.0 github.com/microsoft/kiota-authentication-azure-go v0.6.0 @@ -69,7 +68,6 @@ require ( github.com/go-logr/stdr v1.2.2 // indirect github.com/golang-jwt/jwt/v4 v4.4.2 // indirect github.com/golang/protobuf v1.5.2 // indirect - github.com/hashicorp/errwrap v1.0.0 // indirect github.com/hashicorp/golang-lru v0.5.4 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect diff --git a/src/go.sum b/src/go.sum index fe4f8e5f8..ee1467596 100644 --- a/src/go.sum +++ b/src/go.sum @@ -197,10 +197,6 @@ github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8 github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= github.com/hanwen/go-fuse/v2 v2.1.1-0.20220112183258-f57e95bda82d h1:ibbzF2InxMOS+lLCphY9PHNKPURDUBNKaG6ErSq8gJQ= -github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= -github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= diff --git a/src/internal/connector/exchange/contact_folder_cache.go b/src/internal/connector/exchange/contact_folder_cache.go index 624503563..615afec15 100644 --- a/src/internal/connector/exchange/contact_folder_cache.go +++ b/src/internal/connector/exchange/contact_folder_cache.go @@ -60,7 +60,7 @@ func (cfc *contactFolderCache) Populate( return errors.Wrap(err, "enumerating containers") } - if err := cfc.populatePaths(ctx, false); err != nil { + if err := cfc.populatePaths(ctx, false, errs); err != nil { return errors.Wrap(err, "populating paths") } diff --git a/src/internal/connector/exchange/container_resolver.go b/src/internal/connector/exchange/container_resolver.go index 7adf84a4a..150318c56 100644 --- a/src/internal/connector/exchange/container_resolver.go +++ b/src/internal/connector/exchange/container_resolver.go @@ -4,7 +4,6 @@ import ( "context" "github.com/alcionai/clues" - "github.com/hashicorp/go-multierror" "github.com/pkg/errors" "github.com/alcionai/corso/src/internal/common/ptr" @@ -193,16 +192,29 @@ func (cr *containerResolver) DestinationNameToID(dest string) string { return "" } -func (cr *containerResolver) populatePaths(ctx context.Context, useIDInPath bool) error { - var errs *multierror.Error +func (cr *containerResolver) populatePaths( + ctx context.Context, + useIDInPath bool, + errs *fault.Bus, +) error { + var ( + el = errs.Local() + lastErr error + ) // Populate all folder paths. for _, f := range cr.Items() { + if el.Failure() != nil { + return el.Failure() + } + _, _, err := cr.IDToPath(ctx, ptr.Val(f.GetId()), useIDInPath) if err != nil { - errs = multierror.Append(errs, errors.Wrap(err, "populating path")) + err = clues.Wrap(err, "populating path") + el.AddRecoverable(err) + lastErr = err } } - return errs.ErrorOrNil() + return lastErr } diff --git a/src/internal/connector/exchange/container_resolver_test.go b/src/internal/connector/exchange/container_resolver_test.go index deff809d0..3679be029 100644 --- a/src/internal/connector/exchange/container_resolver_test.go +++ b/src/internal/connector/exchange/container_resolver_test.go @@ -370,7 +370,7 @@ func (suite *ConfiguredFolderCacheUnitSuite) TestPopulatePaths() { t := suite.T() - err := suite.fc.populatePaths(ctx, false) + err := suite.fc.populatePaths(ctx, false, fault.New(true)) require.NoError(t, err, clues.ToCore(err)) items := suite.fc.Items() diff --git a/src/internal/connector/exchange/event_calendar_cache.go b/src/internal/connector/exchange/event_calendar_cache.go index 72d9a93f9..5e57140bd 100644 --- a/src/internal/connector/exchange/event_calendar_cache.go +++ b/src/internal/connector/exchange/event_calendar_cache.go @@ -81,7 +81,7 @@ func (ecc *eventCalendarCache) Populate( return errors.Wrap(err, "enumerating containers") } - if err := ecc.populatePaths(ctx, true); err != nil { + if err := ecc.populatePaths(ctx, true, errs); err != nil { return errors.Wrap(err, "establishing calendar paths") } diff --git a/src/internal/connector/exchange/mail_folder_cache.go b/src/internal/connector/exchange/mail_folder_cache.go index 91ca208ce..520a2611a 100644 --- a/src/internal/connector/exchange/mail_folder_cache.go +++ b/src/internal/connector/exchange/mail_folder_cache.go @@ -85,7 +85,7 @@ func (mc *mailFolderCache) Populate( return errors.Wrap(err, "enumerating containers") } - if err := mc.populatePaths(ctx, false); err != nil { + if err := mc.populatePaths(ctx, false, errs); err != nil { return errors.Wrap(err, "populating paths") } diff --git a/src/internal/observe/observe.go b/src/internal/observe/observe.go index 3476fa94c..50e7411b1 100644 --- a/src/internal/observe/observe.go +++ b/src/internal/observe/observe.go @@ -121,8 +121,7 @@ func SeedWriter(ctx context.Context, w io.Writer, c *config) { contxt, mpb.WithWidth(progressBarWidth), mpb.WithWaitGroup(&wg), - mpb.WithOutput(writer), - ) + mpb.WithOutput(writer)) } // Complete blocks until the progress finishes writing out all data. @@ -172,8 +171,7 @@ func Message(ctx context.Context, msgs ...cleanable) { decor.WC{ W: len(message) + 1, C: decor.DidentRight, - })), - ) + }))) // Complete the bar immediately bar.SetTotal(-1, true) @@ -210,8 +208,7 @@ func MessageWithCompletion( mpb.PrependDecorators( decor.Name(message+":"), decor.Elapsed(decor.ET_STYLE_GO, decor.WC{W: 8})), - mpb.BarFillerOnComplete("done"), - ) + mpb.BarFillerOnComplete("done")) go listen( ctx, diff --git a/src/internal/operations/backup.go b/src/internal/operations/backup.go index 675631b6f..f60641484 100644 --- a/src/internal/operations/backup.go +++ b/src/internal/operations/backup.go @@ -159,11 +159,11 @@ func (op *BackupOperation) Run(ctx context.Context) (err error) { // No return here! We continue down to persistResults, even in case of failure. logger.Ctx(ctx). With("err", err). - Errorw("doing backup", clues.InErr(err).Slice()...) - op.Errors.Fail(errors.Wrap(err, "doing backup")) + Errorw("running backup", clues.InErr(err).Slice()...) + op.Errors.Fail(errors.Wrap(err, "running backup")) } - LogFaultErrors(ctx, op.Errors.Errors(), "doing backup") + LogFaultErrors(ctx, op.Errors.Errors(), "running backup") // ----- // Persistence diff --git a/src/internal/operations/restore.go b/src/internal/operations/restore.go index 3593176a7..d76396a74 100644 --- a/src/internal/operations/restore.go +++ b/src/internal/operations/restore.go @@ -149,11 +149,11 @@ func (op *RestoreOperation) Run(ctx context.Context) (restoreDetails *details.De // No return here! We continue down to persistResults, even in case of failure. logger.Ctx(ctx). With("err", err). - Errorw("doing restore", clues.InErr(err).Slice()...) - op.Errors.Fail(errors.Wrap(err, "doing restore")) + Errorw("running restore", clues.InErr(err).Slice()...) + op.Errors.Fail(errors.Wrap(err, "running restore")) } - LogFaultErrors(ctx, op.Errors.Errors(), "doing restore") + LogFaultErrors(ctx, op.Errors.Errors(), "running restore") // ----- // Persistence