Merge pull request #4314 from MichaelEischer/restore-json

Add JSON support for the restore progress bar
This commit is contained in:
Michael Eischer authored and GitHub committed 2023-06-16 23:53:17 +02:00
commit 4df77e9f26
13 files changed
+268 -135

No files matched your search

+9 -24
View File
@@ -13,34 +13,19 @@ import (
"github.com/restic/restic/internal/restic"
rtest "github.com/restic/restic/internal/test"
"github.com/restic/restic/internal/ui/termstatus"
"golang.org/x/sync/errgroup"
)
func testRunBackupAssumeFailure(t testing.TB, dir string, target []string, opts BackupOptions, gopts GlobalOptions) error {
ctx, cancel := context.WithCancel(context.TODO())
defer cancel()
return withTermStatus(gopts, func(ctx context.Context, term *termstatus.Terminal) error {
t.Logf("backing up %v in %v", target, dir)
if dir != "" {
cleanup := rtest.Chdir(t, dir)
defer cleanup()
}
var wg errgroup.Group
term := termstatus.New(gopts.stdout, gopts.stderr, gopts.Quiet)
wg.Go(func() error { term.Run(ctx); return nil })
t.Logf("backing up %v in %v", target, dir)
if dir != "" {
cleanup := rtest.Chdir(t, dir)
defer cleanup()
}
opts.GroupBy = restic.SnapshotGroupByOptions{Host: true, Path: true}
backupErr := runBackup(ctx, opts, gopts, term, target)
cancel()
err := wg.Wait()
if err != nil {
t.Fatal(err)
}
return backupErr
opts.GroupBy = restic.SnapshotGroupByOptions{Host: true, Path: true}
return runBackup(ctx, opts, gopts, term, target)
})
}
func testRunBackup(t testing.TB, dir string, target []string, opts BackupOptions, gopts GlobalOptions) {
+24 -15
View File
@@ -175,16 +175,20 @@ func runRestore(ctx context.Context, opts RestoreOptions, gopts GlobalOptions,
return err
}
var progress *restoreui.Progress
if !gopts.Quiet && !gopts.JSON {
progress = restoreui.NewProgress(restoreui.NewProgressPrinter(term), calculateProgressInterval(!gopts.Quiet, gopts.JSON))
msg := ui.NewMessage(term, gopts.verbosity)
var printer restoreui.ProgressPrinter
if gopts.JSON {
printer = restoreui.NewJSONProgress(term)
} else {
printer = restoreui.NewTextProgress(term)
}
progress := restoreui.NewProgress(printer, calculateProgressInterval(!gopts.Quiet, gopts.JSON))
res := restorer.NewRestorer(repo, sn, opts.Sparse, progress)
totalErrors := 0
res.Error = func(location string, err error) error {
Warnf("ignoring error for %s: %s\n", location, err)
msg.E("ignoring error for %s: %s\n", location, err)
totalErrors++
return nil
}
@@ -194,12 +198,12 @@ func runRestore(ctx context.Context, opts RestoreOptions, gopts GlobalOptions,
selectExcludeFilter := func(item string, dstpath string, node *restic.Node) (selectedForRestore bool, childMayBeSelected bool) {
matched, err := filter.List(excludePatterns, item)
if err != nil {
Warnf("error for exclude pattern: %v", err)
msg.E("error for exclude pattern: %v", err)
}
matchedInsensitive, err := filter.List(insensitiveExcludePatterns, strings.ToLower(item))
if err != nil {
Warnf("error for iexclude pattern: %v", err)
msg.E("error for iexclude pattern: %v", err)
}
// An exclude filter is basically a 'wildcard but foo',
@@ -217,12 +221,12 @@ func runRestore(ctx context.Context, opts RestoreOptions, gopts GlobalOptions,
selectIncludeFilter := func(item string, dstpath string, node *restic.Node) (selectedForRestore bool, childMayBeSelected bool) {
matched, childMayMatch, err := filter.ListWithChild(includePatterns, item)
if err != nil {
Warnf("error for include pattern: %v", err)
msg.E("error for include pattern: %v", err)
}
matchedInsensitive, childMayMatchInsensitive, err := filter.ListWithChild(insensitiveIncludePatterns, strings.ToLower(item))
if err != nil {
Warnf("error for iexclude pattern: %v", err)
msg.E("error for iexclude pattern: %v", err)
}
selectedForRestore = matched || matchedInsensitive
@@ -237,23 +241,25 @@ func runRestore(ctx context.Context, opts RestoreOptions, gopts GlobalOptions,
res.SelectFilter = selectIncludeFilter
}
Verbosef("restoring %s to %s\n", res.Snapshot(), opts.Target)
if !gopts.JSON {
msg.P("restoring %s to %s\n", res.Snapshot(), opts.Target)
}
err = res.RestoreTo(ctx, opts.Target)
if err != nil {
return err
}
if progress != nil {
progress.Finish()
}
progress.Finish()
if totalErrors > 0 {
return errors.Fatalf("There were %d errors\n", totalErrors)
}
if opts.Verify {
Verbosef("verifying files in %s\n", opts.Target)
if !gopts.JSON {
msg.P("verifying files in %s\n", opts.Target)
}
var count int
t0 := time.Now()
count, err = res.VerifyFiles(ctx, opts.Target)
@@ -263,8 +269,11 @@ func runRestore(ctx context.Context, opts RestoreOptions, gopts GlobalOptions,
if totalErrors > 0 {
return errors.Fatalf("There were %d errors\n", totalErrors)
}
Verbosef("finished verifying %d files in %s (took %s)\n", count, opts.Target,
time.Since(t0).Round(time.Millisecond))
if !gopts.JSON {
msg.P("finished verifying %d files in %s (took %s)\n", count, opts.Target,
time.Since(t0).Round(time.Millisecond))
}
}
return nil
+7 -4
View File
@@ -14,6 +14,7 @@ import (
"github.com/restic/restic/internal/filter"
"github.com/restic/restic/internal/restic"
rtest "github.com/restic/restic/internal/test"
"github.com/restic/restic/internal/ui/termstatus"
)
func testRunRestore(t testing.TB, opts GlobalOptions, dir string, snapshotID restic.ID) {
@@ -26,11 +27,13 @@ func testRunRestoreExcludes(t testing.TB, gopts GlobalOptions, dir string, snaps
Exclude: excludes,
}
rtest.OK(t, runRestore(context.TODO(), opts, gopts, nil, []string{snapshotID.String()}))
rtest.OK(t, testRunRestoreAssumeFailure(snapshotID.String(), opts, gopts))
}
func testRunRestoreAssumeFailure(snapshotID string, opts RestoreOptions, gopts GlobalOptions) error {
return runRestore(context.TODO(), opts, gopts, nil, []string{snapshotID})
return withTermStatus(gopts, func(ctx context.Context, term *termstatus.Terminal) error {
return runRestore(ctx, opts, gopts, term, []string{snapshotID})
})
}
func testRunRestoreLatest(t testing.TB, gopts GlobalOptions, dir string, paths []string, hosts []string) {
@@ -42,7 +45,7 @@ func testRunRestoreLatest(t testing.TB, gopts GlobalOptions, dir string, paths [
},
}
rtest.OK(t, runRestore(context.TODO(), opts, gopts, nil, []string{"latest"}))
rtest.OK(t, testRunRestoreAssumeFailure("latest", opts, gopts))
}
func testRunRestoreIncludes(t testing.TB, gopts GlobalOptions, dir string, snapshotID restic.ID, includes []string) {
@@ -51,7 +54,7 @@ func testRunRestoreIncludes(t testing.TB, gopts GlobalOptions, dir string, snaps
Include: includes,
}
rtest.OK(t, runRestore(context.TODO(), opts, gopts, nil, []string{snapshotID.String()}))
rtest.OK(t, testRunRestoreAssumeFailure(snapshotID.String(), opts, gopts))
}
func TestRestoreFilter(t *testing.T) {
+19
View File
@@ -9,6 +9,7 @@ import (
"os"
"path/filepath"
"runtime"
"sync"
"testing"
"github.com/restic/restic/internal/backend/retry"
@@ -17,6 +18,7 @@ import (
"github.com/restic/restic/internal/repository"
"github.com/restic/restic/internal/restic"
rtest "github.com/restic/restic/internal/test"
"github.com/restic/restic/internal/ui/termstatus"
)
type dirEntry struct {
@@ -356,3 +358,20 @@ func withCaptureStdout(inner func() error) (*bytes.Buffer, error) {
return buf, err
}
func withTermStatus(gopts GlobalOptions, callback func(ctx context.Context, term *termstatus.Terminal) error) error {
ctx, cancel := context.WithCancel(context.TODO())
var wg sync.WaitGroup
term := termstatus.New(gopts.stdout, gopts.stderr, gopts.Quiet)
wg.Add(1)
go func() {
defer wg.Done()
term.Run(ctx)
}()
defer wg.Wait()
defer cancel()
return callback(ctx, term)
}