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:
parent
8bebf579a4
commit
532c5c017b
@ -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
|
||||
|
||||
@ -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
|
||||
// ---------------------------------------------------------------------------------------------------------
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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=
|
||||
|
||||
@ -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")
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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")
|
||||
}
|
||||
|
||||
|
||||
@ -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")
|
||||
}
|
||||
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user