80 lines
1.6 KiB
Go
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()}
|
|
}
|