Make the cli colorful and informational (#4525)
This commit include a few things that improves the progresbars and other info messages are printed out during the CLI run.  --- #### Does this PR need a docs update or release note? - [ ] ✅ Yes, it's included - [x] 🕐 Yes, but in a later PR - [ ] ⛔ No #### Type of change <!--- Please check the type of change your PR introduces: ---> - [x] 🌻 Feature - [ ] 🐛 Bugfix - [ ] 🗺️ Documentation - [ ] 🤖 Supportability/Tests - [ ] 💻 CI/Deployment - [ ] 🧹 Tech Debt/Cleanup #### Issue(s) <!-- Can reference multiple issues. Use one of the following "magic words" - "closes, fixes" to auto-close the Github issue. --> * #<issue> #### Test Plan <!-- How will this be tested prior to merging.--> - [x] 💪 Manual - [ ] ⚡ Unit test - [ ] 💚 E2E
This commit is contained in:
parent
f2102e55f6
commit
b46f242bc4
@ -2,6 +2,7 @@ package backup
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
@ -12,8 +13,10 @@ import (
|
||||
"github.com/alcionai/corso/src/cli/flags"
|
||||
. "github.com/alcionai/corso/src/cli/print"
|
||||
"github.com/alcionai/corso/src/cli/utils"
|
||||
"github.com/alcionai/corso/src/internal/common/color"
|
||||
"github.com/alcionai/corso/src/internal/common/idname"
|
||||
"github.com/alcionai/corso/src/internal/data"
|
||||
"github.com/alcionai/corso/src/internal/observe"
|
||||
"github.com/alcionai/corso/src/pkg/backup"
|
||||
"github.com/alcionai/corso/src/pkg/backup/details"
|
||||
"github.com/alcionai/corso/src/pkg/control"
|
||||
@ -187,8 +190,15 @@ func genericCreateCommand(
|
||||
|
||||
bo, err := r.NewBackupWithLookup(ictx, discSel, ins)
|
||||
if err != nil {
|
||||
errs = append(errs, clues.WrapWC(ictx, err, owner))
|
||||
Errf(ictx, "%v\n", err)
|
||||
cerr := clues.WrapWC(ictx, err, owner)
|
||||
errs = append(errs, cerr)
|
||||
|
||||
meta, err := json.Marshal(cerr.Core().Values)
|
||||
if err != nil {
|
||||
meta = []byte("Unable to marshal error metadata")
|
||||
}
|
||||
|
||||
Errf(ictx, "%s\nMessage: %v\nMetadata:%s", "Unable to complete backup", err, meta)
|
||||
|
||||
continue
|
||||
}
|
||||
@ -208,8 +218,15 @@ func genericCreateCommand(
|
||||
continue
|
||||
}
|
||||
|
||||
errs = append(errs, clues.WrapWC(ictx, err, owner))
|
||||
Errf(ictx, "%v\n", err)
|
||||
cerr := clues.WrapWC(ictx, err, owner)
|
||||
errs = append(errs, cerr)
|
||||
|
||||
meta, err := json.Marshal(cerr.Core().Values)
|
||||
if err != nil {
|
||||
meta = []byte("Unable to marshal error metadata")
|
||||
}
|
||||
|
||||
Errf(ictx, "%s\nMessage: %v\nMetadata:%s", "Unable to complete backup", err, meta)
|
||||
|
||||
continue
|
||||
}
|
||||
@ -217,10 +234,10 @@ func genericCreateCommand(
|
||||
bIDs = append(bIDs, string(bo.Results.BackupID))
|
||||
|
||||
if !DisplayJSONFormat() {
|
||||
Infof(ctx, "Done\n")
|
||||
Infof(ctx, fmt.Sprintf("Backup complete %s %s", observe.Bullet, color.BlueOutput(bo.Results.BackupID)))
|
||||
printBackupStats(ctx, r, string(bo.Results.BackupID))
|
||||
} else {
|
||||
Infof(ctx, "Done - ID: %v\n", bo.Results.BackupID)
|
||||
Infof(ctx, "Backup complete - ID: %v\n", bo.Results.BackupID)
|
||||
}
|
||||
}
|
||||
|
||||
@ -231,9 +248,8 @@ func genericCreateCommand(
|
||||
|
||||
if len(bups) > 0 {
|
||||
Info(ctx, "Completed Backups:")
|
||||
}
|
||||
|
||||
backup.PrintAll(ctx, bups)
|
||||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
sb := fmt.Sprintf("%d of %d backups failed:\n", len(errs), len(selectorSet))
|
||||
|
||||
@ -73,7 +73,7 @@ func preRun(cc *cobra.Command, args []string) error {
|
||||
|
||||
func handleMailBoxFlag(ctx context.Context, c *cobra.Command, flagNames []string) {
|
||||
if !slices.Contains(flagNames, "user") && !slices.Contains(flagNames, "mailbox") {
|
||||
print.Errf(ctx, "either --user or --mailbox flag is required")
|
||||
print.Err(ctx, "either --user or --mailbox flag is required")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
|
||||
@ -107,7 +107,7 @@ func runExport(
|
||||
|
||||
// It would be better to give a progressbar than a spinner, but we
|
||||
// have any way of knowing how many files are available as of now.
|
||||
diskWriteComplete := observe.MessageWithCompletion(ctx, "Writing data to disk")
|
||||
diskWriteComplete := observe.MessageWithCompletion(ctx, observe.ProgressCfg{}, "Writing data to disk")
|
||||
|
||||
err = export.ConsumeExportCollections(ctx, exportLocation, expColl, eo.Errors)
|
||||
|
||||
|
||||
@ -10,6 +10,7 @@ import (
|
||||
"github.com/tidwall/pretty"
|
||||
"github.com/tomlazar/table"
|
||||
|
||||
"github.com/alcionai/corso/src/internal/common/color"
|
||||
"github.com/alcionai/corso/src/internal/observe"
|
||||
)
|
||||
|
||||
@ -83,16 +84,21 @@ func Only(ctx context.Context, e error) error {
|
||||
|
||||
// Err prints the params to cobra's error writer (stdErr by default)
|
||||
// if s is nil, prints nothing.
|
||||
// Prepends the message with "Error: "
|
||||
func Err(ctx context.Context, s ...any) {
|
||||
out(ctx, getRootCmd(ctx).ErrOrStderr(), s...)
|
||||
cw := color.NewColorableWriter(color.Red, getRootCmd(ctx).ErrOrStderr())
|
||||
|
||||
s = append([]any{"Error:"}, s...)
|
||||
|
||||
out(ctx, cw, s...)
|
||||
}
|
||||
|
||||
// Errf prints the params to cobra's error writer (stdErr by default)
|
||||
// if s is nil, prints nothing.
|
||||
// Prepends the message with "Error: "
|
||||
// You should ideally be using SimpleError or OperationError.
|
||||
func Errf(ctx context.Context, tmpl string, s ...any) {
|
||||
outf(ctx, getRootCmd(ctx).ErrOrStderr(), "\nError: \n\t"+tmpl+"\n", s...)
|
||||
cw := color.NewColorableWriter(color.Red, getRootCmd(ctx).ErrOrStderr())
|
||||
tmpl = "Error: " + tmpl
|
||||
outf(ctx, cw, tmpl, s...)
|
||||
}
|
||||
|
||||
// Out prints the params to cobra's output writer (stdOut by default)
|
||||
|
||||
@ -10,6 +10,7 @@ require (
|
||||
github.com/armon/go-metrics v0.4.1
|
||||
github.com/aws/aws-xray-sdk-go v1.8.3
|
||||
github.com/cenkalti/backoff/v4 v4.2.1
|
||||
github.com/fatih/color v1.15.0
|
||||
github.com/golang-jwt/jwt/v5 v5.1.0
|
||||
github.com/google/uuid v1.4.0
|
||||
github.com/h2non/gock v1.2.0
|
||||
@ -51,7 +52,6 @@ require (
|
||||
github.com/aws/aws-sdk-go v1.47.9 // indirect
|
||||
github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a // indirect
|
||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||
github.com/go-test/deep v1.1.0 // indirect
|
||||
github.com/gofrs/flock v0.8.1 // indirect
|
||||
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f // indirect
|
||||
github.com/google/go-cmp v0.5.9 // indirect
|
||||
|
||||
@ -119,6 +119,8 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
|
||||
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
|
||||
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
|
||||
github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY=
|
||||
github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
|
||||
|
||||
36
src/internal/common/color/color.go
Normal file
36
src/internal/common/color/color.go
Normal file
@ -0,0 +1,36 @@
|
||||
package color
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/fatih/color"
|
||||
)
|
||||
|
||||
var (
|
||||
Red = color.FgRed
|
||||
Blue = color.FgBlue
|
||||
Magenta = color.FgMagenta
|
||||
Cyan = color.FgCyan
|
||||
Green = color.FgGreen
|
||||
White = color.FgWhite
|
||||
|
||||
RedOutput = color.New(Red).SprintFunc()
|
||||
BlueOutput = color.New(Blue).SprintFunc()
|
||||
MagentaOutput = color.New(Magenta).SprintFunc()
|
||||
CyanOutput = color.New(Cyan).SprintFunc()
|
||||
GreenOutput = color.New(Green).SprintFunc()
|
||||
GreyOutput = color.New(White).SprintFunc()
|
||||
)
|
||||
|
||||
type colorableWriter struct {
|
||||
color color.Attribute
|
||||
writer io.Writer
|
||||
}
|
||||
|
||||
func NewColorableWriter(clr color.Attribute, writer io.Writer) io.Writer {
|
||||
return &colorableWriter{clr, writer}
|
||||
}
|
||||
|
||||
func (cw *colorableWriter) Write(p []byte) (n int, err error) {
|
||||
return color.New(cw.color).Fprint(cw.writer, string(p))
|
||||
}
|
||||
@ -651,6 +651,7 @@ func (w Wrapper) RepoMaintenance(
|
||||
if len(params.Owner) == 0 || (params.Owner != currentOwner && opts.Force) {
|
||||
observe.Message(
|
||||
ctx,
|
||||
observe.ProgressCfg{},
|
||||
"updating maintenance user@host to ",
|
||||
clues.Hide(currentOwner))
|
||||
|
||||
|
||||
@ -3,7 +3,6 @@ package drive
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
@ -18,7 +17,6 @@ import (
|
||||
"github.com/alcionai/corso/src/internal/m365/collection/drive/metadata"
|
||||
odConsts "github.com/alcionai/corso/src/internal/m365/service/onedrive/consts"
|
||||
"github.com/alcionai/corso/src/internal/m365/support"
|
||||
"github.com/alcionai/corso/src/internal/observe"
|
||||
bupMD "github.com/alcionai/corso/src/pkg/backup/metadata"
|
||||
"github.com/alcionai/corso/src/pkg/control"
|
||||
"github.com/alcionai/corso/src/pkg/fault"
|
||||
@ -283,11 +281,6 @@ func (c *Collections) Get(
|
||||
driveTombstones[driveID] = struct{}{}
|
||||
}
|
||||
|
||||
progressBar := observe.MessageWithCompletion(
|
||||
ctx,
|
||||
observe.Bulletf(path.FilesCategory.HumanString()))
|
||||
defer close(progressBar)
|
||||
|
||||
// Enumerate drives for the specified resourceOwner
|
||||
pager := c.handler.NewDrivePager(c.protectedResource.ID(), nil)
|
||||
|
||||
@ -456,8 +449,6 @@ func (c *Collections) Get(
|
||||
}
|
||||
}
|
||||
|
||||
observe.Message(ctx, fmt.Sprintf("Discovered %d items to backup", c.NumItems))
|
||||
|
||||
collections := []data.BackupCollection{}
|
||||
|
||||
// add all the drives we found
|
||||
|
||||
@ -2,6 +2,7 @@ package exchange
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/alcionai/clues"
|
||||
|
||||
@ -42,6 +43,8 @@ func CreateCollections(
|
||||
ProtectedResource: bpc.ProtectedResource,
|
||||
TenantID: tenantID,
|
||||
}
|
||||
collections map[string]data.BackupCollection
|
||||
err error
|
||||
)
|
||||
|
||||
handler, ok := handlers[category]
|
||||
@ -49,9 +52,15 @@ func CreateCollections(
|
||||
return nil, clues.NewWC(ctx, "unsupported backup category type")
|
||||
}
|
||||
|
||||
pcfg := observe.ProgressCfg{
|
||||
Indent: 1,
|
||||
CompletionMessage: func() string { return fmt.Sprintf("(found %d folders)", len(collections)) },
|
||||
}
|
||||
foldersComplete := observe.MessageWithCompletion(
|
||||
ctx,
|
||||
observe.Bulletf("%s", qp.Category.HumanString()))
|
||||
pcfg,
|
||||
qp.Category.HumanString())
|
||||
|
||||
defer close(foldersComplete)
|
||||
|
||||
rootFolder, cc := handler.NewContainerCache(bpc.ProtectedResource.ID())
|
||||
@ -60,7 +69,7 @@ func CreateCollections(
|
||||
return nil, clues.Wrap(err, "populating container cache")
|
||||
}
|
||||
|
||||
collections, err := populateCollections(
|
||||
collections, err = populateCollections(
|
||||
ctx,
|
||||
qp,
|
||||
handler,
|
||||
|
||||
@ -2,6 +2,8 @@ package site
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
stdpath "path"
|
||||
|
||||
"github.com/alcionai/clues"
|
||||
|
||||
@ -10,6 +12,7 @@ import (
|
||||
"github.com/alcionai/corso/src/internal/m365/collection/drive"
|
||||
betaAPI "github.com/alcionai/corso/src/internal/m365/service/sharepoint/api"
|
||||
"github.com/alcionai/corso/src/internal/m365/support"
|
||||
"github.com/alcionai/corso/src/internal/observe"
|
||||
"github.com/alcionai/corso/src/internal/operations/inject"
|
||||
"github.com/alcionai/corso/src/pkg/account"
|
||||
"github.com/alcionai/corso/src/pkg/count"
|
||||
@ -44,6 +47,18 @@ func CollectLibraries(
|
||||
bpc.Options)
|
||||
)
|
||||
|
||||
msg := fmt.Sprintf(
|
||||
"%s (%s)",
|
||||
path.LibrariesCategory.HumanString(),
|
||||
stdpath.Base(bpc.ProtectedResource.Name()))
|
||||
|
||||
pcfg := observe.ProgressCfg{
|
||||
Indent: 1,
|
||||
CompletionMessage: func() string { return fmt.Sprintf("(found %d items)", colls.NumItems) },
|
||||
}
|
||||
progressBar := observe.MessageWithCompletion(ctx, pcfg, msg)
|
||||
close(progressBar)
|
||||
|
||||
odcs, canUsePreviousBackup, err := colls.Get(ctx, bpc.MetadataCollections, ssmb, errs)
|
||||
if err != nil {
|
||||
return nil, false, graph.Wrap(ctx, err, "getting library")
|
||||
|
||||
@ -2,6 +2,7 @@ package groups
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/alcionai/clues"
|
||||
"github.com/kopia/kopia/repo/manifest"
|
||||
@ -70,11 +71,6 @@ func ProduceBackupCollections(
|
||||
break
|
||||
}
|
||||
|
||||
progressBar := observe.MessageWithCompletion(
|
||||
ctx,
|
||||
observe.Bulletf("%s", scope.Category().PathType().HumanString()))
|
||||
defer close(progressBar)
|
||||
|
||||
var dbcs []data.BackupCollection
|
||||
|
||||
switch scope.Category().PathType() {
|
||||
@ -134,15 +130,27 @@ func ProduceBackupCollections(
|
||||
|
||||
dbcs = append(dbcs, cs...)
|
||||
}
|
||||
|
||||
case path.ChannelMessagesCategory:
|
||||
var (
|
||||
cs []data.BackupCollection
|
||||
canUsePreviousBackup bool
|
||||
err error
|
||||
)
|
||||
|
||||
pcfg := observe.ProgressCfg{
|
||||
Indent: 1,
|
||||
// TODO(meain): Use number of messages and not channels
|
||||
CompletionMessage: func() string { return fmt.Sprintf("(found %d channels)", len(cs)) },
|
||||
}
|
||||
progressBar := observe.MessageWithCompletion(ctx, pcfg, scope.Category().PathType().HumanString())
|
||||
|
||||
if !isTeam {
|
||||
continue
|
||||
}
|
||||
|
||||
bh := groups.NewChannelBackupHandler(bpc.ProtectedResource.ID(), ac.Channels())
|
||||
|
||||
cs, canUsePreviousBackup, err := groups.CreateCollections(
|
||||
cs, canUsePreviousBackup, err = groups.CreateCollections(
|
||||
ctx,
|
||||
bpc,
|
||||
bh,
|
||||
@ -165,6 +173,8 @@ func ProduceBackupCollections(
|
||||
}
|
||||
|
||||
dbcs = append(dbcs, cs...)
|
||||
|
||||
close(progressBar)
|
||||
}
|
||||
|
||||
collections = append(collections, dbcs...)
|
||||
|
||||
@ -2,6 +2,7 @@ package onedrive
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/alcionai/clues"
|
||||
|
||||
@ -9,6 +10,7 @@ import (
|
||||
"github.com/alcionai/corso/src/internal/data"
|
||||
"github.com/alcionai/corso/src/internal/m365/collection/drive"
|
||||
"github.com/alcionai/corso/src/internal/m365/support"
|
||||
"github.com/alcionai/corso/src/internal/observe"
|
||||
"github.com/alcionai/corso/src/internal/operations/inject"
|
||||
"github.com/alcionai/corso/src/internal/version"
|
||||
"github.com/alcionai/corso/src/pkg/fault"
|
||||
@ -55,6 +57,14 @@ func ProduceBackupCollections(
|
||||
su,
|
||||
bpc.Options)
|
||||
|
||||
pcfg := observe.ProgressCfg{
|
||||
Indent: 1,
|
||||
CompletionMessage: func() string { return fmt.Sprintf("(found %d files)", nc.NumFiles) },
|
||||
}
|
||||
progressBar := observe.MessageWithCompletion(ctx, pcfg, path.FilesCategory.HumanString())
|
||||
|
||||
defer close(progressBar)
|
||||
|
||||
odcs, canUsePreviousBackup, err = nc.Get(ctx, bpc.MetadataCollections, ssmb, errs)
|
||||
if err != nil {
|
||||
el.AddRecoverable(ctx, clues.Stack(err).Label(fault.LabelForceNoBackupCreation))
|
||||
|
||||
@ -10,7 +10,6 @@ import (
|
||||
"github.com/alcionai/corso/src/internal/m365/collection/drive"
|
||||
"github.com/alcionai/corso/src/internal/m365/collection/site"
|
||||
"github.com/alcionai/corso/src/internal/m365/support"
|
||||
"github.com/alcionai/corso/src/internal/observe"
|
||||
"github.com/alcionai/corso/src/internal/operations/inject"
|
||||
"github.com/alcionai/corso/src/pkg/account"
|
||||
"github.com/alcionai/corso/src/pkg/count"
|
||||
@ -52,11 +51,6 @@ func ProduceBackupCollections(
|
||||
break
|
||||
}
|
||||
|
||||
progressBar := observe.MessageWithCompletion(
|
||||
ctx,
|
||||
observe.Bulletf("%s", scope.Category().PathType().HumanString()))
|
||||
defer close(progressBar)
|
||||
|
||||
var spcs []data.BackupCollection
|
||||
|
||||
switch scope.Category().PathType() {
|
||||
|
||||
@ -15,13 +15,14 @@ import (
|
||||
"github.com/vbauerster/mpb/v8"
|
||||
"github.com/vbauerster/mpb/v8/decor"
|
||||
|
||||
"github.com/alcionai/corso/src/internal/common/color"
|
||||
"github.com/alcionai/corso/src/pkg/logger"
|
||||
)
|
||||
|
||||
const (
|
||||
hideProgressBarsFN = "hide-progress"
|
||||
retainProgressBarsFN = "retain-progress"
|
||||
progressBarWidth = 32
|
||||
progressBarWidth = 40
|
||||
)
|
||||
|
||||
func init() {
|
||||
@ -170,8 +171,15 @@ const (
|
||||
// Progress Updates
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
type ProgressCfg struct {
|
||||
NewSection bool
|
||||
SectionIdentifier any
|
||||
Indent int
|
||||
CompletionMessage func() string
|
||||
}
|
||||
|
||||
// Message is used to display a progress message
|
||||
func Message(ctx context.Context, msgs ...any) {
|
||||
func Message(ctx context.Context, cfg ProgressCfg, msgs ...any) {
|
||||
var (
|
||||
obs = getObserver(ctx)
|
||||
plainSl = make([]string, 0, len(msgs))
|
||||
@ -186,18 +194,37 @@ func Message(ctx context.Context, msgs ...any) {
|
||||
plain := strings.Join(plainSl, " ")
|
||||
loggable := strings.Join(loggableSl, " ")
|
||||
|
||||
if cfg.SectionIdentifier != nil {
|
||||
plain = fmt.Sprintf(
|
||||
"%s %s %s",
|
||||
plain,
|
||||
Bullet,
|
||||
color.MagentaOutput(plainString(cfg.SectionIdentifier)))
|
||||
loggable = fmt.Sprintf("%s - %v", loggable, cfg.SectionIdentifier)
|
||||
}
|
||||
|
||||
logger.Ctx(ctx).Info(loggable)
|
||||
|
||||
if obs.hidden() {
|
||||
return
|
||||
}
|
||||
|
||||
if cfg.NewSection {
|
||||
// Empty bar to separate out section
|
||||
obs.wg.Add(1)
|
||||
empty := obs.mp.New(-1, mpb.NopStyle())
|
||||
empty.SetTotal(-1, true)
|
||||
waitAndCloseBar(ctx, empty, obs.wg, func() {})()
|
||||
}
|
||||
|
||||
obs.wg.Add(1)
|
||||
|
||||
bar := obs.mp.New(
|
||||
-1,
|
||||
mpb.NopStyle(),
|
||||
mpb.PrependDecorators(decor.Name(
|
||||
mpb.PrependDecorators(
|
||||
decor.Name("", decor.WC{W: cfg.Indent * 2}),
|
||||
decor.Name(
|
||||
plain,
|
||||
decor.WC{
|
||||
W: len(plain) + 1,
|
||||
@ -213,16 +240,38 @@ func Message(ctx context.Context, msgs ...any) {
|
||||
// that switches to "done" when the completion channel is signalled
|
||||
func MessageWithCompletion(
|
||||
ctx context.Context,
|
||||
msg any,
|
||||
cfg ProgressCfg,
|
||||
msgs ...any,
|
||||
) chan<- struct{} {
|
||||
var (
|
||||
obs = getObserver(ctx)
|
||||
plain = plainString(msg)
|
||||
loggable = fmt.Sprintf("%v", msg)
|
||||
log = logger.Ctx(ctx)
|
||||
plainSl = make([]string, 0, len(msgs))
|
||||
loggableSl = make([]string, 0, len(msgs))
|
||||
ch = make(chan struct{}, 1)
|
||||
)
|
||||
|
||||
for _, m := range msgs {
|
||||
plainSl = append(plainSl, plainString(m))
|
||||
loggableSl = append(loggableSl, fmt.Sprintf("%v", m))
|
||||
}
|
||||
|
||||
plain := strings.Join(plainSl, " ")
|
||||
loggable := strings.Join(loggableSl, " ")
|
||||
|
||||
if cfg.SectionIdentifier != nil {
|
||||
plain = fmt.Sprintf(
|
||||
"%s %s %s",
|
||||
plain,
|
||||
Bullet,
|
||||
color.MagentaOutput(plainString(cfg.SectionIdentifier)))
|
||||
loggable = fmt.Sprintf("%s - %v", loggable, cfg.SectionIdentifier)
|
||||
}
|
||||
|
||||
if cfg.Indent > 0 {
|
||||
plain = color.CyanOutput(plain)
|
||||
}
|
||||
|
||||
log.Info(loggable)
|
||||
|
||||
if obs.hidden() {
|
||||
@ -230,17 +279,45 @@ func MessageWithCompletion(
|
||||
return ch
|
||||
}
|
||||
|
||||
if cfg.NewSection {
|
||||
// Empty bar to separate out section
|
||||
obs.wg.Add(1)
|
||||
empty := obs.mp.New(-1, mpb.NopStyle())
|
||||
empty.SetTotal(-1, true)
|
||||
waitAndCloseBar(ctx, empty, obs.wg, func() {})()
|
||||
}
|
||||
|
||||
obs.wg.Add(1)
|
||||
|
||||
frames := []string{"∙∙∙", "●∙∙", "∙●∙", "∙∙●", "∙∙∙"}
|
||||
|
||||
// https://github.com/vbauerster/mpb/issues/71
|
||||
bfoc := mpb.BarFillerOnComplete(color.GreenOutput("done"))
|
||||
if cfg.CompletionMessage != nil {
|
||||
bfoc = mpb.BarFillerMiddleware(func(base mpb.BarFiller) mpb.BarFiller {
|
||||
filler := func(w io.Writer, st decor.Statistics) error {
|
||||
if st.Completed {
|
||||
msg := fmt.Sprintf("%s %s", color.GreenOutput("done"), color.GreyOutput(cfg.CompletionMessage()))
|
||||
_, err := io.WriteString(w, msg)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
return base.Fill(w, st)
|
||||
}
|
||||
|
||||
return mpb.BarFillerFunc(filler)
|
||||
})
|
||||
}
|
||||
|
||||
bar := obs.mp.New(
|
||||
-1,
|
||||
mpb.SpinnerStyle(frames...).PositionLeft(),
|
||||
mpb.PrependDecorators(
|
||||
decor.Name(plain+":"),
|
||||
decor.Name("", decor.WC{W: cfg.Indent * 2}),
|
||||
decor.Name(plain, decor.WC{W: progressBarWidth - cfg.Indent*2, C: decor.DidentRight}),
|
||||
decor.Elapsed(decor.ET_STYLE_GO, decor.WC{W: 8})),
|
||||
mpb.BarFillerOnComplete("done"))
|
||||
bfoc)
|
||||
|
||||
go listen(
|
||||
ctx,
|
||||
@ -520,7 +597,9 @@ func CollectionProgress(
|
||||
obs.wg.Add(1)
|
||||
|
||||
barOpts := []mpb.BarOption{
|
||||
mpb.PrependDecorators(decor.Name(string(category))),
|
||||
mpb.PrependDecorators(
|
||||
decor.Name("", decor.WC{W: 2}),
|
||||
decor.Name(color.CyanOutput(string(category)))),
|
||||
mpb.AppendDecorators(
|
||||
decor.CurrentNoUnit("%d - ", decor.WCSyncSpace),
|
||||
decor.Name(plain)),
|
||||
|
||||
@ -127,6 +127,30 @@ func (suite *ObserveProgressUnitSuite) TestCollectionProgress_unblockOnChannelCl
|
||||
}()
|
||||
}
|
||||
|
||||
func (suite *ObserveProgressUnitSuite) TestObserve_section() {
|
||||
t := suite.T()
|
||||
|
||||
ctx, flush := tester.NewContext(t)
|
||||
defer flush()
|
||||
|
||||
recorder := strings.Builder{}
|
||||
ctx = SeedObserver(ctx, &recorder, config{})
|
||||
|
||||
process := uuid.NewString()[:8]
|
||||
target := uuid.NewString()[:8]
|
||||
|
||||
pcfg := ProgressCfg{
|
||||
NewSection: true,
|
||||
SectionIdentifier: target,
|
||||
}
|
||||
Message(ctx, pcfg, process)
|
||||
|
||||
Flush(ctx)
|
||||
assert.NotEmpty(t, recorder)
|
||||
assert.Contains(t, recorder.String(), process)
|
||||
assert.Contains(t, recorder.String(), target)
|
||||
}
|
||||
|
||||
func (suite *ObserveProgressUnitSuite) TestObserve_message() {
|
||||
t := suite.T()
|
||||
|
||||
@ -138,7 +162,7 @@ func (suite *ObserveProgressUnitSuite) TestObserve_message() {
|
||||
|
||||
message := uuid.NewString()[:8]
|
||||
|
||||
Message(ctx, message)
|
||||
Message(ctx, ProgressCfg{}, message)
|
||||
Flush(ctx)
|
||||
assert.NotEmpty(t, recorder)
|
||||
assert.Contains(t, recorder.String(), message)
|
||||
@ -155,7 +179,7 @@ func (suite *ObserveProgressUnitSuite) TestObserve_progressWithChannelClosed() {
|
||||
|
||||
message := uuid.NewString()[:8]
|
||||
|
||||
ch := MessageWithCompletion(ctx, message)
|
||||
ch := MessageWithCompletion(ctx, ProgressCfg{}, message)
|
||||
|
||||
// Close channel without completing
|
||||
close(ch)
|
||||
@ -180,7 +204,7 @@ func (suite *ObserveProgressUnitSuite) TestObserve_progressWithContextCancelled(
|
||||
|
||||
message := uuid.NewString()[:8]
|
||||
|
||||
_ = MessageWithCompletion(ctx, message)
|
||||
_ = MessageWithCompletion(ctx, ProgressCfg{}, message)
|
||||
|
||||
// cancel context
|
||||
cancel()
|
||||
|
||||
@ -279,7 +279,11 @@ func (op *BackupOperation) Run(ctx context.Context) (err error) {
|
||||
// Execution
|
||||
// -----
|
||||
|
||||
observe.Message(ctx, "Backing Up", observe.Bullet, clues.Hide(op.ResourceOwner.Name()))
|
||||
pcfg := observe.ProgressCfg{
|
||||
NewSection: true,
|
||||
SectionIdentifier: clues.Hide(op.ResourceOwner.Name()),
|
||||
}
|
||||
observe.Message(ctx, pcfg, "Backing Up")
|
||||
|
||||
deets, err := op.do(
|
||||
ctx,
|
||||
@ -528,7 +532,7 @@ func produceBackupDataCollections(
|
||||
counter *count.Bus,
|
||||
errs *fault.Bus,
|
||||
) ([]data.BackupCollection, prefixmatcher.StringSetReader, bool, error) {
|
||||
progressBar := observe.MessageWithCompletion(ctx, "Discovering items to backup")
|
||||
progressBar := observe.MessageWithCompletion(ctx, observe.ProgressCfg{}, "Discovering items to backup")
|
||||
defer close(progressBar)
|
||||
|
||||
bpc := inject.BackupProducerConfig{
|
||||
@ -565,7 +569,7 @@ func consumeBackupCollections(
|
||||
"collection_source", "operations",
|
||||
"snapshot_type", "item data")
|
||||
|
||||
progressBar := observe.MessageWithCompletion(ctx, "Backing up data")
|
||||
progressBar := observe.MessageWithCompletion(ctx, observe.ProgressCfg{}, "Backing up data")
|
||||
defer close(progressBar)
|
||||
|
||||
tags := map[string]string{
|
||||
|
||||
@ -223,7 +223,11 @@ func (op *ExportOperation) do(
|
||||
return nil, clues.Wrap(err, "getting backup and details")
|
||||
}
|
||||
|
||||
observe.Message(ctx, "Exporting", observe.Bullet, clues.Hide(bup.Selector.DiscreteOwner))
|
||||
pcfg := observe.ProgressCfg{
|
||||
NewSection: true,
|
||||
SectionIdentifier: clues.Hide(bup.Selector.DiscreteOwner),
|
||||
}
|
||||
observe.Message(ctx, pcfg, "Exporting")
|
||||
|
||||
paths, err := formatDetailsForRestoration(ctx, bup.Version, op.Selectors, deets, op.ec, op.Errors)
|
||||
if err != nil {
|
||||
@ -239,9 +243,12 @@ func (op *ExportOperation) do(
|
||||
"backup_snapshot_id", bup.SnapshotID,
|
||||
"backup_version", bup.Version)
|
||||
|
||||
observe.Message(ctx, fmt.Sprintf("Discovered %d items in backup %s to export", len(paths), op.BackupID))
|
||||
observe.Message(
|
||||
ctx,
|
||||
observe.ProgressCfg{},
|
||||
fmt.Sprintf("Discovered %d items in backup %s to export", len(paths), op.BackupID))
|
||||
|
||||
kopiaComplete := observe.MessageWithCompletion(ctx, "Enumerating items in repository")
|
||||
kopiaComplete := observe.MessageWithCompletion(ctx, observe.ProgressCfg{}, "Enumerating items in repository")
|
||||
defer close(kopiaComplete)
|
||||
|
||||
dcs, err := op.kopia.ProduceRestoreCollections(ctx, bup.SnapshotID, paths, opStats.bytesRead, op.Errors)
|
||||
@ -332,7 +339,7 @@ func produceExportCollections(
|
||||
exportStats *data.ExportStats,
|
||||
errs *fault.Bus,
|
||||
) ([]export.Collectioner, error) {
|
||||
complete := observe.MessageWithCompletion(ctx, "Preparing export")
|
||||
complete := observe.MessageWithCompletion(ctx, observe.ProgressCfg{}, "Preparing export")
|
||||
defer func() {
|
||||
complete <- struct{}{}
|
||||
close(complete)
|
||||
|
||||
@ -250,7 +250,11 @@ func (op *RestoreOperation) do(
|
||||
return nil, clues.WrapWC(ctx, graph.ErrServiceNotEnabled, "service not enabled for restore")
|
||||
}
|
||||
|
||||
observe.Message(ctx, "Restoring", observe.Bullet, clues.Hide(restoreToProtectedResource.Name()))
|
||||
pcfg := observe.ProgressCfg{
|
||||
NewSection: true,
|
||||
SectionIdentifier: clues.Hide(restoreToProtectedResource.Name()),
|
||||
}
|
||||
observe.Message(ctx, pcfg, "Restoring")
|
||||
|
||||
paths, err := formatDetailsForRestoration(
|
||||
ctx,
|
||||
@ -270,9 +274,16 @@ func (op *RestoreOperation) do(
|
||||
"backup_snapshot_id", bup.SnapshotID,
|
||||
"backup_version", bup.Version)
|
||||
|
||||
observe.Message(ctx, fmt.Sprintf("Discovered %d items in backup %s to restore", len(paths), op.BackupID))
|
||||
if len(paths) == 0 {
|
||||
return nil, clues.New("no items match the provided filters")
|
||||
}
|
||||
|
||||
progressBar := observe.MessageWithCompletion(ctx, "Enumerating items in repository")
|
||||
observe.Message(
|
||||
ctx,
|
||||
observe.ProgressCfg{},
|
||||
fmt.Sprintf("Discovered %d items in backup %s to restore", len(paths), op.BackupID))
|
||||
|
||||
progressBar := observe.MessageWithCompletion(ctx, observe.ProgressCfg{}, "Enumerating items in repository")
|
||||
defer close(progressBar)
|
||||
|
||||
dcs, err := op.kopia.ProduceRestoreCollections(
|
||||
@ -380,7 +391,7 @@ func consumeRestoreCollections(
|
||||
errs *fault.Bus,
|
||||
ctr *count.Bus,
|
||||
) (*details.Details, error) {
|
||||
progressBar := observe.MessageWithCompletion(ctx, "Restoring data")
|
||||
progressBar := observe.MessageWithCompletion(ctx, observe.ProgressCfg{}, "Restoring data")
|
||||
defer close(progressBar)
|
||||
|
||||
rcc := inject.RestoreConsumerConfig{
|
||||
|
||||
@ -89,7 +89,7 @@ func connectToM365(
|
||||
return ctrl, nil
|
||||
}
|
||||
|
||||
progressBar := observe.MessageWithCompletion(ctx, "Connecting to M365")
|
||||
progressBar := observe.MessageWithCompletion(ctx, observe.ProgressCfg{}, "Connecting to M365")
|
||||
defer close(progressBar)
|
||||
|
||||
ctrl, err := m365.NewController(
|
||||
|
||||
@ -153,7 +153,7 @@ func (r *repository) Initialize(ctx context.Context, cfg InitConfig) (err error)
|
||||
return clues.Stack(err)
|
||||
}
|
||||
|
||||
observe.Message(ctx, "Initializing repository")
|
||||
observe.Message(ctx, observe.ProgressCfg{}, "Initializing repository")
|
||||
|
||||
if err := r.setupKopia(ctx, cfg.RetentionOpts, true); err != nil {
|
||||
return err
|
||||
@ -187,7 +187,8 @@ func (r *repository) Connect(ctx context.Context, cfg ConnConfig) (err error) {
|
||||
return clues.Stack(err)
|
||||
}
|
||||
|
||||
observe.Message(ctx, "Connecting to repository")
|
||||
progressBar := observe.MessageWithCompletion(ctx, observe.ProgressCfg{}, "Connecting to repository")
|
||||
defer close(progressBar)
|
||||
|
||||
if err := r.setupKopia(ctx, ctrlRepo.Retention{}, false); err != nil {
|
||||
return clues.Stack(err)
|
||||
@ -209,7 +210,7 @@ func (r *repository) UpdatePassword(ctx context.Context, password string) (err e
|
||||
}
|
||||
}()
|
||||
|
||||
progressBar := observe.MessageWithCompletion(ctx, "Connecting to repository")
|
||||
progressBar := observe.MessageWithCompletion(ctx, observe.ProgressCfg{}, "Connecting to repository")
|
||||
defer close(progressBar)
|
||||
|
||||
repoNameHash, err := r.GenerateHashForRepositoryConfigFileName()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user