fix: make backup and restore operations more robust to non-JSON output events

This commit is contained in:
garethgeorge
2024-06-13 17:18:05 -07:00
parent 5427d75c3a
commit 91e0fdaffe
2 changed files with 17 additions and 48 deletions
+4 -6
View File
@@ -4,7 +4,6 @@ import (
"context"
"fmt"
"os/exec"
"strings"
)
const outputBufferLimit = 1000
@@ -61,13 +60,12 @@ func (e *ErrorWithOutput) Is(target error) bool {
// newErrorWithOutput creates a new error with the given output.
func newErrorWithOutput(err error, output string) error {
firstNewLine := strings.Index(output, "\n")
if firstNewLine > 0 {
output = output[:firstNewLine]
if output == "" {
return err
}
if len(output) == 0 {
return err
if len(output) > outputBufferLimit {
output = output[:outputBufferLimit] + fmt.Sprintf("\n... %d bytes truncated ...\n", len(output)-outputBufferLimit)
}
return &ErrorWithOutput{
+13 -42
View File
@@ -2,6 +2,7 @@ package restic
import (
"bufio"
"bytes"
"encoding/json"
"errors"
"fmt"
@@ -96,34 +97,19 @@ func readBackupProgressEntries(output io.Reader, callback func(event *BackupProg
scanner := bufio.NewScanner(output)
scanner.Split(bufio.ScanLines)
var summary *BackupProgressEntry
nonJSONOutput := bytes.NewBuffer(nil)
// first event is handled specially to detect non-JSON output and fast-path out.
if scanner.Scan() {
var event BackupProgressEntry
if err := json.Unmarshal(scanner.Bytes(), &event); err != nil {
return nil, fmt.Errorf("command output was not JSON: %w", err)
}
if err := event.Validate(); err != nil {
return nil, err
}
if callback != nil {
callback(&event)
}
if event.MessageType == "summary" {
summary = &event
}
}
var summary *BackupProgressEntry
// remaining events are parsed as JSON
for scanner.Scan() {
var event BackupProgressEntry
if err := json.Unmarshal(scanner.Bytes(), &event); err != nil {
// skip it. This is a best-effort attempt to parse the output.
nonJSONOutput.Write(scanner.Bytes())
continue
}
if err := event.Validate(); err != nil {
// skip it. This is a best-effort attempt to parse the output.
nonJSONOutput.Write(scanner.Bytes())
continue
}
if callback != nil {
@@ -134,10 +120,10 @@ func readBackupProgressEntries(output io.Reader, callback func(event *BackupProg
}
}
if err := scanner.Err(); err != nil {
return summary, fmt.Errorf("scanner encountered error: %w", err)
return summary, newErrorWithOutput(err, nonJSONOutput.String())
}
if summary == nil {
return nil, fmt.Errorf("no summary event found")
return nil, newErrorWithOutput(errors.New("no summary event found"), nonJSONOutput.String())
}
return summary, nil
}
@@ -235,35 +221,20 @@ func readRestoreProgressEntries(output io.Reader, callback func(event *RestorePr
scanner := bufio.NewScanner(output)
scanner.Split(bufio.ScanLines)
nonJSONOutput := bytes.NewBuffer(nil)
var summary *RestoreProgressEntry
// first event is handled specially to detect non-JSON output and fast-path out.
if scanner.Scan() {
var event RestoreProgressEntry
if err := json.Unmarshal(scanner.Bytes(), &event); err != nil {
return nil, fmt.Errorf("command output was not JSON: %w", err)
}
if err := event.Validate(); err != nil {
return nil, err
}
if callback != nil {
callback(&event)
}
if event.MessageType == "summary" {
summary = &event
}
}
// remaining events are parsed as JSON
for scanner.Scan() {
var event RestoreProgressEntry
if err := json.Unmarshal(scanner.Bytes(), &event); err != nil {
// skip it. Best effort parsing, restic will return with a non-zero exit code if it fails.
nonJSONOutput.Write(scanner.Bytes())
continue
}
if err := event.Validate(); err != nil {
// skip it. Best effort parsing, restic will return with a non-zero exit code if it fails.
nonJSONOutput.Write(scanner.Bytes())
continue
}
@@ -276,11 +247,11 @@ func readRestoreProgressEntries(output io.Reader, callback func(event *RestorePr
}
if err := scanner.Err(); err != nil {
return summary, fmt.Errorf("scanner encountered error: %w", err)
return summary, newErrorWithOutput(err, nonJSONOutput.String())
}
if summary == nil {
return nil, fmt.Errorf("no summary event found")
return nil, newErrorWithOutput(errors.New("no summary event found"), nonJSONOutput.String())
}
return summary, nil