mirror of
https://github.com/garethgeorge/backrest.git
synced 2025-12-12 08:45:38 +00:00
fix: improve cmd error formatting now that logs are available for all operations
This commit is contained in:
@@ -11,14 +11,10 @@ const outputBufferLimit = 1000
|
|||||||
type CmdError struct {
|
type CmdError struct {
|
||||||
Command string
|
Command string
|
||||||
Err error
|
Err error
|
||||||
Output string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *CmdError) Error() string {
|
func (e *CmdError) Error() string {
|
||||||
m := fmt.Sprintf("command %q failed: %s", e.Command, e.Err.Error())
|
m := fmt.Sprintf("command %q failed: %s", e.Command, e.Err.Error())
|
||||||
if e.Output != "" {
|
|
||||||
m += "\nProcess STDOUT: \n" + e.Output
|
|
||||||
}
|
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,27 +28,22 @@ func (e *CmdError) Is(target error) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// newCmdError creates a new error indicating that running a command failed.
|
// newCmdError creates a new error indicating that running a command failed.
|
||||||
func newCmdError(ctx context.Context, cmd *exec.Cmd, output string, err error) *CmdError {
|
func newCmdError(ctx context.Context, cmd *exec.Cmd, err error) *CmdError {
|
||||||
cerr := &CmdError{
|
cerr := &CmdError{
|
||||||
Command: cmd.String(),
|
Command: cmd.String(),
|
||||||
Err: err,
|
Err: err,
|
||||||
Output: output,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(output) >= outputBufferLimit {
|
|
||||||
cerr.Output = output[:outputBufferLimit] + "\n...[truncated]"
|
|
||||||
}
|
|
||||||
if logger := LoggerFromContext(ctx); logger != nil {
|
if logger := LoggerFromContext(ctx); logger != nil {
|
||||||
logger.Write([]byte(cerr.Error()))
|
logger.Write([]byte(cerr.Error()))
|
||||||
}
|
}
|
||||||
return cerr
|
return cerr
|
||||||
}
|
}
|
||||||
|
|
||||||
func newCmdErrorPreformatted(ctx context.Context, cmd *exec.Cmd, output string, err error) *CmdError {
|
func newCmdErrorPreformatted(ctx context.Context, cmd *exec.Cmd, err error) *CmdError {
|
||||||
cerr := &CmdError{
|
cerr := &CmdError{
|
||||||
Command: cmd.String(),
|
Command: cmd.String(),
|
||||||
Err: err,
|
Err: err,
|
||||||
Output: output,
|
|
||||||
}
|
}
|
||||||
if logger := LoggerFromContext(ctx); logger != nil {
|
if logger := LoggerFromContext(ctx); logger != nil {
|
||||||
logger.Write([]byte(cerr.Error()))
|
logger.Write([]byte(cerr.Error()))
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ func (r *Repo) init(ctx context.Context, opts ...GenericOption) error {
|
|||||||
if strings.Contains(output.String(), "config file already exists") || strings.Contains(output.String(), "already initialized") {
|
if strings.Contains(output.String(), "config file already exists") || strings.Contains(output.String(), "already initialized") {
|
||||||
return errAlreadyInitialized
|
return errAlreadyInitialized
|
||||||
}
|
}
|
||||||
return newCmdError(ctx, cmd, output.String(), err)
|
return newCmdError(ctx, cmd, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
r.initialized = true
|
r.initialized = true
|
||||||
@@ -158,7 +158,7 @@ func (r *Repo) Backup(ctx context.Context, paths []string, progressCallback func
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return summary, newCmdErrorPreformatted(ctx, cmd, string(outputForErr.Bytes()), errors.Join(cmdErr, readErr))
|
return summary, newCmdErrorPreformatted(ctx, cmd, errors.Join(cmdErr, readErr))
|
||||||
}
|
}
|
||||||
return summary, nil
|
return summary, nil
|
||||||
}
|
}
|
||||||
@@ -169,12 +169,12 @@ func (r *Repo) Snapshots(ctx context.Context, opts ...GenericOption) ([]*Snapsho
|
|||||||
r.pipeCmdOutputToWriter(cmd, output)
|
r.pipeCmdOutputToWriter(cmd, output)
|
||||||
|
|
||||||
if err := cmd.Run(); err != nil {
|
if err := cmd.Run(); err != nil {
|
||||||
return nil, newCmdError(ctx, cmd, output.String(), err)
|
return nil, newCmdError(ctx, cmd, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var snapshots []*Snapshot
|
var snapshots []*Snapshot
|
||||||
if err := json.Unmarshal(output.Bytes(), &snapshots); err != nil {
|
if err := json.Unmarshal(output.Bytes(), &snapshots); err != nil {
|
||||||
return nil, newCmdError(ctx, cmd, output.String(), fmt.Errorf("command output is not valid JSON: %w", err))
|
return nil, newCmdError(ctx, cmd, fmt.Errorf("command output is not valid JSON: %w", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, snapshot := range snapshots {
|
for _, snapshot := range snapshots {
|
||||||
@@ -193,18 +193,18 @@ func (r *Repo) Forget(ctx context.Context, policy *RetentionPolicy, opts ...Gene
|
|||||||
output := bytes.NewBuffer(nil)
|
output := bytes.NewBuffer(nil)
|
||||||
r.pipeCmdOutputToWriter(cmd, output)
|
r.pipeCmdOutputToWriter(cmd, output)
|
||||||
if err := cmd.Run(); err != nil {
|
if err := cmd.Run(); err != nil {
|
||||||
return nil, newCmdError(ctx, cmd, output.String(), err)
|
return nil, newCmdError(ctx, cmd, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var result []ForgetResult
|
var result []ForgetResult
|
||||||
if err := json.Unmarshal(output.Bytes(), &result); err != nil {
|
if err := json.Unmarshal(output.Bytes(), &result); err != nil {
|
||||||
return nil, newCmdError(ctx, cmd, output.String(), fmt.Errorf("command output is not valid JSON: %w", err))
|
return nil, newCmdError(ctx, cmd, fmt.Errorf("command output is not valid JSON: %w", err))
|
||||||
}
|
}
|
||||||
if len(result) != 1 {
|
if len(result) != 1 {
|
||||||
return nil, fmt.Errorf("expected 1 output from forget, got %v", len(result))
|
return nil, fmt.Errorf("expected 1 output from forget, got %v", len(result))
|
||||||
}
|
}
|
||||||
if err := result[0].Validate(); err != nil {
|
if err := result[0].Validate(); err != nil {
|
||||||
return nil, newCmdError(ctx, cmd, output.String(), fmt.Errorf("invalid forget result: %w", err))
|
return nil, newCmdError(ctx, cmd, fmt.Errorf("invalid forget result: %w", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
return &result[0], nil
|
return &result[0], nil
|
||||||
@@ -214,10 +214,8 @@ func (r *Repo) ForgetSnapshot(ctx context.Context, snapshotId string, opts ...Ge
|
|||||||
args := []string{"forget", "--json", snapshotId}
|
args := []string{"forget", "--json", snapshotId}
|
||||||
|
|
||||||
cmd := r.commandWithContext(ctx, args, opts...)
|
cmd := r.commandWithContext(ctx, args, opts...)
|
||||||
output := bytes.NewBuffer(nil)
|
|
||||||
r.pipeCmdOutputToWriter(cmd, output)
|
|
||||||
if err := cmd.Run(); err != nil {
|
if err := cmd.Run(); err != nil {
|
||||||
return newCmdError(ctx, cmd, output.String(), err)
|
return newCmdError(ctx, cmd, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -226,23 +224,20 @@ func (r *Repo) ForgetSnapshot(ctx context.Context, snapshotId string, opts ...Ge
|
|||||||
func (r *Repo) Prune(ctx context.Context, pruneOutput io.Writer, opts ...GenericOption) error {
|
func (r *Repo) Prune(ctx context.Context, pruneOutput io.Writer, opts ...GenericOption) error {
|
||||||
args := []string{"prune"}
|
args := []string{"prune"}
|
||||||
cmd := r.commandWithContext(ctx, args, opts...)
|
cmd := r.commandWithContext(ctx, args, opts...)
|
||||||
output := bytes.NewBuffer(nil)
|
|
||||||
r.pipeCmdOutputToWriter(cmd, output)
|
|
||||||
if pruneOutput != nil {
|
if pruneOutput != nil {
|
||||||
r.pipeCmdOutputToWriter(cmd, pruneOutput)
|
r.pipeCmdOutputToWriter(cmd, pruneOutput)
|
||||||
}
|
}
|
||||||
if err := cmd.Run(); err != nil {
|
if err := cmd.Run(); err != nil {
|
||||||
return newCmdError(ctx, cmd, output.String(), err)
|
return newCmdError(ctx, cmd, err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Repo) Restore(ctx context.Context, snapshot string, callback func(*RestoreProgressEntry), opts ...GenericOption) (*RestoreProgressEntry, error) {
|
func (r *Repo) Restore(ctx context.Context, snapshot string, callback func(*RestoreProgressEntry), opts ...GenericOption) (*RestoreProgressEntry, error) {
|
||||||
cmd := r.commandWithContext(ctx, []string{"restore", "--json", snapshot}, opts...)
|
cmd := r.commandWithContext(ctx, []string{"restore", "--json", snapshot}, opts...)
|
||||||
outputForErr := ioutil.NewOutputCapturer(outputBufferLimit) // for error reporting.
|
buf := buffer.New(32 * 1024) // 32KB IO buffer for the realtime event parsing
|
||||||
buf := buffer.New(32 * 1024) // 32KB IO buffer for the realtime event parsing
|
|
||||||
reader, writer := nio.Pipe(buf)
|
reader, writer := nio.Pipe(buf)
|
||||||
r.pipeCmdOutputToWriter(cmd, writer, outputForErr)
|
r.pipeCmdOutputToWriter(cmd, writer)
|
||||||
|
|
||||||
var readErr error
|
var readErr error
|
||||||
var summary *RestoreProgressEntry
|
var summary *RestoreProgressEntry
|
||||||
@@ -273,7 +268,7 @@ func (r *Repo) Restore(ctx context.Context, snapshot string, callback func(*Rest
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return summary, newCmdErrorPreformatted(ctx, cmd, string(outputForErr.Bytes()), errors.Join(cmdErr, readErr))
|
return summary, newCmdErrorPreformatted(ctx, cmd, errors.Join(cmdErr, readErr))
|
||||||
}
|
}
|
||||||
return summary, nil
|
return summary, nil
|
||||||
}
|
}
|
||||||
@@ -289,12 +284,12 @@ func (r *Repo) ListDirectory(ctx context.Context, snapshot string, path string,
|
|||||||
r.pipeCmdOutputToWriter(cmd, output)
|
r.pipeCmdOutputToWriter(cmd, output)
|
||||||
|
|
||||||
if err := cmd.Run(); err != nil {
|
if err := cmd.Run(); err != nil {
|
||||||
return nil, nil, newCmdError(ctx, cmd, output.String(), err)
|
return nil, nil, newCmdError(ctx, cmd, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
snapshots, entries, err := readLs(output)
|
snapshots, entries, err := readLs(output)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, newCmdError(ctx, cmd, output.String(), err)
|
return nil, nil, newCmdError(ctx, cmd, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return snapshots, entries, nil
|
return snapshots, entries, nil
|
||||||
@@ -302,10 +297,8 @@ func (r *Repo) ListDirectory(ctx context.Context, snapshot string, path string,
|
|||||||
|
|
||||||
func (r *Repo) Unlock(ctx context.Context, opts ...GenericOption) error {
|
func (r *Repo) Unlock(ctx context.Context, opts ...GenericOption) error {
|
||||||
cmd := r.commandWithContext(ctx, []string{"unlock"}, opts...)
|
cmd := r.commandWithContext(ctx, []string{"unlock"}, opts...)
|
||||||
output := bytes.NewBuffer(nil)
|
|
||||||
r.pipeCmdOutputToWriter(cmd, output)
|
|
||||||
if err := cmd.Run(); err != nil {
|
if err := cmd.Run(); err != nil {
|
||||||
return newCmdError(ctx, cmd, output.String(), err)
|
return newCmdError(ctx, cmd, err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -316,12 +309,12 @@ func (r *Repo) Stats(ctx context.Context, opts ...GenericOption) (*RepoStats, er
|
|||||||
r.pipeCmdOutputToWriter(cmd, output)
|
r.pipeCmdOutputToWriter(cmd, output)
|
||||||
|
|
||||||
if err := cmd.Run(); err != nil {
|
if err := cmd.Run(); err != nil {
|
||||||
return nil, newCmdError(ctx, cmd, output.String(), err)
|
return nil, newCmdError(ctx, cmd, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var stats RepoStats
|
var stats RepoStats
|
||||||
if err := json.Unmarshal(output.Bytes(), &stats); err != nil {
|
if err := json.Unmarshal(output.Bytes(), &stats); err != nil {
|
||||||
return nil, newCmdError(ctx, cmd, output.String(), fmt.Errorf("command output is not valid JSON: %w", err))
|
return nil, newCmdError(ctx, cmd, fmt.Errorf("command output is not valid JSON: %w", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
return &stats, nil
|
return &stats, nil
|
||||||
@@ -334,10 +327,8 @@ func (r *Repo) AddTags(ctx context.Context, snapshotIDs []string, tags []string,
|
|||||||
args = append(args, snapshotIDs...)
|
args = append(args, snapshotIDs...)
|
||||||
|
|
||||||
cmd := r.commandWithContext(ctx, args, opts...)
|
cmd := r.commandWithContext(ctx, args, opts...)
|
||||||
output := bytes.NewBuffer(nil)
|
|
||||||
r.pipeCmdOutputToWriter(cmd, output)
|
|
||||||
if err := cmd.Run(); err != nil {
|
if err := cmd.Run(); err != nil {
|
||||||
return newCmdError(ctx, cmd, output.String(), err)
|
return newCmdError(ctx, cmd, err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user