Files

80 lines
1.6 KiB
Go

package executor
import (
"bytes"
"context"
"os/exec"
"strings"
"syscall"
"time"
)
type Result struct {
ExitCode int
Stdout string
Stderr string
DurationMS int64
}
type Local struct{ MaxOutput int }
type cappedBuffer struct {
buf bytes.Buffer
max int
}
func (b *cappedBuffer) Write(p []byte) (int, error) {
if b.max <= 0 {
return len(p), nil
}
if b.buf.Len() < b.max {
n := b.max - b.buf.Len()
if n > len(p) {
n = len(p)
}
_, _ = b.buf.Write(p[:n])
}
return len(p), nil
}
func (e Local) Run(ctx context.Context, command, workdir string) Result {
start := time.Now()
if e.MaxOutput <= 0 {
e.MaxOutput = 1024 * 1024
}
cmd := exec.Command("/bin/sh", "-c", command)
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
if workdir != "" {
cmd.Dir = workdir
}
var stdout, stderr cappedBuffer
stdout.max, stderr.max = e.MaxOutput, e.MaxOutput
cmd.Stdout = &stdout
cmd.Stderr = &stderr
if err := cmd.Start(); err != nil {
return Result{ExitCode: 1, Stderr: err.Error(), DurationMS: time.Since(start).Milliseconds()}
}
waitCh := make(chan error, 1)
go func() { waitCh <- cmd.Wait() }()
timedOut := false
var err error
select {
case err = <-waitCh:
case <-ctx.Done():
timedOut = true
_ = syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
err = <-waitCh
}
code := 0
if err != nil {
code = 1
if ee, ok := err.(*exec.ExitError); ok {
code = ee.ExitCode()
}
}
if timedOut || ctx.Err() != nil {
code = 124
}
return Result{code, stdout.buf.String(), strings.TrimSpace(stderr.buf.String()), time.Since(start).Milliseconds()}
}