From 8bd64e8c22aa64661b0607f5c598786a515b757b Mon Sep 17 00:00:00 2001 From: Richard Kosegi Date: Thu, 8 Aug 2024 09:42:39 +0200 Subject: [PATCH] Pipeline: Allow to capture output of executed program in 'exec' op. Signed-off-by: Richard Kosegi --- pipeline/exec_op.go | 37 +++++++++++++++++++++++++++++++++++++ pipeline/exec_op_test.go | 25 +++++++++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/pipeline/exec_op.go b/pipeline/exec_op.go index 0a2560b..25a5849 100644 --- a/pipeline/exec_op.go +++ b/pipeline/exec_op.go @@ -19,6 +19,8 @@ package pipeline import ( "errors" "fmt" + "io" + "os" osx "os/exec" "slices" ) @@ -30,6 +32,12 @@ type ExecOp struct { Args *[]string `yaml:"args,omitempty"` // List of exit codes that are assumed to be valid ValidExitCodes *[]int `yaml:"validExitCodes,omitempty"` + // Path to file where program's stdout will be written upon completion. + // Any error occurred during write will result in panic. + Stdout *string + // Path to file where program's stderr will be written upon completion + // Any error occurred during write will result in panic. + Stderr *string } func (e *ExecOp) String() string { @@ -37,6 +45,7 @@ func (e *ExecOp) String() string { } func (e *ExecOp) Do(_ ActionContext) error { + var closables []io.Closer if e.ValidExitCodes == nil { e.ValidExitCodes = &[]int{} } @@ -44,6 +53,34 @@ func (e *ExecOp) Do(_ ActionContext) error { e.Args = &[]string{} } cmd := osx.Command(e.Program, *e.Args...) + defer func() { + for _, closer := range closables { + _ = closer.Close() + } + }() + type streamTgt struct { + output *string + target *io.Writer + } + for _, stream := range []streamTgt{ + { + output: e.Stdout, + target: &cmd.Stdout, + }, + { + output: e.Stderr, + target: &cmd.Stderr, + }, + } { + if stream.output != nil { + out, err := os.OpenFile(*stream.output, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) + if err != nil { + return err + } + *stream.target = out + closables = append(closables, out) + } + } err := cmd.Run() var exitErr *osx.ExitError if errors.As(err, &exitErr) { diff --git a/pipeline/exec_op_test.go b/pipeline/exec_op_test.go index 1f7aafc..6576b0d 100644 --- a/pipeline/exec_op_test.go +++ b/pipeline/exec_op_test.go @@ -19,6 +19,7 @@ package pipeline import ( "github.com/rkosegi/yaml-toolkit/dom" "github.com/stretchr/testify/assert" + "os" "testing" ) @@ -31,6 +32,30 @@ func TestExecOpDo(t *testing.T) { var ( eo *ExecOp ) + fout, err := os.CreateTemp("", "yt.*.txt") + ferr, err := os.CreateTemp("", "yt.*.txt") + defer func() { + t.Logf("removing %s", fout.Name()) + _ = os.Remove(fout.Name()) + t.Logf("removing %s", ferr.Name()) + _ = os.Remove(ferr.Name()) + }() + assert.NoError(t, err) + eo = &ExecOp{ + Program: "sh", + Args: &[]string{"-c", "echo abcd"}, + Stdout: strPointer(fout.Name()), + Stderr: strPointer(ferr.Name()), + } + assert.NoError(t, eo.Do(mockEmptyActCtx())) + + eo = &ExecOp{ + Program: "sh", + Args: &[]string{"-c", "echo abcd"}, + Stdout: strPointer("/"), + } + assert.Error(t, eo.Do(mockEmptyActCtx())) + eo = &ExecOp{ Program: "sh", Args: &[]string{"-c", "exit 3"},