Split returned stdout and stderr from Restic commands to improve parsing

This commit is contained in:
IamTheFij 2023-04-25 13:59:32 -07:00
parent fed9224c17
commit 8a8bc23376
2 changed files with 47 additions and 11 deletions

View File

@ -273,7 +273,11 @@ func (e *ResticError) Unwrap() error {
return e.OriginalError return e.OriginalError
} }
func (rcmd Restic) RunRestic(command string, options CommandOptions, commandArgs ...string) ([]string, error) { func (rcmd Restic) RunRestic(
command string,
options CommandOptions,
commandArgs ...string,
) (*CapturedCommandLogWriter, error) {
args := []string{} args := []string{}
if rcmd.GlobalOpts != nil { if rcmd.GlobalOpts != nil {
args = rcmd.GlobalOpts.ToArgs() args = rcmd.GlobalOpts.ToArgs()
@ -285,22 +289,22 @@ func (rcmd Restic) RunRestic(command string, options CommandOptions, commandArgs
cmd := exec.Command("restic", args...) cmd := exec.Command("restic", args...)
output := NewCapturedLogWriter(rcmd.Logger) output := NewCapturedCommandLogWriter(rcmd.Logger)
cmd.Stdout = output cmd.Stdout = output.Stdout
cmd.Stderr = output cmd.Stderr = output.Stderr
cmd.Env = rcmd.BuildEnv() cmd.Env = rcmd.BuildEnv()
cmd.Dir = rcmd.Cwd cmd.Dir = rcmd.Cwd
if err := cmd.Run(); err != nil { if err := cmd.Run(); err != nil {
responseErr := ErrRestic responseErr := ErrRestic
if lineIn("Is there a repository at the following location?", output.Lines) { if lineIn("Is there a repository at the following location?", output.Stderr.Lines) {
responseErr = ErrRepoNotFound responseErr = ErrRepoNotFound
} }
return output.Lines, NewResticError(command, output.Lines, responseErr) return output, NewResticError(command, output.AllLines(), responseErr)
} }
return output.Lines, nil return output, nil
} }
func (rcmd Restic) Backup(files []string, opts BackupOpts) error { func (rcmd Restic) Backup(files []string, opts BackupOpts) error {
@ -341,15 +345,18 @@ type Snapshot struct {
} }
func (rcmd Restic) ReadSnapshots() ([]Snapshot, error) { func (rcmd Restic) ReadSnapshots() ([]Snapshot, error) {
lines, err := rcmd.RunRestic("snapshots", GenericOpts{"--json"}) output, err := rcmd.RunRestic("snapshots", GenericOpts{"--json"})
if err != nil { if err != nil {
return nil, err return nil, err
} }
snapshots := new([]Snapshot) if len(output.Stdout.Lines) == 0 {
return nil, fmt.Errorf("no snapshot output to parse: %w", ErrRestic)
}
if err = json.Unmarshal([]byte(lines[0]), snapshots); err != nil { snapshots := new([]Snapshot)
return nil, fmt.Errorf("failed parsing snapshot results from %s: %w", lines[0], err) if err = json.Unmarshal([]byte(output.Stdout.Lines[0]), snapshots); err != nil {
return nil, fmt.Errorf("failed parsing snapshot results from %s: %w", output.Stdout.Lines[0], err)
} }
return *snapshots, nil return *snapshots, nil

View File

@ -5,6 +5,7 @@ import (
"log" "log"
"os" "os"
"os/exec" "os/exec"
"sort"
"strings" "strings"
) )
@ -39,6 +40,7 @@ func NewCapturedLogWriter(logger *log.Logger) *CapturedLogWriter {
return &CapturedLogWriter{Lines: []string{}, logger: logger} return &CapturedLogWriter{Lines: []string{}, logger: logger}
} }
// Write writes the provided byte slice to the logger and stores each captured line.
func (w *CapturedLogWriter) Write(content []byte) (n int, err error) { func (w *CapturedLogWriter) Write(content []byte) (n int, err error) {
message := string(content) message := string(content)
for _, line := range strings.Split(message, "\n") { for _, line := range strings.Split(message, "\n") {
@ -49,6 +51,33 @@ func (w *CapturedLogWriter) Write(content []byte) (n int, err error) {
return len(content), nil return len(content), nil
} }
// LinesMergedWith returns a slice of lines from this logger merged with another.
func (w CapturedLogWriter) LinesMergedWith(other CapturedLogWriter) []string {
allLines := []string{}
allLines = append(allLines, w.Lines...)
allLines = append(allLines, other.Lines...)
sort.Strings(allLines)
return allLines
}
type CapturedCommandLogWriter struct {
Stdout *CapturedLogWriter
Stderr *CapturedLogWriter
}
func NewCapturedCommandLogWriter(logger *log.Logger) *CapturedCommandLogWriter {
return &CapturedCommandLogWriter{
Stdout: NewCapturedLogWriter(logger),
Stderr: NewCapturedLogWriter(logger),
}
}
func (cclw CapturedCommandLogWriter) AllLines() []string {
return cclw.Stdout.LinesMergedWith(*cclw.Stderr)
}
func RunShell(script string, cwd string, env map[string]string, logger *log.Logger) error { func RunShell(script string, cwd string, env map[string]string, logger *log.Logger) error {
cmd := exec.Command("sh", "-c", strings.TrimSpace(script)) //nolint:gosec cmd := exec.Command("sh", "-c", strings.TrimSpace(script)) //nolint:gosec