c0m1c5an5 4 days ago committed by GitHub
commit d009cba212
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -15,15 +15,21 @@ limitations under the License.
package plugin
import "strconv"
// InvokeExecError is returned when a plugin invocation returns a non-zero status/exit code
// - subprocess plugin: child process exit code
// - extism plugin: wasm function return code
type InvokeExecError struct {
ExitCode int // Exit code from plugin code execution
Err error // Underlying error
ExitCode int // Exit code from plugin code execution
Err error // Underlying error
Stderr []byte // Captured stderr output
}
// Error implements the error interface
func (e *InvokeExecError) Error() string {
if len(e.Stderr) > 0 {
return e.Err.Error() + ": " + strconv.Quote(string(e.Stderr))
}
return e.Err.Error()
}

@ -169,17 +169,26 @@ func (r *SubprocessPluginRuntime) InvokeHook(event string) error {
// right now we implement status and error return in 3 slightly different ways in this file
// then replace the other three with a call to this func
func executeCmd(prog *exec.Cmd, pluginName string) error {
var stderrBuf bytes.Buffer
if prog.Stderr != nil {
prog.Stderr = io.MultiWriter(prog.Stderr, &stderrBuf)
} else {
prog.Stderr = &stderrBuf
}
if err := prog.Run(); err != nil {
if eerr, ok := err.(*exec.ExitError); ok {
stderr := bytes.TrimSpace(stderrBuf.Bytes())
slog.Debug(
"plugin execution failed",
slog.String("pluginName", pluginName),
slog.String("error", err.Error()),
slog.Int("exitCode", eerr.ExitCode()),
slog.String("stderr", string(bytes.TrimSpace(eerr.Stderr))))
slog.String("stderr", string(stderr)),
)
return &InvokeExecError{
Err: fmt.Errorf("plugin %q exited with error", pluginName),
ExitCode: eerr.ExitCode(),
Stderr: stderr,
}
}

@ -16,9 +16,11 @@ limitations under the License.
package plugin
import (
"bytes"
"errors"
"fmt"
"os"
"os/exec"
"path/filepath"
"testing"
@ -84,3 +86,45 @@ func TestSubprocessPluginRuntime(t *testing.T) {
assert.Nil(t, output)
}
func TestHelperProcess(t *testing.T) {
if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" {
return
}
fmt.Fprint(os.Stderr, "plugin error\n")
os.Exit(1)
}
func helperStderrCmd(t *testing.T) *exec.Cmd {
t.Helper()
cmd := exec.Command(os.Args[0], "-test.run=TestHelperProcess", "--")
cmd.Env = append(os.Environ(), "GO_WANT_HELPER_PROCESS=1")
return cmd
}
func TestExecuteCmdCapturesStderr(t *testing.T) {
cmd := helperStderrCmd(t)
err := executeCmd(cmd, "test-plugin")
require.Error(t, err)
var ieerr *InvokeExecError
require.ErrorAs(t, err, &ieerr)
assert.Equal(t, 1, ieerr.ExitCode)
assert.Equal(t, []byte("plugin error"), ieerr.Stderr)
assert.Equal(t, `plugin "test-plugin" exited with error: "plugin error"`, ieerr.Error())
}
func TestExecuteCmdTeesStderr(t *testing.T) {
var existing bytes.Buffer
cmd := helperStderrCmd(t)
cmd.Stderr = &existing
err := executeCmd(cmd, "test-plugin")
require.Error(t, err)
var ieerr *InvokeExecError
require.ErrorAs(t, err, &ieerr)
assert.Equal(t, 1, ieerr.ExitCode)
assert.Equal(t, []byte("plugin error"), ieerr.Stderr)
assert.Equal(t, "plugin error\n", existing.String())
}

@ -124,7 +124,7 @@ func loadCLIPlugins(baseCmd *cobra.Command, out io.Writer) {
execErr := &plugin.InvokeExecError{}
if errors.As(err, &execErr) {
return CommandError{
error: execErr.Err,
error: execErr,
ExitCode: execErr.ExitCode,
}
}

Loading…
Cancel
Save