update backup cli output (#2903)

changes a couple things in the backup cli output:
* appends the backup id after completion
* prints an error if the backup fails
* replaces multierr with a clues error
* logs each backup failure individually

---

#### Does this PR need a docs update or release note?

- [x]  No

#### Type of change

- [x] 🧹 Tech Debt/Cleanup

#### Issue(s)

* #1970

#### Test Plan

- [x] 💪 Manual
This commit is contained in:
Keepers 2023-03-22 10:01:02 -06:00 committed by GitHub
parent 8bebf579a4
commit 532c5c017b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 94 additions and 131 deletions

View File

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

View File

@ -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
// ---------------------------------------------------------------------------------------------------------

View File

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

View File

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

View File

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

View File

@ -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=

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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,

View File

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

View File

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