From 8a8bc233765d0de82453214b87136700af0e7a93 Mon Sep 17 00:00:00 2001 From: Ian Fijolek Date: Tue, 25 Apr 2023 13:59:32 -0700 Subject: [PATCH] Split returned stdout and stderr from Restic commands to improve parsing --- restic.go | 29 ++++++++++++++++++----------- shell.go | 29 +++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 11 deletions(-) diff --git a/restic.go b/restic.go index 0d401d8..ef3832e 100644 --- a/restic.go +++ b/restic.go @@ -273,7 +273,11 @@ func (e *ResticError) Unwrap() error { 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{} if rcmd.GlobalOpts != nil { args = rcmd.GlobalOpts.ToArgs() @@ -285,22 +289,22 @@ func (rcmd Restic) RunRestic(command string, options CommandOptions, commandArgs cmd := exec.Command("restic", args...) - output := NewCapturedLogWriter(rcmd.Logger) - cmd.Stdout = output - cmd.Stderr = output + output := NewCapturedCommandLogWriter(rcmd.Logger) + cmd.Stdout = output.Stdout + cmd.Stderr = output.Stderr cmd.Env = rcmd.BuildEnv() cmd.Dir = rcmd.Cwd if err := cmd.Run(); err != nil { 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 } - 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 { @@ -341,15 +345,18 @@ type Snapshot struct { } func (rcmd Restic) ReadSnapshots() ([]Snapshot, error) { - lines, err := rcmd.RunRestic("snapshots", GenericOpts{"--json"}) + output, err := rcmd.RunRestic("snapshots", GenericOpts{"--json"}) if err != nil { 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 { - return nil, fmt.Errorf("failed parsing snapshot results from %s: %w", lines[0], err) + snapshots := new([]Snapshot) + 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 diff --git a/shell.go b/shell.go index b0576e5..2578e03 100644 --- a/shell.go +++ b/shell.go @@ -5,6 +5,7 @@ import ( "log" "os" "os/exec" + "sort" "strings" ) @@ -39,6 +40,7 @@ func NewCapturedLogWriter(logger *log.Logger) *CapturedLogWriter { 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) { message := string(content) 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 } +// 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 { cmd := exec.Command("sh", "-c", strings.TrimSpace(script)) //nolint:gosec