add the exit code and stdout/stderr output to the cmd struct
Some checks are pending
Lint / golangci-lint (push) Waiting to run
Declarative Tests / test (push) Waiting to run

This commit is contained in:
Matthew Rich 2024-10-09 22:16:57 +00:00
parent 0d748fb0bf
commit 37ed8bfa83
2 changed files with 76 additions and 10 deletions

View File

@ -16,6 +16,7 @@ import (
"strings" "strings"
"text/template" "text/template"
"decl/internal/codec" "decl/internal/codec"
"syscall"
) )
var ErrUnknownCommand error = errors.New("Unable to find command in path") var ErrUnknownCommand error = errors.New("Unable to find command in path")
@ -26,6 +27,8 @@ type CommandExists func() error
type CommandArg string type CommandArg string
type CommandInput string
type Command struct { type Command struct {
Path string `json:"path" yaml:"path"` Path string `json:"path" yaml:"path"`
Args []CommandArg `json:"args" yaml:"args"` Args []CommandArg `json:"args" yaml:"args"`
@ -33,9 +36,13 @@ type Command struct {
Split bool `json:"split" yaml:"split"` Split bool `json:"split" yaml:"split"`
FailOnError bool `json:"failonerror" yaml:"failonerror"` FailOnError bool `json:"failonerror" yaml:"failonerror"`
StdinAvailable bool `json:"stdinavailable,omitempty" yaml:"stdinavailable,omitempty"` StdinAvailable bool `json:"stdinavailable,omitempty" yaml:"stdinavailable,omitempty"`
ExitCode int `json:"exitcode,omitempty" yaml:"exitcode,omitempty"`
Stdout string `json:"stdout,omitempty" yaml:"stdout,omitempty"`
Stderr string `json:"stderr,omitempty" yaml:"stderr,omitempty"`
Executor CommandExecutor `json:"-" yaml:"-"` Executor CommandExecutor `json:"-" yaml:"-"`
Extractor CommandExtractAttributes `json:"-" yaml:"-"` Extractor CommandExtractAttributes `json:"-" yaml:"-"`
CommandExists CommandExists `json:"-" yaml:"-"` CommandExists CommandExists `json:"-" yaml:"-"`
Input CommandInput `json:"-" yaml:"-"`
stdin io.Reader `json:"-" yaml:"-"` stdin io.Reader `json:"-" yaml:"-"`
} }
@ -45,7 +52,14 @@ func NewCommand() *Command {
return c return c
} }
func (c *Command) ClearOutput() {
c.Stdout = ""
c.Stderr = ""
c.ExitCode = 0
}
func (c *Command) Defaults() { func (c *Command) Defaults() {
c.ClearOutput()
c.Split = true c.Split = true
c.FailOnError = true c.FailOnError = true
c.CommandExists = func() error { c.CommandExists = func() error {
@ -55,10 +69,14 @@ func (c *Command) Defaults() {
return nil return nil
} }
c.Executor = func(value any) ([]byte, error) { c.Executor = func(value any) ([]byte, error) {
c.ClearOutput()
args, err := c.Template(value) args, err := c.Template(value)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if inputErr := c.SetInput(value); inputErr != nil {
return nil, inputErr
}
cmd := exec.Command(c.Path, args...) cmd := exec.Command(c.Path, args...)
c.SetCmdEnv(cmd) c.SetCmdEnv(cmd)
@ -91,6 +109,10 @@ func (c *Command) Defaults() {
} }
waitErr := cmd.Wait() waitErr := cmd.Wait()
c.Stdout = string(stdOutOutput)
c.Stderr = string(stdErrOutput)
c.ExitCode = c.GetExitCodeFromError(waitErr)
if len(stdOutOutput) > 100 { if len(stdOutOutput) > 100 {
slog.Info("execute()", "path", c.Path, "args", args, "output", string(stdOutOutput[:100]), "error", string(stdErrOutput)) slog.Info("execute()", "path", c.Path, "args", args, "output", string(stdOutOutput[:100]), "error", string(stdErrOutput))
} else { } else {
@ -126,6 +148,15 @@ func (c *Command) Exists() bool {
return c.CommandExists() == nil return c.CommandExists() == nil
} }
func (c *Command) GetExitCodeFromError(err error) (ec int) {
if exitErr, ok := err.(*exec.ExitError); ok {
if status, ok := exitErr.Sys().(syscall.WaitStatus); ok {
return status.ExitStatus()
}
}
return
}
func (c *Command) Template(value any) ([]string, error) { func (c *Command) Template(value any) ([]string, error) {
var args []string = make([]string, 0, len(c.Args) * 2) var args []string = make([]string, 0, len(c.Args) * 2)
for i, arg := range c.Args { for i, arg := range c.Args {
@ -154,6 +185,22 @@ func (c *Command) Execute(value any) ([]byte, error) {
return c.Executor(value) return c.Executor(value)
} }
func (c *Command) SetInput(value any) error {
if len(c.Input) > 0 {
if r, err := c.Input.Template(value); err != nil {
return err
} else {
c.SetStdinReader(strings.NewReader(r.String()))
}
}
return nil
}
func (c *CommandInput) Template(value any) (result strings.Builder, err error) {
err = template.Must(template.New("commandInput").Parse(string(*c))).Execute(&result, value)
return
}
func (c *CommandArg) UnmarshalValue(value string) error { func (c *CommandArg) UnmarshalValue(value string) error {
*c = CommandArg(value) *c = CommandArg(value)
return nil return nil

View File

@ -81,3 +81,22 @@ stdinavailable: true
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, expected, string(out)) assert.Equal(t, expected, string(out))
} }
func TestCommandExitCode(t *testing.T) {
c := NewCommand()
assert.NotNil(t, c)
decl := `
path: ls
args:
- "amissingfile"
`
assert.Nil(t, c.LoadDecl(decl))
assert.Equal(t, "ls", c.Path)
out, err := c.Execute(nil)
assert.NotNil(t, err)
assert.Greater(t, c.ExitCode, 0)
assert.Equal(t, string(out), c.Stdout)
assert.Equal(t, string("ls: amissingfile: No such file or directory\n"), c.Stderr)
}